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!