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


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.