Python 2.x의 nonlocal 키워드


117

Python 2.6에서 클로저를 구현하려고하는데 비 지역 변수에 액세스해야하지만이 키워드는 Python 2.x에서 사용할 수없는 것 같습니다. 이 파이썬 버전에서 클로저의 비 지역 변수에 어떻게 액세스해야합니까?

답변:


125

내부 함수는 2.x에서 비 지역 변수를 읽을 수 있으며 리 바인드 할 수 없습니다 . 이것은 성가신 일이지만 해결할 수 있습니다. 사전을 만들고 그 안에 요소로 데이터를 저장하십시오. 내부 함수는 비 지역 변수가 참조하는 객체를 변경 하는 것을 금지하지 않습니다 .

Wikipedia의 예제를 사용하려면 :

def outer():
    d = {'y' : 0}
    def inner():
        d['y'] += 1
        return d['y']
    return inner

f = outer()
print(f(), f(), f()) #prints 1 2 3

4
사전에서 값을 수정할 수있는 이유는 무엇입니까?
coelhudo

9
@coelhudo 비 지역 변수를 수정할 있기 때문 입니다. 그러나 비 지역 변수에 할당 할 수 없습니다. 예를 들어 UnboundLocalError :를 발생 def inner(): print d; d = {'y': 1}시킵니다. 여기서는 print d외부 d를 읽고 d내부 범위에 비 지역 변수를 만듭니다 .
suzanshakya

21
이 답변에 감사드립니다. 하지만 용어를 개선 할 수 있다고 생각합니다. "읽을 수 있음, 변경할 수 없음"대신 "참조 할 수 있음, 할당 할 수 없음". 로컬이 아닌 범위에서 개체의 내용을 변경할 수 있지만 참조되는 개체는 변경할 수 없습니다.
metamatt 2013

3
또는 사전 대신 임의의 클래스를 사용하고 개체를 인스턴스화 할 수 있습니다. 코드를 리터럴 (사전 키)로 넘치지 않고 메서드를 사용할 수 있기 때문에 훨씬 더 우아하고 읽기 쉽습니다.
Alois Mahdal 2013-08-15

6
파이썬의 경우 "바인딩"또는 "리 바인드"라는 동사를 사용하고 "변수"보다는 명사 "이름"을 사용하는 것이 가장 좋습니다. Python 2.x에서는 개체를 닫을 수 있지만 원래 범위에서 이름을 다시 바인딩 할 수는 없습니다. C와 같은 언어에서 변수를 선언하면 일부 저장소 (정적 저장소 또는 스택의 임시 저장소)가 예약됩니다. Python에서 표현식은 X = 1단순히 이름 X을 특정 객체 ( int값이있는 1)에 바인딩합니다 . X = 1; Y = X두 이름을 동일한 객체에 바인딩합니다. 어쨌든 일부 객체는 변경 가능 하며 값을 변경할 수 있습니다.
steveha

37

다음 솔루션은 Elias Zamaria답변에서 영감을 얻었 지만 그 답변과는 반대로 외부 함수의 여러 호출을 올바르게 처리합니다. "변수" inner.y는의 현재 호출에 로컬입니다 outer. 그것은 금지되어 있기 때문에 변수가 아니라 객체 속성 (객체는 함수 inner자체)입니다. 이것은 매우 추악하지만 (속성은 inner함수가 정의 된 후에 만 생성 될 수 있다는 점에 유의하십시오 ) 효과적입니다.

def outer():
    def inner():
        inner.y += 1
        return inner.y
    inner.y = 0
    return inner

f = outer()
g = outer()
print(f(), f(), g(), f(), g()) #prints (1, 2, 1, 3, 2)

추악 할 필요는 없습니다. inner.y를 사용하는 대신 outer.y를 사용하십시오. def inner 앞에 outer.y = 0을 정의 할 수 있습니다.
jgomo3 dec.

1
좋아, 내 의견이 잘못되었습니다. Elias Zamaria는 outer.y 솔루션도 구현했습니다. 그러나 Nathaniel [1]이 언급 한 것처럼,주의해야합니다. 이 답변이 해결책으로 홍보되어야한다고 생각하지만 outer.y 해결책과주의 사항에 대해 유의하십시오. [1] stackoverflow.com/questions/3190706/…
jgomo3

