小心使用exec和eval在Python中

One of the perceived features of a dynamic programming language like Python is the ability to execute code from a string. In fact many people are under the impression that this is the main difference between something like Python and C#. That might have been true when the people compared Python to things like C. It’s certainly not a necessarily a feature of the language itself. For instance Mono implements the compiler as a service and you can compile C# code at runtime, just like Python compiles code at runtime.

像Python这样的动态编程语言的一种感知功能是能够从字符串执行代码。 实际上,许多人给人的印象是,这是Python和C#之类的主要区别。 当人们将Python与C之类的东西进行比较时,这可能是正确的。它不一定是语言本身的功能。 例如,Mono将编译器作为服务实现,您可以在运行时编译C#代码,就像Python在运行时编译代码一样。

Wait what. Python compiles? That is correct. CPython and PyPy (the implementations worth caring about currently) are in fact creating a code object from the string you pass to exec or eval before executing it. And that’s just one of the things many people don’t know about the exec statement. So this post aims to clean up some of the misconceptions about the exec keyword (or builtin function in Python 3) and why you have to be careful with using it.

等一下 Python编译? 那是正确的。 实际上,CPython和PyPy(当前值得关注的实现)实际上是在执行之前从传递给execeval的字符串中创建代码对象。 这只是许多人对exec语句不了解的事情之一。 因此,本文旨在消除对exec关键字(或Python 3中的内置函数)以及为什么必须谨慎使用它的一些误解。

This post was inspired by a discussion on reddit about the use of the execfile function in the web2py web framework but also applies to other projects. Some of this here might actually not affect web2py at all and is just a general suggestion of how to deal with exec. There are some very good reasons for using exec when that’s the right thing to do.

这篇文章的灵感来自对reddit的讨论,该讨论涉及web2py Web框架中execfile函数的使用,但也适用于其他项目。 这里的某些内容实际上可能根本不会影响web2py,而只是关于如何处理exec的一般建议。 在正确的情况下使用exec有一些很好的理由。

Disclaimer beforehand: the numbers for this post are taken from Python 2.7 on OS X. Do not ever trust benchmarks, take them only as a reference and test it for yourself on your target environment. Also: “Yay, another post about the security implications of eval/exec.” Wrong! I am assuming that everybody already knows how to properly use these two, so I will not talk about security here.

事先免责声明:此文章的数字取自OS X上的Python 2.7。永远不要相信基准,仅将它们作为参考,并在目标环境中针对自己进行测试。 另外:“是的,关于eval / exec的安全性的另一篇文章。” 错误! 我假设每个人都已经知道如何正确使用这两个,所以在这里我不会谈论安全性。

进口幕后花絮 (Behind the Scenes of Imports)

Let’s start with everybody’s favourite topic: Performance. That’s probably the most pointless argument against exec, but well, it’s important to know though. But before that, let’s see what Python roughly does if you import a module (import foo):

