Python magic method

151 阅读7分钟
原文链接: spxcds.com
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
import logging
import time
import unittest

logging.basicConfig(
    level=logging.DEBUG,
    format='[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s',
    datefmt="%Y-%m-%d %H:%M:%S")


class Book(object):

    ## 基本的魔术方法
    @classmethod
    def __new__(cls, *args, **kwargs):
        """
            这是一个静态方法, 你不需要显示的声明这个类方法, 但是如果显示的声明了, 那么参数要和初始化的参数保持一致(不要忘了显示调用super().__new__(cls[, ...])). 这个方法的返回值应该是一个新的对象实例.
            如果这个方法返回一个类的实例, 那么__init__() 这个方法将会被调用, 否则, 不会被调用
        """
        return super(Book, cls).__new__(cls)

    def __init__(self, name):
        """
            当实例被创建的时候调用(通常是调用完__new__之后), 这个方法通常用作初始化参数, 同样, 不要忘了调用父类的初始化方法.
        """
        self.name = name

    def __del__(self):
        """
            在对象被销毁的时候调用, 可以把它看成类的析构函数. 经典的用法可以是类构造的时候申请的一部分资源, 比如说文件描述符, 在这个方法里可以显示把文件描述符关掉.
            注意: 只有在这个类的引用计数为1的时候, 这个方法才会被调用.
        """
        logging.debug('I am in del')
        return

    def __repr__(self):
        """
            这个方法返回一个str类型. 主要是给开发者debug使用, 是对一个对象的官方解释. 如果定义了这个方法而没有定义__str__的时候, str(Book)会调用__repr__方法
            显示的调用方式有两种, 一种是print(b), 另外一种则是repr(b)
        """
        return '<' + self.__module__ + '.' + self.__class__.__name__ + ' object at ' + str(hex(id(self))) + '>'

    def __str__(self):
        """
            支持str()方法, 返回一个str类型
        """
        return 'The book\'s name is {}'.format(self.name)

    def __lt__(self, other):
        """
            实现<号重载, 类似的还有
            object.__le__(self, other)  <=
            object.__eq__(self, other)  ==
            object.__ne__(self, other)  !=
            object.__gt__(self, other)  >
            object.__ge__(self, other)  >=
        """
        return self.name < other.name

    def __bool__(self):
        """
            实现bool(self)操作符
        """
        return self.name

    # 定制化类的属性
    def __getattr__(self, name):
        """
            当默认的获取属性引发了AttributeError的时候, 这个方法将会被调用
        """
        logging.error('{} object has no attribute \'{}\''.format(self.__class__.__name__, name))
        return None
        # return super(Book, self).__getattribute__(name)

    def __getattribute__(self, name):
        """
            这个方法应该返回name属性的值或者抛出一个AttributeError异常.
            如果引发了AttributeError异常, 那么将会调用__getattr__()
            这个地方要小心了, 如果这个方法里边用了self.xx, 那么, 可能会引发无限递归的问题.
        """
        return super(Book, self).__getattribute__(name)

    def __setattr__(self, name, value):
        """
            当给对象属性赋值的时候, 这个方法将会被调用
            同理小心无限递归的问题
        """
        logging.info('in __setattr__, name: {} value: {}'.format(name, value))
        return super(Book, self).__setattr__(name, value)

    def __delattr__(self, name):
        """
            当删除对象的属性的时候, 这个方法将会被调用
            小心无限递归, 里边不要有del self.name 的语法
        """
        logging.info('in __delattr__, name: {}'.format(name))
        return super(Book, self).__delattr__(name)

    # Descriptors 描述符 classmethod 和 staticmethod 就是文件描述符
    def __get__(self, instance, owner):
        """
            当试图获得类的属性或者类的实例的属性的时候调用.
            这个方法必须返回属性的值, 或者抛出一个AttributeError异常
        """
        return super(Book, self).__get__(instance, owner)

    def __set__(self, instance, value):
        """
            当给一个实例的属性赋值的时候, 这个方法会被调用
        """
        return super(Book, self).__set__(instance, value)

    def __delete__(self, instance):
        """
            当删除一个实例的属性的时候, 这个方法会被调用
        """
        return super(Book, self).__delete__(instance)

    def __set_name__(self, owner, name):
        """
            当所属的类被创建的时候调用, 这时, 这个文件描述符被命名为name
            New in version 3.6
        """
        return super(Book, self).__set_name__(owern, name)


