美文网首页
Descriptors: A Practice with Int

Descriptors: A Practice with Int

作者: 9_SooHyun | 来源:发表于2023-03-14 19:54 被阅读0次
# refer to https://docs.python.org/3/howto/descriptor.html#descriptor-howto-guide
# KEY SUMMARIES:
# - A descriptor is what we call any object that defines __get__(), __set__(), or __delete__(). Descriptor is a protocol, like golang interface.
# Descriptor protocol:
# descr.__get__(self, obj, type=None) -> value
# descr.__set__(self, obj, value) -> None
# descr.__delete__(self, obj) -> None
#
# - Descriptors only work when used as class variables.
# - the core idea of descriptor is **Callback**, or **hook**.
# The main motivation for descriptors is to provide a hook allowing objects stored in class variables to control what happens during attribute lookup.
# 数据操作的控制权通过 descriptor 的 hook 转移给descriptor本身


import logging

logging.basicConfig(level=logging.INFO)


# LoggedAgeAccess is a descriptor
class LoggedAgeAccess:

    # param `obj` means the instance of this descriptor's owner class
    def __get__(self, obj, objtype=None):
        value = obj._age
        logging.info('Accessing %r giving %r', 'age', value)
        return value

    def __set__(self, obj, value):
        logging.info('Updating %r to %r', 'age', value)
        obj._age = value


# Person is the owner class of LoggedAgeAccess
class Person:

    age = LoggedAgeAccess()             # age is a Descriptor instance as a class attribute

    def __init__(self, name, input_age):
        self.name = name                # name is regular instance attribute

        # self.age is already a LoggedAgeAccess instance as a class attribute, and the input_age is assigned to it
        self.age = input_age  # This line actually makes a function call to `self.age.__set__(self, input_age)`

    def growup(self):
        self.age += 1                   # Calls both __get__() and __set__()

# In the above classes,
# age is the public attribute of class Person,
# and _age is the private attribute of Person instance, which is created when LoggedAgeAccess.__set__ is called.
# When the public attribute is accessed, the descriptor logs the lookup or update.


def test_simple_descriptor_usage():
    mary = Person('Mary M', 30)
    # INFO:root:Updating 'age' to 30

    # call vars() to look up the descriptor without triggering it
    print(vars(mary))
    # {'name': 'Mary M', '_age': 30} # The actual age data is in a private attribute: _age
    mary.age
    # INFO:root:Accessing 'age' giving 30
    mary.growup()
    # INFO:root:Accessing 'age' giving 30
    # INFO:root:Updating 'age' to 31

# now let's go on how `__set_name__` is used


class LoggedAccess:
    # __set_name__ is an optional method for descriptor
    # __set_name__ will be called immediately when the owner class of this descriptor is defined
    def __set_name__(self, owner, name):
        print("owner is ", owner)  # in this .py module it prints `owner is  <class '__main__.PersonWithFullDescriptor'>`
        self.public_name = name
        self.private_name = '_' + name

    def __get__(self, obj, objtype=None):
        value = getattr(obj, self.private_name)
        logging.info('Accessing %r giving %r', self.public_name, value)
        return value

    def __set__(self, obj, value):
        logging.info('Updating %r to %r', self.public_name, value)
        setattr(obj, self.private_name, value)

# When the PersonWithFullDescriptor class is defined,
# it makes a callback to __set_name__() in LoggedAccess,
# which sets each descriptor its own public_name and private_name
# and sets descriptor's private_name as a dynamic attribute of PersonWithFullDescriptor


class PersonWithFullDescriptor:

    name = LoggedAccess()                # First descriptor instance
    age = LoggedAccess()                 # Second descriptor instance

    def __init__(self, name, age):
        # self.name and self.age are both class attribute
        self.name = name                 # Calls the first descriptor
        self.age = age                   # Calls the second descriptor

    def growup(self):
        self.age += 1


def test_descriptor__set_name__usage():
    print(vars(PersonWithFullDescriptor))
    # {
    #   '__module__': '__main__',
    #   'name': < __main__.LoggedAccess object at 0x7f3d9dafe3d0 > ,
    #   'age': < __main__.LoggedAccess object at 0x7f3d9dafe2b0 > ,
    #    ...
    # }

    print(vars(vars(PersonWithFullDescriptor)['name']))  # call vars() to look up the descriptor without triggering it
    # {'public_name': 'name', 'private_name': '_name'}

    mary = PersonWithFullDescriptor("mary", 10)
    # INFO:root:Updating 'name' to 'mary'
    # INFO:root:Updating 'age' to 10
    print(vars(mary))
    # {'_name': 'mary', '_age': 10}

    print(mary.age)  # access class attribute `age` to access instance private attribute `_age`
    # INFO:root:Accessing 'age' giving 10
    # 10


if __name__ == "__main__":
    # test_simple_descriptor_usage()
    test_descriptor__set_name__usage()


# more practical example
# refer to https://docs.python.org/3/howto/descriptor.html#complete-practical-example

相关文章

网友评论

      本文标题:Descriptors: A Practice with Int

      本文链接:https://www.haomeiwen.com/subject/jgekrdtx.html