让我们从每个人最喜欢的主题开始:性能。 这可能是针对exec的最无意义的论据,但是,尽管如此,了解这一点很重要。 但是在此之前,让我们看一下如果导入模块( import foo ),Python会大致做什么:

  1. it locates the module (surprise). That happens by traversing the sys.path info in various ways. There is builtin import logic, there are import hooks and all in all there is a lot of magic involved I don’t want to go into. If you are curious, check this and this.
  2. Now depending on the import hook responsible it might load bytecode (.pyc) or sourcecode (.py):
    1. If bytecode is available and the magic checksum matches the current Python interpreter’s version, the timestamp of the bytecode file is newer or equal to the source version (or the source does not exist) it will load that.
    2. If the bytecode is missing or outdated it will load the source file and compile that to bytecode. For that it checks magic comments in the file header for encoding settings and decodes according to those settings. It will also check if a special tab-width comment exists to treat tabs as something else than 8 characters if necessary. Some import hooks will then generate .pyc files or store the bytecode somewhere else (__pycache__) depending on Python version and implementation.
  3. The Python interpreter creates a new module object (you can do that on your own by calling imp.new_module or creating an instance of types.ModuleType. Those are equivalent) with a proper name.
  4. If the module was loaded from a file the __file__ key is set. The import system will also make sure that __package__ and __path__ are set properly if packages are involved before the code is executed. Import hooks will furthermore set the __loader__ variable.
  5. The Python interpreter executes the bytecode in the context of the dictionary of the module. Thus the frame locals and frame globals for the executed code are the __dict__ attribute of that module.
  6. The module is inserted into sys.modules.
  1. 它找到模块(惊奇)。 这是通过以各种方式遍历sys.path信息而发生的。 有内置的导入逻辑,有导入钩子,总而言之,我不想参与很多魔术。 如果您好奇,请检查
  2. 现在,根据负责的导入挂钩,它可能会加载字节码( .pyc )或源代码( .py ):
    1. 如果字节码可用并且魔术校验和与当前Python解释器的版本匹配,则字节码文件的时间戳会较新或等于源版本(或源不存在),它将加载该版本。
    2. 如果字节码丢失或过时,它将加载源文件并将其编译为字节码。 为此,它将检查文件头中的魔术注释以进行编码设置,并根据这些设置进行解码。 它还将检查是否存在特殊的制表符宽度注释,以便在必要时将制表符视为8个字符以外的字符。 然后,一些导入挂钩将生成.pyc文件或将字节码存储在其他位置( __pycache__ ),具体取决于Python版本和实现。
  3. Python解释器会使用适当的名称创建一个新的模块对象(您可以通过调用imp.new_module或创建一个types.ModuleType实例来实现 。)
  4. 如果模块是从文件加载的,则设置__file__键。 如果在执行代码之前涉及到程序包,导入系统还将确保正确设置__package____path__ 。 导入挂钩还将进一步设置__loader__变量。
  5. Python解释器在模块字典的上下文中执行字节码。 因此,已执行代码的框架局部变量和框架全局变量是该模块的__dict__属性。
  6. 该模块已插入sys.modules中

Now first of all, none of the above steps ever passed a string to the exec keyword or function. That’s obviously true because that happens deep inside the Python interpreter unless you are using an import hook written in Python. But even if the Python interpreter was written in Python it would never pass a string to the exec function. So what would you want to do if you want to get that string into bytecode yourself? You would use the compile builtin:

现在,首先,以上所有步骤都没有将字符串传递给exec关键字或函数。 显然,这是正确的,因为除非您使用的是用Python编写的导入钩子,否则它会深入Python解释器内部。 但是,即使Python解释器是用Python编写的,它也永远不会将字符串传递给exec函数。 那么,如果您想自己将该字符串转换为字节码,该怎么办? 您将使用内置的编译

>>> >>> code code = = compilecompile (( 'a = 1 + 2''a = 1 + 2' , , '<string>''<string>' , , 'exec''exec' )
)
>>> >>> exec exec code
code
>>> >>> print print a
a
3
3

As you can see, exec happily executes bytecode too. Because the code variable is actually an object of type code and not a string. The second argument to compile is the filename hint. If we are compiling from an actual string there we should provide a value enclosed in angular brackets because this is what Python will do. <string> and <stdin> are common values. If you do have a file to back this up, please use the actual filename there. The last parameter is can be one of 'exec', 'eval' and 'single'. The first one is what exec is using, the second is what the eval function uses. The difference is that the first can contain statements, the second only expressions. 'single' is a form of hybrid mode which is useless for anything but interactive shells. It exists solely to implement things like the interactive Python shell and is very limited in use.

如您所见, exec也很高兴地执行字节码。 因为代码变量实际上是代码类型的对象,而不是字符串。 编译的第二个参数是文件名提示。 如果我们从实际的字符串中进行编译,则应该提供一个用尖括号括起来的值,因为这是Python会做的。 <string><stdin>是通用值。 如果您有备份文件,请在其中使用实际文件名。 最后一个参数可以是'exec''eval''single'之一 。 第一个是exec正在使用的东西,第二个是eval函数使用的东西。 区别在于,第一个可以包含语句,第二个只能包含表达式。 “单一”是混合模式的一种形式,除了交互式外壳之外,它对其他任何东西都没有用。 它仅用于实现诸如交互式Python shell之类的功能,使用范围非常有限。

