[Python] 过程型程序设计进阶(二):Python动态执行代码和动态导入模块

动态代码执行

eval()函数

该函数用于对传入的表达式进行解析和评估,原型如下:

eval(expression, globals=None, locals=None)
# globals:如果指定,必须为一个字典,表示全局上下文
# locals:如果指定,必须为一个映射类型,表示本地上下文

在前面分析repr()函数时说过,会根据repr()的返回值评估一个对象,如:

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return "Student('{0}',{1})".format(self.name,self.age)


if __name__ == "__main__":
    s1 = Student("zhangsan",21)
    s2 = eval(repr(s1))
    print(s2.name)  # zhangsan

除了这种用法之外,还可以执行一般的表达式,如:

result = eval("2+3")   # 5

eval()可以执行表达式,那么如果要动态执行一个函数时,这个方法时不行的,可以使用exec()来实现。

global()和locals()函数分别返回当前的本地和全局字典。

exec()函数

该方法支持动态执行函数代码,原型如下:

exec(object[, globals[, locals]])

对象必须为一个字符串或者来自于compile()的代码对象,返回值为None,对于可选的global和locals参数,如果提供,则分别为全局上下文和本地上下文。
如果在调用exec()时仅仅将object作为其唯一参数,那么没有途径可以获取该代码执行后创建的任何函数或数量,而且exec()函数不能存取任意导入的模块、变量、函数、其他对象,因此,需要传入一个字典类型作为第二个参数,该参数提供了存放对象引用的场所,当执行完exec()函数后,会将所有信息存放在该字典中,比如:

# 定义一个函数字符串
fun = '''
def area(radius):
    return math.pi * (radius ** 2)
'''
# 在exec()执行过程中存放数据的dict
context = {}
# name 'math' is not defined
# 由于exec()执行时并不能存取导入模块,因此需要将该模块存储在dict中
context["math"] = math
result1 = exec(fun, context)  # None
result2 = context["area"]
ar = result2(3)  # 28.274333882308138

当exec(fun,context)后,context中将会以area为key,以area()函数对象的引用为值进行存储。
global()和locals()函数分别返回当前的本地和全局字典,因此,将global()函数作为其第二个参数是很方便的一种方法,但如果这样,则exec()执行时生成的所有的对象都会被添加到全局字典中。所以可以使用全局字典的拷贝来解决,如将上例中的context改为全局字典:

fun = '''
def area(radius):
    return math.pi * (radius ** 2)
'''
con = globals().copy()
exec(fun, con)
result = con["area"](4)
print(result)  # 50.26548245743669

动态导入模块

这里使用两个方法实现动态导入模块,分别为使用代码实现动态导入和使用__import__()方法进行动态导入,先来看使用代码实现动态导入:

def load_modules():
    modules = []
    # 获取当前目录下所有.py文件
    for filename in os.listdir("."):
        if filename.endswith(".py") and not filename.startswith("dynamic_import_self"):
            # 拆分扩展名
            name = os.path.splitext(filename)[0]
            if name.isidentifier():
                fh = None
                try:
                    fh = open(filename, "r", encoding="utf8")
                    code = fh.read()
                    # 得到一个模块对象,并通过传入的name新建一个模块对象
                    # type()方法返回参数对应类型的对象,如type(2)返回一个int对象
                    module = type(sys)(name)
                    # 将新创建的module添加到sys.modules中,sys.modules是一字典,存储已加载的模块,以模块名为key,模块为值
                    sys.modules[name] = module
                    # 通过exec()动态执行代码,并将生成的属性、对象和所需模块放在module.__dict中
                    # 每个module对象都有一个特殊属性__dict,用于存储对象属性的字典
                    exec(code, module.__dict__)
                    modules.append(module)
                except (EnvironmentError, SyntaxError) as err:
                    # 如果发生异常,从sys.modules字典中移除该module对象
                    sys.modules.pop(name, None)
                    print(err)
                finally:
                    if fh is not None:
                        fh.close()
    return modules

这种方式从头到尾是通过os模块和sys模块中提供的一些方法,将模块遍历并进行读取,最终得到module对象。第二种方法就很简单了,只需要通过__import__()方法就可以实现,如下:

try:
    module = __import__(name)
    modules.append(module)
except (EnvironmentError, SyntaxError) as err:
    print(err)

当动态导入模块后,就可以使用该模块中的属性和函数了,那么如何使用呢?python提供了用于操作模块属性的函数,如下:
getattr(obj,name,val):返回对象obj中名为name的属性值,如果没有该属性,且给定了val参数,则返回val。
hasattr(obj,name):如果对象obj中存在名为name的属性,则返回True。
delattr(obj,name):删除obj中名为name的属性
setattr(obj,name,val):将对象obj中名为name的属性值设置为val,如果不存在该属性,则进行创建。
下面是判断是否有“exit”方法,并返回:

def get_function(module):
    # 从module中获取exit属性
    function = getattr(module,"exit")
    if function is not None:
        try:
            # 如果这个属性是可调用对象,所有的可调用对象即函数和方法,
            # 都有__call__属性
            if not hasattr(function,"__call__"):
                raise AttributeError()
            function(0) # sys.exit(0)
        except AttributeError as err:
            print(err)
            function = None
    return function
总结

至此,对动态代码执行、动态模块导入,以及导入模块后调用模块方法有了眉目了,如果仅仅是执行一个表达式,则eval()函数即可,当然exec()也可以;如果要执行一个函数,就需要使用exec()函数来动态执行了。如果需要动态导入模块,则__import__()函数会将函数快速的导入。

你可能感兴趣的:(Python)