In this PyCon ES 2015 talk we will review the descriptors protocol, use cases and direct application in Python's standard library; properties and slots.
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || In this talk we will review the descriptors protocol, use cases and direct application in Python'ʹs standard library; properties and slots Welcome!
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || Let'ʹs use the descriptor from random import randint class RandAttr: # Hello! I'm a descriptor def __init__(self, n=10): self.n = n def __get__(self, inst, cls): print("Called __get__") return randint(0, self.n) class RandAttrClass(object): rand_attr = RandAttr(20) # Descriptors are class attributes inst = RandAttrClass() # Let's instantiate and access it print(inst.rand_attr) Called __get__ 12 print(inst.rand_attr) Called __get__ 5
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || Let'ʹs use the descriptor from random import randint class RandAttr: # Hello! I'm a descriptor def __init__(self, n=10): self.n = n def __get__(self, inst, cls): print("Called __get__") return randint(0, self.n) class RandAttrClass(object): rand_attr = RandAttr(20) # Descriptors are class attributes inst = RandAttrClass() # Let's instantiate and access it print(inst.rand_attr) Called __get__ 12 print(inst.rand_attr) Called __get__ 5
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || A descriptor is an object attribute with binding behaviour, one whose attribute access has been overridden by methods in the descriptor protocol Python descriptors
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || A descriptor is an object attribute with binding behaviour, one whose attribute access has been overridden by methods in the descriptor protocol It may intercept attribute retrieval, modification and/or deletion Python descriptors
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || __get__ with a class attribute class VerboseGetDescriptor: def __get__(self, inst, cls): print("Called __get__ on", self.__class__) print("\tself:", self) print("\tinst:", inst) print("\tcls:", cls) class VerboseGetClass: attr = VerboseGetDescriptor() inst = VerboseGetClass() print(VerboseGetClass.attr) # Now with the class attribute Called __get__ on <class VerboseGetDescriptor> self: <VerboseGetDescriptor object at 0x10e62f310> inst: None cls: <class 'VerboseGetClass'> None
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || __get__ with a class attribute class VerboseGetDescriptor: def __get__(self, inst, cls): print("Called __get__ on", self.__class__) print("\tself:", self) print("\tinst:", inst) print("\tcls:", cls) class VerboseGetClass: attr = VerboseGetDescriptor() inst = VerboseGetClass() print(VerboseGetClass.attr) # Now with the class attribute Called __get__ on <class VerboseGetDescriptor> self: <VerboseGetDescriptor object at 0x10e62f310> inst: None cls: <class 'VerboseGetClass'> None
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || __get__ in real life class LazyAttr: def __init__(self, name, func): self.name = name self.func = func def __get__(self, inst, cls): print("Called __get__") # Let's add the result to the INSTANCE inst.__dict__[self.name] = self.func(inst) return inst.__dict__[self.name] from time import sleep class LazyAttrUser: def expensive_func(self): print("\tCalled expensive func") sleep(9999) return 12345 # We give the same name than the descriptor attr = LazyAttr ('attr', expensive_func)
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || __get__ in real life class LazyAttr: def __init__(self, name, func): self.name = name self.func = func def __get__(self, inst, cls): print("Called __get__") # Let's add the result to the INSTANCE inst.__dict__[self.name] = self.func(inst) return inst.__dict__[self.name] from time import sleep class LazyAttrUser: def expensive_func(self): print("\tCalled expensive func") sleep(9999) return 12345 # We give the same name than the descriptor attr = LazyAttr ('attr', expensive_func) # Let's instantiate the class inst = LazyAttrUser() print(inst.__dict__) {} # Let's retrieve it print(inst.attr) Called __get__ Called expensive func 12345 # Let's do it again print(inst.attr) 12345 print(inst.__dict__) {'attr': 12345}
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || __get__ in real life class LazyAttr: def __init__(self, name, func): self.name = name self.func = func def __get__(self, inst, cls): print("Called __get__") # Let's add the result to the INSTANCE inst.__dict__[self.name] = self.func(inst) return inst.__dict__[self.name] from time import sleep class LazyAttrUser: def expensive_func(self): print("\tCalled expensive func") sleep(9999) return 12345 # We give the same name than the descriptor attr = LazyAttr ('attr', expensive_func) # Let's instantiate the class inst = LazyAttrUser() print(inst.__dict__) {} # Let's retrieve it print(inst.attr) Called __get__ Called expensive func 12345 # Let's do it again print(inst.attr) 12345 print(inst.__dict__) {'attr': 12345}
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || Let'ʹs understand __set__ class VerboseSetDescriptor: def __set__(self, inst, val): print("Called __get__ on", self.__class__) print("\tself:", self) print("\tinst:", inst) print("\tval:", val) # New value instead of the class class VerboseSetClass: attr = VerboseSetDescriptor()
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || __set__ in real life class TypedAttrib: def __init__(self, name, typ): self.name = name self.typ = typ # Avoid using 'type' as attribute name! def __set__(self, inst, val): if isinstance(val, self.typ): inst.__dict__[self.name] = val else: raise TypeError("Wrong type {0}".format(type(val))) class MyTypedClass: string_attr = TypedAttrib('string', str) integer_attr = TypedAttrib('integer', int) inst = MyTypedClass() try: inst.string_attr = 123 except TypeError, e: print(e) Wrong type <class 'int'>
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || Welcome to slots class SlottedClass: __slots__ = ['attr_x', 'attr_y'] # These are slots def __init__(self, x, y): self.attr_x = x self.attr_y = y def sum_content(self): return self.attr_x + self.attr_y inst = SlottedClass(12345, 67890) print(inst.attr_x) 12345 print(inst.attr_y) # We can access its attributes 67890 inst.attr_x = 54321 # We can modify its attributes print(inst.sum_content()) # We can call its methods 122211
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || • Light control of objects structure (can be bypassed!) • Faster access • Smaller instances memory footprint • Improve efficiency (storage and speed) • Really useful to handle huge amounts of objects • Slots are implemented with descriptors • Don'ʹt forget there is no instance __dict__ when using slots • There are other restrictions. Check the docs! Python slots
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || There is a @property decorator class TempClass: def __init__(self, temp=None): self._temp = temp or 0.0 # This is a property @property def temp_celsius(self): " Temperature in Celsius " return self._temp # This is the setter for tmp_celsius @temp_celsius.setter def temp_celsius_set(self, val): self._temp = val # temp_kelvin does not have setter @property def temp_kelvin(self): " Temperature in Kelvin " return c_to_k(self._temp)
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || There is a @property decorator class TempClass: def __init__(self, temp=None): self._temp = temp or 0.0 # This is a property @property def temp_celsius(self): " Temperature in Celsius " return self._temp # This is the setter for tmp_celsius @temp_celsius.setter def temp_celsius_set(self, val): self._temp = val # temp_kelvin does not have setter @property def temp_kelvin(self): " Temperature in Kelvin " return c_to_k(self._temp)
|| PABLO ENFEDAQUE VIDAL || @pablitoev56 || • Properties are a specialized implementation of descriptors • Define getters, setters, deleters and even docstrings used instead of direct attribute access • Alternative declaration with decorators • Use them to keep backwards compatibility with direct attribute access • Use them when you need knowledge of class internals • Use them for more generic logic or less coupled solutions Python properties