Here however we were already using a feature you should never, ever, ever use: executing code in the calling code’s namespace. What should you do instead? Execute against a new environment:

但是,在这里,我们已经在使用您永远都不会使用的功能:在调用代码的名称空间中执行代码。 您应该怎么做呢? 针对新环境执行:

Why should you do that? Cleaner for starters, also because exec without a dictionary has to hack around some implementation details in the interpreter. We will cover that later. For the moment: if you want to use exec and you plan on executing that code more than once, make sure you compile it into bytecode first and then execute that bytecode only and only in a new dictionary as namespace.

你为什么要那样做? 更适合初学者,也因为没有字典的exec必须在解释器中破解一些实现细节。 我们将在后面介绍。 目前:如果您想使用exec并且计划多次执行该代码,请确保先将其编译为字节码,然后仅在新的字典中作为名称空间执行该字节码。

In Python 3 the exec ... in statement disappeared and instead you can use the new exec function which takes the globals and locals dictionaries as parameters.

在Python 3中, exec ... in语句消失了,相反,您可以使用新的exec函数,该函数将globals和locals字典作为参数。

性能特点 (Performance Characteristics)

Now how much faster is executing bytecode over creating bytecode and executing that?:

现在执行字节码要比创建字节码和执行字节码快多少?:


$ python -mtimeit -s 'code = "a = 2; b = 3; c = a * b"' 'exec code'
10000 loops, best of 3: 22.7 usec per loop

$ python -mtimeit -s 'code = compile("a = 2; b = 3; c = a * b",
  "<string>", "exec")' 'exec code'
1000000 loops, best of 3: 0.765 usec per loop

32 times as fast for a very short code example. It becomes a lot worse the more code you have. Why is that the case? Because parsing Python code and converting that into Bytecode is an expensive operation compared to evaluating the bytecode. That of course also affects execfile which totally does not use bytecode caches, how should it. It’s not gonna magically check if there is a .pyc file if you are passing the path to a foo.py file.

一个非常短的代码示例的速度是32倍。 您拥有的代码越多,情况就越糟。 为什么会这样? 因为与评估字节码相比,解析Python代码并将其转换为Bytecode是一项昂贵的操作。 那当然也会影响execfile ,该文件完全不使用字节码缓存,应该怎么使用。 如果您要将路径传递给foo.py文件,则不会神奇地检查是否存在.pyc文件。

Alright, lesson learned. compile + exec > exec. What else has to be considered when using exec? The next thing you have to keep in mind is that there is a huge difference between the global scope and the local scope. While both the global scope and the local scope are using dictionaries as a data storage, the latter actually is not. Local variables in Python are just pulled from the frame local dictionary and put there as necessary. For all calculations that happen between that, the dictionary is never ever used. You can quickly verify this yourself.

好,经验教训。 编译 + exec > exec 。 使用exec时还需要考虑什么? 接下来要记住的是,全局范围和本地范围之间存在巨大差异。 尽管全局范围和局部范围都将字典用作数据存储,但后者却没有。 只是将Python中的局部变量从框架局部字典中提取出来,并根据需要放在其中。 对于这之间发生的所有计算,永远不会使用字典。 您可以自己快速验证。

Execute the following thing in the Python interpreter:

在Python解释器中执行以下操作:

>>> >>>  a a = = 42
42
>>> >>>  localslocals ()[()[ 'a''a' ] ] = = 23
23
>>> >>>  a
a
23
23

Works as expected. Why? Because the interactive Python shell executes code as part of the global namespace like any code outside of functions or class declarations. The local scope is the global scope:

