Friday, December 14, 2012

Prototyping a class in Python

I'm not sure what's the name for this technique (for me it resembles what is called prototyping in javascript -- I know it's not the same, but the fact is that you're building your class outside of it, although in Python it's still in its declaration step).

Anyways, the idea is the following: you want to change the class attributes before it becomes final -- there are things you just can't do in Python after a class is already declared (Python uses this technique for creating properties -- in my specific use-case I'm using it to overcome some limitations that Django has in its model inheritance with fields, but I've already used this many times).

The idea is the following: you get the frame which is being used inside the class declaration and change the locals in it so that the final created class will have the things you declared.

I.e.: This code:

import sys

def prototype_class(frame=None):
    if frame is None:
        frame = sys._getframe().f_back

    frame.f_locals['new_attribute'] = 'New attribute'

class MyNewClass(object):
    prototype_class()

print MyNewClass().new_attribute
 

Is the same as:

class MyNewClass(object):
    new_attribute = 'New attribute'
 

-- Just more complicated and more flexible -- in my case, it'll properly help me to choose how to create and customize the fields of a Django model class without having to copy and paste a bunch of code.

On a separate note, blogspot just sucks for code... why can't they simply create an option to add a piece of code? I'm now manually putting my code in html as described in http://stackoverflow.com/a/8697421/110451 -- it'd certainly be trivial for the blogspot devs to put a button which would do that for me right? (colors would be nicer, but this is the easiest things that just works for me and I'd already settle for it if blogger added it -- I know blogger has the quotes, but I want the code at least on a box with a monospaced font).

8 comments:

Eric said...

If I'm understanding it, that is what a metaclass does. I'd offer an example, but every time I've tried using metaclasses it ended up being the wrong tool. That said, this blog was helpful http://eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example/

Anonymous said...

You could do this with a metaclass too. Metaclass magic might be more portable among interpreters than frame magic.

Fabio Zadrozny said...

Eric:

I find metaclasses particularly more tricky to use (although they do have their uses: I've already used it a couple of times with good results), but I wish more things were done with frame magic than metaclass magic (I find the use that Django makes of it particularly troublesome).

I'd much rather have Django use frame magic doing things such as:

class MyModel(Model):
Model.Fields(
field1 = IntegerField(),
field2 = StringField(),
)

than having it do things with metaclasses as it does now (which IMHO is harder to follow).

I use that same (frame) technique to do my own properties in which do a number of things (such as calling a callback when an attribute is changed or having a property as Python does but which can be overridden) and I find the implementation easier to grasp and extend than using metaclasses.

Anonymous:

Regarding portability, I believe all interpreter support frames for this purpose (if there's one that doesn't, it probably doesn't deserve being called a Python interpreter in the first place, as the class construction step is made with that in mind, so, not supporting that is not supporting Python at all for me).

Benjamin said...

IMO, for the trivial example you gave, a class decorator or decorator factory ( @df(args) ) would be far preferred.

Fabio Zadrozny said...

Hi Benjamim,

I agree with you, if I can wait for the class to be completely declared, there are better ways (but sometimes -- such as when you're declaring a property or declaring django fields dynamically, which has to be done before the Django metaclass breaks in, this may not be possible).

Marius Gedminas said...

Does this trick work in Python 3?

E.g. zope.interface switched from a similar trick to using class decorators because of incompatibilities with Python 3. But perhaps that was merely because it was no longer possible to specify a metaclass by scribbling over __metaclass__ in parent frame?

Fabio Zadrozny said...

As far as I know, yes, that works in Python 3 (and I can't say why did zope stopped using it).

Lennart Regebro said...

zope.interface stopped using it because it specifically changed the __metaclass__ local to modify the metaclass. This doesn't work in Python 3 anymore.