最近看 Python 学习笔记 记录下一些之前没注意的东西。
环境
虚拟机初始化顺序
- 创建解释器和主线程状态对象,这是整个进程的根对象。
- 初始化内置类型。数字、列表等类型都有专门的缓存策略需要处理。
- 创建
__builtin__
模块,该模块持有所有内置类型和函数。
- 创建 sys 模块,其中包含了 sys.path、modules 等重要的运行行期信息。
- 初始化 import 机制。
- 初始化内置 Exception。
- 创建
__main__
模块,准备运行行所需的名字空间。
- 通过 site.py 将 site-packages 中的第三方方扩展库添加到搜索路径列表。
- 执行行入入口口 py 文文件。执行行前会将
__main__.__dict__
作为名字空间传递进去。
- 程序执行行结束。
- 执行行清理操作,包括调用用退出函数,GC 清理现场,释放所有模块等。
- 终止止进程。
对象
一切皆对象,引用计数来进行拉圾回收机制。
1 2 3 4 5 6 7
| import sys a = 456 sys.getrefcount(a) b = a sys.getrefcount(a) del b sys.getrefcount(a)
|
初始化之后计数是 2 的原因是,当你调用 sys.getrefcount
的时候,a
复制了一遍,所以就是 2 了。
名字空间
python 中的变量,更多的时候用名字来表示会更好理解,在 Py 里面,变量仅仅用来指向内存中的某一个实际的对象 name->object
,所以在函数传值也仅仅是告诉函数需要用到的参数是哪个,如下
1 2 3 4 5 6 7 8
| def alter(bar): bar.append(4) bar = [0,1]
foo = [1,2,3] print 'foo', foo alter(foo) print 'foo', foo
|
上述的代码中,bar = [0, 1]
实际上是改变了函数名字空间内的 bar
的指向而已,不对函数外的 foo
有任何影响。
在 Py 中,可以通过函数 globals
和 locals
查看模块的名字空间,以及函数内的名字空间,在函数外部调用的时候,两者相同。
也可以通过 <module>.__dict__
查看其它模块的名字空间。
内存管理
总是引用传递。
避免循环引用。
内置类型
int
int
类型是特殊处理的,[-5, 257) 之前的小数字通过固定数组来存储,通过下标来获得指针。其它情况通过 PyIntBlock
缓存区存储整数类型,直到进程结束,这部分内存才还给操作系统。
所以对大整数一类的操作需要谨慎,多使用 xrange
去替代 range
,这样一些内存就能省下来。
float
精度不足,一些小数的操作需要谨慎
使用 Decimal
去替代 float
就能解决问题。内存方面也采取和 int
的策略,但没有针对小的浮点数去处理。
string
动态生成的字符串可用 intern
进行池化,简单来说,就是让这个字符串变成一个引用,而不是一个每次都重新生成的字符串,用于节省内存。
list
列表对象和存储元素指针的数组是分开的两块内存,后者在堆上分配。
如果需要频繁进行元素的增删,可用数组 array
代替,这个直接内存数据,省了创建对象的开销。
dict
要去判断两个字典间的差异,可以使用视图。
1 2 3 4 5 6 7 8 9
| d1 = dict(a = 1, b = 2) d2 = dict(b = 2, c = 3)
v1 = d1.viewitems() v2 = d2.viewitems()
v1 & v2 v1 | v2 v1 - v2
|
视图还有一个很实用的地方就是,当你更新了原来的 dict
之后,视图也会同时更新。
函数
参数
默认参数使用可变类型时需要注意,它的值在函数创建的时候就存在了,这个很容易出错
1 2 3 4 5 6 7 8
| def fun(a, b=[]): b.append(a) return b
test(1) test(2) test(1, []) test(3)
|
需要用这类可变类型的参数时,可以 b=None
再在函数内进行判断。
作用域
函数参数和内部变量都存在 locals
的名字空间中。
名字的查找顺序为 locals -> enclosing function -> globals -> __builtins__
闭包
当函数离开创建环境时,依然保留其上下文状态。下例,是因为 x 的添加到 func_closure
列表中,引用计数增加了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def test(): x = [1, 2] print hex(id(x))
def a(): x.append(3) print hex(id(x))
def b(): print hex(id(x))
return a, b a, b = test() a() b()
|
协程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| def coroutine(): print 'start' result = None
while True: s = yield result result = s.split(',')
c = coroutine()
c.send(None)
print c.send('a,b') print c.send('c,d')
c.close()
|
上面的代码的流程如下:
- 创建协程后对象,必须使用用 send(None) 或 next() 启动。
- 协程在执行行 yield result 后让出执行行绪,等待消息。
- 调用用方方发送 send(“a,b”) 消息,协程恢复执行行,将接收到的数据保存到 s,执行行后续流程。
- 再次循环到 yeild,协程返回
- 直到关闭或被引发异常
模块
搜索路径
- 当前的进程根目录
- PYTHONPATH 环境变量指定的路径列表
- Python 标准库的目录列表
- 路径文件保存的目录
当进程启动之后,所有的这些路径都被组织到 sys.path
列表中,任何 import
操作都会按照 sys.path
来查找模块。
进程中的模块对象都是唯一的。在首次成功导入之后,模块对象被添加到 sys.modules
,以后的操作都总是先检查模块对象是否已经存在。
类
字段和属性
字段(field)和属性(property)是不同的,
- 实例字段存储在
instance.__dict__
, 代表单个对象实体的状态。
- 静态字段存储在
class.__dict__
, 为所有同类型实例共享。
- 必须通过类型和实例对象才能访问字段。
- 以双下划线开头的
class
和 instance
成员视为私有,会被重命名。(module 成员不变)
属性 (Property) 是由 getter、setter、deleter 几个方方法构成的逻辑,在查找中,属性优先于实例字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class User(object): @property def name(self): return self.__name
@name.setter def name(self, value): self.__name = value
u = User()
print u.__dict__ u.name = "lycheng" print u.__dict__ print u.name
|
方法
特殊方法
__new__
: 创建对象实例。
__init__
: 初始化对象状态。
__del__
: 对象回收前被调用用。
继承
多重继承成员搜索顺序,也就是 mro (method resolution order) 要稍微复杂一一点。归纳一一下就
是:从下到上 (深度优先,从派生生类到基类),从左到右 (基类声明顺序)。mro 和我们前面面提及的成
员查找规则是有区别的,__mro__
列表中并没有 instance。所以在表述时,需要注意区别。
super 依照 mro 搜索顺序搜索基类成员
装饰器
类装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| def singleton(cls): def wrap(*args, **kwargs): o = getattr(cls, "__instance__", None) if not o: o = cls(*args, **kwargs) cls.__instance__ = o return o return wrap
@singleton class A(object): def __init__(self, x): self.x = x
print A
a, b = A(1), A(2) print a is b
|
将 func wrap 换成 class wrap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| def singleton(cls): class wrap(cls): def __new__(cls, *args, **kwargs): o = getattr(cls, "__instance__", None) if not o: o = object.__new__(cls) cls.__instance__ = o return o return wrap
@singleton class A(object): def test(self): print hex(id(self))
a, b = A(), A() print a is b
|
创建继承自自原类型的 class wrap,然后在 __new__
里里面面做手手脚就行行了。上述这个代码可以通过装饰器去修改类型的一些函数,也可以用以下的代码去增加额外的成员。
1 2 3 4 5 6 7 8 9
| def action(cls): cls.func = staticmethod(lambda: 'hello world') return cls
@action class Func(object): pass
print Func.func()
|
元类
元类的关系
1 2 3 4 5 6
| Data = type("Data", (object,), {"x": 1}) class = metaclass(...) instance = class(...)
instance.__class__ is class class.__class__ is metaclass
|
自定义元类,需要注意的是,自定义元类都是从 type
继承而来,并且是重写 __new__
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class InjectMeta(type): def __new__(cls, name, bases, attrs): t = type.__new__(cls, name, bases, attrs)
def print_id(self): print hex(id(self))
t.print_id = print_id t.s = "Hello, World!" return t
def __init__(cls, name, bases, attrs): print 'class', cls type.__init__(cls, name, bases, attrs)
class Data(object): __metaclass__ = InjectMeta
print Data.s Data().print_id()
|
以下是使用元类实现的静态类和密封类(禁止继承)
1 2 3 4 5 6 7 8 9 10 11
| class StaticClassMeta(type): def __new__(cls, name, bases, attr): t = type.__new__(cls, name, bases, attr)
def ctor(cls, *args, **kwargs): raise RuntimeError("Cannot create a instance of the static class!") t.__new__ = staticmethod(ctor) return t
class Data(object): __metaclass__ = StaticClassMeta
|
1 2 3 4 5 6 7 8 9 10 11 12
| class SealedClassMeta(type): _types = set() def __init__(cls, name, bases, attrs): if cls._types and set(bases): raise SyntaxError("Cannot inherit from a sealed class!") cls._types.add(cls)
class Data(object): __metaclass__ = SealedClassMeta
class B(Data): pass
|
标准库
heapq
最小堆,完全平衡二叉树,所有节点都小于其子节点
1 2 3 4 5 6 7 8 9 10 11
| from heapq import heappush, heappop from random import sample
src = sample(xrange(100), 10)
heap = [] for x in src: heappush(heap, x)
while heap: print heappop(heap)
|
参考文档
- https://github.com/qyuhen/book
- https://www.python.org/dev/peps/pep-3129/