로컬이 아닌 변경 가능 상태를 공유하는 하나 이상의 내부 함수를 원하는 경우 이것은 추악합니다. 공유 카운터를 증가 및 감소시키는 외부 inc()에서 dec()반환 된 a 와 a를 말합니다 . 그런 다음 현재 카운터 값을 첨부 할 함수를 결정하고 다른 함수에서 해당 함수를 참조해야합니다. 다소 이상하고 비대칭으로 보입니다. 예를 dec()들어 inc.value -= 1.
BlackJack

33

사전보다는 로컬이 아닌 클래스 가 덜 복잡합니다 . @ChrisB의 예제 수정 :

def outer():
    class context:
        y = 0
    def inner():
        context.y += 1
        return context.y
    return inner

그때

f = outer()
assert f() == 1
assert f() == 2
assert f() == 3
assert f() == 4

각 outer () 호출은 context라는 새롭고 고유 한 클래스를 만듭니다 (단순히 새 인스턴스가 아님). 따라서 공유 컨텍스트에 대한 @Nathaniel의주의를 피 합니다.

g = outer()
assert g() == 1
assert g() == 2

assert f() == 5

좋은! 이것은 실제로 사전보다 더 우아하고 읽기 쉽습니다.
Vincenzo 2015

여기서는 일반적으로 슬롯을 사용하는 것이 좋습니다. 오타로부터 당신을 보호합니다.
DerWeh

@DerWeh 흥미 롭습니다, 나는 그것을 들어 본 적이 없습니다 . 어떤 수업이라도 똑같이 말할 수 있습니까? 나는 오타에 당황하는 것을 싫어한다.
Bob Stein

@BobStein 죄송합니다. 무슨 말인지 잘 모르겠습니다. 그러나 __slots__ = ()클래스를 사용하는 대신 객체를 추가 하고 생성하면 예 context.z = 3를 들어 AttributeError. 슬롯을 정의하지 않는 클래스에서 상속하지 않는 한 모든 클래스가 가능합니다.
DerWeh

@DerWeh 오타를 잡기 위해 슬롯 을 사용하는 것에 대해 들어 본 적이 없습니다 . 클래스 변수가 아닌 클래스 인스턴스에서만 도움이 될 것입니다. pyfiddle에서 작동하는 예제를 만들고 싶을 수도 있습니다. 최소한 두 개의 추가 라인이 필요합니다. 더 복잡하지 않고 어떻게 할 것인지 상상할 수 없습니다.
Bob Stein

14

여기서 핵심은 "액세스"라는 뜻입니다. 클로저 범위 밖의 변수를 읽는 데 문제가 없어야합니다. 예 :

x = 3
def outer():
    def inner():
        print x
    inner()
outer()

예상대로 작동해야합니다 (인쇄 3). 그러나 x 값을 재정의하는 것은 작동하지 않습니다. 예 :

x = 3
def outer():
    def inner():
        x = 5
    inner()
outer()
print x

여전히 인쇄됩니다. 3. PEP-3104에 대한 나의 이해에서 이것은 비 로컬 키워드가 다루려는 의미입니다. PEP에서 언급했듯이 클래스를 사용하여 동일한 작업을 수행 할 수 있습니다 (지저분 함).

class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
    def inner():
        ns.x = 5
    inner()
outer()
print ns.x

대신 클래스를 생성하고 인스턴스의 하나는 단순히 함수를 만들 수 있습니다 def ns(): pass다음에 ns.x = 3. 예쁘지는 않지만 내 눈에는 약간 덜 못 생겼습니다.
davidchambers 2011 년

2
반대표에 대한 특별한 이유가 있습니까? 나는 나의 가장 우아한 해결책이 아니라 인정하지만 ... 작동
ig0774

2
어때 class Namespace: x = 3?
Feuermurmel 2014

3
클로저에서 참조 대신 전역 참조를 생성하기 때문에이 방법이 비 로컬을 시뮬레이션한다고 생각하지 않습니다.
CarmeloS 2014

1
저는 @CarmeloS에 동의합니다. PEP-3104가 제안한 것을 파이썬 2에서 비슷한 것을 수행하는 방법으로 하지 않는 마지막 코드 블록은 ns전역 객체이므로 맨 끝에 ns.x있는 print명령문 의 모듈 수준에서 참조 할 수 있습니다. .
martineau 2015 년

