On 29Apr2019 23:25, Arup Rakshit <a...@zeit.io> wrote:
In the following code, class attributes name and email is set to the
instances of NonBlank.
class NonBlank:
def __init__(self, storage_name):
self.storage_name = storage_name
def __set__(self, instance, value):
if not isinstance(value, str):
raise TypeError("%r must be of type 'str'" % self.storage_name)
elif len(value) == 0:
raise ValueError("%r must not be empty" % self.storage_name)
instance.__dict__[self.storage_name] = value
class Customer:
name = NonBlank('name')
email = NonBlank('email')
def __init__(self, name, email, fidelity=0):
self.name = name
self.email = email
self.fidelity = fidelity
def full_email(self):
return '{0} <{1}>'.format(self.name, self.email)
if __name__ == '__main__':
cus = Customer('Arup', 99)
Running this code throws an error:
Traceback (most recent call last):
File
"/Users/aruprakshit/python_playground/pycon2017/decorators_and_descriptors_decoded/customer.py",
line 25, in <module>
cus = Customer('Arup', 99)
File
"/Users/aruprakshit/python_playground/pycon2017/decorators_and_descriptors_decoded/customer.py",
line 18, in __init__
self.email = email
File
"/Users/aruprakshit/python_playground/pycon2017/decorators_and_descriptors_decoded/customer.py",
line 7, in __set__
raise TypeError("%r must be of type 'str'" % self.storage_name)
TypeError: 'email' must be of type 'str'
Process terminated with an exit code of 1
Now I am not getting how the __set__() method from NonBlank is being
called inside the __init__() method. Looks like some magic is going on
under the hood. Can anyone please explain this how self.name and
self.email assignment is called the __set__ from NonBlank? What is the
name of this concept?
As Steven has mentioned, it looks like NonBlank is a descriptor, which
defined here:
https://docs.python.org/3/glossary.html#term-descriptor
So NonBlank has a __set__ method. The above text says:
When a class attribute is a descriptor, its special binding behavior
is triggered upon attribute lookup. Normally, using a.b to get, set
or delete an attribute looks up the object named b in the class
dictionary for a, but if b is a descriptor, the respective descriptor
method gets called.
So when your new Customer object runs its __init_ method and goes:
self.name = name
Since Customer.name is a descriptor, this effectively calls:
NonBlank.__set__(self, name)
which in turn does some type and value checking and then directly
modifies self.__dict__ to effect the assignment.
So yes, some magic is occurring - that is what the Python dunder methods
are for: to provide the mechanism for particular magic actions.
Descriptors are rarely used directly, however the @property decorator is
quite ommon, where you define methodlike functions which look like
attributes. Untested example:
class Foo:
def __init__(self):
self.timestamp = time.time()
@property
def age(self):
return time.time() - self.timestamp
which you'd access directly as:
foo = Foo()
print("age =", foo.age)
@property arranges this using descriptors: in the example above it
arranges that the class "age" attribute is a descriptor with a __get__
method.
Cheers,
Cameron Simpson <c...@cskk.id.au>
_______________________________________________
Tutor maillist - Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor