Slide 1

Slide 1 text

{ PYTHON DESCRIPTORS PROTOCOL IN DETAIL PYCON ES  2015 PABLO ENFEDAQUE VIDAL @pablitoev56

Slide 2

Slide 2 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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!

Slide 3

Slide 3 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Python  descriptors protocol

Slide 4

Slide 4 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Let'ʹs  implement  a  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)

Slide 5

Slide 5 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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

Slide 6

Slide 6 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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

Slide 7

Slide 7 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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

Slide 8

Slide 8 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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

Slide 9

Slide 9 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Descriptors  protocol  requires   implementing  at  least  one  of: Introduced  in  Python  2.2 Python  descriptors protocol descr.__get__(self, obj, type=None) --> value descr.__set__(self, obj, value) --> None descr.__delete__(self, obj) --> None

Slide 10

Slide 10 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Descriptors  protocol  __get__

Slide 11

Slide 11 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Let'ʹs  understand  __get__ 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()

Slide 12

Slide 12 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Let'ʹs  understand  __get__ 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(inst.attr) Called __get__ on self: inst: cls: None

Slide 13

Slide 13 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Let'ʹs  understand  __get__ 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(inst.attr) Called __get__ on self: inst: cls: None

Slide 14

Slide 14 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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 self: inst: None cls: None

Slide 15

Slide 15 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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 self: inst: None cls: None

Slide 16

Slide 16 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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)

Slide 17

Slide 17 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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}

Slide 18

Slide 18 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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}

Slide 19

Slide 19 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Descriptors  protocol  __set__

Slide 20

Slide 20 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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()

Slide 21

Slide 21 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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() inst = VerboseSetClass() inst.attr = 12345 Called __get__ on self: inst: val: 12345 print(inst.__dict__) # NO modification done {}

Slide 22

Slide 22 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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() inst = VerboseSetClass() inst.attr = 12345 Called __get__ on self: inst: val: 12345 print(inst.__dict__) # NO modification done {}

Slide 23

Slide 23 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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() inst = VerboseSetClass() print(VerboseSetClass.__dict__) {, ...} VerboseSetClass.attr = 12345 print(inst.__dict__) print(VerboseSetClass.__dict__) # We "replaced" it {} {'attr': 12345, ...}

Slide 24

Slide 24 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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() inst.string_attr = "123" print(inst.__dict__) {'string_attr': '123'} print(inst.string_attr) 123

Slide 25

Slide 25 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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

Slide 26

Slide 26 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Don'ʹt  do  this…  duck  typing 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

Slide 27

Slide 27 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Descriptors  protocol  __delete__

Slide 28

Slide 28 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || No  time  today

Slide 29

Slide 29 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Descriptors  in  standard  library

Slide 30

Slide 30 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Python  slots

Slide 31

Slide 31 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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

Slide 32

Slide 32 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || However… 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) inst.attr_xyz = 54321 Traceback (most recent call last): File "", line 1, in AttributeError: 'SlottedClass' object has no attribute 'attr_xyz' print(inst.__dict__) Traceback (most recent call last): File "", line 1, in AttributeError: 'SlottedClass' object has no attribute '__dict__'

Slide 33

Slide 33 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || 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(SlottedClass.__dict__) {'attr_x': , 'attr_y': , '__slots__': ['attr_x', 'attr_y']...} print(SlottedClass.__dict__['attr_x'].__get__(inst, SlottedClass)) 54321 # Slots are descriptors! SlottedClass.__dict__['attr_x'].__set__(inst, 100001) print(inst.attr_x) 100001

Slide 34

Slide 34 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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

Slide 35

Slide 35 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Python  properties

Slide 36

Slide 36 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || First,  some  useful  methods def f_to_c(f): # Fahrenheit to Celsius return (f - 32.0) * 5.0 / 9.0 def c_to_f(c): # Celsius to Fahrenheit return c * 9.0 / 5.0 + 32.0 def k_to_c(k): # Kelvin to Celsius return k - 273.15 def c_to_k(c): # Celsius to Kelvin return c + 273.15

Slide 37

Slide 37 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Welcome  to  properties class TempClass: def __init__(self, temp=None): self._temp = temp or 0.0 def get_temp_c (self): return self._temp def set_temp_c (self, val): self._temp = val # This is a property! temp_celsius = property(get_temp_c, set_temp_c) def get_temp_f (self): return c_to_f(self._temp) def set_temp_f (self, val): self._temp = f_to_c(val) temp_fahrenheit = property(get_temp_f, set_temp_f) def get_temp_k(self): return c_to_k(self._temp) temp_kelvin = property(get_temp_k) # No setter!

Slide 38

Slide 38 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Welcome  to  properties class TempClass: def __init__(self, temp=None): self._temp = temp or 0.0 def get_temp_c (self): return self._temp def set_temp_c (self, val): self._temp = val # This is a property! temp_celsius = property(get_temp_c, set_temp_c) def get_temp_f (self): return c_to_f(self._temp) def set_temp_f (self, val): self._temp = f_to_c(val) temp_fahrenheit = property(get_temp_f, set_temp_f) def get_temp_k(self): return c_to_k(self._temp) temp_kelvin = property(get_temp_k) # No setter!

Slide 39

Slide 39 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Let'ʹs  use  these  properties class TempClass: def __init__(self, temp=None): self._temp = temp or 0.0 def get_temp_c (self): return self._temp def set_temp_c (self, val): self._temp = val # This is a property! temp_celsius = property(get_temp_c, set_temp_c) def get_temp_f (self): return c_to_f(self._temp) def set_temp_f (self, val): self._temp = f_to_c(val) temp_fahrenheit = property(get_temp_f, set_temp_f) def get_temp_k(self): return c_to_k(self._temp) temp_kelvin = property(get_temp_k) # No setter! inst = TempClass(20) print(inst.temp_fahrenheit) 68.0 print(inst.temp_kelvin) 293.15 inst.temp_fahrenheit = 32 print(inst.temp_celsius) 0.0 inst.temp_kelvin = 200 # No setter Traceback (most recent call last): File "", line 1, in AttributeError: can't set attribute

Slide 40

Slide 40 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || print(inst.__dict__) {'_temp': 0.0} print(TempClass.__dict__) {'get_temp_celsius': , 'set_temp_celsius': , 'set_temp_fahrenheit': , 'get_temp_fahrenheit': , 'temp_celsius': , 'temp_fahrenheit': , 'temp_kelvin': , 'get_temp_kelvin': ...} print(TempClass.__dict__['temp_fahrenheit']) print(TempClass.__dict__['temp_fahrenheit'].__get__(inst, TempClass)) # This a descriptor! Yay! 32.0 TempClass.__dict__['temp_celsius'].__set__(inst, 100) print(inst.__dict__) {'_temp': 100}

Slide 41

Slide 41 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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)

Slide 42

Slide 42 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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)

Slide 43

Slide 43 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  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

Slide 44

Slide 44 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || Q&A Thanks  for  coming! Slides:   https://speakerdeck.com/pablito56/python-­‐‑ descriptors-­‐‑in-­‐‑detail

Slide 45

Slide 45 text

|| PYTHON DESCRIPTORS PROTOCOL IN DETAIL || PYCON ES  2015 ||  PABLO ENFEDAQUE VIDAL ||  @pablitoev56  || • https://docs.python.org/3/reference/datamodel.html • https://docs.python.org/3/howto/descriptor.html • http://docs.python.org/3/reference/datamodel.html#slots • http://docs.python.org/3/library/functions.html#property References