可以正常工作。 为什么? 因为交互式Python Shell将代码作为全局名称空间的一部分执行,就像函数或类声明之外的任何代码一样。 本地范围是全局范围:

Now what happens if we do this at function level?

现在,如果我们在功能级别执行此操作,会发生什么?

>>> >>>  def def foofoo ():
():
...  ...  a a = = 42
42
...  ...  localslocals ()[()[ 'a''a' ] ] = = 23
23
...  ...  return return a
a
...
...
>>> >>>  foofoo ()
()
42
42

How unfortunate. No magic variable changing for us. That however is only partially correct. There is a Python opcode for synchronizing the frame dictionary with the variables from the fast local slots. There are two ways this synchronization can happen: from fast local to dictionary and the other way round. The former is implicitly done for you when you call locals() or access the f_locals attribute from a frame object, the latter happens either explicitly when using some opcodes (which I don’t think are used by Python as part of the regular compilation process but nice for hacks) or when the exec statement is used in the frame.

多么的不幸。 没有魔术变量为我们改变。 但是,那只是部分正确的。 有一个Python操作码,用于将帧字典与快速本地插槽中的变量同步。 这种同步可以通过两种方式发生:从快速本地到字典,另一种则是往返。 前者是在您调用locals()或从框架对象访问f_locals属性时为您隐式完成的,后者是在使用某些操作码时显式发生的(我认为Python不将其用作常规编译过程的一部分)但非常适合黑客使用)或在框架中使用exec语句时使用。

So what are the performance characteristics of code executed in a global scope versus code executed at a local scope? This is a lot harder to measure because the timeit module does not allow us to execute code at global scope by default. So we will need to write a little helper module that emulates that:

那么,在全局范围内执行的代码与在本地范围内执行的代码的性能特征是什么? 这很难衡量,因为默认情况下timeit模块不允许我们在全局范围内执行代码。 因此,我们将需要编写一个模拟以下内容的辅助模块:

Here we compile two times the same algorithm into a string. One time directly globally, one time wrapped into a function. Then we have two functions. The first one executes that code in an empty dictionary, the second executes the code in a new dictionary and then calls the function that was declared. Let’s ask timeit how fast we are:

在这里,我们将两次相同的算法编译为一个字符串。 一次直接全局,一次封装到一个函数中。 然后我们有两个功能。 第一个在空字典中执行该代码,第二个在新字典中执行该代码,然后调用已声明的函数。 让我们问一下timeit我们有多快:


$ python -mtimeit -s 'from execcompile import test_global as t' 't()'
10 loops, best of 3: 67.7 msec per loop

$ python -mtimeit -s 'from execcompile import test_local as t' 't()'
100 loops, best of 3: 23.3 msec per loop

Again, an increase in performance [3]. Why is that? That has to do with the fact that fast locals are faster than dictionaries (duh). What is a fast local? In a local scope Python keeps track of the names of variables it knows about. Each of that variable is assigned a number (index). That index is used in an array of Python objects instead of a dictionary. It will only fall back to the dictionary if this is necessary (debugging purposes, exec statement used at local scope). Even though exec still exists in Python 3 (as a function) you no longer it at a local scope to override variables. The Python compiler does not check if the exec builtin is used and will not unoptimize the scope because of that [1].

再次,性能提高[3] 。 这是为什么? 这与以下事实有关:快速的本地人比字典(duh)更快。 什么是快速本地化? 在本地范围内,Python会跟踪它知道的变量名称。 每个变量都分配有一个数字(索引)。 该索引用于Python对象数组而不是字典中。 仅在必要时才退回到字典(调试目的,在本地范围使用exec语句)。 即使exec在Python 3中仍然存在(作为函数),您也不再在本地范围内使用它来覆盖变量。 Python编译器不会检查是否使用了exec内置函数,也不会因为[1]而未优化范围。

All of the above knowledge is good to know if you plan on utilizing the Python interpreter to interpret your own language by generating Python code and compiling it to bytecode. That’s for instance how template engines like Mako, Jinja2 or Genshi work internally in one way or another.

