ftw
ftw

Reputation: 385

Property method in python

Can someone explain me this behavior as to why 1) doesn't work while 2) and 3) works

1)

class bm(object):
    def __init__(self,val):
        self.a=val
    def get(self):
        return self.a
    def set(self,val):
        self.a=val
        a=property(get,set)


In [43]: ob1=bm('vin')

gives me Recursive error ,while the below code works fine

2)

class bm(object):
    def __init__(self,val):
        self._a=val
    def get(self):
        return self._a
    def set(self,val):
        self._a=val
        a=property(get,set)


In [43]: ob1=bm('vin')

Works fine.I can access ob.a and do ob.a=''

Even this works fine

3)

class bm(object):
    def __init__(self,val):
        self.a=val
    def get(self):
        return self._a
    def set(self,val):
        self._a=val
        a=property(get,set)

In [43]: ob1=bm('vin')

Works fine.I can access ob.a and do ob.a=''

Upvotes: 0

Views: 1496

Answers (2)

bruno desthuilliers
bruno desthuilliers

Reputation: 77942

The point here is that in Python, everything (including functions, methods, "properties" - or any other descriptor - classes and even modules) is an object, and that there's no distinct namespaces for "data" and "functions or methods". IOW, in Python, an object has attributes, period - no "member data" nor "member functions". Even the base classes are attributes (and are themselves objects so they have attributes too).

The attribute lookup rules are (quite simplified - I won't mention some special cases like slots etc):

for reading:

  1. first look for an attribute by that name in the parent classes. If found AND this attribute implements the "get" part of the descriptor protocol, call that attribute's __get__ method.
  2. then look for an instance attribute in the instance's __dict__
  3. then if the class (or one of the parent classes) has a __getattr__ method, call it
  4. then raise an AttributeError exception

for setting:

  1. first look for an attribute by that name in the parent classes. If found AND this attribute implements the "set" part of the descriptor protocol, call that attribute's __set__ method.
  2. then store the attribute in the instance's __dict__

I mentionned but did not explain the descriptor protocol. This protocol (if you come from Java, a protocol is kind of an "implied" interface - you don't have to declare it, just to implement it) says that if an object has a __get__ method and eventually a __set__ method (here again I overly simplify by not mentionning the __del__ part) AND is a class attribute then when looked up on an instance of the class it's __get__ method will be called (with the instance and class as arguments) on "read" lookup and it's __set__ method will be called (with the instance and value) on "write".

IOW, the descriptor protocol is the basis for computed attributes in Python.

Now about the property type (yes,it is a class, not a function): it does implement the descriptor protocol, in a very simple way. Here's a simplified version of how the property type would be implemented in pure Python (not taking the __del__ part into account):

class property(object):
    def __init__(self, fget, fset=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.doc = doc

   def __get__(self, instance, cls=None):
       if not instance:
           return self
       return self.fget(instance)

  def __set__(self, instance, value):
      if not self.fset:
          raise AttributeError("attribute is read-only")
      self.fset(instance, value)

Back to the question - given the class statement :

class Something(object):
    def __init__(self,val):
        self.a=val
    def get(self):
        return self.a
    def set(self,val):
        self.a=val
    a=property(get,set)

We have a class object Something with a (class) attribute a which is a property with fget=Something.get and fset=Something.set. Now what happens when we instanciate Something ? The initializer is called with a val argument, and try to bind self.a to that argument. The attribute lookup rule (for "writing" - we should really say 'binding') kicks in and notice that the Something class object has an attribute a which - as an instance of the property type - implements the __set__ part of the protocol descriptor. So the lookup rule calls Something.a.__set__(theinstance, val), which resolves to Something.a.fset(theinstance, val), which is implemented as self.a = val. New attribute lookup, finds a class attribute a implementing the binding part of the descriptor protocol, invoke it, etc..., bang, infinite recursion.

To make a long story short: an attribute is an attribute is an attribute ;)

Note that in your third example, the set method try to set self._a and not self.a. Since the class has no descriptor named _a, this just create an instance attribute by that name, so no recursion here.

For more on the descriptor protocol, cf - http://docs.python.org/reference/datamodel.html#implementing-descriptors - http://wiki.python.org/moin/ComputedAttributesUsingPropertyObjects

and if you want to understand what Python "methods" really are (hint: the function type implement the descriptor protocol) and why the 'self' argument is required, you can read this too: - http://wiki.python.org/moin/FromFunctionToMethod

Upvotes: 3

mipadi
mipadi

Reputation: 411310

In the first example, you're creating the property a, which lets you do things like:

self.a               # Get the value
self.a = some_obj    # Set the value

But within the a property, you're referring to the a property again, via self.a! This will create a problem with recursion.

In the next examples, the property a is backed by the variable self._a, avoiding this recursion issue.

Upvotes: 5

Related Questions