[Tutor] self.name is calling the __set__ method of another class

David L Neil PyTutor at DancesWithMice.info
Mon Apr 29 21:04:39 EDT 2019


Hi Arup,


On 30/04/19 5:55 AM, Arup Rakshit wrote:
> 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?


Use the tools provided - follow the Traceback and interpret each step:-

 >      cus = Customer('Arup', 99)

means: instantiate a Customer object, which takes us to

 >      def __init__(self, name, email, fidelity=0):

where:
- name is set to a string: 'Arup'
- email is set to an *integer*: 99, and
- fidelity is set to become another integer with a value of 0
(in the first instance)

Ignoring name, we arrive at

 >          self.email = email

which *appears to be* the creation of an integer(!) within the cus 
Customer instance.

However (the "magic") when the module was loaded into the Python 
interpreter self.email has already been defined as:

 >      email = NonBlank('email')

which means that:

 >      def __init__(self, storage_name):
 >          self.storage_name = storage_name

made it (past tense!) an instance of the NonBlank object with a 
storage_name of email. (and with a __set__ method).

So, returning to the Trace, specifically:

 >    File 
"/Users/aruprakshit/python_playground/pycon2017/decorators_and_descriptors_decoded/customer.py", 
line 18, in __init__
 >      self.email = email

what now happens is that the self.email instance of a NonBlank object 
receives the value passed-in as email (ie 99), and invokes the method:

 >      def __set__(self, instance, value):

In due course, we find that 99 is not an acceptable value:

 >          if not isinstance(value, str):
 >              raise TypeError("%r must be of type 'str'" % 
self.storage_name)

and thus:

 > TypeError: 'email' must be of type 'str'
 > Process terminated with an exit code of 1

Crash!


Of course it is a 'toy example' - when you could plug two 'is it a 
string' checks straight into Customer, why not keep-it-simple and do 
just that? - without the added abstraction on top of an abstraction!

However, the author is illustrating a useful tool - should you find a 
situation where the 'checks' are much more involved or complex.


(NB in addition to, not an alternative to, the discussions Steven has 
offered)

Given previous conversations, I'm not surprised that you were mystified. 
The fact that I had to read it twice, and that the above explanation is 
NOT a 'straight line', indicates that there is probably a better (more 
simple) approach - and one which is MUCH more likely to be understood by 
our Python programming colleagues (possibly including our 'future selves'!)

As Steven explained, this is a complex environment where only those with 
a good understanding of the meta abstractions would even want to play 
(IMHO). Perhaps you would be better served by actually writing some 
Python applications, and with such experience under-your-belt, adding 
these 'advanced knowledge' ideas at some later time, if/when needed?)

Assuming use of a recent version of Python, you may like to solve this 
specific problem the same way you might in other programming languages:

<<<
typing — Support for type hints

New in version 3.5.

Note
The typing module has been included in the standard library on a 
provisional basis. New features might be added and API may change even 
between minor releases if deemed necessary by the core developers.

This module supports type hints as specified by PEP 484 and PEP 526. The 
most fundamental support consists of the types Any, Union, Tuple, 
Callable, TypeVar, and Generic. For full specification please see PEP 
484. For a simplified introduction to type hints see PEP 483.

The function below takes and returns a string and is annotated as follows:

def greeting(name: str) -> str:
     return 'Hello ' + name

In the function greeting, the argument name is expected to be of type 
str and the return type str. Subtypes are accepted as arguments.
 >>>
https://docs.python.org/3/library/typing.html

-- 
Regards =dn


More information about the Tutor mailing list