У Python 2, як мені записати змінну в батьківській області?


77

У мене є такий код всередині функції:

stored_blocks = {}
def replace_blocks(m):
    block = m.group(0)
    block_hash = sha1(block)
    stored_blocks[block_hash] = block
    return '{{{%s}}}' % block_hash

num_converted = 0
def convert_variables(m):
    name = m.group(1)
    num_converted += 1
    return '<%%= %s %%>' % name

fixed = MATCH_DECLARE_NEW.sub('', template)
fixed = MATCH_PYTHON_BLOCK.sub(replace_blocks, fixed)
fixed = MATCH_FORMAT.sub(convert_variables, fixed)

Додавання елементів stored_blocksпрацює нормально, але я не можу збільшити num_convertedу другій підфункції:

UnboundLocalError: локальна змінна 'num_converted', на яку посилається перед призначенням

Я міг би використовувати, globalале глобальні змінні потворні, і мені справді не потрібно, щоб ця змінна була глобальною.

Тому мені цікаво, як я можу записати змінну в області батьківської функції. nonlocal num_convertedнапевно зробив би цю роботу, але мені потрібне рішення, яке працює з Python 2.x.


4
На відміну від дещо поширеної думки (судячи з такого роду запитань) def, не єдине ключове слово, яке визначає простір імен: існує також class.
Jochen Ritzel

Відповіді:


87

Проблема: Це тому, що правила визначення обсягу Python дементовані. Наявність +=оператора присвоєння позначає ціль num_convertedяк локальну для області дії, що охоплює, і в Python 2.x не існує звукового способу доступу лише до одного рівня масштабування звідти. Тільки globalключове слово може вивести посилання на змінні з поточного обсягу, і воно перенесе вас прямо до початку.

Виправлення: перетворення num_convertedв одноелементний масив.

num_converted = [0]
def convert_variables(m):
    name = m.group(1)
    num_converted[0] += 1
    return '<%%= %s %%>' % name

7
Чи можете ви пояснити, чому це потрібно? Я б очікував, що код OP працює.
Björn Pollex

36
Тому що правила визначення обсягу Python дементовані. Наявність +=оператора присвоєння позначає ціль num_convertedяк локальну для області дії, що охоплює, і в Python 2.x не існує звукового способу доступу лише до одного рівня масштабування звідти. Тільки globalключове слово може вивести посилання на змінні з поточного обсягу, і воно перенесе вас прямо до початку.
Marcelo Cantos

6
Це не розумно, це насправді досить поганий код. Є класи (див. Коментар до запитання). Ця версія використовує глобальну змінну, якої слід завжди уникати. Невикористання globalне означає, що у вас немає глобальної змінної.
schlamar

15
@schlamar: Змінна, про яку йдеться, не є глобальною в жодному сенсі цього слова. Вступне речення OP передбачає, що весь блок коду, який вони представили, знаходиться всередині функції.
Марсело Кантос,

2
@schlamar: Це багато лісів для однієї змінної. Якщо вам потрібна більша ясність, ніж моя відповідь (насправді?), Я б підтримав scope = {'num_converted':0} … scope['num_converted'] += 1.
Марсело Кантос,

29

(див. нижче відредаговану відповідь)

Ви можете використовувати щось на зразок:

def convert_variables(m):
    name = m.group(1)
    convert_variables.num_converted += 1
    return '<%%= %s %%>' % name

convert_variables.num_converted = 0

Таким чином, num_convertedпрацює як "статична" змінна методу convert_variable на зразок C


(відредаговано)

def convert_variables(m):
    name = m.group(1)
    convert_variables.num_converted = convert_variables.__dict__.get("num_converted", 0) + 1
    return '<%%= %s %%>' % name

Таким чином, вам не потрібно ініціалізувати лічильник в основній процедурі.


3
Правильно. І зауважте, що ви повинні створити атрибут convert_variables.num_converted після визначення функції, хоча це виглядає дивно.
Марк ван Левен

