So I'm going to start writing every day. Hold me to that, please.

Today I'm writing about one little corner of Python, the property function. It's a builtin function, around since at least 2.2.

I used a question that involved property as one of the interview questions during a recent developer search, and I found about a 50-50 split of people who knew about it, and those who didn't, and without much correlation to how much Python experience the developer had. So I think it's one of those things that you only really use if you know about it - it's by no means essential, and you can go your whole Python career not knowing about it, but, well, as toolboxes go, this is a pretty nifty screwdriver.

I'm going to show basic usage, and then a couple ways to abuse it.

Basic Usage

  1. class Person(object):
  2. def __init__(self, first_name, last_name):
  3. # pretty straightforward initialization, just set a couple
  4. # attributes
  5. self.first_name = first_name
  6. self.last_name = last_name
  7. def get_full_name(self):
  8. return "%s %s" % (self.first_name, self.last_name)
  9. full_name = property(get_full_name)

And how it works in an interactive session:

>>> me = Person("Adam", "Gomaa")
>>> me.get_full_name()
'Adam Gomaa'
>>> me.full_name
'Adam Gomaa'

Note the lack of parens on the last input line; despite the lack of an explicit call, get_full_name apparently got called anyway!

This is what the property builtin does: it allows you to set getters and setters under some name on instances of the class. I only did a getter in this case, but setters are also possible as the optional second argument:

  1. class Person(object):
  2. # ...
  3. def get_full_name(self):
  4. return "%s %s" % (self.first_name, self.last_name)
  5. def set_full_name(self, full_name):
  6. self.first_name, self.last_name = full_name.split()
  7. full_name = property(get_full_name, set_full_name)

Why?

Now why would you want to do this? In many cases, it's because something you used to have as an actual instance attribute (say, .url) became a computed value, based on other instance attributes. Now, you could update all your code to call .get_url() instead, and write a .set_url() if that's possible... or you could just turn .url into a property.

Be sure, though, to set some ground rules for yourself. Remember, you're simulating an attribute lookup, which, in Python, usually means looking up a value in a dictionary based on a short string key - one of the fastest, most-optimized pieces of Python. So, don't make your property getter overly complex. In general, I'd say that it should have one and only one code path - conditionals are acceptable, but you're better off without them if you really want this to be a leakless abstraction. And stay far, far away from anything that has side effects in property getters - you'll drive yourself and other programmers crazy.

(Don't worry, I'll break all these rules by the time I finish this article.)

Decorator Tricks

The signature of property is:

  1. property(fget=None, fset=None, fdel=None, doc=None)

When you only need a getter - which is the most common use case - then the relevant part becomes:

  1. property(getter)

And so your code, inside your class is going to look something like:

  1. class MyObject(object):
  2. def get_something(self):
  3. return whatever
  4. something = property(get_something)

In fact, at that point, you don't really care about get_something. We could even do something like:

  1. def something(self):
  2. return whatever
  3. something = property(something)

Which, as long as you're using Python 2.4 or above, can be shorthanded to:

  1. @property
  2. def something(self):
  3. return whatever

At that point, accessing .something will call this getter function, without the parens. Be sure not to use .something(), or you'll be calling whatever is returned (helpfully named 'whatever' in the example above), and if it's not callable, you'll get an exception.

Defining Getter, Setter, and Deller in One

Let's take an example adapted from one of the comments at ActiveState:

  1. def Property(func)
  2. return property(**func())
  3. class Person(object):
  4. @Property
  5. def name():
  6. doc = "The person's name"
  7. def fget(self):
  8. return "%s %s" % (self.first_name, self.last_name)
  9. def fset(self, name):
  10. self.first_name, self.last_name = name.split()
  11. def fdel(self):
  12. del self.first_name
  13. del self.last_name
  14. return locals()

The advantage to this strategy is that it allows you to define all the arguments to property without filling up the class's namespace with _get_foo, _set_foo, and so on.

The disadvantage is that you already have 3 levels of indentation in your 'top-level' getter function code. I personally avoid it for this reason. As noted before, though, most of the time you can get away with not having a setter at all.

Example: Django URLs

