When and How to Use the Builtin Function Property() in Python

When and how to use the builtin function property() in python

In languages that rely on getters and setters, like Java, they're not supposed nor expected to do anything but what they say -- it would be astonishing if x.getB() did anything but return the current value of logical attribute b, or if x.setB(2) did anything but whatever small amount of internal work is needed to make x.getB() return 2.

However, there are no language-imposed guarantees about this expected behavior, i.e., compiler-enforced constraints on the body of methods whose names start with get or set: rather, it's left up to common sense, social convention, "style guides", and testing.

The behavior of x.b accesses, and assignments such as x.b = 2, in languages which do have properties (a set of languages which includes but is not limited to Python) is exactly the same as for getter and setter methods in, e.g., Java: the same expectations, the same lack of language-enforced guarantees.

The first win for properties is syntax and readability. Having to write, e.g.,

x.setB(x.getB() + 1)

instead of the obvious

x.b += 1

cries out for vengeance to the gods. In languages which support properties, there is absolutely no good reason to force users of the class to go through the gyrations of such Byzantine boilerplate, impacting their code's readability with no upside whatsoever.

In Python specifically, there's one more great upside to using properties (or other descriptors) in lieu of getters and setters: if and when you reorganize your class so that the underlying setter and getter are not needed anymore, you can (without breaking the class's published API) simply eliminate those methods and the property that relies on them, making b a normal "stored" attribute of x's class rather than a "logical" one obtained and set computationally.

In Python, doing things directly (when feasible) instead of via methods is an important optimization, and systematically using properties enables you to perform this optimization whenever feasible (always exposing "normal stored attributes" directly, and only ones which do need computation upon access and/or setting via methods and properties).

So, if you use getters and setters instead of properties, beyond impacting the readability of your users' code, you are also gratuitously wasting machine cycles (and the energy that goes to their computer during those cycles;-), again for no good reason whatsoever.

Your only argument against properties is e.g. that "an outside user wouldn't expect any side effects as a result of an assignment, usually"; but you miss the fact that the same user (in a language such as Java where getters and setters are pervasive) wouldn't expect (observable) "side effects" as a result of calling a setter, either (and even less for a getter;-). They're reasonable expectations and it's up to you, as the class author, to try and accommodate them -- whether your setter and getter are used directly or through a property, makes no difference. If you have methods with important observable side effects, do not name them getThis, setThat, and do not use them via properties.

The complaint that properties "hide the implementation" is wholly unjustified: most all of OOP is about implementing information hiding -- making a class responsible for presenting a logical interface to the outside world and implementing it internally as best it can. Getters and setters, exactly like properties, are tools towards this goal. Properties just do a better job at it (in languages that support them;-).

How does the @property decorator work in Python?

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 and a property object with just a getter:

>>> def getter(self): print('Get!')
...
>>> def setter(self, value): print('Set to {!r}!'.format(value))
...
>>> def deleter(self): print('Delete!')
...
>>> 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__)

When to use property builtin: auxiliary functions and generators

  • When you have a normal attribute and getting and/or setting it makes sense for a class's user, expose the attribute directly. One big reason that public members are anathema in some languages is that if you need to do something more complex later you would need an API change; in Python you can just define a property.

  • If you are using something you should abstract as attribute access, use property. If you want to make external state (your plot or website or something) aware of the change or if you are wrapping some library that uses members directly, properties might be the way to go.

  • If something isn't attribute-y, don't make it a property. There's no harm in making a method, and it can be advantageous: what it does is more obvious, you can pass around some bound method if you need, you can add keyword arguments without an API change.

    It's hard to imagine a situation where I would use a generator function as a property. The only way to have a normal attribute that would behave at all similarly would require quite a lot of complexity, so that case isn't very reminiscent of attribute access.

  • You point out you can use property for restricting access to some internal attribute _x. This can be true, but keep in mind

    • If you are going to do things like sanitize input for security or something important, explicit is better than implicit. You don't want to feel like code just-works when it comes to such things, because then you'll run into code that doesn't.

    • Sometimes people use property to implement read-only attributes. It's usually better just to have a normal attribute and realize you can't stop a user from doing something dumb and unsupported.

  • Nitpicking you might find interesting:

    • property isn't a keyword; it's a normal name you can rebind. This is sort of
      interesting since property isn't a syntax thing or anything: it's a normal class you could implement yourself in pure Python. It uses the same mechanism that makes methods work in Python—descriptors.

    • You describe what property does as "disguises class method getters and setters", which isn't quite the case. The things that property take are just normal functions, and needn't actually be defined in your class at all; property will pass self for you. Functions don't actually become methods until you look them up, when Python creates method objects on-the-fly. During the class definition, they are just functions. When you have a normal method it's called an "instance method"; in Python "class method" refers to another special thing sort of like properties that changes what happens when an attribute is looked up.

Is there a built-in function to print all the current properties and values of an object?

You are really mixing together two different things.

Use dir(), vars() or the inspect module to get what you are interested in (I use __builtins__ as an example; you can use any object instead).

>>> l = dir(__builtins__)
>>> d = __builtins__.__dict__

Print that dictionary however fancy you like:

>>> print l
['ArithmeticError', 'AssertionError', 'AttributeError',...

or

>>> from pprint import pprint
>>> pprint(l)
['ArithmeticError',
'AssertionError',
'AttributeError',
'BaseException',
'DeprecationWarning',
...

>>> pprint(d, indent=2)
{ 'ArithmeticError': <type 'exceptions.ArithmeticError'>,
'AssertionError': <type 'exceptions.AssertionError'>,
'AttributeError': <type 'exceptions.AttributeError'>,
...
'_': [ 'ArithmeticError',
'AssertionError',
'AttributeError',
'BaseException',
'DeprecationWarning',
...

Pretty printing is also available in the interactive debugger as a command:

(Pdb) pp vars()
{'__builtins__': {'ArithmeticError': <type 'exceptions.ArithmeticError'>,
'AssertionError': <type 'exceptions.AssertionError'>,
'AttributeError': <type 'exceptions.AttributeError'>,
'BaseException': <type 'exceptions.BaseException'>,
'BufferError': <type 'exceptions.BufferError'>,
...
'zip': <built-in function zip>},
'__file__': 'pass.py',
'__name__': '__main__'}

When is a property needed instead of just exposing the variable in Python?

What I think personally is that Python exposes variables as part of its philosophy, so you do not need to make properties out of everything. You can use properties to add logic to what was once a simple variable, or in order to keep the (client) code clean.

Classes like C in are useless outside tutorials/documentation.

(This is my own opinion. I don't know what is "considered" bad practice, as I am not an experienced developer or something)

Using getattr() to access built-in functions

The builtins module:

You could try importing builtins module:

>>> import builtins
>>> getattr(builtins, 'abs')
<built-in function abs>
>>>

As mentioned in the documentation:

This module provides direct access to all ‘built-in’ identifiers of Python; for example, builtins.open is the full name for the built-in function open(). See Built-in Functions and Built-in Constants for documentation.

So the above mentions that builtins.open is the open function. So abs is the same builtins.abs is the same thing as abs. But for gettatr, getattr(builtins, 'abs') is also the same as builtins.abs.

The original __bultins__ (NOT RECOMMENDED):

You could try getting from the __builtins__:

>>> getattr(__builtins__, 'abs')
<built-in function abs>
>>>

As mentioned in the documentation:

CPython implementation detail: Users should not touch __builtins__; it
is strictly an implementation detail. Users wanting to override values
in the builtins namespace should import the builtins module and modify
its attributes appropriately.

The builtins namespace associated with the execution of a code block
is actually found by looking up the name __builtins__ in its global
namespace; this should be a dictionary or a module (in the latter case
the module’s dictionary is used). By default, when in the main
module, __builtins__ is the built-in module builtins; when in any
other module, __builtins__ is an alias for the dictionary of the
builtins module itself.

As you can see, it's not recommended, also usually the __builtins__ is a dict, rather than a module.

If you write your code in modules, __builtins__ would return dictionary aliases of the builtins module, which would give something like: {..., 'abs': <built-in function abs>, ...}.

More on getattr:

Just to have more idea about getattr, as mentioned in the documentation:

Return the value of the named attribute of object. name must be a string. If the string is the name of one of the object’s attributes, the result is the value of that attribute. For example, getattr(x, 'foobar') is equivalent to x.foobar. If the named attribute does not exist, default is returned if provided, otherwise AttributeError is raised.

So:

>>> import builtins
>>> getattr(builtins, 'abs')
<built-in function abs>
>>>

Is the same as:

>>> import builtins
>>> builtins.abs
<built-in function abs>
>>>

So you might be wondering since:

>>> abs
<built-in function abs>

Gives the same thing, why we can't just do:

getattr(abs)

The reason we can't do that is that that getattr is suppose to be calling methods/functions/classes of a classes/modules.

The reason using getattr(builtins, 'abs') works is because builtins is a module and abs is a class/method, it stores all the built-in Python keywords as methods in that module.

All the keywords are showed on this page of the documentation.

Using Python property() inside a method

Properties work using the descriptor protocol, which only works on attributes of a class object. The property object has to be stored in a class attribute. You can't "override" it on a per-instance basis.

You can, of course, provide a property on the class that gets an instance attribute or falls back to some default:

class C(object):
_default_x = 5
_x = None
@property
def x(self):
return self._x or self._default_x
def alpha(self, beta):
self._x = beta

Why does __self__ of built-in functions return the builtin module it belongs to?

I believe I've found the reason for the discrepancy thanks to issue14003. It seems it is due to a change in the module creation API from Python 2 to 3.

In Python 2, during the construction of modules with Py_InitModule4, a PyObject *self argument was available that could have the value of None if writters of extention modules wished, as documented:

If self is non-NULL, it will be passed to the functions of the module as their (otherwise NULL) first parameter

Most of the built-in standard library modules apparently chose that path and as such the result of builtin_function.__self__ is None:

mod = Py_InitModule4("__builtin__", builtin_methods,
builtin_doc, (PyObject *)NULL,
PYTHON_API_VERSION);

In Python 3, the API for creating modules changed and that option disappeared. The function for creating modules, PyModule_Create2, doesn't take a self argument that's allowed to be None. Instead, it calls PyModule_AddFunctions (which calls the internal _add_methods_to_object to add the functions to the module) and unconditionally sets the __self__ attribute on the builtin functions to be the module.

So that's why for len the builtins module is returned. AFAIK, it isn't used anywhere inside the function body so it's purpose isn't special in any way.


Props to @user2357112 for making me feel silly, builtin_method and builtin_function are actually the same struct so it makes sense for builtin functions to share the __self__ attribute found in methods.

The function type, on the other hand, really has no point in having it since it isn't shared in any way with the method type.

How can I extend Python's built-in property class by adding an argument to it without breaking it?

Here's my implementation. It uses the Python implementation of property outlined in the Descriptor HOWTO. I've added a wrapper around this that accepts a function or type which will be called when setting or getting a value. In the wrapper's closure, I defined the special_property_descriptor class which has a .type. This is the function/type given to the wrapper outside. Finally, this property descriptor class is returned by the wrapper having had it's .type attribute set.

def special_property(cls):
class special_property_descriptor(object):
type = cls
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 __set_name__(self, owner, name):
self.name = name

def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError('unreadable attribute')
r = self.fget(obj)
try:
return self.type(r)
except Exception:
raise TypeError(f'attribute {self.name} must '
f'of type {self.type.__name__}')

def __set__(self, obj, value):
try:
value = self.type(value)
except Exception:
raise TypeError(f'attribute {self.name} must '
f'of type {self.type.__name__}')
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__)
return special_property_descriptor

Obviously, you can amend the functionality here. In my example, the descriptor will attempt to cast the value to the desired type before setting/getting it. If you want to, you could do an isinstance(value, self.type) to only enforce type and not attempt to convert invalid values.



Related Topics



Leave a reply



Submit