Python never stops surprising me. While doing my master IT project, I was looking for a way to add dynamic properties to classes (we're speaking of new-style classes of course!). I did a little research and here are the results:
I love Python 3 :)
Even Python 2.x is still used everywhere, it is time to move to Python 3. I made the code fully Python2.6 - compatible, but if you'd like to have a nice output of print(..) function please use
statement.
A simple class
This is a simple class with one property:
The output is:
We keep the local values in attributes that start with the underscore, e.g. self._name. Usually this is a way I separate "private" and "public" members of a class/object [1] (of course this "private" members remain "public").
This code does the same as the code above:
There are two core changes:
Dynamic properties class
Finally, let's write a class called "Properties" that will allow adding dynamic properties.
The properties will require local ("private") fields. We can use the same scheme as above, e.g. for property name, the private member of a class is _name:
The trick in add_property(..) is that we create two lambda objects (those could also be anonymous functions) which use the self._get_property and self._set_property methods with particular value of name argument.
Let's play with this class:
The output is:
A practical usage
After all, what is a practical usage of dynamic properties? I'm sure you may have thought of that if you're reading this post now :) Here is a small example of "locking" the properties:
The output is:
I hope that you learned a thing or two while reading this post. Please don't hesitate to leave comments!
References
1. Reserved classes of identifiers
2. property([fget[, fset[, fdel[, doc]]]])
Even Python 2.x is still used everywhere, it is time to move to Python 3. I made the code fully Python2.6 - compatible, but if you'd like to have a nice output of print(..) function please use
1
from __future__ import print_function
A simple class
This is a simple class with one property:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
class Man(object): def __init__(self, name): self._name = name @property def name(self): return self._name @name.setter def name(self, value): self._name = value m = Man('Alex Black') print(m.name) m.name = 'Kyra Brown' print(m.name)
The output is:
1 2
Alex Black Kyra Brown
We keep the local values in attributes that start with the underscore, e.g. self._name. Usually this is a way I separate "private" and "public" members of a class/object [1] (of course this "private" members remain "public").
This code does the same as the code above:
1 2 3 4 5 6 7 8 9 10 11
class Man(object): def __init__(self, name): self._name = name def _name_get(self): return getattr(self, '_name') def _name_set(self, value): setattr(self, '_name', value) name = property(fget = _name_get, fset = _name_set)
There are two core changes:
- The _name attribute of an object is read and set by getattr(..) and setattr(..) functions.
- The @property decorator is replaced by the built-in property(...) function[2] (which is actually "behind" that decorator)
Dynamic properties class
Finally, let's write a class called "Properties" that will allow adding dynamic properties.
The properties will require local ("private") fields. We can use the same scheme as above, e.g. for property name, the private member of a class is _name:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class Properties(object): def add_property(self, name, value): # create local fget and fset functions fget = lambda self: self._get_property(name) fset = lambda self, value: self._set_property(name, value) setattr(self.__class__, name, property(fget, fset)) # add property to self setattr(self, '_' + name, value) # add corresponding local variable def _set_property(self, name, value): setattr(self, '_' + name, value) def _get_property(self, name): return getattr(self, '_' + name)
The trick in add_property(..) is that we create two lambda objects (those could also be anonymous functions) which use the self._get_property and self._set_property methods with particular value of name argument.
Let's play with this class:
1 2 3 4 5 6 7 8 9 10 11
po = Properties() po.add_property('user', 'noname') po.add_property('speed', 50) print(po.user, po.speed) po.speed = 100 po.name = 'Alex Black' print(po.user, po.speed) print(po._user, po._speed)
The output is:
1 2 3
noname 50 Alex Black 100 Alex Black 100
A practical usage
After all, what is a practical usage of dynamic properties? I'm sure you may have thought of that if you're reading this post now :) Here is a small example of "locking" the properties:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
class Properties(object): def __init__(self, lock = False): self.lock = lock def add_property(self, name, value): # create local fget and fset functions # You can write this instead of lambdas # def fget(self): # return self._get_property(name) # def fset(self, value): # self._set_property(name, value) fget = lambda self: self._get_property(name) fset = lambda self, value: self._set_property(name, value) setattr(self.__class__, name, property(fget, fset)) setattr(self, '_' + name, value) def _set_property(self, name, value): if not self.lock: # locked? setattr(self, '_' + name, value) else: print('Cannot change "{0}": properties are locked'.format(name)) def _get_property(self, name): return getattr(self, '_' + name) po = Properties() po.add_property('user', 'noname') po.add_property('speed', 50) print(po.user, po.speed) po.user = 'a user' po.speed = 200 print(po.speed) po.lock = True po.user = 'a user'
The output is:
1 2 3
noname 50 a user 200 Cannot change "user": properties are locked
I hope that you learned a thing or two while reading this post. Please don't hesitate to leave comments!
References
1. Reserved classes of identifiers
2. property([fget[, fset[, fdel[, doc]]]])
MONDAY, 12 APRIL 2010, 02:27
There are many more interesting features in Python and the standard library to check out, Zaur :)
Have you already tried "collections" and "functool" modules? They're really nice :)
TUESDAY, 13 APRIL 2010, 02:36
Hei, Vova!
I know new OrderedDict collection, but haven't explored that yet :) Thanks for pointing that out!