13

어떤 이유로 든 여기에 대한 답변이 바람직하지 않은 경우 Python 2에서 비 지역 변수를 구현하는 또 다른 방법이 있습니다.

def outer():
    outer.y = 0
    def inner():
        outer.y += 1
        return outer.y
    return inner

f = outer()
print(f(), f(), f()) #prints 1 2 3

변수의 할당 문에서 함수의 이름을 사용하는 것은 중복되지만 변수를 사전에 넣는 것보다 더 간단하고 깔끔해 보입니다. 값은 Chris B.의 답변에서와 같이 한 호출에서 다른 호출로 기억됩니다.


19
주의하십시오 :이 방법으로 구현할 때 수행 f = outer()한 다음 나중에 수행 g = outer()하면 f의 카운터가 재설정됩니다. 이는 둘 다 자체 독립 변수가 아닌 단일 outer.y 변수를 공유하기 때문 입니다. 이 코드는 Chris B의 대답보다 심미적으로 더 좋아 보이지만 outer두 번 이상 호출하려는 경우 그의 방식이 어휘 범위를 에뮬레이트하는 유일한 방법 인 것 같습니다 .
Nathaniel 2013 년

@Nathaniel : 내가 이것을 올바르게 이해하는지 보자. 에 대한 할당 outer.y은 함수 호출 (인스턴스)에 대한 로컬 항목을 포함하지 않지만 둘러싸는 범위 outer()의 이름 outer에 바인딩 된 함수 개체의 속성에 할당합니다 . 그러므로 사람은 동일하게 서면으로 사용할 수도 , 다른 대신의 이름 이 그 범위에 구속되는 것으로 알려져있다 제공. 이 올바른지? outer.youter
Marc van Leeuwen 2013-04-16

1
"해당 범위에 바인딩"한 후에는 유형이 속성 설정을 허용하는 개체 (예 : 함수 또는 클래스 인스턴스)에 대한 수정을 말 했어야합니다. 또한이 범위는 실제로 우리가 원하는 범위보다 멀기 때문에 다음과 같은 매우 추악한 솔루션을 제안하지 않을 것입니다 outer.y. 이름 을 사용하는 대신 inner.y( 우리가 원하는 범위 인 innercall 내부에 바인딩되어 있기 때문에 outer()) 내부 정의 inner.y = 0 이후 의 초기화 (속성이 생성 될 때 객체가 존재해야하기 때문에), 물론 이전에는 ? return inner
Marc van Leeuwen 2013-04-16

@MarcvanLeeuwen 귀하의 의견이 Elias의 inner.y로 솔루션에 영감을 준 것 같습니다. 당신의 좋은 생각.
Ankur Agarwal 2015

전역 변수에 액세스하고 업데이트하는 것은 대부분의 사람들이 클로저 캡처를 변경할 때하려는 작업이 아닐 것입니다.
binki 2015

12

다음은 Alois Mahdal이 다른 답변 에 대한 의견 에서 만든 제안에서 영감을 얻은 것입니다 .

class Nonlocal(object):
    """ Helper to implement nonlocal names in Python 2.x """
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)


def outer():
    nl = Nonlocal(y=0)
    def inner():
        nl.y += 1
        return nl.y
    return inner

f = outer()
print(f(), f(), f()) # -> (1 2 3)

최신 정보

최근에 이것을 되돌아 본 후, 데코레이터와 같은 방식에 놀랐습니다. 하나로 구현하면 더 일반적이고 유용 할 것이라는 생각이 떠 올랐습니다 (하지만 그렇게하면 가독성이 어느 정도 저하 될 수 있습니다).

# Implemented as a decorator.

class Nonlocal(object):
    """ Decorator class to help implement nonlocal names in Python 2.x """
    def __init__(self, **kwargs):
        self._vars = kwargs

    def __call__(self, func):
        for k, v in self._vars.items():
            setattr(func, k, v)
        return func


@Nonlocal(y=0)
def outer():
    def inner():
        outer.y += 1
        return outer.y
    return inner


f = outer()
print(f(), f(), f()) # -> (1 2 3)

두 버전 모두 Python 2와 3에서 모두 작동합니다.


이것은 가독성과 어휘 범위 보존 측면에서 가장 좋은 것 같습니다.
아론 S. 컬 랜드

3

