使用内置名称作为局部变量,也作为内置变量

时间:2016-12-07 23:35:31

标签: python python-3.x

我有以下功能:

def x():
    print(min(0, 1))
    min = 7
    print(min)

从表面上看(天真),它应该打印0,然后打印7。实际上它引发了一个错误:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in x
UnboundLocalError: local variable 'min' referenced before assignment

如何将min定义为min = 7中的局部变量,以防止它被用作之前的内置变量?在编译函数时,Python是否构建了局部变量列表(类似于__slots__)?

2 个答案:

答案 0 :(得分:7)

Python的编译阶段识别在函数范围内分配的所有名称,并将这些名称标记为本地(它们在CPython中的本地变量数组中重新分配了索引,因此使用它们不会&# 39;根本不涉及字典查找。

对于整个方法范围而言,作为本地是全有或全无。对于方法的一部分,您不能将变量视为本地变量,而对于其余部分,您不能将变量视为全局/内置变量。每the language reference on naming and binding

  

范围定义块中名称的可见性。如果在块中定义了局部变量,则其范围包括该块。

与大多数语言标准一样,这相当干燥,但重要的是在块中定义局部变量使其成为(隐含的)整个块的本地变量,而不是定义点。

如果您需要执行此类操作,您最初可以从the qualified name of the builtin创建本地,然后稍后更改,例如:

import builtins  # On Python 2, __builtin__

def x():
    min = builtins.min
    print(min(0, 1))
    min = 7
    print(min)

或者你可以使用基于编译时默认值缓存的俗气黑客来达到同样的目的:

def x(min=min): # Caches builtin function as a default value for local min
    print(min(0, 1))
    min = 7
    print(min)

如果您使用的是Python 3,那么您希望def x(*, min=min):使其成为仅限关键字的参数,因此如果他们意外通过,则无法被调用者覆盖在位置上有太多的论据。

答案 1 :(得分:3)

是的,具体来说,如果您查看生成的字节代码,您会看到Python尝试将其作为LOAD_FAST的本地加载:

>>> dis(x)
  2           0 LOAD_GLOBAL              0 (print)
              3 LOAD_FAST                0 (min)
              6 LOAD_CONST               1 (0)
              9 LOAD_CONST               2 (1)
             12 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
             15 CALL_FUNCTION            1 (1 positional, 0 keyword pair)

在编译函数对象期间(我非常确定它是在构造符号表的过程中),检测到min的赋值,并且将解析该名称的任何查找本地范围。

您实际上可以查看构造的符号表,并查看符号(名称)min的分类方式:

>>> import symtable
>>> s="""
... def x():
...     print(min(0, 1))
...     min = 7
...     print(min)
... """
>>> t = symtable.symtable(s, '', compile_type='exec')
>>> i = t.get_children()[0].get_symbols()[0]
>>> print(i)
<symbol 'min'>
>>> i.is_local()
True

Haven没有仔细研究了symtable的内部结构,但这里可能有一些压倒一切。之后,在执行期间,由于在引用之后发生赋值而没有找到任何值,这将导致失败。