如果您计划通过生成Python代码并将其编译为字节码来计划使用Python解释器来解释您自己的语言,那么上述所有知识都是很不错的。 例如,这就是诸如Mako,Jinja2或Genshi之类的模板引擎在内部以一种或另一种方式工作的方式。

语义和不成文约定 (Semantics and Unwritten Conventions)

However most people are using the exec statement for something else: executing actual Python code from different locations. A very popular use case is executing config files as Python code. That’s for example what Flask does if you tell it to. That’s usually okay because you don’t expect your config file to be a place where you implement actual code. However there are also people that use exec to load actual Python code that declares functions and classes. This is a very popular pattern in some plugin systems and the web2py framework.

但是,大多数人将exec语句用于其他用途:从不同位置执行实际的Python代码。 一个非常流行的用例是将配置文件作为Python代码执行。 举例来说,这就是Flask所做的。 通常没关系,因为您不希望配置文件成为实现实际代码的地方。 但是,也有人使用exec加载声明函数和类的实际Python代码。 在某些插件系统和web2py框架中,这是一种非常流行的模式。

Why is that not a good idea? Because it breaks some (partially unwritten) conventions about Python code:

为什么这不是一个好主意? 因为它违反了一些关于Python代码的约定(部分未编写),所以:

  1. Classes and functions belong into a module. That basic rule holds for all functions and classes imported from regular modules:

    >>> from xml.sax.saxutils import quoteattr
    >>> quoteattr.__module__
    'xml.sax.saxutils'
    
    

    Why is that important? Because that is how pickle works [2]:

    If you are using exec to execute Python code, be prepared that some modules like pickle, inspect, pkgutil, pydoc and probably some others that depend on those will not work as expected.

  2. CPython has a cyclic garbage collector, classes can have destructors and interpreter shutdown breaks up cycles. What does it mean?

    • CPython uses refcounting internally. One (of many) downsides of refcounting is that it cannot detect circular dependencies between objects. Thus Python introduced a cyclic garbage collector at one point.
    • Python however also allows destructors on objects. Destructors however mean that the cyclic garbage collector will skip these objects because it does not know in what order it should delete these objects.

    Now let’s look at an innocent example:

    class Foo(object):
        def __del__(self):
            print 'Deleted'
    foo = Foo()
    
    

    Let’s execute that file:

    Looks good. Let’s try that with exec:

    >>> execfile('test.py', {})
    >>> execfile('test.py', {})
    >>> execfile('test.py', {})
    >>> import gc
    >>> gc.collect()
    27
    
    

    It clearly collected something, but it never collected our Foo instances. What the hell is happening? What’s happening is that there is an implicit cycle between foo, and the __del__ function itself. The function knows the scope it was created in and from __del__ -> global scope -> foo instance it has a nice cycle.

    Now now we know the cause, why doesn’t it happen if you have a module? The reason for that is that Python will do a trick when it shuts down modules. It will override all global values that do not begin with an underscore with None. We can easily verify that if we print the value of foo instead of 'Deleted':

    And of course it’s None:

    
    $ python test.py
    None
    
    

    So if we want to replicate that with exec or friends, we have to apply the same logic, but Python will not do that for us. If we are not careful this could lead to hard to spot memory leaks. And this is something many people rely on, because it’s documented behaviour.

  3. Lifetime of objects. A global namespace sticks around from when it was imported to the point where the interpreter shuts down. With exec you as a user no longer know when this will happen. It might happen at a random point before. web2py is a common offender here. In web2py the magically executed namespace comes and goes each request which is very surprising behaviour for any experienced Python developer.

  1. 类和函数属于模块。 该基本规则适用于从常规模块导入的所有函数和类:

    为什么这么重要? 因为这就是泡菜的工作方式[2]

     >>>  pickle . loads ( pickle . dumps ( quoteattr ))
    <function quoteattr at 0x1005349b0>
    >>>  quoteattr . __module__ = 'fake'
    >>>  pickle . loads ( pickle . dumps ( quoteattr ))
    Traceback (most recent call last):
      ..
    pickle.PicklingError : Can't pickle quoteattr: it's not found as fake.quoteattr
    Traceback (most recent call last):
      ..
    pickle.PicklingError : Can't pickle quoteattr: it's not found as fake.quoteattr
    

    如果您使用exec执行Python代码,请做好准备,使某些模块(例如pickle,check,pkgutil,pydoc以及可能依赖于这些模块的其他模块)无法按预期工作。

  2. CPython具有循环垃圾收集器,类可以具有析构函数,并且解释器关闭会破坏循环。 这是什么意思?

    • CPython在内部使用引用计数。 refcounting的缺点之一是它无法检测对象之间的循环依赖关系。 因此,Python在某一时刻引入了循环垃圾收集器。
    • 但是,Python还允许在对象上使用析构函数。 但是,析构函数意味着循环垃圾收集器将跳过这些对象,因为它不知道应按什么顺序删除这些对象。

    现在让我们看一个无辜的例子:

    让我们执行该文件:

    
    $ python test.py
    Deleted
    
    

    看起来挺好的。 让我们用exec尝试一下:

    它显然收集了一些东西,但从未收集我们的Foo实例。 到底是怎么回事? 发生的是foo__del__函数本身之间存在一个隐式循环。 该函数知道它是在__del__- >全局范围-> foo实例中创建的,它具有良好的循环。

    现在我们知道了原因,如果有模块,为什么不发生呢? 这样做的原因是Python在关闭模块时会发挥作用。 它将覆盖所有不以下划线开头的全局值( 无) 。 我们可以轻松地验证是否打印foo而不是'Deleted'的值

     class Foo ( object ):
        def __del__ ( self ):
            print foo
    foo = Foo ()
    

    当然,这是

    因此,如果我们想与exec或friends复制它,我们必须应用相同的逻辑,但是Python不会为我们这样做。 如果我们不小心,可能会导致难以发现内存泄漏。 这是许多人所依赖的东西,因为这是记录在案的行为

  3. 对象的生命周期。 从导入全局名称空间到解释器关闭的那一刻,它始终存在。 使用exec,您作为用户不再知道何时会发生这种情况。 它可能在之前的任意时间发生。 web2py是这里的常见违规者。 在web2py中,神奇地执行了每个请求的命名空间,对于任何有经验的Python开发人员来说,这都是令人惊讶的行为。