class TestBook(unittest.TestCase):
    def test_mro(self):
        mro = Book.mro()
        """
        打印类的MRO
        MRO(Method Resolution Order):方法解析顺序
        """
        logging.info(mro)

    def test_del(self):
        b = Book('Code Complete')
        c = b
        del b
        logging.info('After del b')
        del c
        """
            结果可以看到, 先打印After del b, 后打印 I am in del
            说明只有在引用计数为1的时候, 才会调用__del__方法
        """

    def test_repr(self):
        b = Book('Code Complete')
        logging.info(b)
        logging.info(repr(b))

    def test_str(self):
        b = Book('Code Complete')
        logging.info(str(b))

    def test_lt(self):
        b = Book('Code Complete')
        d = Book('Bode Complete')

        logging.info(b < d)

    def test_attribute(self):
        b = Book('Code complete')
        logging.info(b.name)
        del b.name
        logging.info(b.author)

    def test_descriptor(self):
        """
            通常, 一个descriptor指的是一个对象的属性绑定了行为, 这时一个对象获取属性的模型行为会被__get__(), __set__() 和 __delete__() 这三个方法覆盖. (文档上说, 如果上面三个方面实现了任意一个, 那么这个对象就被称为descriptor), 通俗一点讲, 就是重新定义了一个属性的查找, 设置和删除行为, 描述对象一般是作为其他类的属性.
            描述符一般是一个类.
            然而, 如果被查找的值是一个描述符, 那么Python将会唤醒文件描述符方法来覆盖掉原先的行为. 方法有如下几个
                1. 直接调用: x.__get__(a)
                2. 绑定实例: 如果绑定了一个对象实例, a.x -> type(a).__dict__['x'].__get__(a, type(a))
                3. 绑定类: 如果绑定了一个类 A.x -> A.__dict__['x'].__get__(None, A)

            描述符唤醒的级别由他实现的方法决定
                数据描述符: 定义了__set__() and/or __delete__()
                非数据描述符: 只实现了__get__()方法, 既没有定义__set__() 也没有定义 __delete__()

            查找a.x时候的调用优先级:
                1.  __getattribute__() // 无论怎样首先调用它
                2. 如果是数据描述符
                3. 实例的__dict__ // a.__dict__['x']
                4. 类的__dict__ // type(a).__dict__['x']
                5. 非数据描述符
                6. 父类的__dict__(直到元类)
                7. __getattr__()
        """

        class Descriptor(object):
            def __init__(self, val):
                self.val = val

            def __get__(self, instance, owner):
                logging.info('in __get__')
                logging.info('self: {}'.format(self))
                logging.info('instance: {}'.format(instance))
                logging.info('owner: {}'.format(owner))
                return self.val

            def __set__(self, instance, value):
                logging.info('in __set__')
                self.val = value

        class X(object):

            a = Descriptor(10)

            def __init__(self):
                self.a = 20  # 调用__set__
                self.b = Descriptor(30)

        x = X()
        print(x.a)
        """
            self: Descriptor实例, 即a
            instance: a
            owner: 所有者: X, 即descriptor的所有者
            调用方式为: type(x).__dict__['a'].__get__(a, X)
            结果为: 20
        """
        print(x.b)
        """
            为什么没有调用__get__方法呢?
            因为它是描述符方法, 调用方式为type(x).__dict__['b'].__get__(x, X)
            因为X方法没有['b']这个属性, 所以, 此处打印: <__main__.TestBook.test_descriptor.<locals>.Descriptor object at 0x103645518>
        """


if __name__ == '__main__':
    suite = unittest.TestSuite()
    # suite.addTest(TestBook('test_mro'))
    # suite.addTest(TestBook('test_del'))
    # suite.addTest(TestBook('test_repr'))
    # suite.addTest(TestBook('test_str'))
    # suite.addTest(TestBook('test_lt'))
    # suite.addTest(TestBook('test_attribute'))
    suite.addTest(TestBook('test_descriptor'))

    unittest.TextTestRunner(verbosity=1).run(suite)