Learning Python - Mark Lutz [431]
... def getage(self):
... return 40
... age = property(getage, None, None, None) # get, set, del, docs
...
>>> x = newprops()
>>> x.age # Runs getage
40
>>> x.name # Normal fetch
AttributeError: newprops instance has no attribute 'name'
For some coding tasks, properties can be less complex and quicker to run than the traditional techniques. For example, when we add attribute assignment support, properties become more attractive—there’s less code to type, and no extra method calls are incurred for assignments to attributes we don’t wish to compute dynamically:
>>> class newprops(object):
... def getage(self):
... return 40
... def setage(self, value):
... print('set age:', value)
... self._age = value
... age = property(getage, setage, None, None)
...
>>> x = newprops()
>>> x.age # Runs getage
40
>>> x.age = 42 # Runs setage
set age: 42
>>> x._age # Normal fetch; no getage call
42
>>> x.job = 'trainer' # Normal assign; no setage call
>>> x.job # Normal fetch; no getage call
'trainer'
The equivalent classic class incurs extra method calls for assignments to attributes not being managed and needs to route attribute assignments through the attribute dictionary (or, for new-style classes, to the object superclass’s __setattr__) to avoid loops:
>>> class classic:
... def __getattr__(self, name): # On undefined reference
... if name == 'age':
... return 40
... else:
... raise AttributeError
... def __setattr__(self, name, value): # On all assignments
... print('set:', name, value)
... if name == 'age':
... self.__dict__['_age'] = value
... else:
... self.__dict__[name] = value
...
>>> x = classic()
>>> x.age # Runs __getattr__
40
>>> x.age = 41 # Runs __setattr__
set: age 41
>>> x._age # Defined: no __getattr__ call
41
>>> x.job = 'trainer' # Runs __setattr__ again
>>> x.job # Defined: no __getattr__ call
Properties seem like a win for this simple example. However, some applications of __getattr__ and __setattr__ may still require more dynamic or generic interfaces than properties directly provide. For example, in many cases, the set of attributes to be supported cannot be determined when the class is coded, and may not even exist in any tangible form (e.g., when delegating arbitrary method references to a wrapped/embedded object generically). In such cases, a generic __getattr__ or a __setattr__ attribute handler with a passed-in attribute name may be preferable. Because such generic handlers can also handle simpler cases, properties are often an optional extension.
For more details on both options, stay tuned for Chapter 37 in the final part of this book. As we’ll see there, it’s also possible to code properties using function decorator syntax, a topic introduced later in this chapter.
__getattribute__ and Descriptors
The __getattribute__ method, available for new-style classes only, allows a class to intercept all attribute references, not just undefined references, like __getattr__. It is also somewhat trickier to use than __getattr__: it is prone to loops, much like __setattr__, but in different ways.
In addition to properties and operator overloading methods, Python supports the notion of attribute descriptors—classes with __get__ and __set__ methods, assigned to class attributes and inherited by instances, that intercept read and write accesses to specific attributes. Descriptors are in a sense a more general form of properties; in fact, properties are a simplified way to define a specific type of descriptor, one that runs functions on access. Descriptors are also used to implement the slots feature we met earlier.
Because properties, __getattribute__, and descriptors are somewhat advanced topics, we’ll defer the rest of their coverage, as well as more on properties, to Chapter 37 in the final part of this book.
Metaclasses
Most of the changes and feature additions of new-style classes integrate with the notion of subclassable types mentioned earlier in this chapter, because subclassable types and new-style classes were introduced in conjunction