Python不是PHP (Python is not PHP)

Don’t try to circumvent Python idioms because some other language does it differently. Namespaces are in Python for a reason and just because it gives you the tool exec it does not mean you should use that tool. C gives you setjmp and longjmp and yet you will be very careful with using it. The combination of exec and compile are a powerful tool for anyone that wants to implement a domain specific language on top of Python or for developers interested in extending (not circumventing) the Python import system.

不要试图绕过Python的习惯用法,因为其他一些语言会做不同的事情。 在Python中使用命名空间是有原因的,仅仅是因为它为您提供了工具exec,但这并不意味着您应该使用该工具。 C为您提供了setjmplongjmp ,但是使用它时您会非常小心。 execcompile的组合对于想要在Python之上实现领域特定语言的任何人或对扩展(而不是规避)Python导入系统感兴趣的开发人员而言,都是一个强大的工具。

A python developer depends on imports doing what they are documented to do and that the namespace has a specific initial value (namely that it’s empty with the exception of a few internal variables such as __name__, __loader__ etc.). A Python developer depends on being able to import that module by a dotted name, on the fact that modules shut down in a specific way, that they are cached in sys.modules etc.

python开发人员依赖于导入操作来执行它们所记录的操作,并且命名空间具有特定的初始值(即,它是空的,除了一些内部变量(例如__name____loader__等)之外)。 Python开发人员依赖于能够以点名导入该模块,模块以特定方式关闭,它们被缓存在sys.modules等中这一事实。

Jacob Kaplan-Moss wrote a comment on Reddit about the use of exec in web2py a while ago which I would recommend reading. He brings up some very good points why changing the semantics of a language is a bad idea.

