Чи можливо змінити змінну в python, що знаходиться у зовнішній, але не глобальній області застосування?


107

Дано наступний код:

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(), але як це змінити?


Вибачте, звичайно 2,7 :). Для python 3 правила розміщення змінилися.
— grigoryvp

Ви можете до тих пір, поки bне змінюється. Завдання bмаскує зовнішню область.
— JimB

4
Це одне з незручностей Python, яке nonlocalне було підкріплено до 2.x. Це невід'ємна частина підтримки закриття.
— Гленн Мейнард

Відповіді:


97

Python 3.x має nonlocalключове слово . Я думаю, що це робить те, що ти хочеш, але я не впевнений, чи працюєш у python 2 або 3.

Нелокальний оператор змушує перелічені ідентифікатори посилатися на раніше пов'язані змінні в найближчій області, що обгороджує. Це важливо, оскільки поведінка за замовчуванням для прив'язки - це спочатку пошук локального простору імен. Заява дозволяє інкапсульованому коду відновлювати змінні за межами локальної області, крім глобальної (модульної) області.

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

приклад:

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

foo()

Виходи:

[1, 1]

16
Приємний спосіб зробити це class nonlocal: passу зовнішній рамці. Потім nonlocal.xможуть бути призначені у внутрішній області.
— kindall

1
Поки що у мене вже є два поради пітона, які прості, але дуже корисні: ваш другий :) Дякую @kindall!
— swdev

@kindall - це чудовий хак - він мінімально відрізняється від синтаксису Python 3 і набагато читає, ніж обходити об'єкт, що змінюється.
— dimo414

2
@kindall дуже акуратний спасибі купи :) Мабуть, потрібна інша назва, хоча це порушує сумісність вперед. У python 3 це конфлікт ключових слів і призведе до SyntaxError. Можливо NonLocal?
— Адам Террі

або, оскільки це технічно клас Nonlocal,? :-)
— kindall

19

Ви можете використовувати порожній клас, щоб утримувати тимчасовий діапазон. Це як мінливий, але трохи гарніший.

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

Це не дивно, що клас із його полями "видимий" у внутрішній функції, але змінних немає, якщо ви не визначите зовнішню змінну за допомогою "нелокального" ключового слова.
— Celdor

12

Я трохи новачок у Python, але я читав трохи про це. Я вважаю, що найкраще, що ви збираєтеся отримати, - це схожа на обробка Java, яка полягає в загортанні зовнішньої змінної у список.

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

# The output is '2'

Редагувати: Я думаю, це було вірно ще до Python 3. Схоже, nonlocalце ваша відповідь.


4

Ні, ви не можете, принаймні таким чином.

Тому що "встановлена ​​операція" створить нове ім'я в поточному обсязі, яке охоплює зовнішнє.


"які охоплюють зовнішню" Що ви маєте на увазі? Визначення об'єкта з іменем b в вкладеній функції не впливає на об'єкт з тим самим іменем у космічному просторі цієї функції
— eyquem

1
@eyquem, тобто де б не було твердження про призначення, воно введе ім'я у всій нинішній області. Наприклад, зразок коду питання, якщо це: def C (): print (b) b = 2, "b = 2" введе ім'я b у всій області функцій C, тому при print (b) воно буде спробуйте отримати b в локальній області C func, але не у зовнішній, локальний b ще не ініціалізований, тому буде помилка.
— zchenah

1

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

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

1

Коротка відповідь, яка просто працюватиме автоматично

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

user@pc: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>ака
  • Другий аргумент - це об'єкт рядка або коду, який потрібно виконати в заданій області
  • Ви також можете викликати його без аргументів для інтерактивної оболонки всередині вашої програми

Довга відповідь

Це складніше. Seapie працює, редагуючи кадри в стеку викликів за допомогою CPython api. 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()не те, з чим ви хочете піти, ви можете наслідувати поведінку, оновивши справжній локальний словник (не той, який повертається місцевими жителями ()). Я скопію приклад із 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!

0

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

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

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

A()

2
Як можна написати функцію, не знаючи про вкладені функції всередині неї? Вкладені функції та закриття є невід'ємною частиною функції, до якої вони укладені.
— Гленн Мейнард

Вам потрібно знати про інтерфейс функцій, укладених у вашій, але вам не слід знати про те, що відбувається всередині них. Крім того, від вас не можна буде знати, що відбувається у функціях, які вони викликають тощо! Якщо функція модифікує неглобальну чи некласмічну особу, вона зазвичай робить це явним через свій інтерфейс, тобто сприймає її як параметр.
— Sideshow Bob

Звичайно, Python не змушує вас бути таким хорошим, звідси і nonlocalключове слово, але використовувати його потрібно з великою обережністю.
— Sideshow Bob

5
@Bob: Я ніколи не вважав, щоб використання подібних закриттів було взагалі небезпечним, окрім випадків мовних вигадок. Розглядайте місцевих жителів як тимчасовий клас, а локальні функції як методи на уроці, і це не складніше за це. YMMV, я думаю.
— Гленн Мейнард

0

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

def A():
    global b
    b = 1

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

    B()
A()

Дивіться мою відповідь, що пояснює потенційний недолік цього рішення
— eyquem

4
Використання глобальної змінної зовсім інше.
— Гленн Мейнард

0

Я не знаю, чи є атрибут функції, який видає __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

.

Нота

У рішення Седріка Жульєна є недолік:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.