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):
 line 25, in <module>
     cus = Customer('Arup', 99)
 line 18, in __init__
     self.email = email
 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):

- 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


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.

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.

Regards =dn
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:

Reply via email to