禁用Python中的全局变量查找

时间:2022-06-24 23:14:18

In short, the question: Is there a way to prevent Python from looking up variables outside the current scope?

简而言之,问题是:是否有一种方法可以防止Python查找当前范围之外的变量?

Details:

细节:

Python looks for variable definitions in outer scopes if they are not defined in the current scope. Thus, code like this is liable to break when not being careful during refactoring:

如果在当前作用域中没有定义变量定义,Python将在外部作用域中查找它们。因此,像这样的代码在重构过程中不小心就容易被破坏:

def line(x, a, b):
    return a + x * b

a, b = 1, 1
y1 = line(1, a, b)
y2 = line(1, 2, 3)

If I renamed the function arguments, but forgot to rename them inside the function body, the code would still run:

如果我重命名函数参数,但是忘记在函数体中重命名它们,代码仍然会运行:

def line(x, a0, b0):
    return a + x * b  # not an error

a, b = 1, 1
y1 = line(1, a, b)  # correct result by coincidence
y2 = line(1, 2, 3)  # wrong result

I know it is bad practice to shadow names from outer scopes. However, there are a few reasons why this is done anyway:

我知道从外部作用域阴影名称是不好的做法。然而,有几个原因可以解释为什么这样做:

  • Sometimes it may make sense to have the same names because they refer to the same thing
  • 有时候,有相同的名字是有意义的,因为它们指的是同一件事
  • One might run out of meaningful variable names
  • 一个可能会用完有意义的变量名
  • laziness
  • 懒惰

Is there a way to prevent Python from looking up variables outside the current scope? (So that accessing a or b raises an Error in the second example.)

是否有一种方法可以防止Python在当前范围之外查找变量?(因此,在第二个示例中访问a或b会引发错误。)

On account of being lazy, I would prefer a solution that works without repeated boiler-plate code :)

由于懒惰,我更喜欢一种不用重复的样板代码的解决方案:)

If the issue is ambiguous in terms of Python version, I'm mostly interested in Python 3.3 and above.

如果这个问题在Python版本方面是不明确的,我主要对Python 3.3及以上版本感兴趣。

4 个解决方案

#1


3  

No, you cannot tell Python not to look names up in the global scope.

不,您不能告诉Python不要在全局范围中查找名称。

If you could, you would not be able to use any other classes or functions defined in the module, no objects imported from other modules, nor could you use built-in names. Your function namespace becomes a desert devoid of almost everything it needs, and the only way out would be to import everything into the local namespace. For every single function in your module.

如果可以,您将不能使用模块中定义的任何其他类或函数,也不能使用从其他模块导入的对象,也不能使用内置的名称。函数名称空间变成了一个沙漠,几乎没有它需要的任何东西,惟一的解决方法是将所有东西都导入到本地名称空间中。对于模块中的每个函数。

Rather than try to break global lookups, keep your global namespace clean. Don't add globals that you don't need to share with other scopes in the module. Use a main() function for example, to encapsulate what are really just locals.

与其尝试中断全局查找,不如保持全局名称空间清洁。不要添加不需要与模块中的其他范围共享的全局变量。例如,使用main()函数来封装真正的本地代码。

Also, add unittesting. Refactoring without (even just a few) tests is always prone to create bugs otherwise.

此外,添加unittest。没有测试的重构(甚至只有少数)总是容易产生bug。

#2


10  

Yes, maybe not in general. However you can do it with functions.

是的,也许不是一般的。但是你可以用函数来做。

The thing you want to do is to have the function's global to be empty. You can't replace the globals and you don't want to modify it's content (becaus that would be just to get rid of global variables and functions).

你要做的是让函数的全局变量为空。您不能替换全局变量,也不希望修改它的内容(因为这只是为了去掉全局变量和函数)。

However: you can create function objects in runtime. The constructor looks like types.FunctionType((code, globals[, name[, argdefs[, closure]]]). There you can replace the global namespace:

但是:您可以在运行时中创建函数对象。构造函数看起来像类型。FunctionType(代码、全局变量[、名称[、argdefs[、闭包]]]]))。在那里,您可以替换全局名称空间:

def line(x, a0, b0):
   return a + x * b  # will be an error

a, b = 1, 1
y1 = line(1, a, b)  # correct result by coincidence

line = types.FunctionType(line.__code__, {})
y1 = line(1, a, b)  # fails since global name is not defined

You can of course clean this up by defining your own decorator:

当然,您可以通过定义自己的decorator来清理这个问题:

import types
noglobal = lambda f: types.FunctionType(f.__code__, {})

@noglobal
def f():
    return x

x = 5
f() # will fail

