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
Related
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__.
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.
This question already has answers here:
Using property() on classmethods
(19 answers)
Closed 3 years ago.
Essentially I want to do something like this:
class foo:
x = 4
#property
#classmethod
def number(cls):
return x
Then I would like the following to work:
>>> foo.number
4
Unfortunately, the above doesn't work. Instead of given me 4 it gives me <property object at 0x101786c58>. Is there any way to achieve the above?
This will make Foo.number a read-only property:
class MetaFoo(type):
#property
def number(cls):
return cls.x
class Foo(object, metaclass=MetaFoo):
x = 4
print(Foo.number)
# 4
Foo.number = 6
# AttributeError: can't set attribute
Explanation: The usual scenario when using #property looks like this:
class Foo(object):
#property
def number(self):
...
foo = Foo()
A property defined in Foo is read-only with respect to its instances. That is, foo.number = 6 would raise an AttributeError.
Analogously, if you want Foo.number to raise an AttributeError you would need to setup a property defined in type(Foo). Hence the need for a metaclass.
Note that this read-onlyness is not immune from hackers.
The property can be made writable by changing Foo's
class:
class Base(type): pass
Foo.__class__ = Base
# makes Foo.number a normal class attribute
Foo.number = 6
print(Foo.number)
prints
6
or, if you wish to make Foo.number a settable property,
class WritableMetaFoo(type):
#property
def number(cls):
return cls.x
#number.setter
def number(cls, value):
cls.x = value
Foo.__class__ = WritableMetaFoo
# Now the assignment modifies `Foo.x`
Foo.number = 6
print(Foo.number)
also prints
6
The property descriptor always returns itself when accessed from a class (ie. when instance is None in its __get__ method).
If that's not what you want, you can write a new descriptor that always uses the class object (owner) instead of the instance:
>>> class classproperty(object):
... def __init__(self, getter):
... self.getter= getter
... def __get__(self, instance, owner):
... return self.getter(owner)
...
>>> class Foo(object):
... x= 4
... #classproperty
... def number(cls):
... return cls.x
...
>>> Foo().number
4
>>> Foo.number
4
I agree with unubtu's answer; it seems to work, however, it doesn't work with this precise syntax on Python 3 (specifically, Python 3.4 is what I struggled with). Here's how one must form the pattern under Python 3.4 to make things work, it seems:
class MetaFoo(type):
#property
def number(cls):
return cls.x
class Foo(metaclass=MetaFoo):
x = 4
print(Foo.number)
# 4
Foo.number = 6
# AttributeError: can't set attribute
Problem with solutions above is that it wouldn't work for accessing class variables from instance variable:
print(Foo.number)
# 4
f = Foo()
print(f.number)
# 'Foo' object has no attribute 'number'
Moreover, using metaclass explicit is not so nice, as using regular property decorator.
I tried to solve this problems. Here how it works now:
#classproperty_support
class Bar(object):
_bar = 1
#classproperty
def bar(cls):
return cls._bar
#bar.setter
def bar(cls, value):
cls._bar = value
# #classproperty should act like regular class variable.
# Asserts can be tested with it.
# class Bar:
# bar = 1
assert Bar.bar == 1
Bar.bar = 2
assert Bar.bar == 2
foo = Bar()
baz = Bar()
assert foo.bar == 2
assert baz.bar == 2
Bar.bar = 50
assert baz.bar == 50
assert foo.bar == 50
As you see, we have #classproperty that works same way as #property for class variables. Only thing we will need is additional #classproperty_support class decorator.
Solution also works for read-only class properties.
Here's implementation:
class classproperty:
"""
Same as property(), but passes obj.__class__ instead of obj to fget/fset/fdel.
Original code for property emulation:
https://docs.python.org/3.5/howto/descriptor.html#properties
"""
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.__class__)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj.__class__, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj.__class__)
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__)
def classproperty_support(cls):
"""
Class decorator to add metaclass to our class.
Metaclass uses to add descriptors to class attributes, see:
http://stackoverflow.com/a/26634248/1113207
"""
class Meta(type):
pass
for name, obj in vars(cls).items():
if isinstance(obj, classproperty):
setattr(Meta, name, property(obj.fget, obj.fset, obj.fdel))
class Wrapper(cls, metaclass=Meta):
pass
return Wrapper
Note: code isn't tested much, feel free to note if it doesn't work as you expect.
The solution of Mikhail Gerasimov is quite complete. Unfortunately, it was one drawback. If you have a class using his classproperty, no child class can use it due to an
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases with class Wrapper.
Fortunately, this can be fixed. Just inherit from the metaclass of the given class when creating class Meta.
def classproperty_support(cls):
"""
Class decorator to add metaclass to our class.
Metaclass uses to add descriptors to class attributes, see:
http://stackoverflow.com/a/26634248/1113207
"""
# Use type(cls) to use metaclass of given class
class Meta(type(cls)):
pass
for name, obj in vars(cls).items():
if isinstance(obj, classproperty):
setattr(Meta, name, property(obj.fget, obj.fset, obj.fdel))
class Wrapper(cls, metaclass=Meta):
pass
return Wrapper