Note that you don't have to use property as a decorator. You can quite happily use it the old way and expose the individual methods in addition to the property:
class A:
def get_x(self, neg=False):
return -5 if neg else 5
x = property(get_x)
>>> a = A()
>>> a.x
5
>>> a.get_x()
5
>>> a.get_x(True)
-5
This may or may not be a good idea depending on exactly what you're doing with it (but I'd expect to see an excellent justification in a comment if I came across this pattern in any code I was reviewing)
Answer from ncoghlan on Stack OverflowNote that you don't have to use property as a decorator. You can quite happily use it the old way and expose the individual methods in addition to the property:
class A:
def get_x(self, neg=False):
return -5 if neg else 5
x = property(get_x)
>>> a = A()
>>> a.x
5
>>> a.get_x()
5
>>> a.get_x(True)
-5
This may or may not be a good idea depending on exactly what you're doing with it (but I'd expect to see an excellent justification in a comment if I came across this pattern in any code I was reviewing)
I think you did not fully understand the purpose of properties.
If you create a property x, you'll accessing it using obj.x instead of obj.x().
After creating the property it's not easily possible to call the underlying function directly.
If you want to pass arguments, name your method get_x and do not make it a property:
def get_x(self, neg=False):
return 5 if not neg else -5
If you want to create a setter, do it like this:
class A:
@property
def x(self): return 5
@x.setter
def x(self, value): self._x = value
In the Python docs: https://docs.python.org/3/library/functions.html#property , we see that the property method takes 4 arguments: a getter, setter, deleter, and docstring. However, in the example they provide (and other similar ones I find), we only ever seem to actually pass the getter directly into the decorator:
@property
def x(self):
"""I'm the 'x' property."""
return self._xAfter that, we modify that property object's setters, deleters, etc:
@x.setter
def x(self, value):
self._x = valueCertainly I can follow and use this process, but I'm a little confused about how it's working, or why it has to be set up this way. If the property method takes in 4 arguments, why do we only ever actually pass it 1, the getter? Is it impossible to pass the property decorator the getter AND the setter AND the deleter in one go (is this a pie syntax issue)?
Why does Python @property decorator take 4 arguments if we only pass it 1?
@property with @classmethod ?
Type hints and methods decorated with @property
How does a decorator with arguments work behind the screen?
Videos
The property() function returns a special descriptor object:
>>> property()
<property object at 0x10ff07940>
It is this object that has extra methods:
>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>
These act as decorators too. They return a new property object:
>>> property().getter(None)
<property object at 0x10ff079f0>
that is a copy of the old object, but with one of the functions replaced.
Remember, that the @decorator syntax is just syntactic sugar; the syntax:
@property
def foo(self): return self._foo
really means the same thing as
def foo(self): return self._foo
foo = property(foo)
so foo the function is replaced by property(foo), which we saw above is a special object. Then when you use @foo.setter(), what you are doing is call that property().setter method I showed you above, which returns a new copy of the property, but this time with the setter function replaced with the decorated method.
The following sequence also creates a full-on property, by using those decorator methods.
First we create some functions:
>>> def getter(self): print('Get!')
...
>>> def setter(self, value): print('Set to {!r}!'.format(value))
...
>>> def deleter(self): print('Delete!')
...
Then, we create a property object with only a getter:
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True
Next we use the .setter() method to add a setter:
>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True
Last we add a deleter with the .deleter() method:
>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True
Last but not least, the property object acts as a descriptor object, so it has .__get__(), .__set__() and .__delete__() methods to hook into instance attribute getting, setting and deleting:
>>> class Foo: pass
...
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!
The Descriptor Howto includes a pure Python sample implementation of the property() type:
class Property: "Emulate PyProperty_Type() in Objects/descrobject.c" def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel if doc is None and fget is not None: doc = fget.__doc__ self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError("unreadable attribute") return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError("can't set attribute") self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError("can't delete attribute") self.fdel(obj) def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.__doc__) def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.__doc__) def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.__doc__)
The documentation says it's just a shortcut for creating read-only properties. So
@property
def x(self):
return self._x
is equivalent to
def getx(self):
return self._x
x = property(getx)