Strictly speaking you do not forbid it to access global variables, you just make the function believe there is no variables in global namespace. Actually you can also use this to emulate static variables since if it declares an variable to be global and assign to it it will end up in it's own sandbox of global namespace.

严格地说,您并不禁止它访问全局变量,您只是让函数相信全局名称空间中没有变量。实际上,您也可以使用它来模拟静态变量,因为如果它声明一个变量为全局变量并将其赋值给它,那么它最终将位于它自己的全局名称空间沙箱中。

If you want to be able to access part of the global namespace then you'll need to populate the functions global sandbox with what you want it to see.

如果您希望能够访问全局名称空间的一部分,那么您将需要用希望它看到的内容填充函数全局沙箱。

#3


1  

To discourage global variable lookup, move your function into another module. Unless it inspects the call stack or imports your calling module explicitly; it won't have access to the globals from the module that calls it.

为了阻止全局变量查找,将您的函数移动到另一个模块中。除非它检查调用堆栈或显式地导入调用模块;它无法从调用它的模块访问全局变量。

In practice, move your code into a main() function, to avoid creating unnecessary global variables.

在实践中,将代码移动到main()函数中,以避免创建不必要的全局变量。

If you use globals because several functions need to manipulate shared state then move the code into a class.

如果使用全局变量,因为多个函数需要操作共享状态,然后将代码移动到类中。

#4


1  

Theoretically you can use your own decorator that removes globals() while a function call. It is some overhead to hide all globals() but, if there are not too many globals() it could be useful. During the operation we do not create/remove global objects, we just overwrites references in dictionary which refers to global objects. But do not remove special globals() (like __builtins__) and modules. Probably you do not want to remove callables from global scope too.

理论上,您可以在调用函数时使用自己的decorator来删除globals()。隐藏所有的globals()需要一些开销,但是,如果没有太多的globals(),那么它可能是有用的。在操作过程中,我们不创建/删除全局对象,而是覆盖字典中引用全局对象的引用。但是不要删除特殊的全局变量()(如__builtins__)和模块。您可能也不希望从全局范围中删除可调用项。

from types import ModuleType
import re

# the decorator to hide global variables
def noglobs(f):
    def inner(*args, **kwargs):
        RE_NOREPLACE = '__\w+__'
        old_globals = {}
        # removing keys from globals() storing global values in old_globals
        for key, val in globals().iteritems():
            if re.match(RE_NOREPLACE, key) is None and not isinstance(val, ModuleType) and not callable(val):
                old_globals.update({key: val})

        for key in old_globals.keys():
            del globals()[key]  
        result = f(*args, **kwargs)
        # restoring globals
        for key in old_globals.iterkeys():
            globals()[key] = old_globals[key]
        return result
    return inner

# the example of usage
global_var = 'hello'

@noglobs
def no_globals_func():
    try:
        print 'Can I use %s here?' % global_var
    except NameError:
        print 'Name "global_var" in unavailable here'

def globals_func():
    print 'Can I use %s here?' % global_var 

globals_func()
no_globals_func()
print 'Can I use %s here?' % global_var

...

Can I use hello here?
Name "global_var" in unavailable here
Can I use hello here?

Or, you can iterate over all global callables (i.e. functions) in your module and decorate them dynamically (it's little more code).

或者,您可以在模块中遍历所有全局调用(例如函数),并动态地修饰它们(只是多了一些代码)。

The code is for Python 2, I think it's possible to create a very similar code for Python 3.

代码是针对Python 2的,我认为可以为Python 3创建一个非常相似的代码。

#1


3  

No, you cannot tell Python not to look names up in the global scope.

不,您不能告诉Python不要在全局范围中查找名称。

If you could, you would not be able to use any other classes or functions defined in the module, no objects imported from other modules, nor could you use built-in names. Your function namespace becomes a desert devoid of almost everything it needs, and the only way out would be to import everything into the local namespace. For every single function in your module.

如果可以,您将不能使用模块中定义的任何其他类或函数,也不能使用从其他模块导入的对象,也不能使用内置的名称。函数名称空间变成了一个沙漠,几乎没有它需要的任何东西,惟一的解决方法是将所有东西都导入到本地名称空间中。对于模块中的每个函数。

Rather than try to break global lookups, keep your global namespace clean. Don't add globals that you don't need to share with other scopes in the module. Use a main() function for example, to encapsulate what are really just locals.

与其尝试中断全局查找,不如保持全局名称空间清洁。不要添加不需要与模块中的其他范围共享的全局变量。例如,使用main()函数来封装真正的本地代码。

Also, add unittesting. Refactoring without (even just a few) tests is always prone to create bugs otherwise.

此外,添加unittest。没有测试的重构(甚至只有少数)总是容易产生bug。

#2


10  

Yes, maybe not in general. However you can do it with functions.

是的,也许不是一般的。但是你可以用函数来做。

The thing you want to do is to have the function's global to be empty. You can't replace the globals and you don't want to modify it's content (becaus that would be just to get rid of global variables and functions).

你要做的是让函数的全局变量为空。您不能替换全局变量,也不希望修改它的内容(因为这只是为了去掉全局变量和函数)。

However: you can create function objects in runtime. The constructor looks like types.FunctionType((code, globals[, name[, argdefs[, closure]]]). There you can replace the global namespace:

但是:您可以在运行时中创建函数对象。构造函数看起来像类型。FunctionType(代码、全局变量[、名称[、argdefs[、闭包]]]]))。在那里,您可以替换全局名称空间:

