To be honest, there is nothing wrong with this kind of approach, but it just isn't pythonic and elegant. The purpose of this article is to give a brief introduction on more advanced Python topics dealing with this problem. In most cases, you will find them to be just a syntactic sugar around setters and getters, but knowing more about these techniques will help you to read and understand code from more experienced Python programmers, and writing more effective and elegant code of your own.
- setters and getters
Person with attribute name that verifies the name is always properly capitalized.class Person:
def setName(self, name):
self._name = " ".join([e.capitalize() for e in name.split()])
def getName(self):
return self._name
>>> p = Person()
>>> p.setName("joe smith")
>>> p.getName()
'Joe Smith'
As shown, this just works, but we need to call functions instead using the attribute name directly. The rest of the examples will show how to avoid this kind of programming.
__setattr__/__getattr__and__setattribute__/__getattribute__
__setattr__ or __setattribute__. Of course, if your goal is to implement something inside getter, and not setter, you would use the __getattr__ and __getattribute__ methods instead. Let's continue with our example - first we will show how to use __setattr__.class Person:
def __setattr__(self, name, value):
if name == "name":
value = " ".join([e.capitalize() for e in value.split()])
self.__dict__[name] = value
Note that simply assigning value to the attribute wouldn't work, as it would loop in the
__setattr__ call indefinitely, so we must add it to the __dict__ explicitly.Approach with
__setattribute__ is very similar, but there is one notable difference. While __setattr__ and __getattr__ will be called only for those attributes that are undefined, i.e. not on an instance and not inherited from a class, __setattribute__ and __getattribute__ will be called every time you try to access the attribute. In this matter, __setattribute__ and __getattribute__ are more generic as they will be called every time. Again, you need to be careful to avoid loops. While implementing setter with __setattribute__ will be the same as with __setattr__, there is a notable difference while implementing the __getattribute__ method. Here we can't use __dict__[name] as calling it would trigger the __getattribute__ again, causing a loop. To avoid this, you would use the object.__getattribute__ instead of self.__getattribute__.- Properties
Properties allow us to reroute the attribute's set, get or even delete operations to the functions of our choice. To do this we will use the
property built-in. The basic syntax that can describe the use of properties is (all of the arguments are defaulting to None, and we will silently ignore the last two arguments for now):attribute = property(get, set, del, doc)
How to use this? Well, it's pretty straightforward. Our example from previous sections rewritten to use properties looks like this:
class Person(object):
def setName(self, name):
self._name = " ".join([e.capitalize() for e in name.split()])
def getName(self):
return self._name
name = property(getName, setName)
Note that we have derived our
Person object from object, stating that we want to use is it as the new style class. This is required for properties to function normally (of course, if you're using Python 3.0+ you won't need this as new style classes are implied).Properties can also be coded with decorators. In this case, base decorator
@property is used for getter, and name.setter is used for setter. Again, the following example is merely syntactic sugar for the previous example:class Person(object):
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = " ".join([e.capitalize() for e in name.split()])
In contrast with the previous methods, properties manage only a single attribute, and therefore are easier to write as you don't need to remember all the gotchas of
__getattr__, __getattribute__ etc.- Descriptors
The final method we will examine are descriptors. In their essence, descriptors are superset of properties, and
property built-in we have used before is only a specific case of descriptors. Descriptors allow us to reroute attribute's get and set (or even delete) operations to methods of another class.The template code for coding descriptors is:
class Descriptor:
def __get__(self, instance, owner):
...
def __set__(self, instance, value):
...
def __delete__(self, instance):
...
We will again ignore attribute deletion for now. A class containing any of these three methods is descriptor. Omitting the
__get__ or the __delete__ method will cause that particular type of access as unsupported, however omitting the __set__ method will not render the attribute as read only, it will only allow its name to be redefined (and therefore descriptor will be hidden). If you want to make your attribute read only, you should implement __set__ with pass statement or raise an exception on assignment.Argument names for descriptor methods are somewhat strange. Let's start with
__get__ method. The owner argument is the name of the class to which the descriptor instance is attached, and the instance argument is the instance of the object through which the attribute was accessed. Meaning of the instance argument in the __set__ method is the same, and the value argument holds the value to be assigned to the attribute.How do we use a descriptor? We just assign it to the attribute name. Our example, this time with descriptors, looks like this:
class Descriptor(object):
def __get__(self, instance, owner):
return instance._name
def __set__(self, instance, value):
instance._name = " ".join([e.capitalize() for e in value.split()])
class Person(object):
name = Descriptor()
Again, we must use new style classes if we're not using Python newer or equal to Python 3.0.
An interesting thing with descriptors is that they can hold state. OK, technically speaking every of the previously described methods can also hold state, at least in the form of global variables, but by using descriptors you can use them elegantly in their own namespace - the descriptor class and instance.
- Conclusion
This is by far not a complete tutorial on using any of techniques described here, as there are many more things to be described, but they are simply out of the scope of this article. You should read the official Python documentation or some of the better books on Python programming (I would strongly recommend Learning Python, 4th ed. from O'Reilly) to get the complete overview of managed attributes possibilities.
0 comments:
Post a Comment