Search code examples
pythonclassmultiple-inheritancepython-decorators

Python IsInstance() for classes and subclasses


This code is from python cook book 3rd edition from classes chapter section 8.13 . The program tries to define various kinds of data structures, but want to enforce constraints on the values that are allowed to be assigned to certain attributes. I am executing the program in python 2.7 with Pycharm IDE.

# Base class. Uses a descriptor to set a value
class Descriptor(object):
    def __init__(self, name=None, **opts):
        self.name = name
        for key, value in opts.items():
            setattr(self, key, value)

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value

# Descriptor for enforcing types
class Typed(Descriptor):
    expected_type = type(None)
    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError('expected ' + str(self.expected_type))
        super(Typed,self).__set__(instance, value)

class Integer(Typed):
    expected_type = int

class String(Typed):
    expected_type = str

class MaxSized(Descriptor):
    def __init__(self, name=None, **opts):
        if 'size' not in opts:
            raise TypeError('missing size option')
        super(MaxSized,self).__init__(name, **opts)

    def __set__(self, instance, value):
        if len(value) >= self.size:
            raise ValueError('size must be < ' + str(self.size))
        super(MaxSized,self).__set__(instance, value)


class SizedString(String, MaxSized):
    pass

# Class decorator to apply constraints
def check_attributes(**kwargs):
    def decorate(cls):
        for key, value in kwargs.items():
            if isinstance(value, Descriptor):
                value.name = key
                setattr(cls, key, value)
            else:
                setattr(cls, key, value(key))
        return cls
    return decorate

# Example
@check_attributes(name=String,shares=Integer,place=SizedString('tester',size=8))
class Stock(object):
    def __init__(self, stkname, stkqty,stkhq):
        self.name = stkname
        self.shares = stkqty
        self.place = stkhq

when executing the code with the below initialization ,

s = Stock('ACME', 50,'hky')
print s.name # print ACME
print s.shares # prints 50
print s.place # prints hky

Condition:

When debugging the below code for @check_attributes place=SizedString('tester',size=8), the below if condition is True where as for name=String and shares=Integer , the else condition is True.

       if isinstance(value, Descriptor):
            value.name = key
            setattr(cls, key, value)
        else:
            setattr(cls, key, value(key))

Questions :

  1. If SizedString is an instance of Descriptor ( based on Inheritance hierarchy- String , Typed , MaxSized, Descriptor ), then String and Integer also should satisfy the If condition right ? because at the end it is also the subclass of ( typed , Descriptor ) ?

    1. What is value(key) in setattr(cls, key, value(key)) means , cant understand what is value(key ) means ?

Sorry for the lengthy context, but wanted to be clear as much as possible .


Solution

    1. If SizedString is an instance of Descriptor ( based on Inheritance hierarchy- String , Typed , MaxSized, Descriptor ), then String and Integer also should satisfy the If condition right ? because at the end it is also the subclass of ( typed , Descriptor ) ?

      We have to look carefully at what is being passed into the check_attributes function. Look closer at what the value of the name and share keyword arguments are:

      @check_attributes(name=String,shares=Integer,place=SizedString('tester',size=8))
      

      Notice the lack of parenthesis after the String and Integer class names? This means the String and Integer class object themselves are being passed into check_attributes, not an instance of either class. And since the String class object and the Integer class object are not sub-classes of Descriptor, isinstance(value, Descriptor) fails.

    2. What is value(key) in setattr(cls, key, value(key)) means , cant understand what is value(key ) means ?

      Think about it. Since value has the value of whatever keyword argument was passed into check_attributes, and the value is not an instance of the Descriptor class, then value must be referring to a class object. (If you don't understand why this is, refer back to my answer to your first question). So calling value(key) is creating an instance of some class, and passing in the key value as a constructor argument.