- 1 建议 54:理解 built-in objects
- 2 建议 55:__init__() 不是构造方法
- 3 建议 56:理解名字查找机制
- 4 建议 57:为什么需要 self 参数
- 5 建议 58:理解 MRO 与多继承
- 6 建议 59:理解描述符机制
- 7 建议 60:区别 __getattr__() 和 __getattribute__() 方法
- 8 建议 61:使用更为安全的 property
- 9 建议 62:掌握 metaclass
- 10 建议 63:熟悉 Python 对象协议
- 11 建议 64:使用操作符重载实现中缀语法
- 12 建议 65:熟悉 Python 的迭代器协议
- 13 建议 66:熟悉 Python 的生成器
- 14 建议 67:基于生成器的协程及 greenlet
- 15 建议 68:理解 GIL 的局限性
- 16 建议 69:对象的管理与垃圾回收
1 建议 54:理解 built-in objects
在 Python 中一切皆对象,type 也是对象
Python2中为了兼容,分为古典类(旧式类)和新式类;Python3中所有类都是新式类
object
和古典类没有基类,其他所有类(包括新式类)都继承自object
Python2中古典类是深度优先原则,新式类中广度优先原则,Python3对于多重继承的属性搜索都是广度优先搜索
新式类中执行
type(s)
与a.__class__
的结果是一样的,但古典类则不一定
2 建议 55:__init__()
不是构造方法
__init__()
并不是真正意义上的构造方法,__init__()
方法所做的工作是在类的对象创建好之后进行变量的初始化。__new__()
方法才会真正创建实例,是类的构造方法。这两个方法都是 object 类中默认的方法,继承自 object 的新式类,如果不覆盖这两个方法将会默认调用 object 中对应的方法。
来看看 __new__()
方法和 __init__()
方法的定义:
object.__new__(cls[, args...])
:其中 cls 代表类,args 为参数列表object.__init__(self[, args...])
:其中 self 代表实例对象,args 为参数列表
__new__()
方法 VS__init__()
方法
__new__()
方法是静态方法,而__init__()
为实例方法__new__()
方法一般需要返回类的对象,当返回类的对象时将会自动调用__init__()
方法进行初始化,如果没有对象返回,则__init__()
方法不会被调用。__init__()
方法不需要显示返回,默认为 None,否则会在运行时抛出 TypeError- 控制实例创建时使用
__new__()
方法,控制实例初始化时使用__init__()
方法- 一般情况下不需要覆盖
__new__()
方法,但当子类继承自不可变类型,如str
、int
、unicode
或者tuple
的时候,往往需要覆盖该方法- 当覆盖
__new__()
和__init__()
方法时,参数必须保持一致,否则将导致异常。
3 建议 56:理解名字查找机制
命名空间(Namespace)是从名称到对象的映射(一对一或一对多)
变量名所在的命名空间直接决定了其能访问到的范围,即变量的作用域
Python 有四种作用域:
- L(Local):最内层,比如一个函数/方法内部的局部变量。调用
locals()
函数可查看 - E(Enclosing):包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的变量名称来说 A 中的作用域就为 nonlocal。
- G(Global):当前脚本的最外层,比如当前模块的全局变量。调用
globals()
函数可查看 - B(Built-in): 包含了内建的变量/关键字等,在
__builtin__
中可查看
变量查找的规则顺序: L –> E –> G –> B。
4 建议 57:为什么需要 self 参数
self 表示的就是实例对象本身,即类的对象在内存中的地址。
self的保留也方便区分静态方法与类方法、局部变量与实例变量
5 建议 58:理解 MRO 与多继承
MRO(Method Resolution Order),对于类中的方法的解析顺序(数据属性也适用)
在古典类中,MRO 搜索采用简单的自左向右的深度优先方法
而新式类采用的而是 C3 MRO 搜索方法,解决原来基于深度优先搜索算法不满足本地优先级,和单调性的问题。
本地优先级:指声明时父类的顺序,比如C(A,B),如果访问C类对象属性时,应该根据声明顺序,优先查找A类,然后再查找B类。
单调性:如果在C的解析顺序中,A排在B的前面,那么在C的所有子类里,也必须满足这个顺序。
具体算法步骤可参考:Python的多重继承问题-MRO和C3算法
6 建议 59:理解描述符机制
每一个类或实例都有一个 __dict__
属性表,其中包含它的所有属性
通过 "." 操作符访问一个属性时,根据访问实例还是类有以下两种情况:
- 通过实例访问,比如代码
obj.x
,如果 x 是一个描述符,那么__getattribute__()
会返回type(obj).__dict__['x'].__get__(obj, type(obj))
结果,即:type(obj)
获取 obj 的类型;type(obj).__dict__['x']
返回的是一个描述符,这里有一个试探和判断的过程;最后调用这个描述符的__get__()
方法。 - 通过类访问的情况,比如代码
cls.x
,则会被__getattribute__()
转换为cls.__dict__['x'].__get__(None, cls)
。
描述符的定义很简单,实现了下列_任意一个方法_的 Python 对象就是一个描述符(descriptor):
__get__(self, obj, type=None)
__set__(self, obj, value)
__delete__(self, obj)
描述符可以用来控制对属性的访问行为,实现计算属性、懒加载属性、属性访问控制等功能
进一步细节可参考理解 Python 中的描述符
7 建议 60:区别 __getattr__()
和 __getattribute__()
方法
__getattr__()
和 __getattribute__()
都可以用作实例属性的获取和拦截
对于所有属性的访问都会调用
__getattribute__()
方法,对类变量的访问不会涉及__getattribute__()
和__getattr__()
方法。__getattribute__()
方法调用后要么返回实际的值,要么抛出异常property
也能控制属性的访问,如果一个类中同时定义了property
、__getattribute__()
和__getattr__()
来对属性进行访问控制,则最先搜索的是__getattribute__()
方法__getattr__()
方法调用发生在以下两种情况:- 当属性不在实例、基类、祖先类的
__dict__
中 - 触发
AttributeError
异常
- 当属性不在实例、基类、祖先类的
AttributeError
异常一般由__getattribute__()
方法或property 中get()
方法抛出__getattr__()
调用后,一般返回AttributeError
异常或者显式返回值,否则返回None
8 建议 61:使用更为安全的 property
property
是一种实现了 __get__()
、__set__()
方法用于管理属性的特殊类
- 若实现了
__set__()
或__delete__()
任一方法,该描述符是一个数据描述符 - 若仅实现
__get__()
方法,该描述符是一个非数据描述符 property
一种特殊的数据描述符,相比于普通描述符所提供的较为低级的控制属性访问机制,property
以标准库的形式提供描述符的高级应用(安全、统一、简洁、可控)
9 建议 62:掌握 metaclass
元类是关于类的类,是类的模版
元类是用来控制如何创建类的,正如类是创建对象的模版一样
元类的实例为类,正如类的实例为对象
10 建议 63:熟悉 Python 对象协议
- 类型转换协议:
__str__()
字符串表示、__repr__()
字符串对象表示、__init__()
实例的变量初始化、__long__()
长整型表示、__float__()
浮点型表示、__nonzero__()
布尔型表示 - 比较大小的协议,依赖于
__cmp__()
,当两者相等时,返回 0,当self < other
时返回负值,反之返回正值。还有__eq__()
、__ne__()
、__lt__()
、__gt__()
等方法来实现相等、不等、小于和大于的判定 - 数值类型相关的协议,加减乘除之类的,不再赘叙
- 容器类型协议,内置函数
len()
,通过__len__()
来完成。而__getitem__()
、__setitem__()
、__delitem__()
则对应读、写和删除。__iter__()
实现了迭代器协议,__reversed__()
提供对内置函数reversed()
的支持,而__contains__()
则提供对判断符 in 和 not in 的支持。 - 可调用对象协议,
__call__
class Functor(object):
def __init__(self, context):
self._context = context
def __call__(self):
print("do something with {}".format(self._context))
lai_functor = Functor("lai")
lai_functor()
- 可哈希对象协议,
__hash__()
(只有支持可哈希协议的类型才能作为 dict 的键) - 描述符协议和属性交互协议,
__getattr__()
、__setattr__()
、__delattr__()
,还有上下文管理器协议,也就是对 with 语句的支持,这个协议通过__enter__()
和__exit__()
两个方法来实现对资源的清理,确保资源无论在什么情况下都会正常清理。
11 建议 64:使用操作符重载实现中缀语法
pipe
模块实现了类似于unix的管道符操作方式(个人之前没怎么用过,不赘述)
示例:找出小于 1000000 的斐波那契数,并计算其中的偶数的平方之和
fib() | take_while(lambda x: x < 1000000) \
| where(lambda x: x % 2) \
| select(lambda x: x * x) \
| sum()
具体可参考Pipe的Github地址
12 建议 65:熟悉 Python 的迭代器协议
迭代器协议简单归纳如下:
实现
__iter__()
方法,返回一个迭代器实现
next()
方法,返回当前的元素,并指向下一个元素的位置,如果当前位置已无元素,则抛出StopIteration
异常。
以斐波那契数列为例,定义一个迭代器:
class Fib(object):
def __init__(self, max=0):
super(Fib, self).__init__()
self.prev = 0
self.curr = 1
self.max = max
def __iter__(self):
return self
def __next__(self):
if self.max > 0:
self.max -= 1
# 当前要返回的元素的值
value = self.curr
# 下一个要返回的元素的值
self.curr += self.prev
# 设置下一个元素的上一个元素的值
self.prev = value
return value
else:
raise StopIteration
if __name__ == '__main__':
fib = Fib(10)
# 调用next()的过程
for n in fib:
print(n)
# raise StopIteration
print(next(fib))
针对迭代器,itertools
模块提供了一系列计算快速、内存高效的函数,比如说:
迭代器 | 实参 | 说明 | 示例 |
---|---|---|---|
count() |
start, [step] | 算术递增数列 | count(10) --> 10 11 12 ... |
cycle() |
p | 无限重复的序列 | cycle('ABC') --> A B C A B C ... |
repeat() |
elem [,n] | 同一个值重复的序列 | repeat(10, 3) --> 10 10 10 |
accumulate() |
p [,func] | 值累积相加的序列 | accumulate([1,2,3,4]) --> 1 3 6 10 |
compress() |
data, selectors | 筛选后的序列 | compress('ABCD', [1,0,0,1]) --> A D |
product() |
p, q, ... [repeat=1] | 笛卡尔积 | |
permutations() |
p[, r] | 全排列 | |
combinations() |
p, r | 无放回抽样 | |
combinations_with_replacement() |
p[, r] | 有放回抽样 |
具体可参考itertools--为高效循环而创建迭代器的函数
13 建议 66:熟悉 Python 的生成器
生成器函数用关键字 yield
代替 return
来返回值,是一种更加优雅的迭代器
以斐波那契数列为例,定义一个生成器:
def fib(max):
prev, curr = 0, 1
while max > 0:
max -= 1
yield curr
prev, curr = curr, prev + curr
if __name__ == '__main__':
fib = fib(6)
# 调用next()的过程
for n in fib:
print(n)
# raise StopIteration
print(next(fib))
参考如何更好地理解Python迭代器和生成器? - 刘志军的回答 - 知乎
14 建议 67:基于生成器的协程及 greenlet
1_study/ComputerScience/programming/进程、线程与协程#协程
15 建议 68:理解 GIL 的局限性
16 建议 69:对象的管理与垃圾回收
Python 中内存管理的方式:Python 使用引用计数器(Reference counting)的方法来管理内存中的对象,即针对每一个对象维护一个引用计数值来表示该对象当前有多少个引用。当引用计数的值为 0 时的时候该对象是个不可达对象,会被垃圾收集器回收。
引用计数算法最明显的缺点是无法解决循环引用的问题,即两个对象相互引用。不过Python 自带的一个 gc 模块能找出复杂数据结构之间的循环引用,同时回收内存垃圾。
触发垃圾回收的两种情况
- 显式地调用
gc.collect()
进行垃圾回收 - 当对象的引用数量超过 threshold 时自动进行垃圾回收