파이썬의 범위 지정 규칙에는 사마귀가 있습니다. 할당은 변수를 즉시 둘러싸는 함수 범위에 로컬로 만듭니다. 전역 변수의 경우 global키워드 로이 문제를 해결할 수 있습니다 .

해결책은 변경 가능한 변수를 포함하지만 할당되지 않은 변수를 통해 자체적으로 참조되는 두 범위간에 공유되는 객체를 도입하는 것입니다.

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

대안은 일부 범위 해커입니다.

def outer(v):
    def inner(varname = 'v', scope = locals()):
        scope[varname] += 1
        return scope[varname]
    return inner

매개 변수 이름을으로 가져 와서 outervarname으로 전달하는 몇 가지 속임수를 알아낼 수 있지만 이름에 의존하지 않고 outerY 결합자를 사용해야합니다.


두 버전 모두이 특정 경우에 작동하지만 nonlocal. 정의 된 시간 locals()outer()s 지역 의 사전을 생성 inner()하지만 해당 사전을 변경해도 in은 변경 되지 않습니다 . 닫힌 오버 변수를 공유하려는 더 많은 내부 함수가 있으면 더 이상 작동하지 않습니다. 발언권 과 그 증가 및 공유 카운터를 감소시킵니다. vouter()inc()dec()
BlackJack

@BlackJack nonlocal은 파이썬 3 기능입니다.
Marcin

네, 알아요. 문제는 일반적으로nonlocal Python 2 에서 Python 3의 효과를 얻는 방법이었습니다 . 당신의 아이디어는 일반적인 경우가 아니라 하나의 내부 기능을 가진 입니다. 예를 들어이 요점 을 살펴보십시오 . 두 내부 함수에는 자체 컨테이너가 있습니다. 다른 답변이 이미 제안했듯이 외부 기능의 범위에 변경 가능한 객체가 필요합니다.
BlackJack

@BlackJack 그것은 질문이 아니지만 귀하의 의견에 감사드립니다.
Marcin

예 그 입니다 문제는. 페이지 상단을보십시오. adinsa 파이썬 2.6이 있고없는 폐쇄에 로컬이 아닌 변수에 액세스해야 nonlocal파이썬 3에서 소개 키워드
블랙 잭

3

이를 수행하는 또 다른 방법 (너무 장황하지만) :

import ctypes

def outer():
    y = 0
    def inner():
        ctypes.pythonapi.PyCell_Set(id(inner.func_closure[0]), id(y + 1))
        return y
    return inner

x = outer()
x()
>> 1
x()
>> 2
y = outer()
y()
>> 1
x()
>> 3

1
실제로 나는 사전을 사용하여 솔루션을 선호하지만,이 하나 :) 멋지다
에셀 페르난데스

0

위의 Martineau 우아한 솔루션을 실용적이고 다소 덜 우아한 사용 사례로 확장하면 다음과 같습니다.

class nonlocals(object):
""" Helper to implement nonlocal names in Python 2.x.
Usage example:
def outer():
     nl = nonlocals( n=0, m=1 )
     def inner():
         nl.n += 1
     inner() # will increment nl.n

or...
    sums = nonlocals( { k:v for k,v in locals().iteritems() if k.startswith('tot_') } )
"""
def __init__(self, **kwargs):
    self.__dict__.update(kwargs)

def __init__(self, a_dict):
    self.__dict__.update(a_dict)

-3

전역 변수 사용

def outer():
    global y # import1
    y = 0
    def inner():
        global y # import2 - requires import1
        y += 1
        return y
    return inner

f = outer()
print(f(), f(), f()) #prints 1 2 3

개인적으로 나는 전역 변수를 좋아하지 않습니다. 하지만 내 제안은 https://stackoverflow.com/a/19877437/1083704 답변을 기반으로합니다.

def report():
        class Rank: 
            def __init__(self):
                report.ranks += 1
        rank = Rank()
report.ranks = 0
report()

사용자가 전역 변수를 선언 ranks해야하는 경우 report. 내 개선으로 사용자로부터 함수 변수를 초기화 할 필요가 없습니다.


사전을 사용하는 것이 훨씬 낫습니다. 에서 인스턴스를 참조 inner할 수 있지만 할당 할 수는 없지만 키와 값을 수정할 수 있습니다. 이것은 전역 변수의 사용을 피합니다.
johannestaas
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.