I am working through the O Reilley Python Cookbook and I have a question about the following code:
class Typed:
def __init__(self, name, expected_type):
self.name = name
self.expected_type = expected_type
def __get__(self, instance, cls):
if instance is None:
return self
else:
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, self.expected_type):
raise TypeError('Expected ' + str(self.expected_type))
instance.__dict__[self.name] = value
def __delete__(self, instance):
del instance.__dict__[self.name]
# Class decorator that applies it to selected attributes
def typeassert(**kwargs):
def decorate(cls):
for name, expected_type in kwargs.items():
# Attach a Typed descriptor to the class
setattr(cls, name, Typed(name, expected_type))
return cls
return decorate
# Example use
@typeassert(name=str, shares=int, price=float)
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
if __name__ == '__main__':
s = Stock('ACME', 100, 490.1)
print(s.name, s.shares, s.price)
s.shares = 50
try:
s.shares = 'a lot'
except TypeError as e:
print(e)
Im confused about the part:
def __get__(self, instance, cls):
if instance is None:
return self
else:
return instance.__dict__[self.name]
if instance is not set (ie None) then it says return 'self', given that self represents the class descriptor what exactly is returned?
Yes, it returns the descriptor instance.
The second argument (the first after self) for __get__ is either the instance on which the descriptor is looked up - or None if it's looked up on the class.
So in the given case it returns the descriptor in case you look up the descriptor on the class.
Just to illustrate that:
class Descriptor:
def __get__(self, instance, owner):
if instance is None:
return self
return 10
class Test:
test = Descriptor()
>>> Test.test
<__main__.Descriptor at 0x2769b7d44c8>
>>> Test.__dict__['test']
<__main__.Descriptor at 0x2769b7d44c8>
Now, if it didn't use return self there it would look like this:
class Descriptor:
def __get__(self, instance, owner):
return 10
class Test:
test = Descriptor()
>>> Test.test
10
>>> Test.__dict__['test']
<__main__.Descriptor at 0x2769b7de208>
The reason this return self is often done is because it allows to get the descriptor instance without having to search in __dict__ (potentially in all superclasses). In most cases it simply makes no sense to do anything when the property is looked up on the class, so returning the instance is a good idea.
It's also what the built-in property does (and also the function-descriptor):
class A:
@property
def testproperty(self):
return 10
def testmethod(self):
return 10
>>> A.testproperty
<property at 0x2769b7db9a8>
>>> A.testproperty.__get__(None, A)
<property at 0x2769b7db9a8>
>>> A.testmethod
<function __main__.A.testmethod(self)>
>>> A.testmethod.__get__(None, A)
<function __main__.A.testmethod(self)>
In those cases where something meaningful should happen when the attribute is looked up on the class (for example the built-in staticmethod and classmethod descriptors) then that's of course different and self should not be returned there!
then it says return 'self', given that self represents the class descriptor what exactly is returned?
You've just answered your own question: it returns the descriptor object itself.
So when the descriptor is accessed on the class, instance will be None, and in this case it just returns the descriptor. Consider the example of property re-implemented in Python from the Descriptor HOWTO:
In [5]: 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:
...: print('obj is None in Property.__get__')
...: 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__)
...:
Then:
In [6]: class Foo:
...: @Property
...: def bar(self):
...: return 42
...:
In [7]: Foo.bar
obj is None in Property.__get__
Out[7]: <__main__.Property at 0x1044d02b0>
In [8]: Foo().bar
Out[8]: 42
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With