So, I'm playing with decorators in Python 2.6, and I'm having some trouble getting them to work. Here is my class file:
class testDec:
#property
def x(self):
print 'called getter'
return self._x
#x.setter
def x(self, value):
print 'called setter'
self._x = value
What I thought this meant is to treat x like a property, but call these functions on get and set. So, I fired up IDLE and checked it:
>>> from testDec import testDec
from testDec import testDec
>>> t = testDec()
t = testDec()
>>> t.x
t.x
called getter
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "testDec.py", line 18, in x
return self._x
AttributeError: testDec instance has no attribute '_x'
>>> t.x = 5
t.x = 5
>>> t.x
t.x
5
Clearly the first call works as expected, since I call the getter, and there is no default value, and it fails. OK, good, I understand. However, the call to assign t.x = 5 seems to create a new property x, and now the getter doesn't work!
What am I missing?
You seem to be using classic old-style classes in python 2. In order for properties to work correctly you need to use new-style classes instead (in python 2 you must inherit from object). Just declare your class as MyClass(object):
class testDec(object):
#property
def x(self):
print 'called getter'
return self._x
#x.setter
def x(self, value):
print 'called setter'
self._x = value
It works:
>>> k = testDec()
>>> k.x
called getter
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/devel/class_test.py", line 6, in x
return self._x
AttributeError: 'testDec' object has no attribute '_x'
>>> k.x = 5
called setter
>>> k.x
called getter
5
>>>
Another detail that might cause problems is that both methods need the same name for the property to work. If you define the setter with a different name like this it won't work:
#x.setter
def x_setter(self, value):
...
And one more thing that is not completely easy to spot at first, is the order: The getter must be defined first. If you define the setter first, you get name 'x' is not defined error.
Just a note for other people who stumble here looking for this exception: both functions need to have the same name. Naming the methods as follows will result in an exception:
#property
def x(self): pass
#x.setter
def x_setter(self, value): pass
Instead give both methods the same name
#property
def x(self): pass
#x.setter
def x(self, value): pass
It is also important to note that the order of the declaration matters. The getter must be defined before the setter in the file or else you will get a NameError: name 'x' is not defined
You need to use new-style classes which you do by deriving your class from object:
class testDec(object):
....
Then it should work.
In case anybody comes here from google, in addition to the above answers I would like to add that this needs careful attention when invoking the setter from the __init__ method of your class based on this answer
Specifically:
class testDec(object):
def __init__(self, value):
print 'We are in __init__'
self.x = value # Will call the setter. Note just x here
#self._x = value # Will not call the setter
#property
def x(self):
print 'called getter'
return self._x # Note the _x here
#x.setter
def x(self, value):
print 'called setter'
self._x = value # Note the _x here
t = testDec(17)
print t.x
Output:
We are in __init__
called setter
called getter
17
Related
When defining a builtin python property using the #property, how does the property object differentiates the setter from the getter method, provided that they are overloaded (have the same name)?
class A:
def __init__(self):
self._x = 12
#property
def x(self) -> int:
return self._x
#notifiable # strangely this stacks on both setter and getter
#x.setter
def x(self, val: int):
self._x = val
If I define a custom property decorator, say:
class my_property:
def __init__(self, getter):
print("__init__ getter %s:" % getter)
def setter(self, setter: FunctionType):
print("setter: %s" % setter)
class B:
def __init__(self):
self._val = 44
#my_property
def x(self):
return self._val
#x.setter
def x(self, val):
self._val = val
Executing the code results in the following output
__init__ getter <function B.x at 0x7ff80c5e1620>:
setter: <function B.x at 0x7ff80c5e1620>
Both the getter and the setter funtions passed to the decorator are the same funtion, but they should be different functions.
If I use the annotation like this:
class C:
def __init__(self):
self._val = 44
#my_property
def x(self):
return self._val
#x.setter
def set_x(self, val):
self._val = val
A different function is printed, as expected.
__init__ getter <function C.x at 0x7f529132c6a8>:
setter: <function C.set_x at 0x7f529132c6a8>
How does python solves this issue with the builtin #property? Is the decorator treated differently from user decorators ?
The reason you're seeing what you're seeing here is because you don't keep a reference to the getter anywhere. This means that once the __init__ method ends, there's no more reference to the first B.x, (i.e. the refcount is zero), so the function is released. Since the original getter function has been released, Python is free to reuse the exact same memory address for another object/function, which is what happens in this case.
If you modify my_property to keep a reference to the original getter method as such:
class my_property:
def __init__(self, getter):
print("__init__ getter %s:" % getter)
self.getter = getter
def setter(self, setter: FunctionType):
print("setter: %s" % setter)
you'll see that the function name (B.x) is still the same (which is ok, as python doesn't use the function name to uniquely identify a function), however the memory address of the two functions are different:
__init__ getter <function B.x at 0x7f9870d60048>
setter: <function B.x at 0x7f9870d600d0>
Is the decorator treated differently from user decorators ?
No, property just a regular decorator. However, if you want to reimplement the property decorator, you'd probably be interested in the descriptor protocol (there's a pure python reimplementation of #property in that page).
Property wrapper methods is a nice feature to have in python, this question is not the subject of such question, I need to know if it is possible to use it with python destructor __del__, a practical example could be a database connection, for simplification purposes let's say we have the following class:
class Point(object):
"""docstring for Point"""
def __init__(self, x, y):
self.x = x
self.y = y
#property
def x(self):
print('x getter got called')
return self._x
#x.setter
def x(self, x):
print('x setter got called')
self._x = x
def __str__(self):
return '[%s:%s]' % (self.x, self.y)
def __del__(self):
print('destructor got called')
del self.x
del self.y
as a test case let's say we have:
a = Point(4, 5)
del a
The output is:
Exception AttributeError: "can't delete attribute" in <bound method Point.__del__ of <__main__.Point object at 0x7f8bcc7e5e10>> ignored
if we deleted the property part, everything goes smooth.
can someone show where's the problem?
Add a deleter to your property x that does the clean up. By default, if no fdel is defined for the property, the AttributeError you see is raised:
#x.deleter
def x(self):
print("x deleter got called")
del self._x
If you don't use #x.deleter to define the delete behavior (like you did with #x.setter) then it's impossible to delete the property.
Before this is flagged as a duplicate, I know this question has been answered before, but the solutions provided there don't seem to apply to my case. I'm trying to programmatically set class properties. I know I can use property for that, so I thought about doing this:
class Foo:
def __init__(self, x):
self._x = x
def getx(): return self._x
def setx(y): self._x = y
self.x = property(fget=getx, fset=setx)
However, when I run this interactively, I get:
>>> f = Foo(42)
>>> f.x
<property object at 0x0000000>
>>> f._x
42
>>> f.x = 1
>>> f.x
1
Is there any way to solve this?
Edit:
I feel I may have left out too much, so here's what I am actually trying to reach. I have a class with a class variable called config, which contains configuration values to set as properties. The class should be subclassed to implement the config variable:
class _Base:
config = ()
def __init__(self, obj, **kwargs):
self._obj = obj()
for kwarg in kwargs:
# Whatever magic happens here to make these properties
# Sample implementation
class Bar(_Base):
config = (
"x",
"y"
)
def __init__(self, obj, x, y):
super().__init__(obj, x=x, y=y)
Which now allows for manipulation:
>>> b = Bar(x=3, y=4)
>>> b.x
3
>>> # Etc.
I'm trying to keep this as DRY as possible because I have to subclass _Base a lot.
property objects are descriptors, and descriptors are only invoked when defined on the class or metaclass. You can't put them directly on an instance; the __getattribute__ implementation for classes simply don't invoke the binding behaviour needed.
You need to put the property on the class, not on each instance:
class Foo:
def __init__(self, x):
self._x = x
#property
def x(self): return self._x
#x.setter
def x(self, y): self._x = y
If you have to have a property that only works on some instances, you'll have to alter your getter and setter methods to vary behaviour (like raise an AttributeError for when the state of the instance is such that the attribute should 'not exist').
class Bar:
def __init__(self, has_x_attribute=False):
self._has_x_attribute = has_x_attribute
self._x = None
#property
def x(self):
if not self._has_x_attribute:
raise AttributeError('x')
return self._x
#x.setter
def x(self, y):
if not self._has_x_attribute:
raise AttributeError('x')
self._x = y
The property object still exists and is bound, but behaves as if the attribute does not exist when a flag is set to false.
I have a class A made by someone else, that I cannot edit:
class A:
def __init__(self, x):
self.x = x
Now I'm trying to inherit my own class B from A, and have x as a property instead of an attribute.
Is this possible?
I already tried:
class B(A):
def __init__(self, x):
super().__init__(x)
#property
def x(self):
return super().x
#x.setter
def x(self, x):
super().x = x
print(x) # my stuff goes here
But as I expected, it's not possible: AttributeError: 'super' object has no attribute 'x'
Is there any other method, some workaroud maybe?
No, you cannot use super() for anything other than class attributes; x is an instance attribute, and there is no inheritance mechanism for attributes.
Instance attributes live in a single namespace; there is no 'parent instance' attribute namespace.
You can still reach the attribute in the instance __dict__ object:
class B(A):
#property
def x(self):
return self.__dict__['x']
#x.setter
def x(self, x):
self.__dict__['x'] = x
print(x) # my stuff goes here
A property is a data descriptor, meaning that it is looked up before the instance attributes are consulted (see the descriptor howto), but you can still access it directly.
I have a class like this, in which I have declared a property x, and overridden __delattr__:
class B(object):
def __init__(self, x):
self._x = x
def _get_x(self):
return self._x
def _set_x(self, x):
self._x = x
def _del_x(self):
print '_del_x'
x = property(_get_x, _set_x, _del_x)
def __delattr__(self, name):
print '__del_attr__'
Now when I run
b = B(1)
del b.x
Only __del_attr__ get invoked, anybody knows why and how to solve this problem?
You have to call the __delattr__ of your ancestor to achieve this correctly.
class B(object):
.....
def __delattr__(self, name):
print '__del_attr__'
super(B, self).__delattr__(name) # explicit call to ancestor (not automatic in python)
And then running :
b = B(1)
del b.x
Will output:
__del_attr__
_del_x
_del_x is called from the default __del_attr__. But since you have overridden __del_attr__, the onus is on you to call _del_x from inside your __del_attr__.