Skip to content

Commit 245dd86

Browse files
committed
Signed-off-by: twowater <[email protected]>
1 parent 94fc86b commit 245dd86

File tree

9 files changed

+500
-2
lines changed

9 files changed

+500
-2
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@
1616
|草根学Python(六) 函数|[掘金](https://juejin.im/post/5946784461ff4b006cf1d8ec)[简书](http://www.jianshu.com/p/d8f2a55edc75)[CSDN](http://blog.csdn.net/Two_Water/article/details/73865622)[个人博客](http://twowater.com.cn/2017/06/29/%E8%8D%89%E6%A0%B9%E5%AD%A6Python-%E5%85%AD-%E5%87%BD%E6%95%B0/)|
1717
|草根学Python(七) 迭代器和生成器|[掘金](https://juejin.im/post/59589fedf265da6c386ce4ac)[简书](http://www.jianshu.com/p/74c0c1db1490)[CSDN](http://blog.csdn.net/Two_Water/article/details/74164652)[个人博客](http://twowater.com.cn/2017/07/02/%E8%8D%89%E6%A0%B9%E5%AD%A6Python-%E4%B8%83-%E8%BF%AD%E4%BB%A3%E5%99%A8%E5%92%8C%E7%94%9F%E6%88%90%E5%99%A8/)|
1818
|草根学Python(八) 模块与包|[掘金](https://juejin.im/post/5962ddf95188252ec34009da)[简书](http://www.jianshu.com/p/7f05f915d2ac)[CSDN](http://blog.csdn.net/Two_Water/article/details/75042211)[个人博客](http://twowater.com.cn/2017/07/12/%E8%8D%89%E6%A0%B9%E5%AD%A6Python-%E5%85%AB-%E6%A8%A1%E5%9D%97%E4%B8%8E%E5%8C%85/)|
19-
|草根学Python(九) 面向对象|[掘金](https://juejin.im/post/596ca6656fb9a06b9b73c8b0)[简书](http://www.jianshu.com/p/6ecaa414c702)[CSDN](http://blog.csdn.net/two_water/article/details/76408890)[个人博客](http://twowater.com.cn/2017/07/31/%E8%8D%89%E6%A0%B9%E5%AD%A6Python-%E4%B9%9D-%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/)|
19+
|草根学Python(九) 面向对象|[掘金](https://juejin.im/post/596ca6656fb9a06b9b73c8b0)[简书](http://www.jianshu.com/p/6ecaa414c702)[CSDN](http://blog.csdn.net/two_water/article/details/76408890)[个人博客](http://twowater.com.cn/2017/07/31/%E8%8D%89%E6%A0%B9%E5%AD%A6Python-%E4%B9%9D-%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/)|
20+
|草根学Python(十)Python 的 Magic Method|[掘金](https://juejin.im/post/59828c2f6fb9a03c56319baa)[简书](http://www.jianshu.com/p/345a80a02546)[CSDN](http://blog.csdn.net/two_water/article/details/77351516)[个人博客](http://twowater.com.cn/2017/08/18/%E8%8D%89%E6%A0%B9%E5%AD%A6Python-%E5%8D%81-Python-%E7%9A%84-Magic-Method/)|

SUMMARY.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,11 @@
4949
- [类的属性](/python9/3.md)
5050
- [类的方法](/python9/4.md)
5151
- [类的继承](/python9/5.md)
52-
- [类的多态](/python9/6.md)
52+
- [类的多态](/python9/6.md)
53+
* [Python 的 Magic Method](/python10/Preface.md)
54+
- [Python 的 Magic Method](/python10/1.md)
55+
- [构造(`__new__`)和初始化(`__init__`)](/python10/2.md)
56+
- [属性的访问控制](/python10/3.md)
57+
- [对象的描述器](/python10/4.md)
58+
- [自定义容器(Container)](/python10/5.md)
59+
- [运算符相关的魔术方法](/python10/6.md)

python10/1.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# 一、Python 的 Magic Method #
2+
3+
在 Python 中,所有以 "__" 双下划线包起来的方法,都统称为"魔术方法"。比如我们接触最多的 `__init__` 。魔术方法有什么作用呢?
4+
5+
使用这些魔术方法,我们可以构造出优美的代码,将复杂的逻辑封装成简单的方法。
6+
7+
那么一个类中有哪些魔术方法呢?
8+
9+
我们可以使用 Python 内置的方法 `dir()` 来列出类中所有的魔术方法.示例如下:
10+
11+
```python
12+
#!/usr/bin/env python3
13+
# -*- coding: UTF-8 -*-
14+
15+
class User(object):
16+
pass
17+
18+
19+
if __name__ == '__main__':
20+
print(dir(User()))
21+
```
22+
23+
输出的结果:
24+
25+
![Python 类的魔术方法](https://user-gold-cdn.xitu.io/2017/8/7/b8baa846d8b0f968b14e1e485afb239d)
26+
27+
可以看到,一个类的魔术方法还是挺多的,截图也没有截全,不过我们只需要了解一些常见和常用的魔术方法就好了。

python10/2.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# 二、构造(`__new__`)和初始化(`__init__`) #
2+
3+
通过上一篇的内容,我们已经知道定义一个类时,我们经常会通过 `__init__(self)` 的方法在实例化对象的时候,对属性进行设置。比如下面的例子:
4+
5+
```python
6+
#!/usr/bin/env python3
7+
# -*- coding: UTF-8 -*-
8+
9+
class User(object):
10+
def __init__(self, name, age):
11+
self.name = name;
12+
self.age = age;
13+
14+
user=User('两点水',23)
15+
```
16+
17+
实际上,创建一个类的过程是分为两步的,一步是创建类的对象,还有一步就是对类进行初始化。`__new__` 是用来创建类并返回这个类的实例, 而`__init__` 只是将传入的参数来初始化该实例.`__new__` 在创建一个实例的过程中必定会被调用,但 `__init__` 就不一定,比如通过pickle.load 的方式反序列化一个实例时就不会调用 `__init__` 方法。
18+
19+
![Python类创建的过程](https://user-gold-cdn.xitu.io/2017/8/3/dd588107e7e243bfbe11dc517fdb5308)
20+
21+
`def __new__(cls)` 是在 `def __init__(self)` 方法之前调用的,作用是返回一个实例对象。还有一点需要注意的是:`__new__` 方法总是需要返回该类的一个实例,而 `__init__` 不能返回除了 `None` 的任何值
22+
23+
具体的示例:
24+
25+
```python
26+
#!/usr/bin/env python3
27+
# -*- coding: UTF-8 -*-
28+
29+
class User(object):
30+
def __new__(cls, *args, **kwargs):
31+
# 打印 __new__方法中的相关信息
32+
print('调用了 def __new__ 方法')
33+
print(args)
34+
# 最后返回父类的方法
35+
return super(User, cls).__new__(cls)
36+
37+
def __init__(self, name, age):
38+
print('调用了 def __init__ 方法')
39+
self.name = name
40+
self.age = age
41+
42+
43+
if __name__ == '__main__':
44+
usr = User('两点水', 23)
45+
```
46+
47+
看看输出的结果:
48+
49+
```txt
50+
调用了 def __new__ 方法
51+
('两点水', 23)
52+
调用了 def __init__ 方法
53+
```
54+
55+
通过打印的结果来看,我们就可以知道一个类创建的过程是怎样的了,先是调用了 `__new__` 方法来创建一个对象,把参数传给 `__init__` 方法进行实例化。
56+
57+
其实在实际开发中,很少会用到 `__new__` 方法,除非你希望能够控制类的创建。通常讲到 `__new__` ,都是牵扯到 `metaclass`(元类)的。
58+
59+
当然当一个对象的生命周期结束的时候,析构函数 `__del__` 方法会被调用。但是这个方法是 Python 自己对对象进行垃圾回收的。

python10/3.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# 三、属性的访问控制 #
2+
3+
之前也有讲到过,Python 没有真正意义上的私有属性。然后这就导致了对 Python 类的封装性比较差。我们有时候会希望 Python 能够定义私有属性,然后提供公共可访问的 get 方法和 set 方法。Python 其实可以通过魔术方法来实现封装。
4+
5+
|方法|说明|
6+
| ---| --- |
7+
|`__getattr__(self, name)`|该方法定义了你试图访问一个不存在的属性时的行为。因此,重载该方法可以实现捕获错误拼写然后进行重定向, 或者对一些废弃的属性进行警告。|
8+
|`__setattr__(self, name, value)`|定义了对属性进行赋值和修改操作时的行为。不管对象的某个属性是否存在,都允许为该属性进行赋值.有一点需要注意,实现 `__setattr__` 时要避免"无限递归"的错误,|
9+
|`__delattr__(self, name)`|`__delattr__``__setattr__` 很像,只是它定义的是你删除属性时的行为。实现 `__delattr__` 是同时要避免"无限递归"的错误|
10+
|`__getattribute__(self, name)`|`__getattribute__` 定义了你的属性被访问时的行为,相比较,`__getattr__` 只有该属性不存在时才会起作用。因此,在支持 `__getattribute__ `的 Python 版本,调用`__getattr__` 前必定会调用 `__getattribute__``__getattribute__` 同样要避免"无限递归"的错误。|
11+
12+
通过上面的方法表可以知道,在进行属性访问控制定义的时候你可能会很容易的引起一个错误,可以看看下面的示例:
13+
14+
```python
15+
def __setattr__(self, name, value):
16+
self.name = value
17+
# 每当属性被赋值的时候, ``__setattr__()`` 会被调用,这样就造成了递归调用。
18+
# 这意味这会调用 ``self.__setattr__('name', value)`` ,每次方法会调用自己。这样会造成程序崩溃。
19+
20+
def __setattr__(self, name, value):
21+
# 给类中的属性名分配值
22+
self.__dict__[name] = value
23+
# 定制特有属性
24+
```
25+
26+
上面方法的调用具体示例如下:
27+
28+
```python
29+
#!/usr/bin/env python3
30+
# -*- coding: UTF-8 -*-
31+
32+
class User(object):
33+
def __getattr__(self, name):
34+
print('调用了 __getattr__ 方法')
35+
return super(User, self).__getattr__(name)
36+
37+
def __setattr__(self, name, value):
38+
print('调用了 __setattr__ 方法')
39+
return super(User, self).__setattr__(name, value)
40+
41+
def __delattr__(self, name):
42+
print('调用了 __delattr__ 方法')
43+
return super(User, self).__delattr__(name)
44+
45+
def __getattribute__(self, name):
46+
print('调用了 __getattribute__ 方法')
47+
return super(User, self).__getattribute__(name)
48+
49+
50+
if __name__ == '__main__':
51+
user = User()
52+
# 设置属性值,会调用 __setattr__
53+
user.attr1 = True
54+
# 属性存在,只有__getattribute__调用
55+
user.attr1
56+
try:
57+
# 属性不存在, 先调用__getattribute__, 后调用__getattr__
58+
user.attr2
59+
except AttributeError:
60+
pass
61+
# __delattr__调用
62+
del user.attr1
63+
64+
```
65+
66+
输出的结果:
67+
68+
```txt
69+
调用了 __setattr__ 方法
70+
调用了 __getattribute__ 方法
71+
调用了 __getattribute__ 方法
72+
调用了 __getattr__ 方法
73+
调用了 __delattr__ 方法
74+
```

python10/4.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# 四、对象的描述器 #
2+
3+
一般来说,一个描述器是一个有“绑定行为”的对象属性 (object attribute),它的访问控制被描述器协议方法重写。这些方法是 `__get__()`, `__set__()` , 和 `__delete__()` 。有这些方法的对象叫做描述器。
4+
5+
默认对属性的访问控制是从对象的字典里面 (`__dict__`) 中获取 (get) , 设置 (set) 和删除 (delete) 。举例来说, `a.x` 的查找顺序是, `a.__dict__['x']` , 然后 `type(a).__dict__['x']` , 然后找 `type(a)` 的父类 ( 不包括元类 (metaclass) ).如果查找到的值是一个描述器, Python 就会调用描述器的方法来重写默认的控制行为。这个重写发生在这个查找环节的哪里取决于定义了哪个描述器方法。注意, 只有在新式类中时描述器才会起作用。在之前的篇节中已经提到新式类和旧式类的,有兴趣可以查看之前的篇节来看看,至于新式类最大的特点就是所有类都继承自 type 或者 object 的类。
6+
7+
在面向对象编程时,如果一个类的属性有相互依赖的关系时,使用描述器来编写代码可以很巧妙的组织逻辑。在 Django 的 ORM 中,models.Model中的 InterField 等字段, 就是通过描述器来实现功能的。
8+
9+
我们先看下下面的例子:
10+
11+
```python
12+
#!/usr/bin/env python3
13+
# -*- coding: UTF-8 -*-
14+
15+
class User(object):
16+
def __init__(self, name='两点水', sex=''):
17+
self.sex = sex
18+
self.name = name
19+
20+
def __get__(self, obj, objtype):
21+
print('获取 name 值')
22+
return self.name
23+
24+
def __set__(self, obj, val):
25+
print('设置 name 值')
26+
self.name = val
27+
28+
29+
class MyClass(object):
30+
x = User('两点水', '')
31+
y = 5
32+
33+
34+
if __name__ == '__main__':
35+
m = MyClass()
36+
print(m.x)
37+
38+
print('\n')
39+
40+
m.x = '三点水'
41+
print(m.x)
42+
43+
print('\n')
44+
45+
print(m.x)
46+
47+
print('\n')
48+
49+
print(m.y)
50+
51+
```
52+
53+
输出的结果如下:
54+
55+
```txt
56+
获取 name 值
57+
两点水
58+
59+
60+
设置 name 值
61+
获取 name 值
62+
三点水
63+
64+
65+
获取 name 值
66+
三点水
67+
68+
69+
5
70+
71+
```
72+
73+
通过这个例子,可以很好的观察到这 `__get__()``__set__()` 这些方法的调用。
74+
75+
再看一个经典的例子
76+
77+
我们知道,距离既可以用单位"米"表示,也可以用单位"英尺"表示。
78+
现在我们定义一个类来表示距离,它有两个属性: 米和英尺。
79+
80+
```python
81+
#!/usr/bin/env python3
82+
# -*- coding: UTF-8 -*-
83+
84+
85+
class Meter(object):
86+
def __init__(self, value=0.0):
87+
self.value = float(value)
88+
89+
def __get__(self, instance, owner):
90+
return self.value
91+
92+
def __set__(self, instance, value):
93+
self.value = float(value)
94+
95+
96+
class Foot(object):
97+
def __get__(self, instance, owner):
98+
return instance.meter * 3.2808
99+
100+
def __set__(self, instance, value):
101+
instance.meter = float(value) / 3.2808
102+
103+
104+
class Distance(object):
105+
meter = Meter()
106+
foot = Foot()
107+
108+
109+
if __name__ == '__main__':
110+
d = Distance()
111+
print(d.meter, d.foot)
112+
d.meter = 1
113+
print(d.meter, d.foot)
114+
d.meter = 2
115+
print(d.meter, d.foot)
116+
117+
```
118+
119+
输出的结果:
120+
121+
```txt
122+
0.0 0.0
123+
1.0 3.2808
124+
2.0 6.5616
125+
```
126+
127+
在上面例子中,在还没有对 Distance 的实例赋值前, 我们认为 meter 和 foot 应该是各自类的实例对象, 但是输出却是数值。这是因为 `__get__` 发挥了作用.
128+
129+
我们只是修改了 meter ,并且将其赋值成为 int ,但 foot 也修改了。这是 `__set__` 发挥了作用.
130+
131+
描述器对象 (Meter、Foot) 不能独立存在, 它需要被另一个所有者类 (Distance) 所持有。描述器对象可以访问到其拥有者实例的属性,比如例子中 Foot 的 `instance.meter`

0 commit comments

Comments
 (0)