yield
当一个函数中出现了yield关键字时,这个函数就变成了一个生成器,调用这个函数时会返回一个generator对象,接下来可以用next函数来驱动这个生成器往下执行,直至碰到一个yield语句,这时候生成器会返回函数yield语句后面的对象,也可以调用生成器的send方法给生成器发送一个值,这个发送的值会成为yield语句的返回值,当生成器执行完最后一个yield语句后,此时继续驱动生成器就会抛出一个StopIteration的异常,生成器的return语句的返回值会赋值给这个异常实例的value属性上。
def g():
a = yield 1
b = yield a
yield b
return "done"
g = g()
g
# <generator object g at 0x101d4c990>
next(g) # equals to `g.send(None)`, variable a will become a `None`
# 1
g.send(2)
# 2
next(g)
# None
try:
next(g)
except StopIteration as e:
print(e.value)
# done
LEGB
LEGB规则定义了Python对命名空间的查找顺序。
具体如下:
Locals 函数内的命名空间
Enclosing 外层函数的命名空间
Globals 所在模块命名空间
Builtins Python内置模块的名字空间
def outside():
v = 2
def inside():
print( # B
v) # E
inside() # L
outside() # G
globals()
返回当前作用域的全局变量空间
locals()
返回当前作用域的本地变量空间
自省
自省
dir:返回指定对象的属性列表。
a = []
dir(a)
'''
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
'''
getattr:获取一个对象中的属性,若不存在则抛出AttributeError异常,如果指定了默认值则返回默认值。
class A:
pass
getattr(A, 'a')
# AttributeError: type object 'A' has no attribute 'a'
getattr(A, 'a', 1)
# 1
hasattr:检查一个对象是否有指定的属性。
hasattr(A, 'a')
# False
setattr:给一个对象的设置属性名和属性名的对应值。
if not hasattr(A, 'a'):
setattr(A, 'a', 2)
if hasattr(A, 'a'):
getattr(A, 'a')
# 2
inspect:一个非常有用的内置模块,能够非常方便的获取对象信息。
import inspect
inspect.getmembers(a)
垃圾回收
Python的垃圾回收方式主要包括引用计数法和分代回收法。
- 引用计数
Python的垃圾回收主要依赖基于引用计数的回收算法,当一个对象的引用数为0时,Python虚拟机就会考虑回收对象占用的内存。
但引用计数的缺点也十分明显,那就是循环引用的问题,试想有两个容器对象,分别引用了对方作为自己的一个属性,这样在删除其中一个变量后,由于其对象还保留着引用,因此它的内存无法被释放。为了解决这个问题,Python使用了mark-sweep算法,定期扫描跟踪对象的引用情况,释放无用对象的内存。
- 分代回收
分代回收算法基于弱代假说的思想,即:相对于存活时间长的对象,GC更加频繁去处理那些年轻的对象。
Python的分代回收机制把对象按照存活时间分成三个集合,每一个集合即为一个“代”,分别对于一个链表,当垃圾回收器进行收集时,会首先考虑去检查存活时间较短的对象集合。
容器
Python包括多种容器数据类型:
数据类型 | 可变 | 有序 |
---|---|---|
list | yes | yes |
tuple | no | yes |
str | no | yes |
range | no | yes |
bytes | no | yes |
bytearray | yes | yes |
array | yes | yes |
set | yes | no |
frozenset | no | no |
dict | yes | no |
OrderedDict | yes | yes |
3.1 函数
3.1.1 栈帧
Python通过栈帧来关联函数之间的调用,每次调用函数时,函数的栈帧会放在调用栈上,调用栈的深度随着函数的调用深度增长,栈帧对象包含了函数的code对象,函数的局部变量数据,前一个栈帧和行号等信息。
def c():
pass
def b():
pass
def a():
b()
c()
def go():
a()
go()
栈
在下面这个例子中,f
函数通过获取调用链中前一个栈帧的信息,拿到add
函数栈帧中局部变量a
与b
的值,使得f
函数在不接收参数的情况下完成了透明的加法:
import inspect
def f():
current_frame = inspect.currentframe()
back_frame = current_frame.f_back
a = back_frame.f_locals['a']
b = back_frame.f_locals['b']
return a + b
def add(a, b):
print(f())
add(1, 2)
# 3
装饰器
装饰器是一个函数,它用于给已有函数扩展功能,它通常接收一个函数作为参数,并返回一个新的函数。
装饰器可配合@符号十分简洁地应用在函数上。
def tag(func):
def wrapper(*args, **kwargs):
func_result = func(*args, **kwargs)
return '<{tag}>{content}</{tag}>'.format(tag=func.__name__, content=func_result)
return wrapper
@tag
def h1(text):
return text
@tag
def p(text):
return text
def div(text):
return text
html = h1(p('Hello World'))
print(html)
# <h1><p>Hello World</p></h1>
html2 = tag(div)('Hello World')
print(html2)
# <div>Hello World</div>
类装饰器
class Tag:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
func_result = self.func(*args, **kwargs)
return '<{tag}>{content}</{tag}>'.format(tag=self.func.__name__, content=func_result)
@Tag
def h1(text):
return text
html = h1('Hello World')
print(html)
yield能把一个函数变成一个generator,与return不同,yield在函数中返回值时会保存函数的状态,使下一次调用函数时会从上一次的状态继续执行,即从yield的下一条语句开始执行。
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield(b)
a, b = b, a + b
n = n + 1
f = fib(10)
next(f)
# 1
next(f)
# 1
next(f)
# 2
f.send(None)
# 3
for num in f:
print(num)
# 5
# 8
# 13
# 21
# 34
# 55
next(f)
# StopIteration
类
可见性
当一个类的方法名前以单下划线或双下滑线开头时,通常表示这个方法是一个私有方法。
但这只是一种约定俗成的方式,对于外部来说,以单下划线开头的方法依然是可见的,而双下划线的方法则被改写成_类名__方法名
存放在名字空间中,所以如果B继承了A,它是访问不到以双下划线开头的方法的。
class A:
def public_method(self):
pass
def _private_method(self):
pass
def __private_method(self):
pass
a = A()
a.public_method()
# None
a._private_method()
# None
a.__private_method()
# AttributeError: 'A' object has no attribute '__A_private_method'
a._A__private_method()
# None
A.__dict__
# mappingproxy({'_A__private_method': <function __main__.A.__private_method>,
# '__dict__': <attribute '__dict__' of 'A' objects>,
# '__doc__': None,
# '__module__': '__main__',
# '__weakref__': <attribute '__weakref__' of 'A' objects>,
# '_private_method': <function __main__.A._private_method>,
# 'public_method': <function __main__.A.public_method>})
函数与绑定方法
类方法本质上是一个函数,通过绑定对象的方式依附在类上成为方法。
假如有下面一个类:
class A:
def __init__(self):
self.val = 1
def f(self):
return self.val
可以看到f是A的属性列表中的一个函数:
A.f
# <function __main__.A.f>
A.__dict__['f']
# <function __main__.A.f>
而当直接用A.f()
的方式调用f
方法时,会抛出一个异常,异常的原因很直接,参数列表有一个self
参数,调用时却没有指定这个参数。而用A().f()
的方式调用f
方法时同样没有指定参数,却能正常执行,这是为什么呢?
执行A().f
语句可以发现,这时候的f
不再是一个函数,而是一个绑定方法,而它绑定的对象则是一个A的实例对象,换句话说,这时候f
参数中的self
已经跟这个实例对象绑定在一起了,所以用实例调用一个普通方法时,无须人为地去指定第一个参数。
A.f()
# TypeError: f() missing 1 required positional argument: 'self'
A().f() # auto binding
# 1
A().f
# <bound method A.f of <__main__.A object at 0x121a3bac8>>
通过调用f
函数的__get__
(函数的描述器行为)方法来为函数绑定到一个对象上:
class B:
def __init__(self):
self.val = 'B'
A.f.__get__(B(), B)
# <bound method A.f of <__main__.B object at 0x111f9b198>>
A.f.__get__(B(), B)()
# B
通过types
模块的MethodType
动态把方法附加到一个实例上:
import types
def g(self):
return self.val + 1
a.g = types.MethodType(g, a)
a.g()
# 2
实例方法、类方法、静态方法
类方法分为普通方法,静态方法和类方法,静态方法和类方法可分别通过classmethod
和staticmethod
装饰器来定义。
区别于普通方法,类方法的第一个参数绑定的是类对象,因此可以不需要实例对象,直接用类调用类方法执行。
而静态方法则没有绑定参数,跟类方法一样,可以通过类或者实例直接调用。
class C:
def instance_method(self):
print(self)
@classmethod
def class_method(cls):
print(cls)
@staticmethod
def static_method():
pass
C.instance_method
# <function __main__.C.instance_method>
C.instance_method()
# TypeError: instance_method() missing 1 required positional argument: 'self'
C().instance_method()
# <__main__.C object at 0x121133b00>
C.class_method
# <bound method C.class_method of <class '__main__.C'>>
C.class_method()
# <class '__main__.C'>
C().class_method()
# <class '__main__.C'>
C.static_method
# <function __main__.C.static_method>
C.static_method()
# None
C().static_method()
# None
通过输出可以看到的一点就是,类方法是一个已经绑定类对象的方法,而普通方法和静态方法在被调用前只是一个普通函数。
property
property
是一个内置函数,它可以将方法与属性调用绑定在一起。
看下面这个例子,Person
类有个年龄属性age
,现在想要在对这个属性赋值时给它加一个类型校验,借助于property
函数便能在不需要修改用法的情况下为属性加上getter/setter方法:
class Person:
def __init__(self, age):
self._age = age
@property
def age(self):
return self._age
@age.setter
def age(self, age):
if not isinstance(age, int):
raise TypeError('age must be an integer')
self._age = age
p = Person(20)
print(p.age)
# 20
p.age = 30
print(p.age)
# 30
p.age = '40'
# Traceback (most recent call last):
# File "test.py", line 20, in <module>
# p.age = '40'
# File "test.py", line 12, in age
# raise TypeError('age must be an integer')
# TypeError: age must be an integer
property
还常用于延迟计算以及属性缓存,例如下面Circle
类的area
属性,只有在被访问到的情况下才进行计算,并把结果缓存到实例的属性上。
class Circle(object):
def __init__(self, radius):
self.radius = radius
@property
def area(self):
if not hasattr(self, '_cache_area'):
print('evalute')
setattr(self, '_cache_area', 3.14 * self.radius ** 2)
return getattr(self, '_cache_area')
c = Circle(4)
print(c.area)
# evalute
# 50.24
print(c.area)
# 50.24
描述器
上面谈论到的property
也好,classmethod
和staticmethod
也好,甚至是所有的函数,本质上都是实现了描述器协议的对象。
描述器是一个绑定行为的对象属性,如果一个对象定义了__set__()
和__get__()
,那么它就是一个数据描述器,如果只定义了__get__()
,那么它就是一个非数据描述器。
下面定义了一个描述器类Integer
,这个类的一个实例作为类属性赋给类C的num
变量上,当C的实例访问这个属性时便会触发描述器协议,调用描述器的__get__
方法,其中第一个参数是实例本身,而第二个参数是实例所归属的类(是否跟实例方法的调用有异曲同工之妙?),如果属性是以类的形式访问,那么第一个参数的值为None
;相似地,当对这个属性进行赋值操作时,则会调用描述器的__set__
方法,借用这个特性可以方便对属性做一些校验操作。
class Integer:
def __init__(self, name):
self.name = name
self.val = None
def __get__(self, obj, obj_type):
print(obj, obj_type)
return self.name
def __set__(self, obj, val):
if not isinstance(val, int):
raise Exception('value must be an integer!')
print('set value to {}'.format(val))
self.val = val
class C:
num = Integer('I')
c = C()
c.num
# <__main__.C object at 0x10cc735c0> <class '__main__.C'>
c.num = 5
# set value to 5
c.num = '5'
# Exception: value must be an integer!
说到这里已经可以猜到property
是如何实现的了,实际上Python官方已经给出了一份模拟property
实现的Python代码:
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
classmethod
的实现则更为简单一些:
class ClassMethod(object):
"Emulate PyClassMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
def newfunc(*args):
return self.f(klass, *args)
return newfunc
网友评论