def line(x, a0, b0):
   return a + x * b  # will be an error

a, b = 1, 1
y1 = line(1, a, b)  # correct result by coincidence

line = types.FunctionType(line.__code__, {})
y1 = line(1, a, b)  # fails since global name is not defined

You can of course clean this up by defining your own decorator:

当然,您可以通过定义自己的decorator来清理这个问题:

import types
noglobal = lambda f: types.FunctionType(f.__code__, {})

@noglobal
def f():
    return x

x = 5
f() # will fail

Strictly speaking you do not forbid it to access global variables, you just make the function believe there is no variables in global namespace. Actually you can also use this to emulate static variables since if it declares an variable to be global and assign to it it will end up in it's own sandbox of global namespace.

严格地说,您并不禁止它访问全局变量,您只是让函数相信全局名称空间中没有变量。实际上,您也可以使用它来模拟静态变量,因为如果它声明一个变量为全局变量并将其赋值给它,那么它最终将位于它自己的全局名称空间沙箱中。

If you want to be able to access part of the global namespace then you'll need to populate the functions global sandbox with what you want it to see.

如果您希望能够访问全局名称空间的一部分,那么您将需要用希望它看到的内容填充函数全局沙箱。

#3


1  

To discourage global variable lookup, move your function into another module. Unless it inspects the call stack or imports your calling module explicitly; it won't have access to the globals from the module that calls it.

为了阻止全局变量查找,将您的函数移动到另一个模块中。除非它检查调用堆栈或显式地导入调用模块;它无法从调用它的模块访问全局变量。

In practice, move your code into a main() function, to avoid creating unnecessary global variables.

在实践中,将代码移动到main()函数中,以避免创建不必要的全局变量。

If you use globals because several functions need to manipulate shared state then move the code into a class.

如果使用全局变量,因为多个函数需要操作共享状态,然后将代码移动到类中。

#4


1  

Theoretically you can use your own decorator that removes globals() while a function call. It is some overhead to hide all globals() but, if there are not too many globals() it could be useful. During the operation we do not create/remove global objects, we just overwrites references in dictionary which refers to global objects. But do not remove special globals() (like __builtins__) and modules. Probably you do not want to remove callables from global scope too.

理论上,您可以在调用函数时使用自己的decorator来删除globals()。隐藏所有的globals()需要一些开销,但是,如果没有太多的globals(),那么它可能是有用的。在操作过程中,我们不创建/删除全局对象,而是覆盖字典中引用全局对象的引用。但是不要删除特殊的全局变量()(如__builtins__)和模块。您可能也不希望从全局范围中删除可调用项。

from types import ModuleType
import re

# the decorator to hide global variables
def noglobs(f):
    def inner(*args, **kwargs):
        RE_NOREPLACE = '__\w+__'
        old_globals = {}
        # removing keys from globals() storing global values in old_globals
        for key, val in globals().iteritems():
            if re.match(RE_NOREPLACE, key) is None and not isinstance(val, ModuleType) and not callable(val):
                old_globals.update({key: val})

        for key in old_globals.keys():
            del globals()[key]  
        result = f(*args, **kwargs)
        # restoring globals
        for key in old_globals.iterkeys():
            globals()[key] = old_globals[key]
        return result
    return inner

# the example of usage
global_var = 'hello'

@noglobs
def no_globals_func():
    try:
        print 'Can I use %s here?' % global_var
    except NameError:
        print 'Name "global_var" in unavailable here'

def globals_func():
    print 'Can I use %s here?' % global_var 

globals_func()
no_globals_func()
print 'Can I use %s here?' % global_var

...

Can I use hello here?
Name "global_var" in unavailable here
Can I use hello here?

Or, you can iterate over all global callables (i.e. functions) in your module and decorate them dynamically (it's little more code).

或者,您可以在模块中遍历所有全局调用(例如函数),并动态地修饰它们(只是多了一些代码)。

The code is for Python 2, I think it's possible to create a very similar code for Python 3.

代码是针对Python 2的,我认为可以为Python 3创建一个非常相似的代码。