Python 反射和动态执行

反射主要应用于类的对象上,在运行时,将对象中的属性和方法反射出来,通过字符串对对象成员(属性、方法)进行查找、获取、删除、添加成员等动作,是一种基于字符串的事件驱动技术。

python是一门动态语言,有许多支持反射和动态执行的方法。

一般使用场景:动态的向对象中添加属性和方法;也可以动态的调用对象中的方法或者属性。

反射的内容包括但不限于如下内容:

  • 反射类中成员
  • 反射对象中的成员
  • 反射模块中的成员

查询类型信息的函数

dir()

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

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() 

内置函数 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__ 里。一些内置的数据类型是没有 __dict__ 属性的。

 

globals()

globals() 返回实现当前模块命名空间的字典。对于函数中的代码,这是在定义函数时设置的,并且无论在何处调用函数都保持不变。

符号表是由编译器维护的一种数据结构,它包含有关程序的所有必要信息。其中包括变量名、方法、类等。符号表主要有两种:局部符号表全局符号表。

局部符号表存储与程序的本地范围相关的所有信息,并在 Python 中使用 locals() 方法进行访问。局部作用域可以在函数、类等中。

同样,全局符号表存储与程序全局范围相关的所有信息,并在Python中使用 globals() 方法进行访问。全局范围包含与任何类或函数都不关联的所有函数和变量。

globals 表字典是当前模块的字典(在函数内部,这是定义它的模块,而不是调用它的模块)。

foo = 123

globals()['foo'] = 456 # 修改
globals()['bar'] = 789 # 新增

foo # 456
bar # 789

locals()

函数 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() 三个内置函数的区别:

  • globals() 始终返回模块命名空间的字典
  • locals() 始终返回当前命名空间的字典
  • vars() 返回当前命名空间的字典(如果调用时没有参数)或对象的字典

更多:

  • globals()
    • 作用:返回当前全局符号表, 通常在是返回当前模块下的全局符号表, 比如全局内建的函数,以及模块里的全局符号(定义声明的变量,类, 实例等), 在函数或者类方法中, globals()
    • 返回的模块符号表是其所在模块, 而不是调用模块.
  • locals()
    • 作用:更新并以字典形式返回当前局部符号表. 自由变量由函数块的 locals() 返回, 而不会由 class 块来返回. 需要注意的是, locals() 字典不应该被修改
  • vars()
    • 作用:返回 __dict__ 属性
    • 比如模块、类、实例或者其它带有 dict 属性的 object.
    • vars() 使用时如果不传参数, 那么作用与 locals() 一样. 需要注意的是, locals 字典只在读操作时使用, 因为对 locals 的更新会被忽略.

注意:

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__() 函数,不过这不怎么常用。它还用于动态加载类和函数。

语法如下:

__import__(name,
           globals=None,
           locals=None,
           fromlist=(),
           level=0
          ) -> module

参数有:

  • name:模块的名字(空间)
  • globals:全局上下文
  • locals:本地上下文
  • fromlist:序列,实现类似 from name import (a, b)
  • level:用于确定是执行绝对导入还是相对导入:0 是绝对导入,而正数是相对于当前模块要搜索的父目录数

由于此函数是供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, /)

参数有:

  • obj:要操作的对象,类或者实例
  • name:属性名,是一个字符串

判断输入的name字符串在对象object中是否存在(属性或方法),存在返回True,否则返回False。此功能是通过调用 getattr(object, name) 看是否有 AttributeError 异常来实现的。

getattr(object, name[, default]) -> value

参数:

  • obj:要操作的对象,类或者实例
  • name:属性名
  • default: 可选,不存在此属性时返回此值

从对象中获取命名属性值,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, /)

参数

  • object -- 对象。
  • 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, /)

参数有:

  • 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()

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()

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()

compile() 函数将指定的源(普通字符串、字节串或 AST 对象)作为代码对象返回。
compile() 方法返回的代码对象,可以使用 exec() 和 eval() 等方法调用,它们将执行动态生成的 Python 代码。

compile(
    source,
    filename,
    mode,
    flags=0,
    dont_inherit=False,
    optimize=-1,
    *,
    _feature_version=-1,
)

参数:

  • source - 源代码可以表示 Python 模块、语句或表达式,包括 string、byte string、AST object
  • filename - 从中读取代码的文件。如果它不是从文件中读取的,你可以自己给它起个名字
  • mode - 字符串,exec 或 eval 或 single。
    • eval - 只接受一个表达式。
    • exec - 它可以采用包含Python语句、类和函数等的代码块
    • single - 如果它由单个交互语句组成
  • flags (可选) - 控制未来哪些语句会影响源代码的编译。默认值:0
  • dont_inherit (可选) - 如果为true,则会停止继承代码调用中任何未来有效语句效果的编译
  • optimize (可选) - 编译器的优化级别。默认值为-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

你可能感兴趣的:(python)