|
| 1 | +>爱人不可虚假,恶要厌恶,善要亲近。爱弟兄,要彼此亲热;恭敬人,要彼此推让。殷勤不可懒惰。要心里火热,常常服侍主。在指望中要有喜乐,在患难中要有忍耐,祷告要恒切。圣徒缺乏要帮补,客要一味地款待。逼迫你们的,要给他们祝福,只要祝福,不可诅咒。与喜乐的人要同乐,与哀哭的人要同哭。要彼此同心,不要志气高大,倒要俯就卑微的人。不要自以为聪明。不要以恶报恶。众人以为美的事,要留心去作。(ROMANS 12:9-17) |
| 2 | +
|
| 3 | +#类(4) |
| 4 | + |
| 5 | +##方法 |
| 6 | + |
| 7 | +在类里面,除了属性,就是方法,当然还有注释和文档,但这些是计算机不看的,只是给人看的。 |
| 8 | + |
| 9 | +关于方法,在通常情况下用实例调用。但是,跟方法有关的一些深入的话题,还需要辨析。 |
| 10 | + |
| 11 | +###绑定方法和非绑定方法 |
| 12 | + |
| 13 | +除了特殊方法,类中的其它的普通方法,是经常要用到的,所以,要对这些普通方法进行研究。 |
| 14 | + |
| 15 | + >>> class Foo: #Python 2: class Foo(object): |
| 16 | + def bar(self): |
| 17 | + print("This is a normal method of class.") #Python 2 使用print 语句 |
| 18 | + |
| 19 | + |
| 20 | + >>> f = Foo() |
| 21 | + >>> f.bar() |
| 22 | + This is a normal method of class. |
| 23 | + |
| 24 | +在类`Foo`中,方法`bar()`本质上是一个函数,只不过这个函数的第一个参数必须是`self`——在类中给它另外一个名字,叫“方法”——跟函数相比,没有本质的不同。 |
| 25 | + |
| 26 | +当建立了实例之后,用实例开调用这个方法的时候,因为Python解释器把实例已经作为第一参数隐式地传给了该方法,所以就不需要显示地写出`self`参数了——这个观点反复强调,就是让读者理解`self`就是实例。 |
| 27 | + |
| 28 | +如果要把实例显示地传给方法,可以用下面的方式进行, |
| 29 | + |
| 30 | + >>> Foo.bar(f) |
| 31 | + This is a normal method of class. |
| 32 | + |
| 33 | +用这种方式,跟证实了前述观点,即实例化之后,`self`和实例`f`是相同的。通常,我们在类里面使用`self`,类外面使用`f`这个实例,两者有分工。 |
| 34 | + |
| 35 | +如果在用类调用方法的时候,不传实例,会怎样? |
| 36 | + |
| 37 | + >>> Foo.bar() |
| 38 | + |
| 39 | +这样执行,不同的Python版本,报错信息有所不同。 |
| 40 | + |
| 41 | +Python 2的报错信息是: |
| 42 | + |
| 43 | + Traceback (most recent call last): |
| 44 | + File "<pyshell#7>", line 1, in <module> |
| 45 | + Foo.bar() |
| 46 | + TypeError: unbound method bar() must be called with Foo instance as first argument (got nothing instead) |
| 47 | + |
| 48 | +而Python 3下报错信息变成了: |
| 49 | + |
| 50 | + Traceback (most recent call last): |
| 51 | + File "<pyshell#7>", line 1, in <module> |
| 52 | + Foo.bar() |
| 53 | + TypeError: bar() missing 1 required positional argument: 'self' |
| 54 | + |
| 55 | +不管你是用什么版本,最好都阅读上述两个报错信息。在Python 2的报错信息中,告诉我们`barr()`是非绑定方法,它必须以`Foo`的实例作为第一个参数;Python 3的报错信息也是告诉我们`bar()`缺少一个必须的参数`self`,它也是一个实例。所以,不管哪个版本,都要传一个实例。 |
| 56 | + |
| 57 | +Python中一切皆对象——又是老生常谈,都是因为此观念之重要。类`Foo`的方法`bar()`也是对象——函数对象,那么,我们就可以像这样来获得该对象了。 |
| 58 | + |
| 59 | + >>> Foo.bar |
| 60 | + <unbound method Foo.bar> #Python 3的显示结果:<function Foo.bar at 0x00000000006AAC80> |
| 61 | + |
| 62 | +在Python 2的结果中,可以很清晰看出,通过类调用的方法对象,是一个非绑定方法——unbound method,又遇到这个词语了。 |
| 63 | + |
| 64 | +此外,还可以通过实例来得到该对象。 |
| 65 | + |
| 66 | + >>> f.bar |
| 67 | + <bound method Foo.bar of <__main__.Foo object at 0x02A9BFB0>> |
| 68 | + |
| 69 | +用实例来得到这个方法对象,不管是Python 2还是Python 3,结果是一样的。在这里我们看到的是bound method——绑定方法。 |
| 70 | + |
| 71 | +下面就要逼近unbound method和bound method的概念本质了。 |
| 72 | + |
| 73 | +在类`Foo`的属性中,有一个`__dict__`的特殊属性,前文已经介绍过了。我们使用它,来窥探内部信息。 |
| 74 | + |
| 75 | + >>> Foo.__dict__['bar'] |
| 76 | + <function bar at 0x02AA98F0> #Python 3显示结果:<function Foo.bar at 0x00000000006AAC80> |
| 77 | + |
| 78 | +从这个层面进一步说明`bar`是一个函数对象。 |
| 79 | + |
| 80 | +这个似乎是已经熟悉的了。 |
| 81 | + |
| 82 | +下面我们再看一个新的东西——描述器。 |
| 83 | + |
| 84 | +什么是描述器? |
| 85 | + |
| 86 | +Python中有几个特殊方法比较特殊,它们分别是`__get__()`、`__set__()`和`__delete__()`,简单地说,有这些方法的对象叫做描述器。 |
| 87 | + |
| 88 | +描述器是属性、实例方法、静态方法、类方法和继承中使用的`super`的背后实现机制,它在Python中使用广泛。这句话中的那些生疏的名词,以后都会用到,稍安勿躁。 |
| 89 | + |
| 90 | +如何使用? |
| 91 | + |
| 92 | +上述三个特殊方法,可以用下面的方式来使用——所谓的描述器协议。 |
| 93 | + |
| 94 | + descr.__get__(self, obj, type=None) --> value |
| 95 | + |
| 96 | + descr.__set__(self, obj, value) --> None |
| 97 | + |
| 98 | + descr.__delete__(self, obj) --> None |
| 99 | + |
| 100 | +关于描述器的内容,本节不重点阐述,这里提及它,目的是要解决“绑定方法”和“非绑定方法”的问题,所以,读者如果有兴趣深入了解描述器,可以去google。 |
| 101 | + |
| 102 | +弱水三千,只取一瓢。我们在这里也只看`__get__()`。 |
| 103 | + |
| 104 | + >>> Foo.__dict__['bar'].__get__(None, Foo) |
| 105 | + <unbound method Foo.bar> #Python 3显示结果:<function Foo.bar at 0x00000000006AAC80> |
| 106 | + |
| 107 | +对照描述器协议,我将`self`赋以了`None`,其返回结果和`Foo.bar`的返回结果是一样的。让`self`为`None`的意思就是没有给定的实例,因此该方法被认为非绑定方法(unbound method)。 |
| 108 | + |
| 109 | +如果给定一个实例呢? |
| 110 | + |
| 111 | + >>> Foo.__dict__['bar'].__get__(f, Foo) |
| 112 | + <bound method Foo.bar of <__main__.Foo object at 0x02A9BFB0>> |
| 113 | + |
| 114 | +这时候的显示结果和`f.bar`是相同的。 |
| 115 | + |
| 116 | +综上所述,可以认为: |
| 117 | + |
| 118 | +- 当通过类来获取方法的时候,得到的是非绑定方法对象。 |
| 119 | +- 当通过实例获取方法的时候,得到的是绑定方法对象。 |
| 120 | + |
| 121 | +所以,通常用实例调用的方法,都是绑定方法。那么非绑定方法在哪里会用到呢?当学习“继承”相关内容的时候,它会再次登场。 |
| 122 | + |
| 123 | +###静态方法和类方法 |
| 124 | + |
| 125 | +先看下面的代码 |
| 126 | + |
| 127 | + #!/usr/bin/env python |
| 128 | + #coding:utf-8 |
| 129 | + |
| 130 | + class Foo(object): |
| 131 | + one = 0 |
| 132 | + |
| 133 | + def __init__(self): |
| 134 | + Foo.one = Foo.one + 1 |
| 135 | + |
| 136 | + def get_class_attr(cls): |
| 137 | + return cls.one |
| 138 | + |
| 139 | + if __name__ == "__main__": |
| 140 | + f1 = Foo() |
| 141 | + print "f1:",Foo.one #Python 3: print("f1:"+str(Foo.one)),下同,从略 |
| 142 | + f2 = Foo() |
| 143 | + print "f2:",Foo.one |
| 144 | + |
| 145 | + print get_class_attr(Foo) |
| 146 | + |
| 147 | +在上述代码中,有一个函数`get_class_attr()`,这个函数的参数我用`cls`,从函数体的代码中看,要求它引用的对象应该具有属性`one`,这就说明,不是随便一个对象就可以的。恰好,就是这么巧,我在前面定义的类`Foo`中,就有`one`这个属性。于是乎,我在调用这个函数的时候,就直接将该类对象传给了它`get_class_attr(Foo)`。 |
| 148 | + |
| 149 | +其运行结果如下: |
| 150 | + |
| 151 | + f1: 1 |
| 152 | + f2: 2 |
| 153 | + 2 |
| 154 | + |
| 155 | +在这个程序中,函数`get_class_attr()`写在了类的外面,但事实上,函数只能调用前面写的那个类对象,因为不是所有对象都有那个特别的属性的。所以,这种写法,使得类和函数的耦合性太强了,不便于以后维护。这种写法是应该避免的。避免的方法就是把函数与类融为一体。于是就有了下面的写法。 |
| 156 | + |
| 157 | + #!/usr/bin/env python |
| 158 | + #coding:utf-8 |
| 159 | + |
| 160 | + class Foo(object): |
| 161 | + one = 0 |
| 162 | + |
| 163 | + def __init__(self): |
| 164 | + Foo.one = Foo.one + 1 |
| 165 | + |
| 166 | + @classmethod |
| 167 | + def get_class_attr(cls): |
| 168 | + return cls.one |
| 169 | + |
| 170 | + if __name__ == "__main__": |
| 171 | + f1 = Foo() |
| 172 | + print "f1:",Foo.one |
| 173 | + f2 = Foo() |
| 174 | + print "f2:",Foo.one |
| 175 | + |
| 176 | + print f1.get_class_attr() |
| 177 | + print "f1.one",f1.one |
| 178 | + print Foo.get_class_attr() |
| 179 | + |
| 180 | + print "*"* 10 |
| 181 | + f1.one = 8 |
| 182 | + Foo.one = 9 |
| 183 | + print f1.one |
| 184 | + print f1.get_class_attr() |
| 185 | + print Foo.get_class_attr() |
| 186 | + |
| 187 | +在这个程序中,出现了`@classmethod`——装饰器——在函数那部分遇到过了。需要注意的是`@classmethod`所装饰的方法的参数中,第一个参数不是`self`,这是和我们以前看到的类中的方法是有区别的。这里我使用了参数`cls`,你用别的也可以,只不过习惯用`cls`。 |
| 188 | + |
| 189 | +再看对类的使用过程。先贴出上述程序的执行结果: |
| 190 | + |
| 191 | + f1: 1 |
| 192 | + f2: 2 |
| 193 | + 2 |
| 194 | + f1.one 2 |
| 195 | + 2 |
| 196 | + ********** |
| 197 | + 8 |
| 198 | + 9 |
| 199 | + 9 |
| 200 | + |
| 201 | +分别建立两个实例,此后类属性`Foo.one`的值是2,然后分别通过实例和类来调用`get_class_attr()`方法(没有显示写`cls` 参数),结果都相同。 |
| 202 | + |
| 203 | +当修改类属性和实例属性,再次通过实例和类调用`get_class_attr()`方法,得到的依然是类属性的结果。这说明,装饰器`@classmethod`所装饰的方法,其参数`cls`引用的对象是类对象`Foo`。 |
| 204 | + |
| 205 | +至此,可以下一个定义了。 |
| 206 | + |
| 207 | +所谓类方法,就是在类里面定义的方法,该方法由装饰器`@classmethod`所装饰,其第一个参数`cls`所引用的是这个类对象,即将类本身作为引用对象传入到此方法中。 |
| 208 | + |
| 209 | +理解了类方法之后,用同样的套路理解另外一个方法——静态方法。还是先看代码——一个有待优化的代码。 |
| 210 | + |
| 211 | + #!/usr/bin/env python |
| 212 | + #coding:utf-8 |
| 213 | + |
| 214 | + T = 1 |
| 215 | + |
| 216 | + def check_t(): |
| 217 | + T = 3 |
| 218 | + return T |
| 219 | + |
| 220 | + class Foo(object): |
| 221 | + def __init__(self,name): |
| 222 | + self.name = name |
| 223 | + |
| 224 | + def get_name(self): |
| 225 | + if check_t(): |
| 226 | + return self.name |
| 227 | + else: |
| 228 | + return "no person" |
| 229 | + |
| 230 | + if __name__ == "__main__": |
| 231 | + f = Foo("canglaoshi") |
| 232 | + name = f.get_name() |
| 233 | + print name #Python 3: print(name) |
| 234 | + |
| 235 | +先观察上面的程序,发现在类`Foo`里面使用了外面定义的函数`check_t()`。这种类和函数的关系,也是由于有密切关系,从而导致程序维护有困难,于是在和前面同样的理由之下,就出现了下面比较便于维护的程序。 |
| 236 | + |
| 237 | + #!/usr/bin/env python |
| 238 | + #coding:utf-8 |
| 239 | + |
| 240 | + T = 1 |
| 241 | + |
| 242 | + class Foo(object): |
| 243 | + def __init__(self,name): |
| 244 | + self.name = name |
| 245 | + |
| 246 | + @staticmethod |
| 247 | + def check_t(): |
| 248 | + T = 1 |
| 249 | + return T |
| 250 | + |
| 251 | + def get_name(self): |
| 252 | + if self.check_t(): |
| 253 | + return self.name |
| 254 | + else: |
| 255 | + return "no person" |
| 256 | + |
| 257 | + if __name__ == "__main__": |
| 258 | + f = Foo("canglaoshi") |
| 259 | + name = f.get_name() |
| 260 | + print name #Python 3: print(name) |
| 261 | + |
| 262 | +经过优化,将原来放在类外面的函数,移动到了类里面,也就是函数`check_t()`现在位于类`Foo`的命名空间之内了。但是,不是简单的移动,还要在这个函数的前面加上`@staticmethod`装饰器,并且要注意的是,虽然这个函数位于类的里面,跟其它的方法不同,它不以`self`为第一个参数。当使用它的时候,可以通过实例调用,比如`self.check_t()`;也可以通过类调用这个方法,比如`Foo.check_t()`。 |
| 263 | + |
| 264 | +从上面的程序可以看出,尽管`check_t()`位于类的命名空间之内,它却是一个独立的方法,跟类没有什么关系,仅仅是为了免除前面所说的维护上的困难,写在类的作用域内的普通函数罢了。但,它的存在也是有道理的,以上的例子就是典型说明。当然,在类的作用域里面的时候,前面必须要加上一个装饰器`@staticmethod`。我们将这种方法也给予命名,称之为静态方法。 |
| 265 | + |
| 266 | +方法,是类的重要组成部分。本节专门讲述了方法中的几种特殊方法,它们为我们使用类的方法提供了更多便利的工具。但是,类的重要特征之一——继承,还没有亮相。 |
| 267 | + |
| 268 | +------ |
| 269 | + |
| 270 | +[总目录](./index.md) | [上节:类(3)](./208.md) | [下节:类(5)](./209.md) |
| 271 | + |
| 272 | +如果你认为有必要打赏我,请通过支付宝: **[email protected]**,不胜感激。 |
0 commit comments