The Python descriptor How-To describes how one could implement the property() in terms of descriptors. I do not understand the reason of the first if-block in the __get__ method. Under what circumstances will obj be None? What is supposed to happen then? Why do the __get__ and __del__ methods not check for that?
Code is a bit lengthy, but it's probably better to give the full code rather than just a snippet. Questionable line is marked.
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
# =====>>> What's the reason of this if block? <<<=====
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
You can see what the effect is by making another version that leaves that test out. I made a class Property that uses the code you posted, and another BadProperty that leaves out that if block. Then I made this class:
class Foo(object):
#Property
def good(self):
print("In good getter")
return "good"
#good.setter
def good(self, val):
print("In good setter")
#BadProperty
def bad(self):
print("In bad getter")
return "bad"
#bad.setter
def bad(self, val):
print("In bad setter")
The similarities and differences can be seen in this example:
>>> x = Foo()
# same
>>> x.good
In good getter
'good'
>>> x.bad
In bad getter
'bad'
>>> x.good = 2
In good setter
>>> x.bad = 2
In bad setter
# different!
>>> Foo.good
<__main__.Property object at 0x0000000002B71470>
>>> Foo.bad
In bad getter
'bad'
The effect of the if block is to return the raw property object itself if it is accessed via the class. Without this check, the getter is called even when accessing the descriptor via the class.
The __set__ and __del__ methods do not need such a check, since the descriptor protocol is not invoke at all when setting/deleting attributes on a class (only on an instance). This is not totally obvious from the documentation, but can be seen in the difference between the description of __get__ vs. those of __set__/__del__ in the docs, where __get__ can get the attribute of "the owner class or an instance" but __set__/__del__ only set/delete the attribute on an instance.
From the descriptor documentation:
The details of invocation depend on whether obj is an object or a class.
Basically, instances call descriptors as type(b).__dict__['x'].__get__(b, type(b)), while classes call descriptors as B.__dict__['x'].__get__(None, B). If obj is None means the getter was called from the class, not an instance.
This machinery is used for example to implement classmethods.
The __set__ and __delete__ do not check for obj is None because they can never be called like this. Only __get__ is invoked when called from the class. Doing cls.prop = 2 or del cls.prop will directly overwrite or delete the property object, without invoking __set__ or __delete__.
Related
In the below code that emulates property creation code:
class Property:
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
in __get__, self is the instance of the property class?
Also, what does obj refer to? Why is obj passed to self.fget?
When I test it I get the following error:
class A:
func = Property(fget=func)
a = A()
a.func
TypeError: 'Property' object is not callable
How does it work then if it's not callable? I was under the impression that when defining a decorator as an instance you need to make sure it implements the __call__ method?
What am I missing?
self is the instance of Property itself. Its main purpose is to simply hold references to the getter, setter, and/or deleter so that you can call them when the property is accessed. When those functions are called, they get a reference to the object from which the property was accessed, so in a.func, self is the instance of Property and obj is a.
The Property instance itself doesn't need to be callable, because it's not what gets called. Property.__get__ only returns self if you access it from the class, not an instance of the class. The built-in property works the same way.
>>> class A:
... func = property(lambda self: 3)
...
>>> A.func
<property object at 0x10a33bb88>
>>> A().func
3
func is a class attribute whose value is an instance of property. The result of accessing the attribute differs depending on whether you look it up on the class itself or an instance of the class.
When accessed via the class, you get back the property itself.
A.func == A.__dict__['func'].__get__(None, A)
== A.__dict__['func']
When accessed via an instance, you get back the result of calling the original function passed to property.
a = A()
a.func == A.__dict__['func'].__get__(a, A)
== A.__dict__['func'].fget(a)
== (lambda self: 3)(a)
== 3
I want to make an attribute accessible from instance and class that cannot be reassigned. I have the "prevent from reassigned" part tackled thanks to a metaclass. However, now the attribute cannot be read from the class. How to make it so.
Having this:
class Meta(type):
def __setattr__(cls, attr, val):
if attr =="frozen":
print "You can't unfreeze the frozen"
else:
cls.name = val
class Fixed(object):
__metaclass__ = Meta
frzn = 'I AM AWESOME'
def holdset(_,val):
_.frzn = _.frzn
print "frozen is frozen not setting to ", val
def get(_):
return _.frzn
frozen = property(fset=holdset,fget=get)
When calling
print Fixed.frozen
print Fixed().frozen
Gives
<property object at 0x106dbeba8>
I AM AWESOME
Why doesn't it give the same? How to make it give the same?
A property normally only works on an instance. To make a property work on a class too, you'll need to create your own descriptor instead:
class ClassAndInstanceProperty(object):
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
obj = objtype
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
The only difference between the above and a regular property is in the __get__ method; a normal property object does (in C) the equivalent of:
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
e.g. on a class the descriptor returns self, while my version sets obj to objtype in that case.
Also note that a descriptor __set__ method is only called when working with an instance; Fixed.frozen = 'something' would not invoke the descriptor __set__, only Fixed().frozen = 'something' would. Your metaclass __setattr__ intercepts the attribute assignment on the class instead.
You could also put a descriptor object on the metaclass, which would then be given the opportunity to have it's __set__ called for attribute assignment on the class itself.
By convention a Python descriptor returns itself when not called with an instance. Properties are just a shortcut for a descriptor, to do this you need to implement your own descriptor. The following should do what you want, without needing a metaclass:
class Frozen(object):
def __init__(self, fixed_value):
self.fixed_value = fixed_value
def __get__(self, instance, owner=None):
return self.fixed_value
def __set__(self, instance, value):
print "You can't unfreeze the frozen"
class Fixed(object):
frzn = Frozen("I AM AWESOME")
You would also probably want to override the delete method which is part of the descriptor interface. There are some good articles explaining exactly how descriptors work.
Is there a way to see what the application of a Python decorator has done with a function to which I have applied it. For example if I have
class A(object):
#property
def something(self):
return 0
I'd like to see what the code that's executed for something actually looks like. Is there a way to do this?
A decorator doesn't produce code; a decorator is really only syntactic sugar:
#property
def something(self):
return 42
is really interpreted as:
def something(self):
return 42
something = property(something)
e.g. the expression following the # sign is evaluated, and the result is called, passing in the function or class following the # line. Whatever the decorator then returns replaces the original object.
For introspection purposes, the # line is not retained; you'd have to parse the source code itself to discover any decorators present. A decorator is not obliged to return a new object; you can return the original object unaltered and you cannot, with introspection, know the difference.
Your best bet is to return to the source of the decorator then and just read the code. The property decorator is implemented in C, but the descriptor howto contains a Python implementation that does the same thing:
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
I have some questions regarding the following code:
1 class Test(object):
2 def __init__(self):
3 print "Object instance created."
4 self._x = raw_input("Initial value of x = ")
5 print "Initial value of x set."
6
7 def Property(func):
8 return property(**func())
9
10 #Property
11 def x():
12 def fget(self):
13 print 'Getting x'
14 return self._x
15 def fset(self, val):
16 print 'Setting x'
17 self._x = val
18 def fdel(self):
19 print 'Deleting x'
20 del self._x
21 doc = "A test case"
22 return locals()
Why is the Property() function necessary?
Why can't I just return locals() and then use #property as a decorator directly?
When I do that I get an error saying x takes no arguments, one given (presumably 'self'). I know python has the #x.setter option, however I'm forced to use 2.4 regularly, so it's not an option for me. Even then, #x.setter still seems less elegant than defining it all in one block.
Is there a way to define it all in one block using #property?
You can't use property as a decorator directly for the code you have posted because it was not designed to be used that way, and it won't work.
If used as a decorator, property converts the function into the getter; if used as a function, you can pass in the getter, setter, deleter, and a doc.
locals() returns all the locals, so you would have a dictionary with fget, fset, fdel, doc, Property, and __init__ -- causing property to blow up because it was passed too many arguments.
Personally, I like the #x.setter and #x.deleter style, as I don't end up with unnecessary function names in the class name space.
If you have to use 2.4 regularly, just roll your own (or steal the latest from 2.6 like I did ;):
class property(object):
"2.6 properties for 2.5-"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.__doc__ = doc or fget.__doc__
def __call__(self, func):
self.fget = func
if not self.__doc__:
self.__doc__ = fget.__doc__
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def setter(self, func):
self.fset = func
return self
def deleter(self, func):
self.fdel = func
return self
You can do it all in one block: not by using #property by defining and instantiating a class that has __get__(), __set__(), and __delete__() methods. See Implementing Descriptors for more details:
class Test(object):
def __init__(self):
print "Object instance created."
self._x = raw_input("Initial value of x = ")
print "Initial value of x set."
class x(object):
def __get__(self, instance, owner):
print 'Getting x'
return instance._x
def __set__(self, instance, value):
print 'Setting x'
instance._x = value
def __delete__(self, instance):
print 'Deleting x'
del instance._x
__doc__ = "A test case"
x = x()
property() is a shortcut for writing the above, and the Property() method in your example class is a shortcut for having to write the functions separately and pass them to property(); instead you write a function that defines the functions, then returns them, where they get passed to property().
The reason you can't use #property is that decorators decorate a single object. So you'd need a container, such as a class, and so you might as well just write a descriptor directly at that point.
I want to create a decorator that works like a property, only it calls the decorated function only once, and on subsequent calls always return the result of the first call. An example:
def SomeClass(object):
#LazilyInitializedProperty
def foo(self):
print "Now initializing"
return 5
>>> x = SomeClass()
>>> x.foo
Now initializing
5
>>> x.foo
5
My idea was to write a custom decorator for this. So i started, and this is how far I came:
class LazilyInitializedProperty(object):
def __init__(self, function):
self._function = function
def __set__(self, obj, value):
raise AttributeError("This property is read-only")
def __get__(self, obj, type):
# problem: where to store the value once we have calculated it?
As you can see, I do not know where to store the cached value. The simplest solution seems to be to just maintain a dictionary, but I am wondering if there is a more elegant solution for this.
EDIT Sorry for that, I forgot to mention that I want the property to be read-only.
Denis Otkidach's CachedAttribute is a method decorator which makes attributes lazy (computed once, accessible many). To make it also read-only, I added a __set__ method. To retain the ability to recalculate (see below) I added a __delete__ method:
class ReadOnlyCachedAttribute(object):
'''Computes attribute value and caches it in the instance.
Source: Python Cookbook
Author: Denis Otkidach https://stackoverflow.com/users/168352/denis-otkidach
This decorator allows you to create a property which can be computed once and
accessed many times. Sort of like memoization
'''
def __init__(self, method, name=None):
self.method = method
self.name = name or method.__name__
self.__doc__ = method.__doc__
def __get__(self, inst, cls):
if inst is None:
return self
elif self.name in inst.__dict__:
return inst.__dict__[self.name]
else:
result = self.method(inst)
inst.__dict__[self.name]=result
return result
def __set__(self, inst, value):
raise AttributeError("This property is read-only")
def __delete__(self,inst):
del inst.__dict__[self.name]
For example:
if __name__=='__main__':
class Foo(object):
#ReadOnlyCachedAttribute
# #read_only_lazyprop
def bar(self):
print 'Calculating self.bar'
return 42
foo=Foo()
print(foo.bar)
# Calculating self.bar
# 42
print(foo.bar)
# 42
try:
foo.bar=1
except AttributeError as err:
print(err)
# This property is read-only
del(foo.bar)
print(foo.bar)
# Calculating self.bar
# 42
One of the beautiful things about CachedAttribute (and
ReadOnlyCachedAttribute) is that if you del foo.bar, then the next time you
access foo.bar, the value is re-calculated. (This magic is made possible by
the fact that del foo.bar removes 'bar' from foo.__dict__ but the property
bar remains in Foo.__dict__.)
If you don't need or don't want this ability to recalculate,
then the following (based on Mike Boers' lazyprop) is a simpler way to make a read-only lazy property.
def read_only_lazyprop(fn):
attr_name = '_lazy_' + fn.__name__
#property
def _lazyprop(self):
if not hasattr(self, attr_name):
setattr(self, attr_name, fn(self))
return getattr(self, attr_name)
#_lazyprop.setter
def _lazyprop(self,value):
raise AttributeError("This property is read-only")
return _lazyprop