[python] 목록 이해는 이해 범위 이후에도 이름을 리 바인드합니다. 이게 옳은 거니?

Comprehensions는 범위 지정과 예상치 못한 상호 작용을합니다. 이것이 예상되는 동작입니까?

방법이 있습니다.

def leave_room(self, uid):
  u = self.user_by_id(uid)
  r = self.rooms[u.rid]

  other_uids = [ouid for ouid in r.users_by_id.keys() if ouid != u.uid]
  other_us = [self.user_by_id(uid) for uid in other_uids]

  r.remove_user(uid) # OOPS! uid has been re-bound by the list comprehension above

  # Interestingly, it's rebound to the last uid in the list, so the error only shows
  # up when len > 1

징징 거리는 위험에 처하면 이것은 잔인한 오류의 원인입니다. 새 코드를 작성할 때 가끔 리 바인딩으로 인해 매우 이상한 오류를 발견합니다. 지금도 문제라는 것을 알고 있습니다. “언제나 밑줄이있는 목록 이해의 임시 변수 머리말”과 같은 규칙을 만들 필요가 있지만, 그렇다고해서 절대적인 것은 아닙니다.

이 임의의 시한 폭탄 대기 종류가 있다는 사실은 목록 이해의 모든 멋진 “사용 편의성”을 무효화합니다.



답변

List comprehensions는 Python 2에서 루프 제어 변수를 유출하지만 Python 3에서는 유출하지 않습니다. 다음은 Guido van Rossum (Python의 창시자) 이 그이면의 역사를 설명 합니다.

또한 목록 이해와 생성기 표현식 간의 동등성을 개선하기 위해 Python 3에서 또 다른 변경을했습니다. Python 2에서 목록 이해는 루프 제어 변수를 주변 범위로 “누수”합니다.

x = 'before'
a = [x for x in 1, 2, 3]
print x # this prints '3', not 'before'

이것은 목록 이해의 원래 구현의 결과물이었습니다. 그것은 수년간 파이썬의 “더러운 작은 비밀”중 하나였습니다. 목록 이해력을 맹목적으로 빠르게 만들기위한 의도적 인 타협으로 시작되었으며 초보자에게는 일반적인 함정은 아니지만 때때로 사람들을 찌르는 경우가 있습니다. 생성기 표현식의 경우이 작업을 수행 할 수 없습니다. 생성기 표현식은 생성기를 사용하여 구현되며 실행에는 별도의 실행 프레임이 필요합니다. 따라서 생성기 표현식 (특히 짧은 시퀀스를 반복하는 경우)은 목록 이해보다 효율성이 떨어졌습니다.

그러나 Python 3에서는 생성기 표현식과 동일한 구현 전략을 사용하여 목록 이해의 “더티 작은 비밀”을 수정하기로 결정했습니다. 따라서 Python 3에서 위의 예제 (print (x)를 사용하도록 수정 한 후 :-)는 ‘before’를 인쇄하여 목록 이해의 ‘x’가 일시적으로 음영 처리되지만 주변의 ‘x’를 재정의하지 않음을 증명합니다. 범위.


답변

예, for 루프와 마찬가지로 Python 2.x에서 목록 내포물이 변수를 “누출”합니다.

돌이켜 보면 이것은 실수로 인식되었고 생성기 표현식으로 피했습니다. 편집 : Matt B.가 지적 했듯이 Python 3에서 set 및 dictionary 이해 구문을 백 포트 할 때도 피했습니다.

List Comprehensions의 동작은 Python 2에서와 같이 남아 있어야했지만 Python 3에서는 완전히 수정되었습니다.

이는 다음을 의미합니다.

list(x for x in a if x>32)
set(x//4 for x in a if x>32)         # just another generator exp.
dict((x, x//16) for x in a if x>32)  # yet another generator exp.
{x//4 for x in a if x>32}            # 2.7+ syntax
{x: x//16 for x in a if x>32}        # 2.7+ syntax

x항상 이러한 동안 표현에 로컬 :

[x for x in a if x>32]
set([x//4 for x in a if x>32])         # just another list comp.
dict([(x, x//16) for x in a if x>32])  # yet another list comp.

Python 2.x에서는 모두 x변수를 주변 범위로 유출합니다 .


UPDATE for Python 3.8 (?) : PEP 572의도적 으로 이해와 생성기 표현식에서 유출 되는 :=할당 연산자를 도입 합니다 ! 본질적으로 두 가지 사용 사례에 의해 동기가 부여됩니다 : 및 다음 과 같은 조기 종료 기능에서 “증인”캡처 .any()all()

if any((comment := line).startswith('#') for line in lines):
    print("First comment:", comment)
else:
    print("There are no comments")

변경 가능한 상태 업데이트 :

total = 0
partial_sums = [total := total + v for v in values]

정확한 범위 지정 은 부록 B 를 참조하십시오 . 변수는 주변의 가장 가까운에 할당 def하거나 lambda그 기능을 선언하지 않는 한, nonlocal또는 global.


답변

예, for루프 에서와 마찬가지로 할당이 발생합니다 . 새 범위가 생성되지 않습니다.

이것은 확실히 예상되는 동작입니다. 각주기에서 값은 지정한 이름에 바인딩됩니다. 예를 들어

>>> x=0
>>> a=[1,54,4,2,32,234,5234,]
>>> [x for x in a if x>32]
[54, 234, 5234]
>>> x
5234

일단 그것이 인식되면 피하는 것이 충분히 쉬워 보입니다. 이해 범위 내에서 변수에 대해 기존 이름을 사용하지 마십시오.


답변

흥미롭게도 이것은 사전이나 집합 이해력에 영향을 미치지 않습니다.

>>> [x for x in range(1, 10)]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> x
9
>>> {x for x in range(1, 5)}
set([1, 2, 3, 4])
>>> x
9
>>> {x:x for x in range(1, 100)}
{1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12, 13: 13, 14: 14, 15: 15, 16: 16, 17: 17, 18: 18, 19: 19, 20: 20, 21: 21, 22: 22, 23: 23, 24: 24, 25: 25, 26: 26, 27: 27, 28: 28, 29: 29, 30: 30, 31: 31, 32: 32, 33: 33, 34: 34, 35: 35, 36: 36, 37: 37, 38: 38, 39: 39, 40: 40, 41: 41, 42: 42, 43: 43, 44: 44, 45: 45, 46: 46, 47: 47, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 58: 58, 59: 59, 60: 60, 61: 61, 62: 62, 63: 63, 64: 64, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 91: 91, 92: 92, 93: 93, 94: 94, 95: 95, 96: 96, 97: 97, 98: 98, 99: 99}
>>> x
9

그러나 위에서 언급 한대로 3에서 수정되었습니다.


답변

이 동작이 바람직하지 않은 경우 Python 2.6의 일부 해결 방법

# python
Python 2.6.6 (r266:84292, Aug  9 2016, 06:11:56)
Type "help", "copyright", "credits" or "license" for more information.
>>> x=0
>>> a=list(x for x in xrange(9))
>>> x
0
>>> a=[x for x in xrange(9)]
>>> x
8


답변

목록 이해 중에 python3에서 변수는 범위가 끝난 후에도 변경되지 않지만 간단한 for 루프를 사용하면 변수가 범위를 벗어나 다시 할당됩니다.

i = 1 print (i) print ([i in range (5)]) print (i) i의 값은 1로만 유지됩니다.

이제 단순히 for 루프를 사용하면 i의 값이 다시 할당됩니다.


답변