生成器(generator)

生成器是调用一个生成器函数(generator function)返回的对象,多用于集合对象的迭代。

__iter__: 仅仅是一个可迭代的标记。
gi_code: 生成器对应的code对象。
gi_frame: 生成器对应的frame对象。
gi_running: 生成器函数是否在执行。生成器函数在yield以后、执行yield的下一行代码前处于frozen状态,此时这个属性的值为0。
next|close|send|throw: 这是几个可调用的方法,并不包含元数据信息,如何使用可以查看生成器的相关文档。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def gen():
    for n in range(5):
        yield n
g = gen()
print(g)              # 输出 <generator object gen at 0x7f817174d4c0>
print(g.gi_code)      # 输出 <code object gen at 0x7f81717eb930, file "main.py", line 1>
print(g.gi_frame)     # 输出 <frame object at 0x561f95135098>
print(g.gi_running)   # 输出 False
print(next(g))        # 输出 0
print(next(g))        # 输出 1
for n in g:
    print(n)          # 输出 2 3 4

执行以上程序会输出如下结果:

1
2
3
4
5
6
7
8
9
<generator object gen at 0x7f817174d4c0>
<code object gen at 0x7f81717eb930, file "main.py", line 1>
<frame object at 0x561f95135098>
False
0
1
2
3
4

接下来讨论的是几个不常用到的内置对象类型。这些类型在正常的编码过程中应该很少接触,除非你正在自己实现一个解释器或开发环境之类。所以这里只列出一部分属性,如果需要一份完整的属性表或想进一步了解,可以查看文末列出的参考文档。

代码块(code)

代码块可以由类源代码、函数源代码或是一个简单的语句代码编译得到。这里我们只考虑它指代一个函数时的情况;2.5节中我们曾提到可以使用函数的func_code属性获取到它。code的属性全部是只读的。

co_argcount: 普通参数的总数,不包括*参数和**参数。
co_names: 所有的参数名(包括*参数和**参数)和局部变量名的元组。
co_varnames: 所有的局部变量名的元组。
co_filename: 源代码所在的文件名。
co_flags: 这是一个数值,每一个二进制位都包含了特定信息。较关注的是0b100(0x4)和0b1000(0x8),如果co_flags & 0b100 != 0,说明使用了*args参数;如果co_flags & 0b1000 != 0,说明使用了**kwargs参数。另外,如果co_flags & 0b100000(0x20) != 0,则说明这是一个生成器函数(generator function)。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Cat:
    def __init__(self, name='kitty'):
        self.name = name
    def sayHi(self):
        print(self.name, 'says Hi!')

cat = Cat()

co = cat.sayHi.__code__
print(co.co_argcount)        # 输出 1
print(co.co_names)           # 输出 ('print', 'name')
print(type(co.co_names))     # 输出 <class 'tuple'>
print(co.co_varnames)        # 输出 ('self',)
print(type(co.co_varnames))  # 输出 <class 'tuple'>
print(co.co_flags & 0b100)   # 输出 0

执行以上程序会输出如下结果:

1
2
3
4
5
6
1
('print', 'name')
<class 'tuple'>
('self',)
<class 'tuple'>
0

栈帧(frame)

栈帧表示程序运行时函数调用栈中的某一帧。函数没有属性可以获取它,因为它在函数调用时才会产生,而生成器则是由函数调用返回的,所以有属性指向栈帧。想要获得某个函数相关的栈帧,则必须在调用这个函数且这个函数尚未返回时获取。你可以使用sys模块的_getframe()函数、或inspect模块的currentframe()函数获取当前栈帧。这里列出来的属性全部是只读的。

f_back: 调用栈的前一帧。
f_code: 栈帧对应的code对象。
f_locals: 用在当前栈帧时与内建函数locals()相同,但你可以先获取其他帧然后使用这个属性获取那个帧的locals()。
f_globals: 用在当前栈帧时与内建函数globals()相同,但你可以先获取其他帧……。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import inspect
def add(x, y=1):
    f = inspect.currentframe()
    print(f.f_back)       #输出 <frame object at 0x...>
    print(f.f_code)       #输出 <code object add at 0x7f782e78c930, file "main.py", line 2>
    print(f.f_locals)     # 等同于locals()
    print(f.f_globals)    # 等同于globals()
    return x+y

add(2)
locals()
globals()

执行以上程序会输出如下结果:

1
2
3
4
5
6
<frame object at 0x55d0594cef98>
<code object add at 0x7efe3248f930, file "main.py", line 2>
{'f': <frame object at 0x55d0594f3f98>, 'y': 1, 'x': 2}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7efe323ed438>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'main.py', '__cached__': None, 'inspect': <module 'inspect' from '/usr/lib64/python3.6/inspect.py'>, 'add': <function add at 0x7efe324bee18>}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7efe323ed438>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'main.py', '__cached__': None, 'inspect': <module 'inspect' from '/usr/lib64/python3.6/inspect.py'>, 'add': <function add at 0x7efe324bee18>}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7efe323ed438>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'main.py', '__cached__': None, 'inspect': <module 'inspect' from '/usr/lib64/python3.6/inspect.py'>, 'add': <function add at 0x7efe324bee18>}

