__setattr__ only for names not found in the object's attributes`? - python

I want to use __setattr__ only when the attribute was not found in the object's attributes, like __getattr__.
Do I really have to use try-except?
def __setattr__(self, name, value):
try:
setattr(super(Clazz, self), name, value)
except AttributeError:
# implement *my* __setattr__
pass

You can use hasattr():
def __setattr__(self, name, value):
if hasattr(super(Clazz, self), name):
setattr(super(Clazz, self), name, value)
else:
# implement *my* __setattr__
pass

There are many times when calling hasattr won't work the way you expect (e.g., you've overridden __getattr__ to always return a value), so another way to set the right attribute in the right place would be something like this:
def __setattr__(self, k, v):
if k in self.__dict__ or k in self.__class__.__dict__:
super(Clazz, self).__setattr__(k, v)
else:
# implement *my* __setattr__
pass

__setattr__, if it exists, is called for every attribute set on the object.
Your example code, though, is rather confusing for me. What are you trying to do with the statement:
setattr(super(Clazz, self), name, value) ??
Set an attribute on self, with self viewed as an instance of its superclass? That makes no sense, because the object is still "self".
On the other hand trying to use "setattr" on the object returned by a call to "super" will always yield an attribute error, regardless if the attribute exists on the superclass or not. That is because super returns not the superclass itself, but a wrapper object that wil fetch attributes there when they are needed - so you can use "hasattr" in the object returned by super, but not setattr. I thought it would behave so, and just tried it on the console :
>>> class A(object):pass
...
>>> class B(A): pass
...
>>> b = B()
>>> super(B,b)
<super: <class 'B'>, <B object>>
>>> setattr(super(B,b), "a", 5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'super' object has no attribute 'a'
>>> A.a = 1
>>> setattr(super(B,b), "a", 5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'super' object has no attribute 'a'
But then, you can just use "hasattr" in the object itself, and proceed like this:
def __setattr__(self, attr, value):
if hasattr(self, value):
#this works because retrieving "__setattr__" from the
# result of the supercall gives the correct "__setattr__" of the superclass.
super(Clazz, self).__setattr__(self, attr, value)
else:
# transform value /or attribute as desired in your code
super(Clazz, self).__setattr__(self, attr, value)

Related

TypeError for class instance when checking attributes as suggested by #jusbueno

I am referring to the question asked in How to force/ensure class attributes are a specific type? (shown bellow).
The type checking works as suggested. However, the class instance has an error. Namely, when instantiate the class as follows and call __dict__ on it, the error comes up.
excel_parser.py:
one_foo = Foo()
one_foo.__dict__
results in:
Traceback (most recent call last):
File "C:/Users/fiona/PycharmProjects/data_processing/excel_parser.py", line 80, in <module>
Foo.__dict__
TypeError: descriptor '__dict__' for 'Foo' objects doesn't apply to a 'Foo' object
How can I prevent this from happening? Thx
def getter_setter_gen(name, type_):
def getter(self):
return getattr(self, "__" + name)
def setter(self, value):
if not isinstance(value, type_):
raise TypeError(f"{name} attribute must be set to an instance of {type_}")
setattr(self, "__" + name, value)
return property(getter, setter)
def auto_attr_check(cls):
new_dct = {}
for key, value in cls.__dict__.items():
if isinstance(value, type):
value = getter_setter_gen(key, value)
new_dct[key] = value
# Creates a new class, using the modified dictionary as the class dict:
return type(cls)(cls.__name__, cls.__bases__, new_dct)
#auto_attr_check
class Foo(object):
bar = int
baz = str
bam = float

How to add attribute to arbitrary object python?

For a project I'm working on, I want to be able to associate a name with an object. The way I would like to do it is to set the .name attribute of the object to the name I want. What I really need is a function that takes an instance of an object, and returns something that is identical in every way but with a .name attribute. The problem is that I don't know what type of data the object will be ahead of time, so I can't use subclassing for example
Every method I've tried has hit a problem. Trying to give it a .name attribute directly doesnt work, for example:
>>> cats = ['tabby', 'siamese']
>>> cats.name = 'cats'
Traceback (most recent call last):
File "<pyshell#197>", line 1, in <module>
cats.name = 'cats'
AttributeError: 'list' object has no attribute 'name'
Using setattr has the same problem.
I've tried creating a new class that on init copies all attributes from the instance and also has a .name attribute, but this doesn't work either. If I try:
class NamedThing:
def __init__(self, name, thing):
thing_dict = {#not all types have a .__dict__ method
name: getattr(thing, name) for name in dir(thing)
}
self.__dict__ = thing_dict
self.name = name
It copies over the dict without a problem, but for some reason unless I directly call the new methods, python fails to find them, so the object loses all of its functionality. For example:
>>> cats = ['tabby', 'siamese']
>>> named_thing_cats = NamedThing('cats', cats)
>>> named_thing_cats.__repr__()#directly calling .__repr__()
"['tabby', 'siamese']"
>>> repr(named_thing_cats)#for some reason python does not call the new repr method
'<__main__.NamedThing object at 0x0000022814C1A670>'
>>> hasattr(named_thing_cats, '__iter__')
True
>>> for cat in named_thing_cats:
print(cat)
Traceback (most recent call last):
File "<pyshell#215>", line 1, in <module>
for cat in named_thing_cats:
TypeError: 'NamedThing' object is not iterable
I've also tried setting the type and attributes by setting class directly:
class NamedThing:
def __init__(self, name, thing):
thing_dict = {#not all types have a .__dict__ method
name: getattr(thing, name) for name in dir(thing)
}
self.__class__ = type('NamedThing', (type(thing),), thing_dict)
self.name = name
But this runs into a problem depending on what type thing is:
>>> cats = ['tabby', 'siamese']
>>> named_thing_cats = NamedThing('cats', cats)
Traceback (most recent call last):
File "<pyshell#217>", line 1, in <module>
named_thing_cats = NamedThing('cats', cats)
File "C:/Users/61490/Documents/Python/HeirachicalDict/moduleanalyser.py", line 12, in __init__
self.__class__ = type('NamedThing', (type(thing),), thing_dict)
TypeError: __class__ assignment: 'NamedThing' object layout differs from 'NamedThing'
I'm really stuck, help would be great
What you want is called an object proxy. This is some pretty sophisticated stuff, as you're getting into the data model of python and manipulating some pretty fundamental dunder (double underscore) methods in interesting ways
class Proxy:
def __init__(self, proxied):
object.__setattr__(self, '_proxied', proxied)
def __getattribute__(self, name):
try:
return object.__getattribute__(self, name)
except AttributeError:
p = object.__getattribute__(self, '_proxied')
return getattr(p, name)
def __setattr__(self, name, value):
p = object.__getattribute__(self, '_proxied')
if hasattr(p, name):
setattr(p, name, value)
else:
setattr(self, name, value)
def __getitem__(self, key):
p = object.__getattribute__(self, '_proxied')
return p[key]
def __setitem__(self, key, value):
p = object.__getattribute__(self, '_proxied')
p[key] = value
def __delitem__(self, key):
p = object.__getattribute__(self, '_proxied')
del p[key]
The most obvious thing that's going on here is that internally this class has to use the object implementation of the dunders to avoid recursing infinitely. What this does is holds a reference to a proxied object, then if you try to get or set an attribute it will check the proxied object, if the proxied object has that attribute it uses it, otherwise it sets the attribute on itself. For indexing, like with a list, it just directly acts on the proxied object, since the Proxy itself doesn't allow indexing.
If you need to use this in production, there's a package called wrapt you should probably look at instead.
Why not just create a __iter__ magic method with yield from:
class NamedThing():
def __init__(self, name, thing):
self.thing = thing
self.name = name
def __iter__(self):
yield from self.thing
cats = ['tabby', 'siamese']
named_thing_cats = NamedThing('cats', cats)
for cat in named_thing_cats:
print(cat)
Output;
tabby
siamese
Does this work?
class Thingy(list):
def __init__(self, name, thing):
list.__init__(self, thing)
self.name = name
cats = Thingy('cats', ['tabby', 'siamese'])
print(cats.name) # shows 'cats'
for cat in cats:
print(cat) # shows tabby, siamese
Or you could do:
class Thingy:
def __init__(self, name, thing):
self.thing = thing
self.name = name

Correct handling of AttributeError in __getattr__ when using property

I have a difficulty implementing properties and __getattr__ so that
when an error happens, it is reported correctly. This is my MWE (python 3.6):
class A:
#property
def F(self):
return self.moo # here should be an error
#property
def G(self):
return self.F
def __getattr__(self, name):
print('call of __getattr__ with name =', name)
if name == 'foo':
return 0
raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, name))
a = A()
print(a.G)
The output is as follows:
call of __getattr__ with name = moo
call of __getattr__ with name = F
call of __getattr__ with name = G
Traceback (most recent call last):
line 18 in <module>
print(a.G)
line 15, in __getattr__
raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, name))
AttributeError: 'A' object has no attribute 'G'
But the error that should be raised is:
AttributeError: 'A' object has no attribute 'moo'
I know that properties and attributes in the __dict__ are attempted before __getattr__ is called in an error-free scenario.
It seems incorrect to me that when a property exists but fails, __getattr__ is still attempted instead of letting the error from the property to go through. How can this be avoided?
The initial error message that was generated about failing to get attribute 'foo' has been lost. The final error message 'A' object has no attribute 'G' is particularly misleading and annoying. How to implement __getattr__ in order to see the initial error?
(EDIT) A related problem is simultaneously to achieve that
hasattr(a, 'moo') returns False while hasattr(a, 'G') returns True or raises an exception of the missing 'moo' attribute. Does that make sense?
What is happening?
First, a little heads up as to why this happens. From the doc on __getattr__:
Called when the default attribute access fails with an AttributeError [...] or __get__() of a name property raises AttributeError.
In this case, since you are using #property, we are looking at an AttributeError raised from the __get__ method of the property F when trying to recover self.moo. This is what your call stack looks like at that moment.
__main__
a.G.__get__
a.F.__get__
a.__getattr__ # called with 'moo' <-- this is where the error is raised
The attribute getter protocol sees an error being raised from inside a.F.__get__, it thus fallback on calling a.__getattr__('F') and that despite the fact the error had been raised because of 'moo'. The same then happens for a.G.__get__
This behaviour is considered normal in Python, since the top-most property that failed to return a value is indeed a.G.
Solution
Now what you want is for an AttributeError raised by a __get__ method to bubble up instead of being caught. To do that you need not to have a __getattr__ method.
Thus, in this particular case, what you want to use is __getattribute__ instead.
Of course, with this solution you have to make sure yourself not to override an existing attribute.
class A:
#property
def F(self):
return self.moo # here should be an error
#property
def G(self):
return self.F
def __getattribute__(self, name):
print('call of __getattribute__ with name =', name)
if name == 'foo':
return 0
else:
return super().__getattribute__(name)
Example
A().G
Output
call of __getattribute__ with name = G
call of __getattribute__ with name = F
call of __getattribute__ with name = moo
Traceback (most recent call last):
...
AttributeError: 'A' object has no attribute 'moo'
Here's a hacky solution, replacing the AttributeError with another exception type:
from functools import wraps
def no_AttributeError(f):
#wraps(f)
def wrapped(self):
try:
return f(self)
except AttributeError as e:
raise Exception('AttributeError inside a property getter') from e
return wrapped
class A:
#property
#no_AttributeError
def F(self):
return self.moo # here should be an error
#property
#no_AttributeError
def G(self):
return self.F
def __getattr__(self, name):
print('call of __getattr__ with name =', name)
if name == 'foo':
return 0
raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, name))
a = A()
print(a.G)
This results in the following output:
call of __getattr__ with name = moo
Traceback (most recent call last):
File ".\test_getattr_redir.py", line 7, in wrapped
return f(self)
File ".\test_getattr_redir.py", line 17, in F
return self.moo # here should be an error
File ".\test_getattr_redir.py", line 28, in __getattr__
raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, name))
AttributeError: 'A' object has no attribute 'moo'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File ".\test_getattr_redir.py", line 31, in <module>
print(a.G)
File ".\test_getattr_redir.py", line 7, in wrapped
return f(self)
File ".\test_getattr_redir.py", line 22, in G
return self.F
File ".\test_getattr_redir.py", line 9, in wrapped
raise Exception('AttributeError inside a property getter') from e
Exception: AttributeError inside a property getter
As an addendum, to make it explicit why Python does what it does, here's an excerpt from the documentation:
[__getattr__ is called] when the default attribute access fails with an AttributeError (either __getattribute__() raises an AttributeError because name is not an instance attribute or an attribute in the class tree for self; or __get__() of a name property raises AttributeError). This method should either return the (computed) attribute value or raise an AttributeError exception.
(It looks like you know this but I think it's good to have it written out for other people running into the same issue.)
So that means when self.moo raises an AttributeError, it results in A.__getattr__(a, 'F') being called, which results into another AttributeError
Given the answers above, I have tried the following solution for the case when __getattr__ is already defined by the base class P that we cannot change.
class P:
def __getattr__(self, name):
print('call of __getattr__ with name =', name)
if name == 'foo':
return 0
raise AttributeError("Cannot recover attribute '{}'".format(name))
class A(P):
e = None
#property
def F(self):
return self.moo
#property
def G(self):
return self.F
def __getattr__(self, name):
raise A.e
def __getattribute__(self, name):
try:
return object.__getattribute__(self, name)
except AttributeError as e1:
try:
return P.__getattr__(self, name)
except AttributeError as e2:
A.e = AttributeError(str(e1) + ' -> ' + str(e2))
raise AttributeError
a = A()
print(a.G)
It replicates what python does when looking for attributes: the order of calls and semantics are kept. It only changes the final error message to
AttributeError: 'A' object has no attribute 'moo' -> Cannot recover attribute 'moo' -> Cannot recover attribute 'F' -> Cannot recover attribute 'G'
However, it might be causing more problems in the derived code than it is solving, so I don't know.

Accessing an object's attribute inside __setattr__

Python bit me today. I'm trying to access an object's attribute inside its __setattr__ implementation - I can't figure out how. This is what I've tried so far:
class Test1(object):
def __init__(self):
self.blub = 'hi1'
def __setattr__(self, name, value):
print self.blub
class Test2(object):
def __init__(self):
self.blub = 'hi2'
def __setattr__(self, name, value):
print object.__getattr__(self, 'blub')
class Test3(object):
def __init__(self):
self.blub = 'hi3'
def __setattr__(self, name, value):
print object.__getattribute__(self, 'blub')
class Test4(object):
def __init__(self):
self.blub = 'hi4'
def __setattr__(self, name, value):
print self.__getattr__('blub')
class Test5(object):
def __init__(self):
self.blub = 'hi5'
def __setattr__(self, name, value):
print self.__getattribute__('blub')
class Test6(object):
def __init__(self):
self.blub = 'hi6'
def __setattr__(self, name, value):
print self.__dict__['blub']
Testing:
try:
TestX().bip = 'bap'
except Exception as ex:
print ex
with X from 1 to 6
Output:
'Test1' object has no attribute 'blub'
type object 'object' has no attribute '__getattr__'
'Test3' object has no attribute 'blub'
'Test4' object has no attribute '__getattr__'
'Test5' object has no attribute 'blub'
'blub'
Any suggestions?
Because inside the __init__ it is trying to set blub which calls __setattr__; and it does not set anything but tries to access (and print) blub, finds nothing and raises the error. Check this:
>>> class Test2(object):
def __init__(self):
print "__init__ called"
self.blub = 'hi2'
print "blub was set"
def __setattr__(self, name, value):
print "__setattr__ called"
print self.blub
>>> Test2()
__init__ called
__setattr__ called
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
Test2()
File "<pyshell#9>", line 4, in __init__
self.blub = 'hi2'
File "<pyshell#9>", line 9, in __setattr__
print self.blub
AttributeError: 'Test2' object has no attribute 'blub'
>>>
OP, you haven't told us the whole story. You did not just run code like this:
TestX().bip = 'bap'
You ran code like this:
try:
TestX().bip = 'bap'
except Exception as ex:
print ex
There's a big difference. Why, you ask? Well, your output seems on first glance to indicate that Test6 works, and several comments and answers assumed that it did. Why does it appear to work? Reading the code, there's no way it should work. A closer inspection of the source code reveals that if it had worked, it should have printed hi6, not 'blub'.
I put a breakpoint at the print ex line in pdb to examine the exception:
(Pdb) ex
KeyError('blub',)
(Pdb) print ex
'blub'
For some reason print ex does not print KeyError: blub like you'd expect, but just 'blub', which was why Test6 appeared to work.
So we've cleared that up. In the future, please do not leave out code like this because it might be important.
All the other answers correctly point out that you have not set the attribute you're attempting to print, and that this is your problem. The answer you had accepted previously, before you accepted this answer istead, prescribed the following solution:
def __setattr__(self, name, value):
self.__dict__[name] = value
print self.__dict__[name]
While this solution does indeed work, it is not good design. This is because you might want to change the base class at some point, and that base class might might have important side effects when setting and/or getting attributes, or it might not store the attributes in self.__dict__ at all! It is better design to avoid messing around with __dict__.
The correct solution is to invoke the parent __setattr__ method, and this was suggested by at least one other answer, though with the wrong syntax for python 2.x. Here's how I'd do it:
def __setattr__(self, name, value):
super(Test6, self).__setattr__(name, value)
print getattr(self, name)
As you see I'm using getattr instead of __dict__ to look up attributes dynamically. Unlike using __dict__ directly, this will call self.__getattr__ or self.__getattribute__, as appropriate.
__setattr__ works on the class, so when you're over-riding - you need to make it actually set the attribute... otherwise, it'll never exist... eg:
object.__setattr__(self, name, value)
So, in your __init__, when you do self.blub = 'hi' that's in effect a no-op.
You never actually set the attribute in your __setattr__ so of course the object doesn't have it.
def __setattr__(self, name, value):
self.name = value
# this doesn't work, since it calls itself (the __setattr__)
def __setattr__(self, name, value):
super().__setattr__(name, value)
# call the default implementation directly in Py 3.x
def __setattr__(self, name, value):
super(TestX, self).__setattr__(name, value) # for Python 2.x
Of course doing this alone is good for nothing, you usually want to add some functionality oround this like some condition, debug printing, caching or whatever you need.
print self.__dict__['blub']
Prints out blub which is correct. You have to set the new value first, because python won't do that for you, like that:
def __setattr__(self, name, value):
self.__dict__[name] = value
print self.__dict__[name]
Then test6.blub = 'test' should print out test
Edit:
As suggested by #Lauritz you can also use
def __setattr__(self, name, value):
super(Test6, self).__setattr__(name, value)
print self.__dict__[name]
Which invokes the parent function, so if you superclass has already a __setattr__ function it won't get overridden.

Python class which instances' atributes can't be edited

What would be the most convenient way to create a class which instances' attributes can't be changed from outside the class (you could still get the value), so it'd be possible to call self.var = v inside the class' methods, but not ClassObject().var = v outside of the class?
I've tried messing with __setattr__() but if I override it, the name attribute cannot be initiated in the __init__() method. Only way would be to override __setattr__() and use object.__setattr__(), which I am doing at the moment:
class MyClass(object):
def __init__(self, name):
object.__setattr__(self, "name", name)
def my_method(self):
object.__setattr__(self, "name", self.name + "+")
def __setattr__(self, attr, value):
raise Exception("Usage restricted")
Now this solution works, and it's enough, but I was wondering if there's even a better solution. The problem with this one is: I can still call object.__setattr__(MyClass("foo"), "name", "foo_name") from anywhere outside the class.
Is there any way to totally prevent setting the variable to anything from outside of the class?
EDIT: Stupid me not mentioning I'm not looking for property here, some of you already answered it, however it's not enough for me since it will leave self._name changeable.
No, you cannot do this in pure python.
You can use properties to mark your attributes as read-only though; using underscore-prefixed 'private' attributes instead:
class Foo(object):
def __init__(self, value):
self._spam = value
#property
def spam(self):
return self._spam
The above code only specifies a getter for the property; Python will not let you set a value for Foo().spam now:
>>> class Foo(object):
... def __init__(self, value):
... self._spam = value
... #property
... def spam(self):
... return self._spam
...
>>> f = Foo('eggs')
>>> f.spam
'eggs'
>>> f.spam = 'ham'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Of course, you can still access the 'private' _spam attribute from outside:
>>> f._spam
'eggs'
>>> f._spam = 'ham'
>>> f.spam
'ham'
You could use the double underscore convention, where attribute names with __ at the start (but not at the end!) are renamed on compilation. This is not meant for making a attribute inaccessible from the outside, it's intent is to protect an attribute from being overwritten by a subclass instead.
class Foo(object):
def __init__(self, value):
self.__spam = value
#property
def spam(self):
return self.__spam
You can still access those attributes:
>>> f = Foo('eggs')
>>> f.spam
'eggs'
>>> f.__spam
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute '__spam'
>>> f._Foo__spam
'eggs'
>>> f._Foo__spam = 'ham'
>>> f.spam
'ham'
There is no strict way of doing encapsulation on Python. The best you can do is prepend 2 underscores __ to the intended to be private attributes. This will cause them to be mangled with the class name (_ClassName_AttribName), so if you try to use them on an inherited class, the base member won't be referenced. The names are not mangled if you use getattrib() or setattrib() though.
You can also override __dir()__ in order to hide them.
You can use properties to simulate such a behavior but like Martijn said, it'll be possible to access the variable directly.
Doing this is a signal of you not understanding Python philosophy, check this out.
Why Python is not full object-oriented?
The properties way:
http://docs.python.org/2/library/functions.html#property
class C(object):
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
raise Exception("Usage restricted")
x = property(getx, setx)

Categories

Resources