Отключить поиск глобальных переменных в Python
Короче говоря, вопрос: Есть ли способ предотвратить Python от поиска переменных за пределами текущей области?
Подробнее:
Python ищет определения переменных во внешних областях, если они не определены в текущей области. Таким образом, код, подобный этому, может сломаться, если не быть осторожным во время рефакторинга:
def line(x, a, b):
return a + x * b
a, b = 1, 1
y1 = line(1, a, b)
y2 = line(1, 2, 3)
Если я переименовал аргументы функции, но забыл переименовать их внутри тела функции, код все равно будет выполняться:
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
Я знаю Плохая практика теневых имен из внешних областей. Однако есть несколько причин, почему это делается в любом случае:
- Иногда может иметь смысл иметь одинаковые имена, потому что они относятся к одной и той же вещи
- Возможно, закончились значащие имена переменных
- лени
Есть ли способ предотвратить Python от поиска переменных за пределами текущей области? (Чтобы доступ к a
или b
вызывал ошибку во втором примере.)
Из-за лени, я бы предпочел решение, которое работает без повторного кода котельной:)
Если проблема неоднозначна с точки зрения версии Python, меня в основном интересует Python 3.3 и выше.
Ответы
Ответ 1
Нет, вы не можете сказать Python не искать имена в глобальной области.
Если бы вы могли, вы не смогли бы использовать какие-либо другие классы или функции, определенные в модуле, объекты, импортированные из других модулей, и вы не могли бы использовать встроенные имена. Ваше пространство имен функций становится пустыней, лишенной почти всего, в чем она нуждается, и единственным выходом было бы импортировать все в локальное пространство имен. Для каждой отдельной функции в вашем модуле.
Вместо того, чтобы пытаться нарушить глобальный поиск, сохраните свое глобальное пространство имен в чистоте. Не добавляйте глобальные переменные, которые вам не нужны для совместного использования с другими областями в модуле. Например, используйте функцию main()
, чтобы инкапсулировать то, что действительно есть только локальные.
Также добавьте unittesting. Рефакторинг без (даже нескольких) тестов всегда склонен создавать ошибки в противном случае.
Ответ 2
Да, может быть, и не в общем. Однако вы можете сделать это с помощью функций.
Вещь, которую вы хотите сделать, состоит в том, чтобы функция global была пустой. Вы не можете заменить глобалы, и вы не хотите изменять его содержимое (becaus
это было бы просто для того, чтобы избавиться от глобальных переменных и функций).
Однако: вы можете создавать объекты функции во время выполнения. Конструктор выглядит как types.FunctionType((code, globals[, name[, argdefs[, closure]]])
. Там вы можете заменить глобальное пространство имен:
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
Вы можете, конечно, очистить это, указав свой собственный декоратор:
import types
noglobal = lambda f: types.FunctionType(f.__code__, {})
@noglobal
def f():
return x
x = 5
f() # will fail
Строго говоря, вы не запрещаете ему обращаться к глобальным переменным, вы просто заставляете функцию полагать, что в глобальном пространстве имен нет переменных. Фактически вы также можете использовать это для эмуляции статических переменных, поскольку, если объявить переменную глобальной и назначить ей, она будет в ней собственной песочнице глобального пространства имен.
Если вы хотите иметь доступ к части глобального пространства имен, вам нужно будет заполнить функции global sandbox тем, что вы хотите видеть.
Ответ 3
Чтобы отменить поиск глобальных переменных, переместите вашу функцию в другой модуль. Если он не проверяет стек вызовов или явно не импортирует ваш вызывающий модуль; он не будет иметь доступ к глобалям модуля, который его вызывает.
На практике переместите код в функцию main()
, чтобы избежать создания ненужных глобальных переменных.
Если вы используете глобальные переменные, потому что некоторые функции должны управлять общим состоянием, тогда переместите код в класс.
Ответ 4
Теоретически вы можете использовать свой собственный декоратор, который удаляет globals()
во время вызова функции. Чтобы скрыть все globals()
, нужно globals()
немного времени, но, если globals()
не слишком много globals()
это может быть полезно. Во время операции мы не создаем/удаляем глобальные объекты, мы просто перезаписываем ссылки в словаре, который ссылается на глобальные объекты. Но не удаляйте специальные globals()
(например, __builtins__
) и модули. Возможно, вы тоже не хотите удалять callable
из глобальной области видимости.
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?
Или вы можете перебрать все глобальные вызываемые элементы (т.е. функции) в вашем модуле и динамически их декорировать (это немного больше кода).
Код для Python 2
, я думаю, что возможно создать очень похожий код для Python 3
.
Ответ 5
С ответом @skyking я не смог получить доступ ни к какому импорту.
@Ax3l комментарий улучшил это. Тем не менее я не смог получить доступ к импортированным переменным (from module import var
).
Поэтому я предлагаю это:
def noglobal(f):
return types.FunctionType(f.__code__, globals().copy())
Для каждого определения функции с @noglobal
это создает копию globals()
определенных до сих пор. Это обеспечивает доступность импортированных переменных (обычно импортируемых в верхней части документа). Если вы сделаете это, как я, сначала определите свои функции, а затем свои переменные, это обеспечит желаемый эффект доступа к импортированным переменным в вашей функции, но не к тем, которые вы определяете в своем коде. Поскольку copy()
создает неглубокую копию (Понимание dict.copy() - неглубокую или глубокую?), Это также должно быть довольно эффективным с точки зрения памяти.
Для справки, я копирую версию @Ax3l из его Gist:
def imports():
for name, val in globals().items():
# module imports
if isinstance(val, types.ModuleType):
yield name, val
# functions / callables
if hasattr(val, '__call__'):
yield name, val
noglobal = lambda fn: types.FunctionType(fn.__code__, dict(imports()))