追踪(traceback)

追踪是在出现异常时用于回溯的对象,与栈帧相反。由于异常时才会构建,而异常未捕获时会一直向外层栈帧抛出,所以需要使用try才能见到这个对象。你可以使用sys模块的exc_info()函数获得它,这个函数返回一个元组,元素分别是异常类型、异常对象、追踪。traceback的属性全部是只读的。

tb_next: 追踪的下一个追踪对象。
tb_frame: 当前追踪对应的栈帧。
tb_lineno: 当前追踪的行号。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import sys
def div(x, y):
    try:
        return x/y
    except:
        tb = sys.exc_info()[2]    # return (exc_type, exc_value, traceback)
        print(tb)
        print(tb.tb_lineno)       # 输出"return x/y" 的行号 4

div(1, 0)

执行以上程序会输出如下结果:

1
2
<traceback object at 0x7f7dc295b388>
4

使用inspect模块

inspect模块提供了一系列函数用于帮助使用自省。下面仅列出较常用的一些函数,想获得全部的函数资料可以查看inspect模块的文档。

检查对象类型

is{module|class|function|method|builtin}(obj):
检查对象是否为模块、类、函数、方法、内建函数或方法。

isroutine(obj):
用于检查对象是否为函数、方法、内建函数或方法等等可调用类型。用这个方法会比多个is()更方便,不过它的实现仍然是用了多个is()。

例如:

1
2
3
im = cat.sayHi
if inspect.isroutine(im):
    im()

对于实现了call的类实例,这个方法会返回False。如果目的是只要可以直接调用就需要是True的话,不妨使用isinstance(obj, collections.Callable)这种形式。我也不知道为什么Callable会在collections模块中,抱歉!我猜大概是因为collections模块中包含了很多其他的ABC(Abstract Base Class)的缘故吧:)

获取对象信息

getmembers(object[, predicate]):
这个方法是dir()的扩展版,它会将dir()找到的名字对应的属性一并返回,形如[(name, value), …]。另外,predicate是一个方法的引用,如果指定,则应当接受value作为参数并返回一个布尔值,如果为False,相应的属性将不会返回。使用is*作为第二个参数可以过滤出指定类型的属性。

getmodule(object):
还在为第2节中的module属性只返回字符串而遗憾吗?这个方法一定可以满足你,它返回object的定义所在的模块对象。

get{file|sourcefile}(object):
获取object的定义所在的模块的文件名|源代码文件名(如果没有则返回None)。用于内建的对象(内建模块、类、函数、方法)上时会抛出TypeError异常。

get{source|sourcelines}(object):
获取object的定义的源代码,以字符串|字符串列表返回。代码无法访问时会抛出IOError异常。只能用于module/class/function/method/code/frame/traceack对象。

getargspec(func):
仅用于方法,获取方法声明的参数,返回元组,分别是(普通参数名的列表, *参数名, **参数名, 默认值元组)。如果没有值,将是空列表和3个None。如果是2.6以上版本,将返回一个命名元组(Named Tuple),即除了索引外还可以使用属性名访问元组中的元素。

例如:

1
2
3
4
def add(x, y=1, *z):
    return x + y + sum(z)
print inspect.getargspec(add)
#ArgSpec(args=['x', 'y'], varargs='z', keywords=None, defaults=(1,))

getargvalues(frame):
仅用于栈帧,获取栈帧中保存的该次函数调用的参数值,返回元组,分别是(普通参数名的列表, *参数名, **参数名, 帧的locals())。如果是2.6以上版本,将返回一个命名元组(Named Tuple),即除了索引外还可以使用属性名访问元组中的元素。

例如:

1
2
3
4
5
def add(x, y=1, *z):
    print inspect.getargvalues(inspect.currentframe())
    return x + y + sum(z)
add(2)
#ArgInfo(args=['x', 'y'], varargs='z', keywords=None, locals={'y': 1, 'x': 2, 'z': ()})

getcallargs(func[, *args][, **kwds]):
返回使用args和kwds调用该方法时各参数对应的值的字典。这个方法仅在2.7版本中才有。

getmro(cls):
返回一个类型元组,查找类属性时按照这个元组中的顺序。如果是新式类,与cls.mro结果一样。但旧式类没有mro这个属性,直接使用这个属性会报异常,所以这个方法还是有它的价值的。

例如:

1
2
3
4
5
6
7
8
print inspect.getmro(Cat)
#(<class '__main__.Cat'>, <type 'object'>)
print Cat.__mro__
#(<class '__main__.Cat'>, <type 'object'>)
class Dog: pass
print inspect.getmro(Dog)
#(<class __main__.Dog at 0x...>,)
print Dog.__mro__ # AttributeError

currentframe():
返回当前的栈帧对象。


转载请注明本网址。