Подтвердить что ты не робот

Возможно ли изменить переменную в python, которая находится во внешней, но не глобальной области?

Учитывая следующий код:

def A() :
    b = 1

    def B() :
        # I can access 'b' from here.
        print( b )
        # But can i modify 'b' here? 'global' and assignment will not work.

    B()
A()

Для кода в функции B() переменная b находится во внешней области, но не в глобальной области. Можно ли изменить переменную b из функции B()? Конечно, я могу прочитать его здесь и print(), но как его изменить?

4b9b3361

Ответ 1

Python 3.x имеет ключевое слово nonlocal. Я думаю, что это делает то, что вы хотите, но я не уверен, что вы используете python 2 или 3.

Нелокальный оператор заставляет перечисленные идентификаторы ссылаться на ранее связанные переменные в ближайшей охватывающей области. Это важно, потому что поведение по умолчанию для привязки - поиск локальное пространство имен. Этот оператор позволяет инкапсулированному коду перегруппировки переменных за пределами локальной области, помимо глобальных (модуль).

Для python 2 я обычно просто использую изменяемый объект (например, список или dict) и изменяю значение вместо переназначения.

Пример:

def foo():
    a = []
    def bar():
        a.append(1)
    bar()
    bar()
    print a

foo()

Выходы:

[1, 1]

Ответ 2

Вы можете использовать пустой класс для хранения временной области. Это похоже на изменчивое, но немного красивое.

def outer_fn():
   class FnScope:
     b = 5
     c = 6
   def inner_fn():
      FnScope.b += 1
      FnScope.c += FnScope.b
   inner_fn()
   inner_fn()
   inner_fn()

Это дает следующий интерактивный вывод:

>>> outer_fn()
8 27
>>> fs = FnScope()
NameError: name 'FnScope' is not defined

Ответ 3

Я немного новичок в Python, но я немного читал об этом. Я считаю, что лучшее, что вы получите, - это обходной путь Java, заключающийся в переносе вашей внешней переменной в список.

def A():
   b = [1]
   def B():
      b[0] = 2
   B()
   print(b[0])

# The output is '2'

Изменение: Я думаю, это было правдой до Python 3. Похоже, nonlocal ваш ответ.

Ответ 4

Нет, вы не можете, по крайней мере, таким образом.

Потому что "операция set" создаст новое имя в текущей области видимости, которое будет охватывать внешнюю область.

Ответ 5

Для тех, кто смотрит на это намного позже на более безопасном, но более тяжелом обходном пути. Без необходимости передавать переменные в качестве параметров.

def outer():
    a = [1]
    def inner(a=a):
        a[0] += 1
    inner()
    return a[0]

Ответ 6

Я не думаю, что вы должны это делать. Функции, которые могут изменять вещи в их окружающем контексте, опасны, поскольку этот контекст может быть написан без знания функции.

Вы можете сделать это явным, либо сделав B открытым методом, либо C - приватным методом в классе (возможно, лучший способ); или с помощью изменяемого типа, такого как список, и передать его явно C:

def A():
    x = [0]
    def B(var): 
        var[0] = 1
    B(x)
    print x

A()

Ответ 7

Вы можете, но вам придется использовать глобальный статус (не очень хорошее решение, как всегда при использовании глобальных переменных, но оно работает):

def A():
    global b
    b = 1

    def B():
      global b
      print( b )
      b = 2

    B()
A()

Ответ 8

Я не знаю, есть ли атрибут функции, которая дает __dict__ внешнего пространства функции, когда это внешнее пространство не является глобальным пространством == модуль, который имеет место, когда функция является вложенной функцией, в Python 3.

Но в Python 2, насколько мне известно, такого атрибута нет.

Таким образом, единственные возможности делать то, что вы хотите:

1) с использованием изменяемого объекта, как говорят другие

2)

def A() :
    b = 1
    print 'b before B() ==', b

    def B() :
        b = 10
        print 'b ==', b
        return b

    b = B()
    print 'b after B() ==', b

A()

результат

b before B() == 1
b == 10
b after B() == 10

,

Nota

Решение Cédric Julien имеет недостаток:

def A() :
    global b # N1
    b = 1
    print '   b in function B before executing C() :', b

    def B() :
        global b # N2
        print '     b in function B before assigning b = 2 :', b
        b = 2
        print '     b in function B after  assigning b = 2 :', b

    B()
    print '   b in function A , after execution of B()', b

b = 450
print 'global b , before execution of A() :', b
A()
print 'global b , after execution of A() :', b

результат

global b , before execution of A() : 450
   b in function B before executing B() : 1
     b in function B before assigning b = 2 : 1
     b in function B after  assigning b = 2 : 2
   b in function A , after execution of B() 2
global b , after execution of A() : 2

Глобальный b после выполнения A() был изменен, и его нельзя

Это случай, только если есть объект с идентификатором b в глобальном пространстве имен

Ответ 9

Короткий ответ, который просто будет работать автоматически

Я создал библиотеку Python для решения этой конкретной проблемы. Это выпущено под неуважением, так что используйте это, как Вы желаете. Вы можете установить его с помощью pip install seapie или проверить домашнюю страницу здесь https://github.com/hirsimaki-markus/SEAPIE

[email protected]:home$ pip install seapie

from seapie import Seapie as seapie
def A():
    b = 1

    def B():
        seapie(1, "b=2")
        print(b)

    B()
A()

выходы

2

аргументы имеют следующее значение:

  • Первый аргумент - область выполнения. 0 будет означать локальный B(), 1 означает родительский A(), а 2 будет означать прародителя <module> aka global
  • Второй аргумент - это строка или кодовый объект, который вы хотите выполнить в заданной области
  • Вы также можете вызывать его без аргументов для интерактивной оболочки внутри вашей программы

Длинный ответ

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

Волшебные слова, которые, вероятно, вас заинтересуют, если вы читаете это, следующие:

frame = sys._getframe(1)          # 1 stands for previous frame
parent_locals = frame.f_locals    # true dictionary of parent locals
parent_globals = frame.f_globals  # true dictionary of parent globals

exec(codeblock, parent_globals, parent_locals)

ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),ctypes.c_int(1))
# the magic value 1 stands for ability to introduce new variables. 0 for update-only

Последнее заставит обновления переходить в локальную область. локальные области, однако, оптимизируются иначе, чем глобальные области, поэтому создание новых объектов имеет некоторые проблемы, когда вы пытаетесь вызывать их напрямую, если они никак не инициализируются. Я скопирую несколько способов обойти эти проблемы со страницы github

  • Сбор, импорт и определение ваших объектов заранее
  • Предварительно присваивать заполнители вашим объектам
  • Переназначить объект в основной программе для обновления таблицы символов: x = locals() ["x"]
  • Используйте exec() в основной программе вместо прямого вызова, чтобы избежать оптимизации. Вместо вызова x do: exec ("x")

Если вы чувствуете, что использование exec() - это не то, что вы хотите использовать, вы можете эмулируйте поведение, обновляя истинный локальный словарь (а не тот, который возвращается locals()). Я скопирую пример из https://faster-cpython.readthedocs.io/mutable.html

import sys
import ctypes

def hack():
    # Get the frame object of the caller
    frame = sys._getframe(1)
    frame.f_locals['x'] = "hack!"
    # Force an update of locals array from locals dict
    ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),
                                          ctypes.c_int(0))

def func():
    x = 1
    hack()
    print(x)

func()

Выход:

hack!