Confused about properties, descriptors, and attributes...
Bruno Desthuilliers
bdesth.quelquechose at free.quelquepart.fr
Thu Apr 20 21:00:15 EDT 2006
redefined.horizons at gmail.com a écrit :
> I've been reading about Python Classes, and I'm a little confused about
> how Python stores the state of an object.
Simply put: a Python object is mostly a hashtable, with attribute names
as keys and references to other objects as values - one of these
name/ref pairs pointing to the class object.
> I was hoping for some help.
>
> I realize that you can't create an empty place holder for a member
> variable of a Python object. It has to be given a value when defined,
> or set within a method.
There's a special Python object named None.
> But what is the difference between an Attribute of a Class, a
> Descriptor in a Class, and a Property in a Class?
Everything is an attribute, one way or another. Let's start simple: an
object has a special attribute named __dict__, in which (references to)
instance attributes - those you set with 'self.attr=val' - are stored.
As I mentioned above, our object has another special attribute named
__class__, that points to the class object - which, being an object,
has itself a __dict__ attribute in which class attributes are stored,
and a __class__ attribute pointing to the metaclass - the class of the
class.
Note BTW that Python functions being objects, the term 'attribute'
include methods... (or more exactly, functions that will be turned into
methods when looked up)
Ok, now how is this used. <overly-simplified> When a name is looked up
on an object (via the lookup operator '.'), it is first looked up in the
object's __dict__. If this fails, it's then looked up in the class's
__dict__, then in parent classes __dict__s. If the attribute object
found is a callable, it is wrapped in a method object before. If the
object is a descriptor (and has been found in a class __dict__), then
the __get__ method of the descriptor is called.</overly-simplified>
NB : a descriptor is an object that implements at least a __get__ method
that takes an instance (the one on which the lookup occurs) and a class
as params. Properties are descriptors. methods too.
The __dict__ of a class is populated with the names and functions
defined in the class: block. The __dict__ of an instance is populated
with the attributes bound to the instance (usually inside method, and
mostly inside the __init__() method, which is called just after object's
creation) - with the exception that, if the name already refers to a
descriptor having a __set__ method, then this will take precedence.
All this is a 1000 feet high, overly simplified view of Python's object
model - the lookup rules are a bit more complex (cf the
__getattr__/__setattr__/__getattribute___ methods, and the complete
definition of the dexcriptor protocol), and I didn't even talked about
slots nor metaclasses.
To summarize: a sample class
class MyObject(object):
classattr = "I am a class attribute"
# the __init__ function will also be
# an attribute of class MyObject
def __init__(self, name):
# _name will be an instance attribute
self._name = name
def _set_name(self, value):
self._name = name
# name will be an attribute of MyClass.
# it's a property - which is a descriptor
name = property(fget=lambda self: self._name, fset=_set_name)
# we don't want to pollute the class's namespace:
del _set_name
and some musing with it:
>>> MyObject.__dict__.keys()
['__module__', 'name', 'classattr', '__dict__', '__weakref__',
'__doc__', '__init__']
>>> MyObject.__dict__['classattr']
'I am a class attribute'
>>> MyObject.__dict__['__init__']
<function __init__ at 0x404134fc>
>>> MyObject.__init__
<unbound method MyObject.__init__>
>>> MyObject.name
<property object at 0x4041661c>
>>> MyObject.name.__class__.__dict__.keys()
['fset', '__new__', '__set__', '__getattribute__', '__doc__', 'fget',
'__get__', 'fdel', '__init__', '__delete__']
>>>> MyObject.name.__class__.__dict__['fget']
<member 'fget' of 'property' objects>
>>> MyObject.name.__class__.fget
<member 'fget' of 'property' objects>
>>> MyObject.name.fget
<function <lambda> at 0x4041356c>
>>> MyObject.name.fset
<function _set_name at 0x40413534>
>>> MyObject.__class__
<type 'type'>
>>> m = MyObject('parrot')
>>> m
<__main__.MyObject object at 0x40418dcc>
>>> m.name
'parrot'
>>> MyObject.name.fget(m)
'parrot'
>>> dir(m)
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__',
'__hash__', '__init__', '__module__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__',
'_name', 'classattr', 'name']
>>> m.__dict__.keys()
['_name']
>>> m.__dict__['_name']
'parrot'
>>> m.classattr
'I am a class attribute'
>>> MyObject.classattr
'I am a class attribute'
>>> MyObject.classattr = 'yes I am'
>>> m.classattr
'yes I am'
>>> m.classattr = "and I am in fact an instance attribute shadowing
MyObject.classattr"
>>> m.classattr
'and I am in fact an instance attribute shadowing MyObject.classattr'
>>> MyObject.classattr
'yes I am'
>>> m.__dict__
{'classattr': 'and I am in fact an instance attribute shadowing
MyObject.classattr', '_name': 'parrot'}
>>> del m.classattr
>>> m.classattr
'yes I am'
>>> def sayHello(obj, who):
... return "hello, %s, my name is %s" % (who, obj.name)
...
>>> sayHello(m, 'Scott')
'hello, Scott, my name is parrot'
>>> import types
>>> m.greet = types.MethodType(sayHello, m, m.__class__)
>>> m.greet
<bound method MyObject.sayHello of <__main__.MyObject object at 0x40418dcc>>
>>> m.__dict__
{'greet': <bound method MyObject.sayHello of <__main__.MyObject object
at 0x40418dcc>>, '_name': 'parrot'}
>>> >>> m.greet('Joe')
'hello, Joe, my name is parrot'
>>> MyObject.greet
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: type object 'MyObject' has no attribute 'greet'
>>>
If I had a Monster Class, and I wanted to give each Monster a member
> variable called ScaryFactor, how would I define it?
class Monster(object):
def __init__(self, scary_factor):
self.scary_factor = scary_factor
> Does a Property
> simply provide methods that access an Attribute?
A property is mean to have simple computed attributes. The two main uses
are to restrict access to an existing attribute (ie: make it readonly)
or to compute something from existing attributes. Now you can use custom
descriptors for more advanced computed attributes (that may have nothing
to do with the object on which there are called).
HTH
> P.S. - I'm speaking about the "new" Python classes, not the "old" ones.
Hopefully. I don't see much reason to use old-style classes
> I hope my questions make sense.
It does. Python's object model is much more complex than it seems at
first sight, and learning how to use it is worth the time spent if you
want to really take advantage of Python's power.
More information about the Python-list
mailing list