tkbe

November 5, 2006

Python :: defprop - a very simple idiom for defining properties

Filed under: python — tb @ 3:21 pm

Most attempts at making it easier to define properties in Python seem to quickly descend into metaclass obscurity. Metaclasses can surely solve the problem for an appropriate definition of solve, but "Explicit is better than implicit", and "Simple is better than complex," so none of the metaclass solutions (mine included) have had great appeal. It's always fun to visit the Cookbook, and today I found a great recipe from Sean Ross at http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205183.

To recap, the traditional way of defining properties looks something like this

PYTHON:
  1. class MyClass(object):
  2.     def __init__(self):
  3.         self._foo = "foo"
  4.  
  5.     def getfoo(self):
  6.         return self._foo
  7.     def setfoo(self, value):
  8.         self._foo = value
  9.     def delfoo(self):
  10.         del self._foo
  11.     foo = property(getfoo, setfoo, delfoo, "property foo's doc string")

All the getters and setters pollute your class namespace (think about adding a bar property), visually there is no Pythonic grouping of the code that belongs to the property, and you don't really know that it's going to be a property until the end.

At the end of the recipe Sean says that if decorators are accepted (they were) his idiom would be even better, so here's my attempt:

PYTHON:
  1. # a simple decorator
  2. def defprop(doc):
  3.     def _defprop(fn):
  4.         return property(doc=doc, **fn())
  5.     return _defprop
  6.  
  7. class MyClass(object):
  8.     def __init__(self):
  9.         self._x = 42
  10.  
  11.     @defprop("The answer to life the universe and Everything")
  12.     def foo():
  13.         def fget(self):
  14.             return self._x
  15.         def fset(self, v):
  16.             self._x = v
  17.         return locals()

Overall, I think it doesn't suck too bad. The name defprop is based on the fact that def is an abbreviation also and property is taken.

Update: turns out I've found it easier to not use the decorator, just like the recipe mentioned above:

PYTHON:
  1. class MyClass(object):
  2.     def __init__(self):
  3.         self._x = 42
  4.  
  5.     def foo():
  6.         def fget(self):
  7.             return self._x
  8.         def fset(self, v):
  9.             self._x = v
  10.         return locals()
  11.     foo = property(**foo())

it's an easy idiom to remember, easier than writing the property from scratch, and apparantly easier than figuring out where to put a property so that it will be available everywhere I happen to be...

November 2, 2006

Django :: looking at newforms

Filed under: django — tb @ 1:52 am

(This is rapidly getting outdated, go read the source at http://code.djangoproject.com/browser/django/trunk/django/newforms for the current state...).

Django is getting new form handling code, currently in the module django.newforms but it will migrate to django.forms in a future release. The code is very new and under active development. Here are just some quick notes after browsing the code (r3945).

The newforms package is divided into fields, forms, util, and widgets. Widgets are classes that have a render method that returns the html widget as a unicode string (flatatt is a function that flattens a dictionary):

PYTHON:
  1. class TextInput(Widget):
  2.     def render(self, name, value, attrs=None):
  3.         if value is None: value = ''
  4.         final_attrs = dict(self.attrs, type='text', name=name)
  5.         if attrs:
  6.             final_attrs.update(attrs)
  7.         if value != '': final_attrs['value'] = value # Only add the 'value' attribute if a value is non-empty.
  8.         return u'<input %s />' % flatatt(final_attrs)

The currently defined widgets include TextInput, Textarea, and CheckboxInput.

Fields are classes encapsulating the validation of input data. They should have constructors that take a widget parameter in addition to captioning validation parameters like max_length etc., and a to_python method that converts an input value to a Python value:

PYTHON:
  1. class MyField(django.newforms.Field):
  2.     def __init__(self, ..., required=True, widget=None):
  3.         # set the widget
  4.         super(MyField, self).__init__(self, required, widget)
  5.         ... save other validation parameters here ...
  6.  
  7.     def to_python(self, value):
  8.         # validates empty values vs. required fields
  9.         super(MyField, self).to_python(value)
  10.         ... validate value here ...

The current field classes include CharField, IntegerField, DateField, DateTimeField, RegexField, EmailField, and BooleanField. The date fields are by default US centric but you can pass a tuple of format strings in the input_formats parameter to suit your local traditions. The django.newforms.util.ValidationErrors that are thrown currently contain English text unless I'm reading the code the wrong way (it'll probably be internationalized shortly).

Finally the Form class contains a metaclass to allow for convenient declaration of fields

PYTHON:
  1. class MyForm(django.newform.Form):
  2.     name = CharField(max_length=35)
  3.     dob = DateField(input_formats=['%d/%m/%Y'], required=False, widget=TextInput)
  4.     ...

The field names are available to methods of MyForm in the self.fields dictionary.The default widget is TextInput.

« Previous Page

Powered by WordPress