现在的位置: 首页 > 综合 > 正文

Property in Python

2013年05月14日 ⁄ 综合 ⁄ 共 3874字 ⁄ 字号 评论关闭

  Reprint:http://adam.gomaa.us/blog/2008/aug/11/the-python-property-builtin/

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.

抱歉!评论已关闭.