Skip to content

Commit ab89420

Browse files
committed
p3
1 parent 48f85f4 commit ab89420

File tree

4 files changed

+291
-17
lines changed

4 files changed

+291
-17
lines changed

208.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,6 @@ self是一个很神奇的参数。
446446

447447
------
448448

449-
[总目录](./index.md)   |   [上节:类(2)](./207.md)   |   [下节:类(4)](./209.md)
449+
[总目录](./index.md)   |   [上节:类(2)](./207.md)   |   [下节:类(4)](./238.md)
450450

451451
如果你认为有必要打赏我,请通过支付宝:**[email protected]**,不胜感激。

238.md

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
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)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[上节:类(3)](./208.md)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[下节:类(5)](./209.md)
271+
272+
如果你认为有必要打赏我,请通过支付宝:**[email protected]**,不胜感激。

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,15 @@
7171
1. [类(1)](./206.md)==>类的初步认识和基本概念理解:问题空间、对象、面向对象、类和实例化类
7272
2. [类(2)](./207.md)==>新式类和旧式类,初步创建类和实例化
7373
3. [类(3)](./208.md)==>类属性、创建实例、实例属性、self作用、类内外数据流转
74-
4. [类(4)](./209.md)==>继承,多重继承,super函数
75-
5. [类(5)](./210.md)==>静态方法和类方法,两者的区别,类的文档
76-
6. [多态和封装](./211.md)==>多态,封装和私有化
77-
7. [特殊方法(1)](./212.md)==>`__dict__``__slots__`
78-
8. [特殊方法(2)](./213.md)==>`__getattr__`,`__setattr__`以及查找属性顺序,双划线解释
79-
9. [迭代器](./214.md)==>迭代器方法`__iter__`,`netx()`
80-
10. [生成器](./215.md)==>生成器定义,yield,生成器方法
81-
11. [上下文管理器](./235.md)==>上下文管理器的基本概念,使用方法和contextlib模块
74+
4. [类(4)](./238.md)==>绑定方法和非绑定方法,类方法和静态方法
75+
5. [类(5)](./209.md)==>继承,多重继承,super函数
76+
6. [类(6)](./210.md)==>静态方法和类方法,两者的区别,类的文档
77+
7. [多态和封装](./211.md)==>多态,封装和私有化
78+
8. [特殊方法(1)](./212.md)==>`__dict__``__slots__`
79+
9. [特殊方法(2)](./213.md)==>`__getattr__`,`__setattr__`以及查找属性顺序,双划线解释
80+
10. [迭代器](./214.md)==>迭代器方法`__iter__`,`netx()`
81+
11. [生成器](./215.md)==>生成器定义,yield,生成器方法
82+
12. [上下文管理器](./235.md)==>上下文管理器的基本概念,使用方法和contextlib模块
8283

8384
##第伍章 错误和异常
8485

index.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,15 @@
7171
1. [类(1)](./206.md)==>类的初步认识和基本概念理解:问题空间、对象、面向对象、类和实例化类
7272
2. [类(2)](./207.md)==>新式类和旧式类,类的命名,构造函数,实例化及方法和属性,self的作用
7373
3. [类(3)](./208.md)==>类属性、创建实例、实例属性、self作用、类内外数据流转
74-
4. [类(4)](./209.md)==>继承,多重继承,super函数
75-
5. [类(5)](./210.md)==>静态方法和类方法,两者的区别,类的文档
76-
6. [多态和封装](./211.md)==>多态,封装和私有化
77-
7. [特殊方法(1)](./212.md)==>`__dict__``__slots__`
78-
8. [特殊方法(2)](./213.md)==>`__getattr__`,`__setattr__`以及查找属性顺序,双划线解释
79-
9. [迭代器](./214.md)==>迭代器方法`__iter__`,`netx()`
80-
10. [生成器](./215.md)==>生成器定义,yield,生成器方法
81-
11. [上下文管理器](./235.md)==>上下文管理器的基本概念,使用方法和contextlib模块
74+
4. [类(4)](./238.md)==>绑定方法和非绑定方法,类方法和静态方法
75+
5. [类(5)](./209.md)==>继承,多重继承,super函数
76+
6. [类(6)](./210.md)==>静态方法和类方法,两者的区别,类的文档
77+
7. [多态和封装](./211.md)==>多态,封装和私有化
78+
8. [特殊方法(1)](./212.md)==>`__dict__``__slots__`
79+
9. [特殊方法(2)](./213.md)==>`__getattr__`,`__setattr__`以及查找属性顺序,双划线解释
80+
10. [迭代器](./214.md)==>迭代器方法`__iter__`,`netx()`
81+
11. [生成器](./215.md)==>生成器定义,yield,生成器方法
82+
12. [上下文管理器](./235.md)==>上下文管理器的基本概念,使用方法和contextlib模块
8283

8384
##第伍章 错误和异常
8485

0 commit comments

Comments
 (0)