Как изменить локальное пространство имен в Python - PullRequest
6 голосов
/ 17 июля 2009

Как я могу изменить локальное пространство имен функции в python? Я знаю, что locals () возвращает локальное пространство имен функции при вызове внутри нее, но я хочу сделать что-то подобное (у меня есть причина, почему я хочу сделать это, когда g недоступна для f, но быстрее тривиальный, глупый пример, иллюстрирующий проблему):

def g():
   pass

def f():
    g()

f.add_to_locals({'g':g})

Ответы [ 7 ]

9 голосов
/ 17 июля 2009

У вас есть несколько вариантов. Во-первых, обратите внимание, что g в вашем примере на самом деле не является локальным для функции (т.е. не назначен внутри нее), это является глобальным (т.е. не был назначен локальной переменной). Это означает, что он будет найден в модуле, в котором определена функция. Это удачно, поскольку нет никакого способа изменить локальные переменные извне (кроме исправления байт-кода), поскольку они назначаются при запуске функции, а не до. 1001 *

Один из вариантов - просто внедрить вашу функцию в пространство имен модуля функции. Это будет работать, но повлияет на каждую функцию в этом модуле, которая обращается к переменной, а не только на одну функцию.

Чтобы повлиять только на одну функцию, вам нужно вместо этого указать func_globals где-то еще. К сожалению, это свойство только для чтения, но вы можете делать все, что хотите, воссоздав функцию с тем же телом, но с другим глобальным пространством имен:

import new
f = new.function(f.func_code, {'g': my_g_function}, f.func_name, f.func_defaults, f.func_closure)

f теперь будет идентичным, за исключением того, что будет искать глобальные переменные в предоставленном dict. Обратите внимание, что это перепутывает все глобальное пространство имен - если есть переменные, которые f просматривает , убедитесь, что вы также предоставили их. Это также довольно забавно и может не работать на версиях python, отличных от cpython.

3 голосов
/ 17 июля 2009

Почему бы вам просто не добавить аргумент к f() и передать ссылку на g()?

def g():
    pass

def f(func):
    func()

f(g)
3 голосов
/ 17 июля 2009

Поскольку функция не вызывается, у нее пока нет «локального» стекового фрейма. Самое простое решение - использовать глобальный контекст:

handler = None
def f():
    handler()

def g(): pass

handler = g

Или вы можете установить g для объекта функции:

f.g = g

Но я не уверен, как вы можете получить объект функции из самой функции. Если бы это был метод, вы бы использовали self.

2 голосов
/ 17 июля 2009

Полагаю, вы хотите это сделать, потому что функция f определяется не вами, а каким-то другим модулем. Итак, вы хотите изменить работу f (). В частности, вы хотите изменить то, что вызывается, когда вызывается g.

Итак, я предлагаю это:

import thirdpartypackage

def mynewg():
   pass

thirdpartypackage.g = mynewg

Это изменит глобальный g для модуля thirdpartypackage. Поэтому, когда теперь вызывается thirdpartypackage.f (), она будет вызывать mynewg () вместо g ().

Если это не решит проблему, возможно, g () действительно импортирован из функции f () или чего-то еще. Тогда решение таково:

import thirdpartypackage

def mynewg():
   pass

deg mynewf():
   mynewg()

thirdpartypackage.f = mynewf

То есть вы полностью переопределяете f () модифицированной версией, которая делает то, что вам нужно.

2 голосов
/ 17 июля 2009

Я думаю, вы могли бы решить проблему, решая ее с совершенно другой точки. Функции объектные, со своими словарями; следовательно, вы можете добавить g к f и использовать его:

def g():
   print "g"

def f():
    f.g()

f.g = g
1 голос
/ 17 июля 2009

Функция, которая не выполняется, не имеет локальных элементов; локальный контекст создается при запуске функции и уничтожается при ее выходе, поэтому нет «локального пространства имен» для изменения извне функции.

Вы можете сделать что-то вроде этого, хотя:

def f():
    g = [1]
    def func():
        print g[0]
    return func, g

f, val = f()
f()
val[0] = 2
f()

Используется массив для имитации ссылки.

0 голосов
/ 05 января 2012

Это похоже на работу

def add_to_locals(l):
    l['newlocal'] = 1

add_to_locals(locals())
assert newlocal
...