I spent the last two days studying (and relearning) advanced Python and Smalltalk.
Here are some of my Python notes.
Disclaimer: I'm not a Python master, though I want to be one.
Classes and Objects
I thought that when you create a class without specifying a parent class, it sets object as the parent class, ala Ruby. I was wrong. Classes that inherit from object are new style classes.
Check this out:
>>> class A: pass
...
>>> class B(object): pass
...
>>> dir(A)
['__doc__', '__module__']
>>> dir(B)
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__']
And you can easily add attributes to objects (instances of a class).
>>> class Cat: pass
...
>>> felix = Cat()
>>> felix.name = 'Felix'
>>> felix.name
'Felix'
>>> def meow(self):
... if hasattr(self, 'name'):
... print "%s: meowwww" % self.name
... else:
... print "purrrrr"
...
>>> Cat.meow = meow
>>> dir(felix)
['__doc__', '__module__', 'meow', 'name']
>>> felix.meow()
Felix: meowwww
>>> sylvester = Cat()
>>> sylvester.meow()
purrrrr
I'm thinking of metaprogramming in Python right now. Like, how can I dynamically add a dynamically named method to a class? setattr maybe? And the last time I checked, decorators and descriptors are the most common metaprogramming techniques used in Python.
Descriptors
Here's a guide to Python descriptors.
Descriptors allow you to create attributes of an object that can do some magic when those attributes are read, written, or deleted.
Here's my example:
class EncryptionKey(object):
def __set__(self, inst, value):
self.key = value
if hasattr(inst, 'encrypt'):
inst.encrypt(value)
else:
pass
def __get__(self, inst, objtype):
return self.key
class EncryptableString(str):
key = EncryptionKey()
def encrypt(self, key):
self.encrypted = __import__('string').join([chr(ord(c)^key) for c in self], '')
s = EncryptableString("hello")
s.key = 67
print s.encrypted
# output
+&//,
When s.key is set to 67, encrypt is called by the EncryptableString object, which encrypts the current object and the result is saved in the object's encryted attribute. The descriptor protocol allows this callback method to be called. Nice.
Note that for descriptors to work in a class, the class must be a new style class, i.e. a class that inherits from object or type.
Mixin Classes
Got this from here.
You can create mixin classes using multiple inheritance.
class EatingMixin(object):
def eat(self, food="5 gallons of ice cream"):
print "I'm now eating %s." % food
return self
class WalkingMixin(object):
def walk(self, distance=5):
print "I'm now walking %d miles."
return self
class Person(EatingMixin, WalkingMixin):
def __init__(self, name):
super(Person, self).__init__(self)
self.name = name
print "Hi! I'm %s." % self.name
tim = Person('Tim')
tim.walk()
tim.eat()
tim.eat('lots of vegetables')
# Output:
Hi! I'm Tim
I'm now walking 5 miles.
I'm now eating 5 gallons of ice cream.
I'm now eating lots of vegetables.
Another way is to modify the __bases__ of the class. Using the mixin example above:
class DressupMixin:
def dressup(self, dress="red evening gown"):
print "Do I look cute in this %s?" % dress
return self
class Woman(Person):
pass
Woman.__bases__ += (DressupMixin,)
Woman('Eve').walk().eat().dressup()
# Output
Hi! I'm Eve.
I'm now walking 5 miles.
I'm now eating 5 gallons of ice cream.
Do I look cute in this red evening gown?
Note that you can modify the __bases__ of the classes on the fly. How's that for power! You can do this:
new_bases = list(Woman.__bases__)
new_bases.remove(DressupMixin)
Woman.__bases__ = tuple(new_bases)
Now, the Woman class and its instances won't have the dressup method anymore. This makes Python classes pluggable.
The only problem I encountered was when I set the __bases__ to () or None. This causes a TypeError to be raised.
More to Come
So, there.
I also played with __metaclass__. And I'll also add my notes on decorators.