For a long time, in the Django world, .get_absolute_url() was the way you got URLs for objects. django.core.urlresolvers.reverse has gained some ground - and in terms of DRYness, is a better answer - but let me tell you, reverse("appname-modelname", args=[object.slug]) is not so fun, particularly when you change that URL pattern name and have to update a few dozen {% url %} tags. (sed and grep help a lot with that, but many developers, myself included on most days, don't have enough sed-fu to use it without the man page open).

.get_absolute_url() has it's own set of problems. For starters, it ties the model layer to the URL layer, kind of, sort of, maybe. But reverse() is ugly because you have to remember what the arguments are, what order they're in, and how you're supposed to get the damned things anyway. If you're doing a RESTful, hierarchy-oriented URL scheme, this can get ugly fast:

# to get '/books/a-midsummer-nights-dream/reviews/overall-nice-odd-grammar/update/':
review_update_url = reverse("books-review-update", args=[self.book.slug, self.slug])

And, there's no way of knowing that you need the book slug, as opposed to the book id, unless you go look up the URLconf. And that'll interrupt your flow, or you can just guess, and then you'll interrupt your flow anyway to debug the NoReverseMatch that you just got.

Enter property. Who's really the authority on Review's URLs? Well, technically, the URLconf. But in some ethereal sense, shouldn't Review be arbitrating it's own URLs? That's what .get_absolute_url() did, and it worked pretty well; a considered, balanced 'denormalization' for the sake of convenience at the expense of DRY.

Unfortunately, get_absolute_url() has its own problem (besides the theoretical ones) - namely, there's only one of it. For each object, typically, you'll have several URLs:

/books/ - an index/list/search page
/books/new/ - A new submission form
/books/(regexp)/ - a view/update page
/books/(regexp)/delete/ - A delete/confirm delete page
/books/(regexp)/reviews/ - *another* index/list/search page
...

At first, property only makes this less painful:

  1. from django.core.urlresolvers import reverse
  2. class Book(models.Model):
  3. # for compatibility, I'm leaving .get_absolute_url()
  4. def get_absolute_url(self):
  5. return reverse("books-view", args=[self.slug])
  6. absolute_url = property(get_absolute_url)
  7. @property
  8. def reviews_url(self):
  9. return reverse("books-reviews", args=[self.slug])
  10. @property
  11. def delete_url(self):
  12. return reverse("books-delete", args=[self.slug])

But if you're paying attention, you'll see all these seem to follow the same pattern:

  1. @property
  2. def SOMETHING_url(self):
  3. return reverse("books-SOMETHING", args=[self.slug])

If you can see where I'm going, now's probably the time to start running away.

Now, duplicated code means "think about an abstraction." I'm about to break the first rule I set, about not making properties overly complex. In reality, a quick little method could do what I'm about to show you in a much easier way:

  1. def object_url(self, url_type):
  2. if url_type == "reviews":
  3. return reverse(...
  4. elif url_type == "delete":
  5. # .. and so on

But come on now, that wouldn't be very much fun, would it?

Overly Complex Properties

So instead, let's think about how we would want to define a named set of urls for a single object. We could do it with multiple FOO_url properties:

book.absolute_url
book.reviews_url
book.delete_url

Or we could make a .url property with dictionary access:

book.urls['absolute']
book.urls['reviews']
book.urls['delete']

Or heck, even with attribute access

book.urls.absolute
book.urls.reviews
book.urls.delete

At that point, your view code for redirects becomes utterly wonderful:

return HttpResponseRedirect(book.urls.reviews)

and so on. I just love that, because it's so astonishingly close to what I'm trying to say: send them to the reviews page for this book.

I'm going to show the attribute access code, but you can pretty much substitute for dictionary syntax by replacing __getattr__ with __getitem__ in this code:

  1. def attrproperty(getter_function):
  2. class _Object(object):
  3. def __init__(self, obj):
  4. self.obj = obj
  5. def __getattr__(self, attr):
  6. return getter_function(self.obj, attr)
  7. return property(_Object)

Of course, that's still rather indented, but at least its a library function rather than something you're putting into your model code. Usage looks something like this:

  1. class Book(models.Model):
  2. @attrproperty
  3. def urls(self, name):
  4. if name == "absolute":
  5. urlpattern_name = "books-view"
  6. elif name == "reviews":
  7. urlpattern_name = "books-reviews"
  8. elif name == "delete":
  9. urlpattern_name = "books-delete"
  10. return reverse(urlpattern_name, args=[self.slug])

Thus allowing book.urls.whatever. Dictionary syntax is actually a little nicer, since you can more easily stick a variable in there (books.urls[action], for example) but I like the look of attribute access.

Anyway, it's pretty obvious that this is a gross abuse of property. But I'm not satisfied yet.

Caching: Side-Effect Properties

Let's go back to our .FOO_url properties from before. One thing about reverse() calls is that if you have to traverse intermediary models, building urls can be, well, expensive. If Review #142 has to look up the slug of Book #21, it probably has to load that object from the database (unless your PK is also what you're using in the URL, but we can't count on that). That can make rendering an HTML page, with dozens or hundreds of links and .FOO_url accesses, kind of expensive.

But, of course, you're coding so those URLs don't change, right? So why recompute it each time? Just compute it once for each object, and throw it into a cache.

  1. from functools import wraps
  2. from django.core.cache import cache
  3. def cached_property(func):
  4. @wraps(func)
  5. def _closure(self):
  6. cache_key = "%s.%s.%s(%s)" % (self.__class__.__module__,
  7. self.__class__.__name__,
  8. func.__name__,
  9. self.pk)
  10. val = cache.get(cache_key)
  11. if val is None:
  12. val = func(self)
  13. cache.set(cache_key, val)
  14. return val
  15. return property(_closure)

Throw that around your .FOO_url properties:

  1. @cached_property
  2. def reviews_url(self):
  3. return reverse("books-reviews", args=[self.slug])
  4. @cached_property
  5. def delete_url(self):
  6. return reverse("books-delete", args=[self.slug])

and now they'll only make DB calls the first time they're called, per object that accessed on. That's certainly acceptable.

You could combine this with @attrproperty to get cached obj.urls.foo access, but that will be left as an exercise for the reader.

Finally...

That's about all I have for today. While researching this post I looked into the Python descriptor protocol, which I might make the subject of a future post.