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

다음 코드가 주어집니다.

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()있지만 수정하는 방법은 무엇입니까?



답변

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

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

파이썬 2의 경우 일반적으로 변경 가능한 객체 (목록 또는 사전과 같은)를 사용하고 재 할당하는 대신 값을 변경합니다.

예:

def foo():
    a = []
    def bar():
        a.append(1)
    bar()
    bar()
    print a

foo()

출력 :

[1, 1]


답변

빈 클래스를 사용하여 임시 범위를 유지할 수 있습니다. 그것은 가변적이지만 조금 더 예쁘다.

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


답변

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

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

# The output is '2'

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


답변

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

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


답변

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

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


답변

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

이 특정 문제를 해결하기 위해 파이썬 라이브러리를 만들었습니다. 무의미하에 출시되었으므로 원하는대로 사용하십시오. 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!


답변

난 당신이 생각하지 않아요 해야 이 작업을 수행 할 수. 둘러싸고있는 컨텍스트를 변경할 수있는 함수는 해당 컨텍스트가 함수에 대한 지식없이 작성 될 수 있으므로 위험합니다.

B를 공용 메서드로 만들고 C를 클래스의 개인 메서드로 만들면이를 명시 적으로 만들 수 있습니다 (아마 가장 좋은 방법). 또는 목록과 같은 변경 가능한 유형을 사용하고 명시 적으로 C에 전달합니다.

def A():
    x = [0]
    def B(var): 
        var[0] = 1
    B(x)
    print x

A()