雅各布·卡普兰·莫斯 Jacob Kaplan-Moss 不久前在Reddit上发表了有关在web2py中使用exec 的评论 ,我建议阅读。 他提出了一些很好的观点,为什么更改语言的语义不是一个好主意。

However web2py and it’s use of execfile are not the only offenders in the Python web community. Werkzeug has it’s fair share of abusing Python conventions as well. We were shipping (and still do) an on-demand import system which caused more problems than it solved and are currently in the progress of moving away from it (despite all the pain this is for us). Django abused Python internals as well. It was generating Python code on the fly and totally changing semantics (to the point where imports vanished without warning!). They learned their lesson as well and fixed that problem in the magic removal branch. Same goes for web.py which was abusing the print statement to write into an internal thread-local buffer that was then sent out as response to the browser. Also something that turned out to be a bad idea and was subsequently removed.

但是,web2py及其对execfile的使用并不是Python网络社区中唯一的冒犯者。 Werkzeug也有相当一部分滥用Python约定。 我们正在运送(并且仍在这样做)按需导入系统,该系统导致的问题超出了解决的范围,并且目前正在逐步取消(尽管这给我们带来了所有痛苦)。 Django也滥用了Python内部。 它正在动态生成Python代码,并且语义发生了完全变化(以至于导入在没有任何警告的情况下就消失了!)。 他们也吸取了教训,并在魔术清除分支中解决了该问题。 web.py也是如此,它滥用了打印语句以写入内部线程本地缓冲区,然后将该缓冲区作为响应发送给浏览器。 事实证明这也是一个坏主意,后来被删除了。

With that I encourage the web2py developers to reconsider their decision on the use of the exec statement and using regular Python modules.

因此,我鼓励web2py开发人员重新考虑他们对使用exec语句和使用常规Python模块的决定。

Because one of the things we all have to keep in mind: if a Python developer starts his journeys in the twisted world of wrongly executed Python modules they will be very confused when they continue their travels in another Python environment. And having different semantics in different frameworks/modules/libraries is very hurtful for Python as a runtime and language.

因为我们所有人都必须牢记一件事:如果Python开发人员开始在错误执行的Python模块这个扭曲的世界中踏上旅程,那么当他们继续在另一个Python环境中旅行时,他们会感到非常困惑。 在不同的框架/模块/库中具有不同的语义对于Python作为运行时和语言来说是非常有害的。

[1][1] if one wants to argue that this is obvious: it should be. But
Python does track another builtin function to change the behaviour of
the compiler: super. So it would have been possible to do the same
with exec. It’s for the better however that this does not happen.
如果有人想证明这是显而易见的:那就应该了。 但
Python确实跟踪了另一个内置函数来更改行为
编译器: super 。 因此本来可以做同样的事情
exec 。 更好的是,这不会发生。
[2][2]

if you however set __module__ to None you will notice that Python is magically still able to find your function if it originated from a module registered in sys.modules. How does that work? It will actually walk through all the modules and look at all to find that function again.

但是,如果将__module__设置为None,那么您会发现Python神奇地仍然能够找到您的函数,如果它源自sys.modules中注册的模块。 这是如何运作的? 实际上,它将遍历所有模块,并仔细查看所有内容以再次找到该功能。

I have no idea who came up with that idea, but it’s an incredible slow operation if a lot of modules are loaded.

我不知道是谁提出了这个主意,但是如果加载了许多模块,这将是一个令人难以置信的缓慢操作。

[3][3] I actually made a mistake in this benchmark. As correctly
pointed out by @thp4 the benchmark was
flawed because it was comparing different iterations. This has since
been fixed.
我实际上在该基准测试中犯了一个错误。 如正确
@ thp4指出基准是
有缺陷的,因为它正在比较不同的迭代。 这是因为
已修复。

翻译自: https://www.pybloggers.com/2011/01/be-careful-with-exec-and-eval-in-python/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值