Zamknięcie Pythona: zapis do zmiennej w zakresie rodzica

Mam następujący kod wewnątrz funkcji:

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)

Dodawanie elementów do stored_blocks działa dobrze, ale nie mogę zwiększyć num_converted w drugiej podfunkcji:

UnboundLocalError: zmienna lokalna 'num_converted' odwołująca się przed przypisaniem

Mógłbym użyć global ale zmienne globalne są brzydkie i naprawdę nie potrzebuję tej zmiennej, aby w ogóle była globalna.

Więc jestem ciekaw jak mogę napisać do zmiennej w zakresie funkcji rodzica. nonlocal num_converted prawdopodobnie zrobiłby zadanie, ale potrzebuję rozwiązania, które działa z Pythonem 2.x.

Author: ThiefMaster, 2011-01-31

6 answers

Problem: dzieje się tak dlatego, że reguły zakresów Pythona są obłąkane. Obecność += operatora przypisania oznacza cel num_converted jako lokalny do zakresu funkcji zamykającej, a w Pythonie 2 nie ma ścieżki dźwiękowej.x, aby uzyskać dostęp tylko do jednego poziomu zasięgu stamtąd. Tylko słowo kluczowe global może podnieść odwołania do zmiennych z bieżącego zakresu i zabierze cię prosto na górę.

Fix: zamienia num_converted w tablicę jednoelementową.

num_converted = [0]
def convert_variables(m):
    name = m.group(1)
    num_converted[0] += 1
    return '<%%= %s %%>' % name
 81
Author: Marcelo Cantos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-09 09:00:01

(Zobacz poniżej edytowaną odpowiedź)

Możesz użyć czegoś w rodzaju:

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

convert_variables.num_converted = 0

W ten sposób, num_converted działa jako "statyczna" zmienna typu C metody convert_variable


(edytowane)

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

W ten sposób nie musisz inicjalizować licznika w głównej procedurze.

 28
Author: PabloG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-03 16:51:37

Użycie słowa kluczowego global jest w porządku. Jeśli napiszesz:

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

... num_converted nie staje się "zmienną globalną" (tzn. nie staje się widoczna w żadnych innych nieoczekiwanych miejscach), to po prostu oznacza, że można ją modyfikować wewnątrz convert_variables. Wydaje się, że dokładnie tego chcesz.

Mówiąc Inaczej, num_convertedjest już zmienną globalną. Składnia global num_converted mówi Pythonowi: "wewnątrz tej funkcji nie twórz lokalnej zmiennej num_converted, zamiast tego używaj istniejącej globalnej.

 9
Author: Emile,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-31 13:47:45

A co z użyciem instancji klasy do utrzymywania stanu? Tworzysz instancję klasy i przekazujesz metody instancji do subs, a te funkcje będą miały odniesienie do self...

 6
Author: Seb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-31 13:46:26

Mam kilka uwag.

Po pierwsze, jedna aplikacja dla takich zagnieżdżonych funkcji pojawia się podczas radzenia sobie z nieprzetworzonymi wywołaniami zwrotnymi, jak są używane w bibliotekach takich jak xml.parsery.expat. (To, że autorzy biblioteki wybrali takie podejście może być oburzające, ale.. jednak istnieją powody, aby z niego korzystać.)

Po Drugie: wewnątrz klasy są znacznie ładniejsze alternatywy dla tablicy (num_converted[0]). Przypuszczam, że o tym mówił Sebastjan.

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

It ' s still odd enough aby zasłużyć na komentarz w kodzie... Ale zmienna jest co najmniej lokalna dla klasy.

 6
Author: Steve White,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-27 13:26:33

Zmodyfikowano z: https://stackoverflow.com/a/40690954/819544

Możesz wykorzystać moduł inspect, Aby uzyskać dostęp do dict globals calling scope i zapisać do niego. Oznacza to, że ta sztuczka może być nawet wykorzystana, aby uzyskać dostęp do zakresu wywołania z zagnieżdżonej funkcji zdefiniowanej w zaimportowanej podmodule.

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

Baw się argumentem scope_level w razie potrzeby. Ustawienie scope_level=1 działa dla funkcji zdefiniowanej w podmodule, scope_level=2 dla funkcji wewnętrznej zdefiniowanej w dekoratorze w submodule itp.

NB: tylko dlatego, że ty możesz to zrobić, nie znaczy, że powinieneś.

 0
Author: David Marx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-02 00:00:43