反射主要应用于类的对象上,在运行时,将对象中的属性和方法反射出来,通过字符串对对象成员(属性、方法)进行查找、获取、删除、添加成员等动作,是一种基于字符串的事件驱动技术。
python是一门动态语言,有许多支持反射和动态执行的方法。
一般使用场景:动态的向对象中添加属性和方法;也可以动态的调用对象中的方法或者属性。
反射的内容包括但不限于如下内容:
dir([object])
如果没有实参,则返回当前本地作用域中的名称列表。如果有实参,它会尝试返回该对象的有效属性列表。
如果对象有一个名为 __dir__() 的方法,那么该方法将被调用,并且必须返回一个属性列表。这允许实现自定义 __getattr__() 或 __getattribute__() 函数的对象能够自定义 dir() 来报告它们的属性。
如果对象未提供 __dir__() 方法,该函数会尽量从对象的 __dict__ 属性和其类型对象中收集信息。得到的列表不一定是完整,如果对象带有自定义 __getattr__() 方法时,结果可能不准确。
默认的 dir() 机制对不同类型的对象行为不同,它会试图返回最相关而不是最全的信息:
如果对象是模块对象,则列表包含模块的属性名称。
如果对象是类型或类对象,则列表包含它们的属性名称,并且递归查找所有基类的属性。
否则,列表包含对象的属性名称,它的类属性名称,并且递归查找它的类的所有基类的属性。
返回的列表按字母表排序。例如:
>>>import struct
>>>dir() # show the names in the module namespace
['__builtins__', '__name__', 'struct']
>>>dir(struct) # show the names in the struct module
['Struct', '__all__', '__builtins__', '__cached__', '__doc__', '__file__',
'__initializing__', '__loader__', '__name__', '__package__',
'_clearcache', 'calcsize', 'error', 'pack', 'pack_into',
'unpack', 'unpack_from']
>>>class Shape:
def __dir__(self):
return ['area', 'perimeter', 'location']
>>>s = Shape()
>>>dir(s)
['area', 'location', 'perimeter']
定义__dir__()
class Test:
def __dir__(self):
return ['a','b','c']
t = Test()
print(dir(t))
’’’
['a', 'b', 'c’]
‘’’
sys.modules是一个全局字典,该字典是python启动后就加载在内存中。每当程序员导入新的模块,sys.modules都将记录这些模块。字典sys.modules对于加载模块起到了缓冲的作用。当某个模块第一次导入,字典sys.modules将自动记录该模块。当第二次再导入该模块时,python会直接到字典中查找,从而加快了程序运行的速度。
字典sys.modules具有字典所拥有的一切方法,可以通过这些方法了解当前的环境加载了哪些模块
import sys
print(sys.modules[__name__])
#
print(sys.modules.values())
#dict_values([, ,…, ])
print(sys.modules.keys())
#dict_keys(['sys', 'builtins', '_frozen_importlib', '_imp', '_thread', '_warnings', '_weakref', '_io', 'marshal', 'posix', '_frozen_importlib_external', 'time', 'zipimport', '_codecs', 'codecs', 'encodings.aliases', 'encodings', 'encodings.utf_8', '_signal', '_abc', 'abc', 'io', '__main__', '_stat', 'stat', '_collections_abc', 'genericpath', 'posixpath', 'os.path', 'os', '_sitebuiltins', '_virtualenv', '_distutils_hack', 'site'])
print(sys.modules.items())
#dict_items([('sys', ), ..., ('site', )])
#demo6.py
Demo_Value='Demo6'
def foo6(name:str):
print('--foo6()--')
return 'Hello ' + name
#demo5.py
import sys
import math
import demo6
print(sys.modules['math'])
print(sys.modules['demo6'].Demo_Value)
print(sys.modules['demo6'].foo6('John’))
‘’'
Demo6
--foo6()--
Hello John
‘''
import sys
import math
import demo6
print(sys.modules['math'])
print(sys.modules['demo6'].Demo_Value)
print(sys.modules['demo6'].foo6('John'))
if 'demo6' in sys.modules:
print('demo6 in sys.modules.')
else:
print('demo6 not in sys.modules.')
del sys.modules['demo6']
demo6.foo6('Rose') #不受影响,继续可以使用
if 'demo6' in sys.modules:
print('demo6 in sys.modules.')
else:
print('demo6 not in sys.modules.')
#print(sys.modules['demo6'].foo6('John')) #KeyError
‘’'
Load demo6!
Demo6
--foo6()--
Hello John
demo6 in sys.modules.
--foo6()--
demo6 not in sys.modules.
‘''
内置函数 vars() 返回对象的属性和属性值的字典对象。如果没有参数,就返回当前调用位置的属性和属性值,行为类似 locals()。
语法为 vars([object])
,其中对象可有可没有。来用返回模块、类、实例或任何其它具有 __dict__
属性的对象的 __dict__
属性。
带参数:
模块和实例这样的对象具有可更新的 __dict__
属性;但是,其它对象的 __dict__
属性可能会设为限制写入(例如,类会使用 types.MappingProxyType 来防止直接更新字典)。
如果指定了一个对象但它没有 __dict__
属性(例如,当它所属的类定义了 __slots__
属性时)则会引发 TypeError 异常。
不带参数时,vars() 的行为类似 locals(), 用来更新并返回表示当前本地符号表的字典。 请注意,locals 字典仅对于读取起作用,因为对 locals 字典的更新会被忽略。
如,我们进行赋值操作时,如 bar = 1,执行赋值语句后,名称 bar 引用到值 1,名字为键,将我们要取这个值时,可以用 vars() 返回的字典里用此键取得其引用值:
bar = 1
vars()['bar']
# 1
'bar' in vars().keys()
# True
# 在终端输入
vars()
# 输出(已美化格式)
{'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__loader__': ,
'__spec__': None,
'__annotations__': {},
'__builtins__':
}
# 1 对象没有__dict__ 属性
vars(1)
# TypeError: vars() argument must have __dict__ attribute
class Foo:
def __init__(self):
self.__dict__ = {'name':'Foo'}
f = Foo()
vars(f)
# {'name': 'Foo'}
# 再终端执行
vars()
# 输出(已美化格式)
{'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__loader__': ,
'__spec__': None,
'__annotations__': {},
'__builtins__': ,
'Foo': ,
'f': <__main__.Foo object at 0x7fccf35f5120>
}
# 注意最后两项
类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的 __dict__
里。一些内置的数据类型是没有 __dict__
属性的。
globals() 返回实现当前模块命名空间的字典。对于函数中的代码,这是在定义函数时设置的,并且无论在何处调用函数都保持不变。
符号表是由编译器维护的一种数据结构,它包含有关程序的所有必要信息。其中包括变量名、方法、类等。符号表主要有两种:局部符号表全局符号表。
局部符号表存储与程序的本地范围相关的所有信息,并在 Python 中使用 locals() 方法进行访问。局部作用域可以在函数、类等中。
同样,全局符号表存储与程序全局范围相关的所有信息,并在Python中使用 globals() 方法进行访问。全局范围包含与任何类或函数都不关联的所有函数和变量。
globals 表字典是当前模块的字典(在函数内部,这是定义它的模块,而不是调用它的模块)。
foo = 123
globals()['foo'] = 456 # 修改
globals()['bar'] = 789 # 新增
foo # 456
bar # 789
函数 locals() 更新并返回表示当前本地符号表的字典,它没有可传入的参数。 在函数代码块但不是类代码块中调用 locals() 时将返回自由变量。 请注意在模块层级上,locals() 和 globals() 是同一个字典。Python 的命名空间通过一种字典的形式来体现, 而具体到函数也就是 locals() 和 globals(), 分别对应着局部命名空间和全局命名空间。
# 在终端输入
locals()
# 输出(已美化格式)
{'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__loader__': ,
'__spec__': None,
'__annotations__': {},
'__builtins__':
}
# 1 对象没有__dict__ 属性
vars(1)
# TypeError: vars() argument must have __dict__ attribute
class Foo:
def __init__(self):
self.__dict__ = {'name':'Foo'}
f = Foo()
# 再终端执行
locals()
# 输出(已美化格式)
{'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__loader__': ,
'__spec__': None,
'__annotations__': {},
'__builtins__': ,
'Foo': ,
'f': <__main__.Foo object at 0x7fccf35f5120>
}
# 注意最后两项
我们进行赋值操作时,如 bar = 1,执行赋值语句后,名称 bar 引用到值 1,名字为键,将我们要取这个值时,可以用 vars() 返回的字典里用此键取得其引用值:
bar = 1
locals()['bar']
# 1
'bar' in locals().keys()
# True
以下是几个相关内置函数对比表:
函数 | 有参数 | 无参数 | 返回类型 |
---|---|---|---|
globals() | - | 当前作用域全局变量,可以更新 | dict |
locals() | - | 当前作用域的局部变量,不应该被修改 | dict |
vars([object]) | 对象属性,同 object.__dict__ |
同 locals(),可以更新 | dict |
dir([object]) | 对象的属性、方法(含特殊方法)名称 | 当前作用域中的名称 | list |
Python globals() locals() vars() 三个内置函数的区别:
更多:
__dict_
_ 属性注意:
locals 和 vars 需要再解释一下。如果在函数中调用了 locals(),它将使用当前局部变量名称空间(加上任何闭包变量)的值更新 dict,并返回它。在同一栈帧中多次调用 locals(),每次都返回相同的 dict——它作为 f_locals 属性附加到栈帧对象。dict 的内容在每次 locals() 调用和每个 f_locals 属性访问时更新,但仅在此类调用或属性访问时更新。分配变量时,它不会自动更新,在dict 中分配条目不会分配相应的局部变量:
import inspect
def f():
x = 1
l = locals()
print(l) # {'x': 1}
locals()
print(l) # {'x': 1, 'l': {...}}
x = 2
print(x, l['x']) # 2 1
l['x'] = 3
print(x, l['x']) # 2 3
inspect.currentframe().f_locals
print(x, l['x']) # 2 2
f()
# 输出
'''
{'x': 1}
{'x': 1, 'l': {...}}
2 1
2 3
2 2
'''
第一次 print(l) 只显示一个“x”条目,因为对l的赋值发生在 locals() 调用之后。再次调用 locals() 后的第二个 print(l) 显示了一个l条目,尽管我们没有保存返回值。第三次和第四次打印显示,赋值变量不会更新l,反之亦然,但在访问 f_locals 后,局部变量会再次复制到 locals() 中。
除了 import 语句来导入模块外,还有一个内置的 __import__()
函数,不过这不怎么常用。它还用于动态加载类和函数。
语法如下:
__import__(name,
globals=None,
locals=None,
fromlist=(),
level=0
) -> module
参数有:
由于此函数是供Python解释器使用的,而不是一般用途,因此最好使用 importlib.import_module()
以编程方式导入模块。
如果仔细阅读,您会感觉到 API 最初是为了允许从模块延迟加载函数。然而,这不是 CPython 的工作方式,我不知道是否有其他 Python 实现能够做到这一点。
相反,CPython 在第一次导入时执行模块名称空间中的所有代码,然后将模块缓存在 sys.modules
中。__import__()
仍然有用。但是,根据文档了解它的功能相当困难。
此函数会由 import 语句发起调用。 它可以被替换 (通过导入 builtins 模块并赋值给 builtins.__import__)
以便修改 import 语句的语义,但是 强烈 不建议这样做,因为使用导入钩子 (参见 PEP 302) 通常更容易实现同样的目标,并且不会导致代码问题,因为许多代码都会假定所用的是默认实现。 同样也不建议直接使用 import() 而应该用 importlib.import_module()
。
本函数会导入模块 name,利用 globals 和 locals 来决定如何在包的上下文中解释该名称。fromlist 给出了应从 name 模块中导入的对象或子模块的名称。标准的实现代码完全不会用到 locals 参数,只用到了 globals 用于确定 import 语句所在的包上下文。
level 指定是使用绝对还是相对导入。 0 (默认值) 意味着仅执行绝对导入。 level 为正数值表示相对于模块调用 __import__()
的目录,将要搜索的父目录层数 (详情参见 PEP 328)。
当 name 变量的形式为 package.module
时,通常将会返回最高层级的包(第一个点号之前的名称),而 不是 以 name 命名的模块。 但是,当给出了非空的 fromlist 参数时,则将返回以 name 命名的模块。
例如,语句 import spam
的结果将为与以下代码作用相同的字节码:
spam = __import__('spam', globals(), locals(), [], 0)
语句 import spam.ham 的结果将为以下调用:
spam = __import__('spam.ham', globals(), locals(), [], 0)
请注意在这里 import() 是如何返回顶层模块的,因为这是通过 import 语句被绑定到特定名称的对象。
另一方面,语句 from spam.ham import eggs, sausage as saus
的结果将为
_temp = __import__('spam.ham', globals(), locals(), ['eggs', 'sausage'], 0)
eggs = _temp.eggs
saus = _temp.sausage
在这里, spam.ham
模块会由 __import__()
返回。 要导入的对象将从此对象中提取并赋值给它们对应的名称。
def importer(name, root_package=False, relative_globals=None, level=0):
""" We only import modules, functions can be looked up on the module.
Usage:
from foo.bar import baz
>>> baz = importer('foo.bar.baz')
import foo.bar.baz
>>> foo = importer('foo.bar.baz', root_package=True)
>>> foo.bar.baz
from .. import baz (level = number of dots)
>>> baz = importer('baz', relative_globals=globals(), level=2)
"""
return __import__(name, locals=None, # locals has no use
globals=relative_globals,
fromlist=[] if root_package else [None],
level=level)
baz = importer('foo.bar.baz')
foo = importer('foo.bar.baz', root_package=True)
baz2 = importer('bar.baz', relative_globals=globals(), level=2)
assert foo.bar.baz is baz is baz2
for name in dir(baz):
print(getattr(baz, name))
可以使用 __import__()
更改或拦截导入行为。在这种情况下,让我们只打印它得到的参数,以证明我们正在拦截它:
old_import = __import__
def noisy_importer(name, locals, globals, fromlist, level):
print(f'name: {name!r}')
print(f'fromlist: {fromlist}')
print(f'level: {level}')
return old_import(name, locals, globals, fromlist, level)
import builtins
builtins.__import__ = noisy_importer
>>> from os.path import join as opj
name: 'os.path'
fromlist: ('join',)
level: 0
>>> opj
支持反射的常见方法有下面:
方法 | 用法 |
---|---|
hasattr(obj,name_str) | 判断输入的name_str字符串在对象obj中是否存在(属性或方法),存在返回True,否则返回False。 |
getattr(obj,name_str) | 将按照输入的name_str字符串在对象obj中查找,如找到同名属性,则返回该属性;如找到同名方法,则返回方法的引用;如果未能找到同名的属性或者方法,则抛出异常:AttributeError。 |
setattr(obj,name_str,value) | name_str为属性名或者方法名,value为属性值或者方法的引用。 |
delattr(obj,name_str) | 将你输入的字符串name_str在对象obj中查找,如找到同名属性或者方法就进行删除。 |
hasattr(obj, name, /)
参数有:
判断输入的name字符串在对象object中是否存在(属性或方法),存在返回True,否则返回False。此功能是通过调用 getattr(object, name)
看是否有 AttributeError
异常来实现的。
getattr(object, name[, default]) -> value
参数:
从对象中获取命名属性值,getattr(x, 'y')
相当于 x.y
操作。当给定默认参数时,当属性不存在时返回;如果没有它,在这种情况下会出现一个例外。
name 必须是字符串。如果该字符串是对象的属性之一,则返回该属性的值。
如果指定的属性不存在,且提供了 default 值,则返回它,否则触发 AttributeError。
getattr()可以返回类、对象、模块的的属性和方法。
反射获取成员方法,返回方法是一个引用地址,要想执行该方法,需要在后面加上小括号
class Person():
# 定义类变量
Nationality = "China"
def __init__(self,name,age,id):
self.name = name
self.age = age
self.id = id
def info(self):
return (self.Nationality, self.name, self.age, self.id)
wang = Person('WangBin', 40, '330103197504013070')
print(wang.name)
print(hasattr(wang, 'name'))
print(hasattr(wang, 'nice'))
print(getattr(wang, 'name'))
print(getattr(wang, 'Nationality'))
print(getattr(wang, 'info'))
print(getattr(wang, 'info')())
‘’'
WangBin
True
False
WangBin
China
>
('China', 'WangBin', 40, '330103197504013070’)
‘''
也可以直接在模块上操作:
#demo6.py
Demo_Value='Demo6'
def foo6(name:str):
print('--foo6()--')
return 'Hello ' + name
#demo5.py
import demo6
print(getattr(demo6, 'Demo_Value’))
#Demo6
setattr(obj, name, value, /)
参数
可以使用该方法设置类属性、成员属性和成员方法。
setattr(x, 'y', v)
相当于 x.y = v
操作。
如果对象已经有要设置的属性,则新值进行覆盖。setattr() 法返回值是 None。
设置成员方法时,name为设置加入的方法名,value为要加入方法的引用(不需要加引号)。
class Person():
# 定义类变量
Nationality = "China"
def __init__(self,name,age,id):
self.name = name
self.age = age
self.id = id
def info(self):
return (self.Nationality, self.name, self.age, self.id)
wang = Person('WangBin', 40, '330103197504013070')
print(wang.name)
setattr(wang, 'name', '王彬')
print(wang.name)
setattr(wang, 'nicename', '小王')
print(wang.nicename)
def out(self):
print(f'{self.name=}, {self.nicename=}')
setattr(wang, 'out', out)
#wang.out() #TypeError: out() missing 1 required positional argument: 'self'
wang.out(wang)
#getattr(wang, 'out')()#TypeError: out() missing 1 required positional argument: 'self'
getattr(wang, 'out')(wang)
‘’'
WangBin
王彬
小王
self.name='王彬', self.nicename='小王'
self.name='王彬', self.nicename='小王'
‘''
上面的例子可以看到,设置的成员方法调用的时候,要显示的把自己作为第一个参数传入。
setattr()
函数只能用于设置对象的属性或类的属性,而不能用于设置模块的属性或内置类型的属性。此外,如果要设置属性的名称是一个变量,则应该使用setattr()
函数,而不是直接将属性名称作为字符串传递给对象的__setattr__()
方法。
delattr(obj, name, /)
参数有:
将字符串name在对象object中查找,如找到同名属性或者方法就进行删除。如果对象允许,该函数将删除指定的属性。例如 delattr(x, 'foobar')
等价于 del x.foobar
class Person():
# 定义类变量
Nationality = "China"
def __init__(self,name,age,id):
self.name = name
self.age = age
self.id = id
def info(self):
return (self.Nationality, self.name, self.age, self.id)
wang = Person('WangBin', 40, '330103197504013070')
print(wang.name)
delattr(wang, 'name')
print(wang.name) #AttributeError: 'Person' object has no attribute 'name'
作为动态语言,python支持动态加载一段字符串代码并执行。
按是否返回结果,简单分为两种:exec和eval。
exec负责执行字符串代码,可支持多行,可定义变量,但无法返回结果.
def pr(x):
print('My result: {}'.format(x))
if __name__ == "__main__":
s = '''
a = 15
b = 3
if a > b:
pr(a+b)
'''
exec(s)
# My result: 18
eval可以返回结果,但只能执行单行表达式
def select_max(x, y):
return x if x > y else y
if __name__ == "__main__":
a = 3
b = 5
c = eval('select_max(a , b)')
print("c is {}".format(c))
#c is 5
compile() 函数将指定的源(普通字符串、字节串或 AST 对象)作为代码对象返回。
compile() 方法返回的代码对象,可以使用 exec() 和 eval() 等方法调用,它们将执行动态生成的 Python 代码。
compile(
source,
filename,
mode,
flags=0,
dont_inherit=False,
optimize=-1,
*,
_feature_version=-1,
)
参数:
将源代码编译成可由 exec() 或 eval()执行的代码对象。
compile() 将 source 编译成代码或 AST(Abstract Syntax Trees) 对象。代码对象可以被 exec() 或 eval() 执行。source 可以是常规的字符串、字节字符串,或者 AST 对象。
filename 实参需要是代码读取的文件名;如果代码不需要从文件中读取,可以传入一些可辨识的值(经常会使用 '
mode 实参指定了编译代码必须用的模式。如果 source 是语句序列,可以是 'exec';如果是单一表达式,可以是 'eval';如果是单个交互式语句,可以是 'single'。(在最后一种情况下,如果表达式执行结果不是 None 将会被打印出来。)
可选参数 flags 和 dont_inherit 控制应当激活哪个 编译器选项 以及应当允许哪个 future 特性。 如果两者都未提供 (或都为零) 则代码会应用与调用 compile() 的代码相同的旗标来编译。 如果给出了 flags 参数而未给出 dont_inherit (或者为零) 则会在无论如何都将被使用的旗标之外还会额外使用 flags 参数所指定的编译器选项和 future 语句。 如果 dont_inherit 为非零整数,则只使用 flags 参数 -- 外围代码中的旗标 (future 特性和编译器选项) 会被忽略。
编译器选项和 future 语句是由比特位来指明的。 比特位可以通过一起按位 OR 来指明多个选项。 指明特定 future 特性所需的比特位可以在 __future__ 模块的 _Feature 实例的 compiler_flag 属性中找到。 编译器旗标 可以在 ast 模块中查找带有 PyCF_ 前缀的名称。
optimize 实参指定编译器的优化级别;默认值 -1 选择与解释器的 -O 选项相同的优化级别。显式级别为 0 (没有优化;__debug__ 为真)、1 (断言被删除, __debug__ 为假)或 2 (文档字符串也被删除)。
如果编译的源码不合法,此函数会触发 SyntaxError 异常;如果源码包含 null 字节,则会触发 ValueError 异常。
在 'single' 或 'eval' 模式编译多行代码字符串时,输入必须以至少一个换行符结尾。 这使 code 模块更容易检测语句的完整性。
codeInString = 'a = 5\nb=6\nsum=a+b\nprint("sum =",sum)'
codeObejct = compile(codeInString, 'sumstring', 'exec')
exec(codeObejct)
# sum = 11
eval(codeObejct)
# sum = 11