来源:AI 领域中的培养与开发 ——by 丁宁
1 关于双下划线
1.1 怎么读
-
_name
:single underscore name -
__name
:double underscore name -
__name__
:dunder name -
__init__()
: dunder init method(function)
1.2 双下划线开头和结尾的变量或方法叫什么
- 类别:special;magic;dunder
- 实体:attribute;method
1.3 如何认识 Python 的 special method(摘自官方文档)
- special method:method with special name(dunder)
- Why use it?:A class can implement certain operations that are invoked by special syntax
- original intention of design:operator overloading
Allowing classes to define their own behavior with respect to language operators.
个人补充:尽可能的保证 Python 行为的一致性(Special Method 更像是一种协议)
2 从语言设计层面理解 Python 的数据模型
2.1 一切都是对象
- Python 中的数据模型是 Objects
- Python 程序中所有数据都是用 objects 或者 objects 之间的关系表示的
- 甚至 Python 代码都是 objects
2.2 Objects 的组成1:identity(标识)
- 当 objects 创建后,identity 再也不会改变直到被销毁
-
id()
与is
关注的就是一个 object 的 identity
a = 1
print(id(a))
a = 1.1
print(id(a))
a = 1
print(id(a))
b = 1
print(id(b))
c = 1.1
print(id(c))
下面是在 Ipython 中的结果:
1582197824
1547884955568
1582197824
1582197824
1547884955784
要点:
- 变量存的是创建的 object 的 identity
- 创建出来的不同的 object 有不同的 identity
- 变量的
id
变了不是因为 object 的 identity 变了,而是对应的 object 变了 - 对于 immutable object,计算结果如果已经存在可直接返回相同的 identity
注意:由于浮点数并不精确,所以上面的 1.1
的两个引用的 id
的不同的。
2.2 Objects 的组成2:type
- 当 objects 创建后,其
type
也是不会改变的 -
type()
函数返回一个 Object 的type
-
type
决定了了一个 object 支持哪些运算,可能的值在什么范围内
2.3 Objects 的组成3:value
- 有些 Objects 的 value 是可以改变的:mutable object
- 有些 Objects 的 value 是不能改变的:immutable object
- 需要注意当一个 Object 是个 container 的情况
- 一个 Object 的
type
决定了它是否 mutable
2.4 存放其他 Object 的 reference 的 Object :Container
- 当我们聚焦于 Container 的 values 时,我们关注的是
value
- 当我们聚焦于 Container 的 mutability 时,关注的是
identity
3 Pythonic OOP with Special Method and Attribute
3.1 The implicit superclass – object & type object
- 每一个
class
在定义的时候如果没有继承,都会隐式继承object
这个 superclass - 每一个自定义的
class
在 Python 中都是一个type
(object
的子类)
class X:
pass
class Y(X):
pass
def main():
x = X()
y = Y()
print(x.__class__.__name__)
print(y.__class__.__name__)
print(X.__class__.__name__)
print(Y.__class__.__name__)
print(x.__class__.__base__.__name__)
print(y.__class__.__base__.__name__)
print(X.__class__.__base__.__name__)
print(Y.__class__.__base__.__name__)
if __name__ == "__main__":
main()
X
Y
type
type
object
X
object
object
3.2 Integrating Seamlessly with Python
class X:
pass
class Y:
"""Class Y"""
def __str__(self):
return "{} object".format(self.__class__.__name__)
def __len__(self):
return 10
def __bool__(self):
return False
def check_bool(x):
if x:
print("I'm {}. My bool value is True.".format(str(x)))
else:
print("I'm {}. My bool value is False.".format(str(x)))
def main():
x = X()
y = Y()
print(x)
print(y)
# print(len(x))
print(len(y))
check_bool(x)
check_bool(y)
print(X.__doc__)
print(Y.__doc__)
if __name__ == "__main__":
main()
<__main__.X object at 0x0000016866469438>
Y object
10
I'm <__main__.X object at 0x0000016866469438>. My bool value is True.
I'm Y object. My bool value is False.
None
Class Y
要点:
- 之所以要实现 special method,是为了让自定义的
class
与 Python 的内置函数无缝衔接 - Python 有⼤大量的内置函数,而这些函数大部分都是调用的对象里的 special method
- 想查看 Python 中到底有多少 special method见 A Guide to Python's Magic Methods。
4 Attribute Access and Properties
Attribute 相关的操作一般有:
- Create
- Read
- Update
- Delete
数据库领域中的 CRUD 亦是如此
4.1 Level I:Basic Access(Default Access)
class X:
pass
if __name__ == "__main__":
# print(X.a)
X.a = "a"
print(X.a)
X.a = "aa"
print(X.a)
del X.a
# print(X.a)
说明:
- 默认情况下,CRUD 都⽀支持,而且是在
public
情况下都支持(除了双下划线开头的) - 如果对象中没有这个 Attribute,访问时会报错
4.2 Level II:Property(长得像 Attribute 的 Method)(推荐使用)
Property 的设计初衷:
- 代码复用
- 延迟计算
- 更加规范的对象属性访问管理
场景:我要减肥,需要监控 BMI 指标,但是只能测量体重,每天更新体重,隔几天看一次 BMI 指数
class X:
def __init__(self, w, h):
self.w = w # 体重
self.h = h # 身高
self.BMI = w / h**2
def main():
x = X(75, 1.83)
print(x.BMI)
x.w = 74
x.w = 73
x.w = 72
print(x.BMI)
if __name__ == "__main__":
main()
输出:
22.395413419331717
22.395413419331717
并没有得到预期输出,原因是 __init__
只执行一次。
改进⼀:
class X:
def __init__(self, w, h):
self.__w = w
self.__h = h
self.BMI = w / h**2
def update_w(self, w):
self.__w = w
self._update_bmi()
def _update_bmi(self):
self.BMI = self.__w / self.__h**2
def main():
x = X(75, 1.83)
print(x.BMI)
x.update_w(74)
x.update_w(73)
x.update_w(72)
print(x.BMI)
if __name__ == "__main__":
main()
便可得到预期输出:
22.395413419331717
21.49959688255845
分析:
-
w
变为私有,更新需要通过对象方法类执行,并将 BMI 的更新放于其中,实现功能逻辑 - BMI 属性依旧可以被外部访问和修改
- 与
w
相关的代码全部被更改 - 无论 BMI 属性是否被访问,每次
w
更新均会更新 BMI,造成一定的计算资源浪费
改进二:
class X:
def __init__(self, w, h):
self.w = w
self.h = h
def get_bmi(self):
return self.w / self.h**2
def main():
x = X(75, 1.83)
print(x.get_bmi())
x.w = 74
x.w = 73
x.w = 72
print(x.get_bmi())
if __name__ == "__main__":
main()
分析:
- 保持
w
和h
属性可以随意更改,BMI 指数仅在被访问时实时计算出结果 - 访问 BMI 的方式由属性改为方法,造成一定程度上的代码修改
- 在
w
更新频率高于 BMI 访问频率时,节省了计算资源 - 当
w
未更新,却多次调用 BMI 指数时,造成了重复计算
改进三:
class X:
def __init__(self, w, h):
self._w = w # 单下划线是告诉读者,最好不要改变
self._h = h
self._bmi = w / h**2
def get_w(self):
return self._w
def set_w(self, value):
if value <= 0:
raise ValueError("Weight below 0 is not possible.")
self._w = value
self._bmi = self._w / self._h**2
def get_bmi(self):
return self._bmi
w = property(get_w, set_w)
BMI = property(get_bmi)
def main():
x = X(75, 1.83)
print(x.BMI)
x.w = 74
x.w = 73
x.w = 72
print(x.BMI)
if __name__ == "__main__":
main()
分析:
- 通过
Property
对象显式的控制属性的访问 - 仅在
w
被更改的时候更新BMI,充分避免了重复计算 - 很容易的增加了异常处理,对更新属性进行预检验
- 完美复用原始调用代码,在调用方不知情的情况完成功能添加
说明:
-
Property
对象声明必须在访问控制函数之后,否则无法创建Property
对象 - 这里的写法是为了深⼊入剖析
Property
对象,其实一般不这样写,有更加优雅的写法
改进四:
class X:
def __init__(self, w, h):
self._w = w
self._h = h
self._bmi = w / h**2
@property
def w(self):
return self._w
@w.setter
def w(self, value):
if value <= 0:
raise ValueError("Weight below 0 is not possible.")
self._w = value
self._bmi = self._w / self._h**2
@property
def BMI(self):
return self._bmi
def main():
x = X(75, 1.83)
print(x.BMI)
x.w = 74
x.w = 73
x.w = 72
print(x.BMI)
if __name__ == "__main__":
main()
说明:
- 从改进三中我们发现,传给
Property
对象的其实是函数名,那么优雅的方式当属Decorator
了
关于 Property
的用法
property(fget=None, fset=None, fdel=None, doc=None)
- 使用
@Property
默认实现了可读 - 被
@Property
装饰过的 method 可以通过@method.setter
继续装饰单输入参数方法实现可写
5 Cross-Cutting and Duck Typing (Python 设计理念)
5.1 单继承 vs 多态
- 单继承保证了纵向的复用和一致性
- 多态保证了跨类型的复用和一致性
5.2 传统 OOP vs 鸭子类型
- 传统 OOP 基于类别进行设计,从类别出发逐步扩展
- 鸭子类型仅考虑功能,从需要满足的功能出发进行设计
5.3 传统 OOP 的多态 vs 鸭子类型的多态
- 传统 OOP 中的多态大多基于共同的基类进行设计
- Python 中的鸭子类型无需考虑继承关系,实现了某个通用的接口就可以完成多态设计(Special Method)
5.4 MixIn:基于鸭子类型的视角看 Multi-Inheritance
class X:
def f1():
pass
class Y(X):
def f2():
pass
class A:
def f3():
pass
def do_f1(x):
x.f1()
def do_f2(x):
x.f2()
def do_f3(x):
x.f3()
class Z(Y, A):
pass
MixIn 的设计用中文讲比较贴切的应该是:赋能
5.5 Cross-Cutting:基于鸭子类型的视角看 Decorator 与 Special Method
- 通过给一个类实现一个个的 Special Method,你就让这个类越来越像 Python 的 Built-in Class
- 实现 Special Method 是从语言衔接层面为你的 Class 赋能
- 实现 Decorator 是从通用的函数功能层面为你的 Class 赋能
- 通过 Multi-Inheritance,利用 MixIn 的理念,你可以为你的 Class 批量化的赋能
Python 语言设计的三个关键词(其实是一件事的三个不同视角)
- Duck Typing
- Consistency
- 赋能