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/

A49edf2521f9e75826c6546c7585f17a?s=128

makoto tsuyuki

May 08, 2020
Tweet

Transcript

  1. DescriptorΛ΋͏গ͠ 2020.05.08 Remote.py #1 @everes a.k.a tsuyukimakoto

  2. ۦ͚ग़͠ YouTuber ʢνϟϯωϧొ࿥Φωγϟεʣ ͓લ୭Αʁ ͑἖͊͞ΜͬͯݺΜͰ͍ͩ͘͞ʢͱࡢ೥຤͔Βݴ͍࢝Ί·ͨ͠ʣ

  3. ͓લ୭Αʁ ͑἖͊͞ΜͬͯݺΜͰ͍ͩ͘͞ʢͱࡢ೥຤͔Βݴ͍࢝Ί·ͨ͠ʣ PythonΛ͞ΘΓ͸͡Ίͯ·ͩ 20೥ܦͬͯͳ͍͘Β͍ django-jaॳ୅୅ද https://twitter.com/everes #followme

  4. 2020೥6݄1೔ վగ2൛Ͱ·͢ʂ ύʔϑΣΫτPython վగ2൛ https://amzn.to/2zaJIiV ༧໿ड෇தʂ

  5. INTERMEDIATE΁ͷೖΓޱతͳʁ ຊ೔ͷ͓୊ • Attribute • Property • Descriptor • Meta

    Class
  6. ATTRIBUTEʢΞτϦϏϡʔτʣ

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

    class Spam: ... egg = "I'm egg!" ... >>>
  8. $ 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!"
  9. $ 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!"
  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 = 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!"
  11. $ 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"
  12. $ 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"
  13. $ 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"
  14. $ 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"
  15. $ python Python 3.8.2 (default, Mar 11 2020, 00:29:50) >>>

    class Spam: ... egg = "I'm egg!" ... >>> del spam.egg >>> spam.egg ʁʁʁ
  16. $ 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"
  17. $ 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"
  18. Class Spam egg = “poached egg” spam1 (instance) egg =

    “boiled egg” spam2 (instance) >>> spam1.egg >>> spam2.egg
  19. PROPERTYʢϓϩύςΟʣ

  20. 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
  21. 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'>
  22. 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
  23. 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
  24. $ 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!”
  25. >>> 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͸࡞Βͳ͍
  26. >>> 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͸࡞Βͳ͍
  27. >>> 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͸࡞Βͳ͍
  28. DESCRIPTORʢσΟεΫϦϓλʔʣ

  29. •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ͭҎ্Λ࣋ͭΦϒδΣΫτ
  30. •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ʢμϯμʔʣ
  31. >>> 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
  32. 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
  33. 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)
  34. 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()
  35. 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
  36. >>> 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
  37. https://github.com/python/cpython/blob/3.8/Objects/descrobject.c#L1380 property

  38. META CLASSʢϝλΫϥεʣ

  39. from django.db import models class SampleModel(models.Model): num = models.IntegerField()

  40. from django.db import models class SampleModel(models.Model): num = models.IntegerField() >>>

    SampleModel.num <django.db.models.query_utils.DeferredAttribute object at 0x10e4cdf70>
  41. 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) ... #লུ
  42. >>> 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__ ʹσόοάΞ΢τΛ࢓ࠐΜͰ΍ͬͯΈΔͱ໘ന͍͔΋ʙ
  43. >>> 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
  44. >>> 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
  45. >>> 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
  46. 2020೥6݄1೔ վగ2൛Ͱ·͢ʂ ύʔϑΣΫτPython վగ2൛ https://amzn.to/2zaJIiV ༧໿ड෇தʂ