Alternative @property Syntax

2012-12-19 22:04

As you probably know very well, in Python you can add properties to your classes. They behave like instance fields syntactically, but under the hood they call accessor functions whenever you want to get or set the property value:

  1. import os
  2.  
  3. class Directory(object):
  4.     """Simple class representing a directory in the file system."""
  5.     def __init__(self, path):
  6.         self.path = path
  7.  
  8.     @property
  9.     def parent(self):
  10.         """Parent directory."""
  11.         return Directory(os.path.join(self.path, os.pardir))
  12.  
  13. # usage: no () after .parent
  14. home_dir = Directory('/home/xion')
  15. root_dir = home_dir.parent.parent

Often – like in the example above – properties are read-only, providing only the getter method. It’s very easy to define them, too: just stick a @property decorator above method definition and you’re good to go.

iGet, iSet

Occasionally though, you will want to define a read-write property. (Or read-delete, but those are very rare). One function won’t cut it, since you need a setter in addition to getter. The canonical way Python docs recommend in such a case (at least since 2.6) is to use the @property.setter decorator:

  1. class TracedObject(object):
  2.     """Object that tracks changes to its properties."""
  3.     def __init__(self):
  4.         self.changed = False
  5.  
  6.     @property
  7.     def x(self):
  8.         return self._x
  9.  
  10.     @x.setter
  11.     def x(self, value):
  12.         self._x = value
  13.         self.changed = True

Besides that I find it ugly to split a single property between two methods, this approach will annoy many static code analyzers (including PEP8 checker) due to redefinition of x. Warnings like that are very useful in general, so we certainly don’t want to turn them off completely just to define a property or two.

So if our analyzer doesn’t support line-based warning suppression (like, again, pep8), we may want to look for a different solution.

All in one

I’ve found one by complete accident: I unintentionally triggered autocompletion for “property” stem in my text editor. When you do that inside Python file opened in Sublime Text 2, you will be served with the following template snippet (which has multiple cursors conveniently placed at every occurrence of foo – yes, Sublime is that awesome):

  1. def foo():
  2.     doc = "The foo property."
  3.     def fget(self):
  4.         return self._foo
  5.     def fset(self, value):
  6.         self._foo = value
  7.     def fdel(self):
  8.         del self._foo
  9.     return locals()
  10. foo = property(**foo())

As you can see, this is quite clever trick. We define a function returning dictionary of keyword arguments for property; these include getter and optionally setter, deleter and/or docstring. It just happens that this dictionary is simply the dict of locals(), cleverly named to match the argument names for property.

Neat, isn’t it? Best part is that it looks almost like a dedicated syntactic construct, kinda like the property block in C#. It’s almost self-contained, too: just the property call at the end stands out like a sore thumb.

My solution

I thought I could fix that, as well as the weird docstring-in-variable shenanigan. Most importantly, I wanted a decorator back again, since I consider them one of the most beautiful features in Python.

And so I made an objectproperty decorator, with code published in this gist. It allows to improve the previous example and arrive at the following syntax:

  1. @objectproperty
  2. def foo():
  3.     """The foo property."""
  4.     def fget(self):  # or 'get'
  5.         return self._foo
  6.     def fset(self, value):  # or 'set' if you can shadow the built-in
  7.         self._foo = value
  8.     def fdel(self):  # 'del' is a keyword :(
  9.         del self._foo
  10.     return locals()

With some stubbornness and stackframe trickery, you might be able to get rid of the return locals() at the end. But this is the level of magic I dare not to approach, recommending others to do likewise :)

Tags: , ,
Author: Xion, posted under Programming »


2 comments for post “Alternative @property Syntax”.
  1. lqc:
    December 19th, 2012 o 22:46

    If you really want to get rid of locals, you can change def to class:

    https://gist.github.com/4340763

  2. Kos:
    December 20th, 2012 o 19:03

    So much hacking for a simple thing, huh?

    Too bad you can’t just say:


    @x.setter
    def x_set(self, value):

Comments are disabled.
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.