Можно ли изменить переменную в python, которая находится во внешней, но не глобальной области видимости? - PullRequest
79 голосов
/ 09 декабря 2011

Дан следующий код:

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(), но как его изменить?

Ответы [ 8 ]

71 голосов
/ 09 декабря 2011

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]
16 голосов
/ 11 февраля 2014

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

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
8 голосов
/ 09 декабря 2011

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

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

# The output is '2'

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

4 голосов
/ 09 декабря 2011

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

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

2 голосов
/ 02 сентября 2014

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

def outer():
    a = [1]
    def inner(a=a):
        a[0] += 1
    inner()
    return a[0]
1 голос
/ 09 декабря 2011

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

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

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

A()
0 голосов
/ 09 декабря 2011

Я не знаю, существует ли атрибут функции, который дает __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

У решения Седрика Жюльена есть недостаток:

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

0 голосов
/ 09 декабря 2011

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

def A():
    global b
    b = 1

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

    B()
A()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...