[python] Python 2.x의 nonlocal 키워드

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



답변

내부 함수는 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


답변

다음 솔루션은 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)


답변

사전보다는 로컬이 아닌 클래스 가 덜 복잡합니다 . @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


답변

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

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


답변

어떤 이유로 든 여기에 대한 답변이 바람직하지 않은 경우 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.의 답변에서와 같이 한 호출에서 다른 호출로 기억됩니다.


답변

다음은 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에서 모두 작동합니다.


답변

파이썬의 범위 지정 규칙에는 사마귀가 있습니다. 할당은 변수를 즉시 둘러싸는 함수 범위에 로컬로 만듭니다. 전역 변수의 경우 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 결합자를 사용해야합니다.