Отключить поиск глобальных переменных в 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()))