IT박스

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

itboxs 2020. 9. 6. 09:09
반응형

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


다음 코드가 주어집니다.

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"은 현재 범위에 외부 이름을 포함하는 새 이름을 생성하기 때문입니다.


I don't think you should want to do this. Functions that can alter things in their enclosing context are dangerous, as that context may be written without the knowledge of the function.

You could make it explicit, either by making B a public method and C a private method in a class (the best way probably); or by using a mutable type such as a list and passing it explicitly to C:

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

A()

For anyone looking at this much later on a safer but heavier workaround is. Without a need to pass variables as parameters.

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

You can, but you'll have to use the global statment (not a really good solution as always when using global variables, but it works):

def A():
    global b
    b = 1

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

    B()
A()

I don't know if there is an attribute of a function that gives the __dict__ of the outer space of the function when this outer space isn't the global space == the module, which is the case when the function is a nested function, in Python 3.

But in Python 2, as far as I know, there isn't such an attribute.

So the only possibilities to do what you want is:

1) using a mutable object, as said by others

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()

result

b before B() == 1
b == 10
b after B() == 10

.

Nota

The solution of Cédric Julien has a drawback:

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

result

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

The global b after execution of A() has been modified and it may be not whished so

That's the case only if there is an object with identifier b in the global namespace


The short answer that will just work automagically

I created a python library for solving this specific problem. It is released under the unlisence so use it however you wish. You can install it with pip install seapie or check out the home page here https://github.com/hirsimaki-markus/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()

outputs

2

the arguments have following meaning:

  • The first argument is execution scope. 0 would mean local B(), 1 means parent A() and 2 would mean grandparent <module> aka global
  • The second argument is a string or code object you want to execute in the given scope
  • You can also call it without arguments for interactive shell inside your program

The long answer

This is more complicated. Seapie works by editing the frames in call stack using CPython api. CPython is the de facto standard so most people don't have to worry about it.

The magic words you are probably most likely interesed in if you are reading this are the following:

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

The latter will force updates to pass into local scope. local scopes are however optimized differently than global scope so intoducing new objects has some problems when you try to call them directly if they are not initialized in any way. I will copy few ways to circumvent these problems from the github page

  • Assingn, import and define your objects beforehand
  • Assingn placeholder to your objects beforehand
  • Reassign object to itself in main program to update symbol table: x = locals()["x"]
  • Use exec() in main program instead of directly calling to avoid optimization. Instead of calling x do: exec("x")

If you are feeling that using exec() is not something you want to go with you can emulate the behaviour by updating the the true local dictionary (not the one returned by locals()). I will copy an example from 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()

Output:

hack!

참고URL : https://stackoverflow.com/questions/8447947/is-it-possible-to-modify-variable-in-python-that-is-in-outer-but-not-global-sc

반응형