@PabloG найзадовільніша відповідь на це питання, крім нелокальної в 3.x; використання змінного типу [] - це дешеве рішення.
user2290820

9

Використання globalключового слова - це нормально. Якщо ви пишете:

num_converted = 0
def convert_variables(m):
    global num_converted
    name = m.group(1)
    num_converted += 1
    return '<%%= %s %%>' % name

... num_converted не стає "глобальною змінною" (тобто вона не стає видимою в інших несподіваних місцях), це просто означає, що її можна змінити всередині convert_variables. Здається, це саме те, що ви хочете.

Іншими словами, num_convertedце вже глобальна змінна. Все, що global num_convertedробить синтаксис, це сказати Python "всередині цієї функції, не створювати локальну num_convertedзмінну, натомість використовувати існуючу глобальну.


4
globalв 2.x працює майже так само, як nonlocalі в 3.x.
Даніель Роузман

2
"Іншими словами, num_converted - це вже глобальна змінна" - мій код працює всередині функції .., тому він наразі не глобальний.
ThiefMaster

2
Ах, я не звертав уваги на частину "всередині функції", вибачте - у такому випадку список довжини одного списку Марсело може бути кращим (але негарним) рішенням.
Еміль

7

А як щодо використання екземпляра класу для утримання стану? Ви створюєте екземпляр класу та передаєте методи екземпляра до subs, і ці функції мають посилання на self ...


7
Звучить трохи надмірно і схоже на рішення від програміста Java. ; p
ThiefMaster

1
@ThiefMaster Чому це надмірно? Якщо ви хочете отримати доступ до батьківської області, вам слід використовувати клас у Python.
schlamar

2
@schlamar, тому що в будь-якій іншій здоровій мові з підтримкою функцій першого класу (JS, функціональні мови) закриття просто працює.
Дзюгару

6

У мене є кілька зауважень.

По-перше, одна програма для таких вкладених функцій з’являється при роботі з необробленими зворотними викликами, які використовуються в таких бібліотеках, як xml.parsers.expat. (Те, що автори бібліотеки обрали такий підхід, може бути неприємним, але ... все ж є підстави використовувати його.)

По-друге: всередині класу є набагато приємніші альтернативи масиву (num_converted [0]). Я припускаю, що саме про це говорив Себастьян.

class MainClass:
    _num_converted = 0
    def outer_method( self ):
        def convert_variables(m):
            name = m.group(1)
            self._num_converted += 1
            return '<%%= %s %%>' % name

Це все ще досить дивно, щоб заслужити коментар у коді ... Але змінна є принаймні локальною для класу.


Привіт, ласкаво просимо до Stack Overflow - але розміщення "зауваження" як відповіді насправді не те, що ви можете зробити тут. У нас є коментарі щодо цього (однак для їх розміщення потрібен представник - але не публікуйте відповіді лише тому, що у вас недостатньо представників для коментаря)
ThiefMaster

5
Привіт, Ви також ласкаво просимо! Я не розумію вашого зауваження чи кількох термінів, якими ви користуєтесь. А я зайнятий чоловік, просто намагаюся бути корисним!
Steve White

Немає проблем - подивіться на stackoverflow.com/about . Будучи корисним, завжди цінуємо коментар, який розміщуємо, з часом буде видалений незалежно від того, наскільки він хороший.
ThiefMaster

0

Змінено з: https://stackoverflow.com/a/40690954/819544

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

import inspect 

def get_globals(scope_level=0):
    return dict(inspect.getmembers(inspect.stack()[scope_level][0]))["f_globals"]

num_converted = 0
def foobar():
    get_globals(0)['num_converted'] += 1

foobar()
print(num_converted) 
# 1

Пограйте з scope_levelаргументом за потреби. Налаштування scope_level=1працює для функції, визначеної в підмодулі, scope_level=2для внутрішньої функції, визначеної в декораторі в підмодулі тощо.

NB: Те, що ви можете це зробити, не означає, що ви повинні.

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