함수 내에서 클래스를 만들고 포함하는 함수의 범위에 정의 된 함수에 액세스
편집 :
이 질문의 하단에서 전체 답변을 참조하십시오.
tl; dr 대답 : Python에는 정적으로 중첩 된 범위가 있습니다. 정적 양태는 자명하지 않은 결과를 산출 암시 변수 선언과 상호 작용할 수있다.
(이것은 언어의 일반적으로 동적 인 특성 때문에 특히 놀랍습니다.)
나는 Python의 범위 지정 규칙에 대해 꽤 잘 다루고 있다고 생각했지만이 문제로 인해 완전히 지쳐 있었고 내 google-fu가 실패했습니다 (놀랍지 않습니다-질문 제목을보십시오.).
예상대로 작동하는 몇 가지 예제부터 시작하겠습니다. 그러나 육즙이 많은 부분은 예제 4로 건너 뛰어도됩니다.
예 1.
>>> x = 3
>>> class MyClass(object):
... x = x
...
>>> MyClass.x
3
간단합니다. 클래스 정의 중에 외부 (이 경우 전역) 범위에 정의 된 변수에 액세스 할 수 있습니다.
예 2.
>>> def mymethod(self):
... return self.x
...
>>> x = 3
>>> class MyClass(object):
... x = x
... mymethod = mymethod
...
>>> MyClass().mymethod()
3
다시 말하지만 ( 왜이 일을하고 싶어하는지는 무시하고 ) 여기서 예상치 못한 것은 없습니다. 우리는 외부 범위의 함수에 액세스 할 수 있습니다.
참고 : Frédéric이 아래에서 지적했듯이이 기능은 작동하지 않는 것 같습니다. 대신 예제 5 (및 그 이상)를 참조하십시오.
예 3.
>>> def myfunc():
... x = 3
... class MyClass(object):
... x = x
... return MyClass
...
>>> myfunc().x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in myfunc
File "<stdin>", line 4, in MyClass
NameError: name 'x' is not defined
이것은 본질적으로 예제 1과 동일합니다. 우리는 클래스 정의 내에서 외부 범위에 액세스합니다. 이번에는 범위가 전역 적이 지 않습니다 myfunc()
.
편집 5 : 마찬가지로 @ user3022222 아래 지적 , 나는 내 원래의 게시물이 예를 망친. 함수 (이 클래스 정의와 같은 다른 코드 블록이 아님) 만 둘러싸는 범위의 변수에 액세스 할 수 있기 때문에 이것이 실패한다고 생각합니다. 비 기능 코드 블록의 경우 로컬, 글로벌 및 내장 변수 만 액세스 할 수 있습니다. 이 질문 에 더 자세한 설명이 있습니다.
하나 더:
예 4.
>>> def my_defining_func():
... def mymethod(self):
... return self.y
... class MyClass(object):
... mymethod = mymethod
... y = 3
... return MyClass
...
>>> my_defining_func()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in my_defining_func
File "<stdin>", line 5, in MyClass
NameError: name 'mymethod' is not defined
음 ... 실례합니다?
이것이 예제 2와 다른 점은 무엇입니까?
나는 완전히 당황합니다. 저를 분류 해주세요. 감사!
추신 : 이것이 내 이해의 문제가 아니라는 점에서 나는 이것을 Python 2.5.2 및 Python 2.6.2에서 시도했습니다. 불행히도 내가 지금 접근 할 수있는 모든 것이지만 둘 다 동일한 동작을 나타냅니다.
http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces 에 따라 편집 : 실행 중 언제든지 네임 스페이스에 직접 액세스 할 수있는 중첩 된 범위가 세 개 이상 있습니다.
- 먼저 검색되는 가장 안쪽 범위에는 로컬 이름이 포함됩니다.
- 가장 가까운 둘러싸는 범위로 시작하여 검색되는 모든 둘러싸는 함수의 범위에는 로컬이 아닌 이름이 포함되지만 글로벌 이름도 포함되지 않습니다.
- 다음에서 마지막 범위에는 현재 모듈의 전역 이름이 포함됩니다.
- 가장 바깥 쪽 범위 (마지막으로 검색 됨)는 내장 이름을 포함하는 네임 스페이스입니다.
# 4. 두 번째에 대한 반례 인 것 같습니다.
편집 2
예 5.
>>> def fun1():
... x = 3
... def fun2():
... print x
... return fun2
...
>>> fun1()()
3
편집 3
@ Frédéric이 지적했듯이 외부 범위에있는 것과 동일한 이름의 변수에 대한 할당은 외부 변수를 "마스킹"하여 할당이 작동하지 못하게하는 것처럼 보입니다.
따라서이 수정 된 버전의 예제 4가 작동합니다.
def my_defining_func():
def mymethod_outer(self):
return self.y
class MyClass(object):
mymethod = mymethod_outer
y = 3
return MyClass
my_defining_func()
그러나 이것은 그렇지 않습니다.
def my_defining_func():
def mymethod(self):
return self.y
class MyClass(object):
mymethod_temp = mymethod
mymethod = mymethod_temp
y = 3
return MyClass
my_defining_func()
이 마스킹이 발생하는 이유를 아직 완전히 이해하지 못합니다. 할당이 발생할 때 이름 바인딩이 발생하지 않아야합니까?
이 예제는 최소한 몇 가지 힌트 (및 더 유용한 오류 메시지)를 제공합니다.
>>> def my_defining_func():
... x = 3
... def my_inner_func():
... x = x
... return x
... return my_inner_func
...
>>> my_defining_func()()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in my_inner_func
UnboundLocalError: local variable 'x' referenced before assignment
>>> my_defining_func()
<function my_inner_func at 0xb755e6f4>
따라서 로컬 변수는 함수 생성 (성공)시 정의되어 로컬 이름이 "예약"되어 함수가 호출 될 때 외부 범위 이름을 마스킹하는 것처럼 보입니다.
흥미 롭군.
답변에 대해 Frédéric에게 감사드립니다!
참고로, 파이썬 문서에서 :
범위는 텍스트로 결정된다는 점을 인식하는 것이 중요합니다. 모듈에 정의 된 함수의 전역 범위는 함수가 호출되는 위치 또는 별칭에 관계없이 해당 모듈의 네임 스페이스입니다. 반면에 이름에 대한 실제 검색은 런타임에 동적으로 수행됩니다. 그러나 언어 정의는 "컴파일"시간에 정적 이름 확인으로 발전하고 있으므로 동적 이름 확인에 의존하지 마십시오! (사실 지역 변수는 이미 정적으로 결정되어 있습니다.)
편집 4
진짜 대답
이 겉보기에 혼란스러워 보이는 동작은 PEP 227에 정의 된대로 Python의 정적으로 중첩 된 범위로 인해 발생합니다 . 실제로 PEP 3104 와는 아무 관련이 없습니다 .
PEP 227에서 :
이름 확인 규칙은 일반적으로 정적으로 범위가 지정된 언어 [...] [제외] 변수가 선언되지 않습니다. 이름 바인딩 작업이 함수의 어디에서나 발생하면 해당 이름은 함수에 대해 로컬로 처리되고 모든 참조는 로컬 바인딩을 참조합니다. 이름이 바인딩되기 전에 참조가 발생하면 NameError가 발생합니다.
[...]
Tim Peters의 예는 선언이 없을 때 중첩 된 범위의 잠재적 인 함정을 보여줍니다.
i = 6 def f(x): def g(): print i # ... # skip to the next page # ... for i in x: # ah, i *is* local to f, so this is what g sees pass g()
g () 호출은 for 루프에 의해 f ()에 바인딩 된 변수 i를 참조합니다. 루프가 실행되기 전에 g ()가 호출되면 NameError가 발생합니다.
Tim 예제의 두 가지 간단한 버전을 실행 해 보겠습니다.
>>> i = 6
>>> def f(x):
... def g():
... print i
... # ...
... # later
... # ...
... i = x
... g()
...
>>> f(3)
3
when g()
doesn't find i
in its inner scope, it dynamically searches outwards, finding the i
in f
's scope, which has been bound to 3
through the i = x
assignment.
But changing the order the final two statements in f
causes an error:
>>> i = 6
>>> def f(x):
... def g():
... print i
... # ...
... # later
... # ...
... g()
... i = x # Note: I've swapped places
...
>>> f(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in f
File "<stdin>", line 3, in g
NameError: free variable 'i' referenced before assignment in enclosing scope
Remembering that PEP 227 said "The name resolution rules are typical for statically scoped languages", lets look at the (semi-)equivalent C version offer:
// nested.c
#include <stdio.h>
int i = 6;
void f(int x){
int i; // <--- implicit in the python code above
void g(){
printf("%d\n",i);
}
g();
i = x;
g();
}
int main(void){
f(3);
}
compile and run:
$ gcc nested.c -o nested
$ ./nested
134520820
3
So while C will happily use an unbound variable (using whatever happens to have been stored there before: 134520820, in this case), Python (thankfully) refuses.
As an interesting side-note, statically nested scopes enable what Alex Martelli has called "the single most important optimization the Python compiler does: a function's local variables are not kept in a dict, they're in a tight vector of values, and each local variable access uses the index in that vector, not a name lookup."
That's an artifact of Python's name resolution rules:
you only have access to the global and the local scopes, but not to the scopes in-between, e.g. not to your immediate outer scope.
EDIT: The above was poorly worded, you do have access to the variables defined in outer scopes, but by doing x = x
or mymethod = mymethod
from a non-global namespace, you're actually masking the outer variable with the one you're defining locally.
In example 2, your immediate outer scope is the global scope, so MyClass
can see mymethod
, but in example 4 your immediate outer scope is my_defining_func()
, so it can't, because the outer definition of mymethod
is already masked by its local definition.
See PEP 3104 for more details about nonlocal name resolution.
Also note that, for the reasons explained above, I can't get example 3 to run under either Python 2.6.5 or 3.1.2:
>>> def myfunc():
... x = 3
... class MyClass(object):
... x = x
... return MyClass
...
>>> myfunc().x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in myfunc
File "<stdin>", line 4, in MyClass
NameError: name 'x' is not defined
But the following would work:
>>> def myfunc():
... x = 3
... class MyClass(object):
... y = x
... return MyClass
...
>>> myfunc().y
3
This post is a few years old, but it is among the rare ones to discuss the important problem of scope and static binding in Python. However, there is an important misunderstanding of the author for example 3 that might confuse readers. (do not take as granted that the other ones are all correct, it is just that I only looked at the issues raised by example 3 in details). Let me clarify what happened.
In example 3
def myfunc():
x = 3
class MyClass(object):
x = x
return MyClass
>>> myfunc().x
must return an error, unlike what the author of the post said. I believe that he missed the error because in example 1 x
was assigned to 3
in the global scope. Thus a wrong understanding of what happened.
The explanation is extensively described in this post How references to variables are resolved in Python
'code' 카테고리의 다른 글
런타임에 사용자 정의 파일에 기록하도록 log4j 구성 (0) | 2020.12.03 |
---|---|
제곱근 함수는 어떻게 구현됩니까? (0) | 2020.12.02 |
파이썬에서 희소 3D 행렬 / 배열? (0) | 2020.12.02 |
하위 프로세스를 사용할 때 Python에서 티 동작을 복제하는 방법은 무엇입니까? (0) | 2020.12.02 |
range (len (a))가 필요합니까? (0) | 2020.12.02 |