Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Descriptorをもう少し / python-descriptor

Descriptorをもう少し / python-descriptor

インターミディエイトへの入り口って言われることが多いPythonのDescriptorについて。
Descriptorを学ぶにあたって必要な以下の3つとともに見ていきます。

- Attribute
- Property
- Meta Class

Descriptorは、フレームワーク的な、そのプロジェクトでの型を作るようなタイプの人しか触らない可能性もあります。そういう意味ではやはり中級向けなのかもしれません。

実践的な内容を扱うPythonの勉強会 remote.py の第一回に呼んでいただきました。10分を目指して12分くらいで話せる予定です。
https://lapras.connpass.com/event/174515/

makoto tsuyuki

May 08, 2020
Tweet

More Decks by makoto tsuyuki

Other Decks in Programming

Transcript

  1. $ python Python 3.8.2 (default, Mar 11 2020, 00:29:50) >>>

    class Spam: ... egg = "I'm egg!" ... >>>
  2. $ python Python 3.8.2 (default, Mar 11 2020, 00:29:50) >>>

    class Spam: ... egg = "I'm egg!" ... >>> Spam.egg "I'm egg!" >>> spam = Spam() >>> spam.egg "I'm egg!"
  3. $ python Python 3.8.2 (default, Mar 11 2020, 00:29:50) >>>

    class Spam: ... egg = "I'm egg!" ... >>> Spam.egg "I'm egg!" >>> spam = Spam() >>> spam.egg "I'm egg!" >>> Spam.egg "I'm egg!" >>> spam = Spam() >>> spam.egg "I'm egg!"
  4. $ python Python 3.8.2 (default, Mar 11 2020, 00:29:50) >>>

    class Spam: ... egg = "I'm egg!" ... >>> Spam.egg "I'm egg!" >>> spam = Spam() >>> spam.egg "I'm egg!" >>> Spam.egg "I'm egg!" >>> spam = Spam() >>> spam.egg "I'm egg!" >>> Spam.egg "I'm egg!" >>> spam = Spam() >>> spam.egg "I'm egg!"
  5. $ python Python 3.8.2 (default, Mar 11 2020, 00:29:50) >>>

    class Spam: ... egg = "I'm egg!" ... >>> spam.egg = "I'm boiled egg" >>> Spam.egg "I'm egg!" >>> spam.egg "I'm boiled egg"
  6. $ python Python 3.8.2 (default, Mar 11 2020, 00:29:50) >>>

    class Spam: ... egg = "I'm egg!" ... >>> spam.egg = "I'm boiled egg" >>> Spam.egg "I'm egg!" >>> spam.egg "I'm boiled egg" >>> spam.egg = "I'm boiled egg" >>> Spam.egg "I'm egg!" >>> spam.egg "I'm boiled egg"
  7. $ python Python 3.8.2 (default, Mar 11 2020, 00:29:50) >>>

    class Spam: ... egg = "I'm egg!" ... >>> spam.egg = "I'm boiled egg" >>> Spam.egg "I'm egg!" >>> spam.egg "I'm boiled egg" >>> spam.egg = "I'm boiled egg" >>> Spam.egg "I'm egg!" >>> spam.egg "I'm boiled egg" >>> spam.egg = "I'm boiled egg" >>> Spam.egg "I'm egg!" >>> spam.egg "I'm boiled egg"
  8. $ python Python 3.8.2 (default, Mar 11 2020, 00:29:50) >>>

    class Spam: ... egg = "I'm egg!" ... >>> spam.egg = "I'm boiled egg" >>> Spam.egg "I'm egg!" >>> spam.egg "I'm boiled egg" >>> spam.egg = "I'm boiled egg" >>> Spam.egg "I'm egg!" >>> spam.egg "I'm boiled egg" >>> spam.egg = "I'm boiled egg" >>> Spam.egg "I'm egg!" >>> spam.egg "I'm boiled egg" >>> spam.egg = "I'm boiled egg" >>> Spam.egg "I'm egg!" >>> spam.egg "I'm boiled egg"
  9. $ python Python 3.8.2 (default, Mar 11 2020, 00:29:50) >>>

    class Spam: ... egg = "I'm egg!" ... >>> del spam.egg >>> spam.egg ʁʁʁ
  10. $ python Python 3.8.2 (default, Mar 11 2020, 00:29:50) >>>

    class Spam: ... egg = "I'm egg!" ... >>> spam.egg "I'm egg!" >>> Spam.egg = "I'm poached egg" >>> spam.egg "I'm poached egg"
  11. $ python Python 3.8.2 (default, Mar 11 2020, 00:29:50) >>>

    class Spam: ... egg = "I'm egg!" ... >>> spam.egg "I'm egg!" >>> Spam.egg = "I'm poached egg" >>> spam.egg "I'm poached egg" >>> spam.egg "I'm egg!" >>> Spam.egg = "I'm poached egg" >>> spam.egg "I'm poached egg"
  12. Class Spam egg = “poached egg” spam1 (instance) egg =

    “boiled egg” spam2 (instance) >>> spam1.egg >>> spam2.egg
  13. class C: def __init__(self): self._x = None def getx(self): return

    self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.") ఆٛ: class property(fget=None, fset=None, fdel=None, doc=None) https://docs.python.org/3/library/functions.html#property
  14. class C: def __init__(self): self._x = None def getx(self): return

    self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.") ఆٛ: class property(fget=None, fset=None, fdel=None, doc=None) https://docs.python.org/3/library/functions.html#property >>> type(property) <class ‘type’> >>> type(int) <class ‘type'>
  15. class C: def __init__(self): self._x = None def getx(self): return

    self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.") ఆٛ: class property(fget=None, fset=None, fdel=None, doc=None) https://docs.python.org/3/library/functions.html#property
  16. class C: def __init__(self): self._x = None @property def x(self):

    """I'm the 'x' property.""" return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x ఆٛ: class property(fget=None, fset=None, fdel=None, doc=None) https://docs.python.org/3/library/functions.html#property
  17. $ python Python 3.8.2 (default, Mar 11 2020, 00:29:50) >>>

    class Spam: ... def __init__(self): ... self.egg = None ... >>> spam = Spam() >>> spam.egg = “I’m egg!”
  18. >>> class Spam: ... def __init__(self): ... self.__egg = None

    ... @property ... def egg(self): ... return self.__egg ... @egg.setter ... def egg(self, egg): ... self.__egg = egg ແҙຯͳ setter/getter͸࡞Βͳ͍
  19. >>> class Spam: ... def __init__(self): ... self.__egg = None

    ... @property ... def egg(self): ... return self.__egg ... @egg.setter ... def egg(self, egg): ... self.__egg = egg >>> spam.egg = “I’m egg” લఏͱͯ͠ ແҙຯͳ setter/getter͸࡞Βͳ͍
  20. >>> class Spam: ... def __init__(self): ... self.__egg = None

    ... @property ... def egg(self): ... return self.__egg ... @egg.setter ... def egg(self, egg): ... self.__egg = egg >>> spam.egg “I’m egg” લఏͱͯ͠ ແҙຯͳ setter/getter͸࡞Βͳ͍
  21. •descr.__get__(self, obj, type=None) -> value •descr.__set__(self, obj, value) -> None

    •descr.__delete__(self, obj) -> None •descr.__set_name__(self, owner, name) -> None
 ʢPython3.6Ҏ߱ʣ Descriptor ҎԼͷ্͔Β3ϝιουͷ͍ͣΕ͔1ͭҎ্Λ࣋ͭΦϒδΣΫτ
  22. •descr.__get__(self, obj, type=None) -> value •descr.__set__(self, obj, value) -> None

    •descr.__delete__(self, obj) -> None •descr.__set_name__(self, owner, name) -> None
 ʢPython3.6Ҏ߱ʣ Descriptor ҎԼͷ্͔Β3ϝιουͷ͍ͣΕ͔1ͭҎ্Λ࣋ͭΦϒδΣΫτ __ ← double under ← dunderʢμϯμʔʣ
  23. >>> var1 = SomeClass() >>> var2 = SomeClass() >>> var1.data1

    = 3 >>> var1.data2 = -5 >>> var2.data1 = -2 >>> print(var1.data1) 3 >>> print(var1.data2) 5 >>> print(var2.data1) 2
  24. def absolute_integer(value): if value is None: return 0 if not

    isinstance(value, int): raise TypeError if value < 0: return value * -1 return value
  25. class AbsoluteInteger: def __set_name__(self, owner, name): self.name = '_' +

    name def __set__(self, instance, value): setattr(instance, self.name, absolute_integer(value)) def __get__(self, instance, owner): if not hasattr(instance, self.name): return self return getattr(instance, self.name) def __delete__(self, instance): if not hasattr(instance, self.name): return delattr(instance, self.name) self = σΟεΫϦϓλʔ͕ొ࿥͞Ε͍ͯΔΫϥεͷΞτϦϏϡʔτ (SomeClass.data1, SomeClass.data2) instance = σΟεΫϦϓλʔ͕ొ࿥͞Ε͍ͯΔΫϥεͷΠϯελϯε(var1, var2) owner = σΟεΫϦϓλʔ͕ొ࿥͞Ε͍ͯΔΫϥε(SomeClass)
  26. class AbsoluteInteger: def __set_name__(self, owner, name): self.name = '_' +

    name def __set__(self, instance, value): setattr(instance, self.name, absolute_integer(value)) def __get__(self, instance, owner): if not hasattr(instance, self.name): return self return getattr(instance, self.name) def __delete__(self, instance): if not hasattr(instance, self.name): return delattr(instance, self.name) self = σΟεΫϦϓλʔ͕ొ࿥͞Ε͍ͯΔΫϥεͷΞτϦϏϡʔτ (SomeClass.data1, SomeClass.data2) instance = σΟεΫϦϓλʔ͕ొ࿥͞Ε͍ͯΔΫϥεͷΠϯελϯε(var1, var2) owner = σΟεΫϦϓλʔ͕ొ࿥͞Ε͍ͯΔΫϥε(SomeClass) >>> class SomeClass: >>> data1 = AbsoluteInteger() >>> data2 = AbsoluteInteger() ... >>> var1 = SomeClass() >>> var2 = SomeClass()
  27. class AbsoluteInteger: def __set_name__(self, owner, name): self.name = '_' +

    name def __set__(self, instance, value): setattr(instance, self.name, absolute_integer(value)) def __get__(self, instance, owner): if not hasattr(instance, self.name): return self return getattr(instance, self.name) def __delete__(self, instance): if not hasattr(instance, self.name): return delattr(instance, self.name) self = σΟεΫϦϓλʔ͕ొ࿥͞Ε͍ͯΔΫϥεͷΞτϦϏϡʔτ (SomeClass.data1, SomeClass.data2) instance = σΟεΫϦϓλʔ͕ొ࿥͞Ε͍ͯΔΫϥεͷΠϯελϯε(var1, var2) owner = σΟεΫϦϓλʔ͕ొ࿥͞Ε͍ͯΔΫϥε(SomeClass) >>> class SomeClass: >>> data1 = AbsoluteInteger() >>> data2 = AbsoluteInteger() ... >>> var1 = SomeClass() >>> var2 = SomeClass() ← SomeClass.data1.name, SomeClass.data2.name
  28. >>> class SomeClass: >>> data1 = AbsoluteInteger() >>> data2 =

    AbsoluteInteger() ... >>> var1 = SomeClass() >>> var2 = SomeClass() >>> var1.data1 = 3 >>> var1.data2 = -5 >>> var2.data1 = -2 >>> print(var1.data1) 3 >>> print(var1.data2) 5 >>> print(var2.data1) 2 >>> del var1.data1 >>> print(var1.data1) <AbsoluteInteger object at 0x105e57dc0> >>> print(var2.data2) <AbsoluteInteger object at 0x105e57f70> >>> print(var1._data2) 5
  29. from django.db import models class SampleModel(models.Model): num = models.IntegerField() >>>

    SampleModel.num <django.db.models.query_utils.DeferredAttribute object at 0x10e4cdf70>
  30. https://github.com/django/django/blob/3.0.6/django/db/models/base.py#L71 class ModelBase(type): """Metaclass for all models.""" def __new__(cls, name,

    bases, attrs, **kwargs): super_new = super().__new__ ... #লུ # Pass all attrs without a (Django-specific) contribute_to_class() # method to type.__new__() so that they're properly initialized # (i.e. __set_name__()). contributable_attrs = {} for obj_name, obj in list(attrs.items()): if _has_contribute_to_class(obj): contributable_attrs[obj_name] = obj else: new_attrs[obj_name] = obj new_class = super_new(cls, name, bases, new_attrs, **kwargs) ... #লུ
  31. >>> sample = SampleModel(num=2020)
 >>> sample.save()
 >>> instance = SampleModel.objects.get(pk=1)


    >>> instance.num
 2020
 >>> del instance.num
 >>> instance.num
 2020 https://github.com/django/django/blob/3.0.6/django/db/models/query_utils.py#L117 DeferredAttributeͷ __get__ ʹσόοάΞ΢τΛ࢓ࠐΜͰ΍ͬͯΈΔͱ໘ന͍͔΋ʙ
  32. >>> sample = SampleModel(num=2020)
 >>> sample.save()
 >>> instance = SampleModel.objects.get(pk=1)


    >>> instance.num
 2020
 >>> del instance.num
 >>> instance.num
 2020 https://github.com/django/django/blob/3.0.6/django/db/models/query_utils.py#L117 DeferredAttributeͷ __get__ ʹσόοάΞ΢τΛ࢓ࠐΜͰ΍ͬͯΈΔͱ໘ന͍͔΋ʙ >>> sample = SampleModel(num=2020)
 >>> sample.save()
 >>> instance = SampleModel.objects.get(pk=1)
 >>> instance.num
 2020
 >>> del instance.num
 >>> instance.num
 2020
  33. >>> sample = SampleModel(num=2020)
 >>> sample.save()
 >>> instance = SampleModel.objects.get(pk=1)


    >>> instance.num
 2020
 >>> del instance.num
 >>> instance.num
 2020 https://github.com/django/django/blob/3.0.6/django/db/models/query_utils.py#L117 DeferredAttributeͷ __get__ ʹσόοάΞ΢τΛ࢓ࠐΜͰ΍ͬͯΈΔͱ໘ന͍͔΋ʙ >>> sample = SampleModel(num=2020)
 >>> sample.save()
 >>> instance = SampleModel.objects.get(pk=1)
 >>> instance.num
 2020
 >>> del instance.num
 >>> instance.num
 2020 >>> sample = SampleModel(num=2020)
 >>> sample.save()
 >>> instance = SampleModel.objects.get(pk=1)
 >>> instance.num
 2020
 >>> del instance.num
 >>> instance.num
 2020
  34. >>> sample = SampleModel(num=2020)
 >>> sample.save()
 >>> instance = SampleModel.objects.get(pk=1)


    >>> instance.num
 2020
 >>> del instance.num
 >>> instance.num
 2020 https://github.com/django/django/blob/3.0.6/django/db/models/query_utils.py#L117 DeferredAttributeͷ __get__ ʹσόοάΞ΢τΛ࢓ࠐΜͰ΍ͬͯΈΔͱ໘ന͍͔΋ʙ >>> sample = SampleModel(num=2020)
 >>> sample.save()
 >>> instance = SampleModel.objects.get(pk=1)
 >>> instance.num
 2020
 >>> del instance.num
 >>> instance.num
 2020 >>> sample = SampleModel(num=2020)
 >>> sample.save()
 >>> instance = SampleModel.objects.get(pk=1)
 >>> instance.num
 2020
 >>> del instance.num
 >>> instance.num
 2020 >>> sample = SampleModel(num=2020)
 >>> sample.save()
 >>> instance = SampleModel.objects.get(pk=1)
 >>> instance.num
 2020
 >>> del instance.num
 >>> instance.num
 2020