파이썬에서 전역이 아닌 외부 범위에있는 변수를 수정할 수 있습니까?


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 :). 파이썬 3의 경우 범위 지정 규칙이 변경되었습니다.
grigoryvp 2011

b변경할 수있는 한 가능합니다. 에 대한 할당 b은 외부 범위를 마스킹합니다.
JimB 2011

4
nonlocal2.x로 백 포트되지 않은 것은 파이썬의 당혹 스러움 중 하나입니다 . 폐쇄 지원의 본질적인 부분입니다.
Glenn Maynard 2011

답변:


97

Python 3.x에는 nonlocal키워드가 있습니다. 나는 이것이 당신이 원하는 것을한다고 생각하지만 파이썬 2 또는 3을 실행 중인지 확실하지 않습니다.

nonlocal 문은 나열된 식별자가 가장 가까운 둘러싸는 범위에서 이전에 바인딩 된 변수를 참조하도록합니다. 바인딩의 기본 동작은 먼저 로컬 네임 스페이스를 검색하기 때문에 중요합니다. 이 명령문을 사용하면 캡슐화 된 코드가 전역 (모듈) 범위 외에 로컬 범위 외부의 변수를 리 바인드 할 수 있습니다.

파이썬 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 매우 깔끔한 감사합니다 힙 :) 앞으로 호환성을 깨기 때문에 다른 이름이 필요할 것입니다. 파이썬 3에서는 키워드 충돌이며 SyntaxError. 아마도 NonLocal?
Adam Terrey

또는 기술적으로 클래스이기 때문에 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

필드가있는 클래스가 내부 함수에서 "표시"되지만 "nonlocal"키워드로 외부 변수를 정의하지 않는 한 변수는 표시되지 않는 것이 이상합니다.
Celdor

12

저는 Python을 처음 접했지만 이것에 대해 조금 읽었습니다. 나는 당신이 얻을 수있는 최선의 방법이 외부 변수를 목록으로 감싸는 Java 해결 방법과 유사하다고 생각합니다.

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

# The output is '2'

편집 : 나는 이것이 아마도 파이썬 3 이전에 사실이라고 생각합니다 nonlocal. 당신의 대답은 같습니다 .


4

적어도 이런 식으로는 할 수 없습니다.

"set operation"은 현재 범위에 외부 이름을 포함하는 새 이름을 생성하기 때문입니다.


"외부를 덮는" 무슨 뜻이야? 이름을 가진 객체 정의 B를 중첩 함수는이 함수의 외부 공간에서 동일한 이름을 가진 객체에 아무런 영향이 없다
eyquem

1
@eyquem 즉, 할당 문이 어디에 있든 현재 전체 범위에서 이름을 소개합니다. 질문의 샘플 코드와 같이 다음과 같은 경우 : def C () : print (b) b = 2 "b = 2"는 전체 C func 범위에 b라는 이름을 도입하므로 print (b)는 로컬 C func 범위에서 b를 얻으려고 시도하지만 외부 범위가 아닌 경우 로컬 b가 아직 초기화되지 않았으므로 오류가 발생합니다.
zchenah

1

나중에 더 안전하지만 더 무거운 해결 방법을 찾고있는 사람을위한 것입니다. 변수를 매개 변수로 전달할 필요가 없습니다.

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

1

자동으로 작동하는 짧은 대답

이 특정 문제를 해결하기 위해 파이썬 라이브러리를 만들었습니다. 무의미하에 출시되었으므로 원하는대로 사용하십시오. https://github.com/hirsimaki-markus/SEAPIEpip install 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(), 한 수단의 부모 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를 호출하는 대신 : exec ( "x")

using exec()이 함께 가고 싶은 것이 아니라고 생각되면 실제 로컬 사전 을 업데이트하여 동작을 에뮬레이션 할 수 있습니다 (locals ()에서 반환 한 사전이 아님). 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 2011

파이썬은 당연히 그다지 좋은 사람이되도록 강요하지는 않습니다. 따라서 nonlocal키워드는-하지만 신중하게 사용하는 것은 여러분의 몫입니다.
Sideshow Bob 2011

5
@Bob : 이런 클로저를 사용하는 것이 언어상의 문제 외에는 전혀 위험하다는 사실을 발견 한 적이 없습니다. 로컬을 임시 클래스로 생각하고 로컬 함수를 클래스의 메서드로 생각하면 그보다 더 복잡하지 않습니다. YMMV라고 생각합니다.
Glenn Maynard 2011

0

할 수는 있지만 전역 구문 을 사용해야합니다 (전역 변수를 사용할 때 항상 좋은 해결책은 아니지만 작동합니다).

def A():
    global b
    b = 1

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

    B()
A()

이 솔루션의 잠재적 인 단점을 설명하는 내 답변 참조
eyquem

4
전역 변수를 사용하는 것은 완전히 다릅니다.
Glenn Maynard 2011

0

__dict__이 우주 공간이 전역 공간이 아닌 경우 함수의 외부 공간 을 제공하는 함수의 속성이 있는지 모르겠습니다 == 모듈, 함수가 중첩 된 함수 인 경우, 파이썬 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

.

Nota

Cédric Julien의 솔루션에는 다음과 같은 단점이 있습니다.

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

의 실행 후 글로벌 bA() 가 수정되어 표시되지 않을 수 있습니다.

전역 네임 스페이스에 식별자 b 가있는 개체가있는 경우에만 해당됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.