etc./StackOverFlow

"최소한의 놀라움"과 변경 가능한 기본 인수

청렴결백한 만능 재주꾼 2021. 10. 9. 02:26
반응형

질문자 :Stefano Borini


Python을 오랫동안 만지작거리는 사람은 다음 문제에 물린(또는 조각난) 사람입니다.

 def foo(a=[]): a.append(5) return a

Python 초보자는 이 함수가 항상 하나의 요소만 있는 목록을 반환할 것으로 예상할 것입니다: [5] . 결과는 매우 다르며 매우 놀랍습니다(초보자의 경우).

 >>> foo() [5] >>> foo() [5, 5] >>> foo() [5, 5, 5] >>> foo() [5, 5, 5, 5] >>> foo()

내 매니저는 이 기능을 처음 접했고 그것을 언어의 "극적인 디자인 결함"이라고 불렀습니다. 나는 그 행동에 근본적인 설명이 있다고 대답했고, 내부를 이해하지 못하면 실제로 매우 당혹스럽고 예상치 못한 일입니다. 그러나 나는 다음과 같은 질문에 (자신에게) 대답할 수 없었습니다. 함수 실행이 아닌 함수 정의에서 기본 인수를 바인딩하는 이유는 무엇입니까? 경험이 풍부한 행동이 실제로 사용되는지 의심스럽습니다(누가 실제로 버그를 번식시키지 않고 C에서 정적 변수를 사용했습니까?)

편집 :

Baczek은 흥미로운 예를 들었습니다 . 귀하의 대부분의 의견과 특히 Utaal의 의견과 함께 저는 다음과 같이 자세히 설명했습니다.

 >>> def a(): ... print("a executed") ... return [] ... >>> >>> def b(x=a()): ... x.append(5) ... print(x) ... a executed >>> b() [5] >>> b() [5, 5]

나에게 디자인 결정은 매개 변수의 범위를 어디에 둘 것인지에 따라 결정된 것 같습니다. 함수 내부 또는 "함께"?

하고있는 것을 의미 함수 내에서 바인딩 x 효과적으로 깊은 결함을 제시 할 것 함수가 정의되어 있지,라고 지정된 기본, 뭔가에 바인딩 다음 def 라인의 일부가 결합한다는 의미에서 "하이브리드"가 될 것입니다 (함수 객체의) 정의 시 발생하고 일부(기본 매개변수 할당)는 함수 호출 시 발생합니다.

실제 동작은 더 일관성이 있습니다. 즉, 해당 행이 실행될 때 해당 행의 모든 것이 평가됩니다. 즉, 함수 정의에서를 의미합니다.



사실 이것은 설계상의 하자도 아니고, 내부나 성능 때문도 아니다.
그것은 단순히 파이썬의 함수가 코드 조각이 아니라 일급 객체라는 사실에서 비롯됩니다.

이런 식으로 생각하면 완전히 이해가 됩니다. 함수는 정의에 따라 평가되는 객체입니다. 기본 매개변수는 일종의 "멤버 데이터"이므로 해당 상태는 다른 개체에서와 마찬가지로 한 호출에서 다른 호출로 변경될 수 있습니다.

어쨌든 Effbot은 Python의 기본 매개변수 값에서 이 동작에 대한 이유에 대해 아주 잘 설명합니다.
나는 그것이 매우 명확하다는 것을 알았고, 함수 객체가 어떻게 작동하는지에 대한 더 나은 지식을 위해 그것을 읽는 것이 좋습니다.


rob

다음 코드가 있다고 가정합니다.

 fruits = ("apples", "bananas", "loganberries") def eat(food=fruits): ...

내가 eat 선언을 볼 때 가장 놀라운 것은 첫 번째 매개변수가 제공되지 않으면 튜플 ("apples", "bananas", "loganberries")

그러나 나중에 코드에서 다음과 같은 작업을 수행한다고 가정합니다.

 def some_random_function(): global fruits fruits = ("blueberries", "mangos")

그런 다음 기본 매개변수가 함수 선언이 아니라 함수 실행 시 바인딩된 경우 과일이 변경되었음을 발견하고 (매우 나쁜 방식으로) 놀랐을 것입니다. foo 함수가 목록을 변경하고 있다는 것을 발견하는 것보다 더 놀라운 IMO가 될 것입니다.

실제 문제는 가변 변수에 있으며 모든 언어가 어느 정도 이 문제를 가지고 있습니다. 다음은 질문입니다. Java에 다음 코드가 있다고 가정합니다.

 StringBuffer s = new StringBuffer("Hello World!"); Map<StringBuffer,Integer> counts = new HashMap<StringBuffer,Integer>(); counts.put(s, 5); s.append("!!!!"); System.out.println( counts.get(s) ); // does this work?

StringBuffer 키의 값을 사용합니까, 아니면 키를 참조로 저장합니까? 어느 쪽이든, 누군가는 놀랐습니다. Map 에서 개체를 꺼내려고 하는 사람 또는 사용 중인 키가 문자 그대로 동일함에도 불구하고 개체를 검색할 수 없는 것처럼 보이는 사람 지도에 넣는 데 사용된 객체(이것이 실제로 Python이 변경 가능한 내장 데이터 유형을 사전 키로 사용하는 것을 허용하지 않는 이유입니다).

귀하의 예는 Python 초보자가 놀라고 물릴 좋은 사례입니다. 그러나 우리가 이것을 "고치면" 그들이 대신 물린 다른 상황을 만들 뿐이며 그 상황은 훨씬 덜 직관적이라고 주장하고 싶습니다. 게다가, 이것은 변경 가능한 변수를 다룰 때 항상 해당됩니다. 어떤 코드를 작성하는지에 따라 누군가가 직관적으로 하나 또는 반대 동작을 예상할 수 있는 경우가 항상 있습니다.

저는 개인적으로 Python의 현재 접근 방식을 좋아합니다. 기본 함수 인수는 함수가 정의될 때 평가되고 해당 객체는 항상 기본값입니다. 나는 그들이 빈 목록을 사용하여 특수 케이스를 사용할 수 있다고 생각하지만, 그런 종류의 특수 케이스는 이전 버전과 호환되지 않는 것은 말할 것도 없고 훨씬 더 놀라운 일을 야기할 것입니다.


Eli Courtwright

문서 의 관련 부분:

기본 매개변수 값은 함수 정의가 실행될 때 왼쪽에서 오른쪽으로 평가됩니다. 이는 함수가 정의될 때 표현식이 한 번 평가되고 각 호출에 대해 동일한 "미리 계산된" 값이 사용됨을 의미합니다. 이것은 기본 매개변수가 목록이나 사전과 같은 변경 가능한 개체인 경우를 이해하는 데 특히 중요합니다. 함수가 개체를 수정하면(예: 목록에 항목을 추가하여) 기본값이 사실상 수정됩니다. 이것은 일반적으로 의도한 것이 아닙니다. 이 문제를 해결하는 방법은 None 을 기본값으로 사용하고 함수 본문에서 명시적으로 테스트하는 것입니다. 예:

 def whats_on_the_telly(penguin=None): if penguin is None: penguin = [] penguin.append("property of the zoo") return penguin

glglgl

나는 파이썬 인터프리터 내부 작동에 대해 아무것도 모릅니다(그리고 저는 컴파일러와 인터프리터의 전문가도 아닙니다). 그래서 제가 합리적이지 않거나 불가능한 것을 제안하더라도 저를 비난하지 마십시오.

파이썬 객체 가 변경 가능하다면 기본 인수를 디자인할 때 이것이 고려되어야 한다고 생각합니다. 목록을 인스턴스화할 때:

 a = []

에서 참조 하는 새 목록을 얻을 것으로 예상 a .

a=[] 에서

 def x(a=[]):

호출이 아닌 함수 정의에 대한 새 목록을 인스턴스화하시겠습니까? "사용자가 인수를 제공하지 않으면 새 목록을 인스턴스화 하고 호출자가 생성한 것처럼 사용하십시오"라고 묻는 것과 같습니다. 나는 이것이 대신 모호하다고 생각합니다.

 def x(a=datetime.datetime.now()):

사용자는 원하는 할 a 당신이 정의 또는 실행 때에 해당하는 날짜에 기본값으로 x ? 이 경우 이전 인수와 마찬가지로 기본 인수 "할당"이 함수의 첫 번째 명령( 함수 호출 시 호출되는 datetime.now() 반면에 사용자가 정의 시간 매핑을 원하면 다음과 같이 작성할 수 있습니다.

 b = datetime.datetime.now() def x(a=b):

알아요, 알아요. 폐쇄입니다. 또는 Python은 정의 시간 바인딩을 강제 실행하는 키워드를 제공할 수 있습니다.

 def x(static a=b):

Utaal

글쎄요, 그 이유는 코드가 실행될 때 바인딩이 수행되고, 함수 정의가 실행될 때, 음... 함수가 정의될 때 수행되기 때문입니다.

이것을 비교하십시오:

 class BananaBunch: bananas = [] def addBanana(self, banana): self.bananas.append(banana)

이 코드는 정확히 똑같은 예상치 못한 상황을 겪고 있습니다. 바나나는 클래스 속성이므로 여기에 항목을 추가하면 해당 클래스의 모든 인스턴스에 추가됩니다. 이유는 정확히 같습니다.

그것은 단지 "작동 방식"일 뿐이며, 함수의 경우 다르게 작동하게 하는 것은 아마도 복잡할 것이고, 클래스의 경우에는 불가능하거나 적어도 객체 인스턴스화 속도를 많이 늦추는 것입니다. 객체가 생성될 때 실행합니다.

예, 예상치 못한 일입니다. 그러나 한 푼도 떨어지면 일반적으로 Python이 작동하는 방식과 완벽하게 맞습니다. 사실, 그것은 좋은 교육 보조 도구이며, 왜 이런 일이 발생하는지 이해하면 파이썬을 훨씬 더 잘 이해하게 될 것입니다.

그것은 좋은 파이썬 튜토리얼에서 두드러지게 등장해야 한다고 말했습니다. 당신이 언급했듯이 조만간 모든 사람이 이 문제에 직면하기 때문입니다.


Lennart Regebro

왜 자기 성찰을 하지 않습니까?

아무도 호출 가능 항목 23 적용)이 제공하는 통찰력 있는 내성을 수행하지 않았다는 사실에 정말 놀랐습니다.

다음과 같이 정의된 간단한 작은 함수 func

 >>> def func(a = []): ... a.append(5)

파이썬이 그것을 만났을 때 가장 먼저 할 일은 이 함수에 대한 code 이 컴파일 단계가 완료되는 동안, 파이썬은 * 평가하고 (빈리스트 기본 인수를 저장 [] 여기) 함수 객체 자체입니다. 상단의 대답은 언급 한 바와 같이 : 목록 a 이제 함수의 일원으로 간주 될 수있다 func .

따라서 함수 개체 에서 목록이 확장되는 방식을 조사하기 위해 전후에 약간의 내부 검사를 수행해 보겠습니다. 저는 Python 3.x 를 사용하고 있습니다. Python 2의 경우에도 동일하게 적용됩니다(Python 2에서는 __defaults__ 또는 func_defaults 사용, 예, 동일한 항목에 대해 두 개의 이름 사용).

실행 전 기능:

 >>> def func(a = []): ... a.append(5) ...

Python이 이 정의를 실행한 후 지정된 기본 매개변수( a = [] )를 가져 와서 함수 객체 __defaults__ 속성(관련 섹션: Callables)에 넣습니다.

 >>> func.__defaults__ ([],)

좋아, 예상대로 __defaults__ 의 단일 항목으로 빈 목록이 있습니다.

실행 후 기능:

이제 이 함수를 실행해 보겠습니다.

 >>> func()

__defaults__ 다시 살펴보겠습니다.

 >>> func.__defaults__ ([5],)

놀랐어? 개체 내부의 값이 변경됩니다! 함수에 대한 연속 호출은 이제 단순히 포함된 list 개체에 추가됩니다.

 >>> func(); func(); func() >>> func.__defaults__ ([5, 5, 5, 5],)

이 '결함'이 발생하는 이유는 기본 인수가 함수 개체의 일부이기 때문입니다. 여기에서 이상한 일이 벌어지고 있는 것은 아닙니다. 단지 약간 놀라울 뿐입니다.

이를 해결하기 위한 일반적인 솔루션은 None 을 기본값으로 사용한 다음 함수 본문에서 초기화하는 것입니다.

 def func(a = None): # or: a = [] if a is None else a if a is None: a = []

함수 본문 매번 새롭게 실행되기 때문에 더 인수가 전달되지 않은 경우, 당신은 항상 신선한 새로운 빈 목록을 가져 . a


__defaults__ func 함수에서 사용된 것과 동일한지 추가로 확인하려면 함수 본문 내에서 사용된 목록 a id 를 반환하도록 함수를 변경할 수 있습니다. 그런 다음에 목록에 비교 __defaults__ (위치 [0] 에서 __defaults__ )와이 실제로 동일한 목록 인스턴스로 다스 려하는 방법을 볼 수 있습니다 :

 >>> def func(a = []): ... a.append(5) ... return id(a) >>> >>> id(func.__defaults__[0]) == func() True

모든 것은 내성의 힘으로!


* Python이 함수를 컴파일하는 동안 기본 인수를 평가하는지 확인하려면 다음을 실행해 보세요.

 def bar(a=input('Did you just see me without calling the function?')): pass # use raw_input in Py2

알다시피, input() bar 바인딩하는 프로세스가 수행되기 전에 호출됩니다.


Dimitris Fasarakis Hilliard

파이썬 방어 5점

  1. 단순성 : 행동은 다음과 같은 의미에서 단순합니다. 대부분의 사람들은 이 함정에 여러 번 빠지지 않고 한 번만 빠지게 됩니다.

  2. 일관성 : Python은 항상 이름이 아닌 객체를 전달합니다. 기본 매개변수는 분명히 함수 머리글의 일부입니다(함수 본문이 아님). 따라서 함수 호출 시간이 아니라 모듈 로드 시간(중첩되지 않은 경우 모듈 로드 시간에만)을 평가해야 합니다.

  3. 유용성 : Frederik Lundh가 "Python의 기본 매개변수 값" 에 대한 설명에서 지적했듯이 현재 동작은 고급 프로그래밍에 매우 유용할 수 있습니다. (적당히 사용하십시오.)

  4. 충분한 문서 : 가장 기본적인 Python 문서인 자습서에서 이 문제는 "함수 정의에 대한 추가 정보" 섹션의 첫 번째 하위 섹션에서 "중요한 경고" 로 큰 소리로 발표됩니다. 경고는 제목 외부에 거의 적용되지 않는 볼드체도 사용합니다. RTFM: 훌륭한 매뉴얼을 읽으십시오.

  5. 메타 학습 : 함정에 빠지는 것은 실제로 매우 유용한 순간입니다(적어도 당신이 반성적인 학습자라면). 왜냐하면 이후에 위의 "일관성" 요점을 더 잘 이해하고 Python에 대해 많은 것을 배울 것이기 때문입니다.


Lutz Prechelt

나는 런타임에 객체를 생성하는 것이 더 나은 접근 방식이라고 생각하곤 했습니다. 나는 당신이 몇몇 유용한 기능을 잃어버렸기 때문에 덜 확실하지만, 단순히 초보자의 혼란을 방지하기 위해 가치가 있을지도 모릅니다. 이렇게 하는 경우의 단점은 다음과 같습니다.

1. 성능

 def foo(arg=something_expensive_to_compute())): ...

호출 시간 평가를 사용하면 인수 없이 함수를 사용할 때마다 값비싼 함수가 호출됩니다. 호출할 때마다 값비싼 대가를 치르거나 외부에 값을 수동으로 캐시해야 하여 네임스페이스를 오염시키고 자세한 정보를 추가해야 합니다.

2. 바운드 매개변수 강제 실행

유용한 트릭은 람다가 생성될 때 람다의 매개변수를 변수의 현재 바인딩에 바인딩하는 것입니다. 예를 들어:

 funcs = [ lambda i=i: i for i in range(10)]

이것은 각각 0,1,2,3...을 반환하는 함수 목록을 반환합니다. 동작이 변경되면 대신 i호출 시간 9 반환하는 함수 목록을 얻을 수 있습니다.

그렇지 않으면 이것을 구현하는 유일한 방법은 i 바인딩된 추가 클로저를 만드는 것입니다. 즉:

 def make_func(i): return lambda: i funcs = [make_func(i) for i in range(10)]

3. 자기 성찰

코드를 고려하십시오.

 def foo(a='test', b=100, c=[]): print a,b,c

inspect 모듈을 사용하여 인수와 기본값에 대한 정보를 얻을 수 있습니다.

 >>> inspect.getargspec(foo) (['a', 'b', 'c'], None, None, ('test', 100, []))

이 정보는 문서 생성, 메타프로그래밍, 데코레이터 등과 같은 작업에 매우 유용합니다.

이제 기본값의 동작이 다음과 같도록 변경될 수 있다고 가정합니다.

 _undefined = object() # sentinel value def foo(a=_undefined, b=_undefined, c=_undefined) if a is _undefined: a='test' if b is _undefined: b=100 if c is _undefined: c=[]

그러나 우리는 내성을 상실하고 기본 인수 무엇인지 확인합니다. 객체가 생성되지 않았기 때문에 실제로 함수를 호출하지 않고는 객체를 잡을 수 없습니다. 우리가 할 수 있는 최선은 소스 코드를 저장하고 그것을 문자열로 반환하는 것입니다.


Brian

이 동작은 다음과 같이 쉽게 설명됩니다.

  1. 함수(클래스 등) 선언은 한 번만 실행되어 모든 기본값 개체를 생성합니다.
  2. 모든 것은 참조로 전달됩니다

그래서:

 def x(a=0, b=[], c=[], d=0): a = a + 1 b = b + [1] c.append(1) print a, b, c
  1. 변경되지 않습니다 - 모든 과제를 호출 새 int 객체를 생성 - 새로운 객체를 인쇄
  2. b 는 변경되지 않음 - 새 배열이 기본값에서 빌드되고 인쇄됨
  3. c 변경 - 동일한 개체에 대해 작업이 수행되고 인쇄됩니다.

ymv

1) 소위 "변경 가능한 기본 인수" 문제는 일반적으로 다음을 보여주는 특별한 예입니다.
"이 문제가 있는 모든 함수 는 실제 매개변수에서도 유사한 부작용 문제를 겪습니다."
이는 일반적으로 바람직하지 않으며 둘 다 함께 수정해야 하는 함수형 프로그래밍 규칙에 위배됩니다.

예시:

 def foo(a=[]): # the same problematic function a.append(5) return a >>> somevar = [1, 2] # an example without a default parameter >>> foo(somevar) [1, 2, 5] >>> somevar [1, 2, 5] # usually expected [1, 2]

솔루션 : 사본
절대적으로 안전한 솔루션은 copy 하거나 deepcopy 카피한 다음 복사로 무엇이든 하는 것입니다.

 def foo(a=[]): a = a[:] # a copy a.append(5) return a # or everything safe by one line: "return a + [5]"

많은 내장 가변 유형에는 some_dict.copy() 또는 some_set.copy() somelist[:] 또는 list(some_list) 처럼 쉽게 복사할 수 있습니다. copy.copy(any_object) copy.deepcopy() 더 철저하게 복사할 수 있습니다(후자는 가변 객체가 가변 객체로 구성된 경우 유용합니다). 일부 개체는 기본적으로 "파일" 개체와 같은 부작용에 기반을 두고 있어 복사로 의미 있게 재현할 수 없습니다. 사자

유사한 SO 질문에 대한 예제 문제

 class Test(object): # the original problematic class def __init__(self, var1=[]): self._var1 = var1 somevar = [1, 2] # an example without a default parameter t1 = Test(somevar) t2 = Test(somevar) t1._var1.append([1]) print somevar # [1, 2, [1]] but usually expected [1, 2] print t2._var1 # [1, 2, [1]] but usually expected [1, 2]

이 함수에 의해 반환된 인스턴스의 공용 속성에 저장되어서도 안 됩니다. (instance의 private 속성은 규칙에 따라 이 클래스 또는 하위 클래스 외부에서 수정되어서는 안 된다고 가정합니다 _var1 은 private 속성입니다)

결론:
입력 매개변수 개체는 제자리에서 수정(변경)되어서는 안 되며 함수에서 반환된 개체에 바인딩되어서도 안 됩니다. (부작용이 없는 프로그래밍을 선호한다면 강력히 권장합니다. "부작용"에 대한 Wiki 참조(처음 두 단락은 이 문맥과 관련이 있습니다.) .)

2)
실제 매개변수에 대한 부작용이 필요하지만 기본 매개변수에서 원하지 않는 경우에만 유용한 솔루션은 def ...(var1=None): if var1 is None: var1 = [] More..

3) 어떤 경우에는 기본 매개변수의 변경 가능한 동작이 유용합니다 .


hynekcer

당신이 묻는 이유는 다음과 같습니다.

 def func(a=[], b = 2): pass

내부적으로 다음과 동일하지 않습니다.

 def func(a=None, b = None): a_default = lambda: [] b_default = lambda: 2 def actual_func(a=None, b=None): if a is None: a = a_default() if b is None: b = b_default() return actual_func func = func()

우리가 무시할 func(None, None)을 명시적으로 호출하는 경우를 제외하고.

즉, 기본 매개변수를 평가하는 대신 각각을 저장하고 함수가 호출될 때 평가하지 않는 이유는 무엇입니까?

한 가지 대답은 아마도 바로 거기에 있을 것입니다. 기본 매개변수가 있는 모든 함수를 클로저로 효과적으로 전환할 것입니다. 완전한 폐쇄가 아니라 인터프리터에 모두 숨겨져 있더라도 데이터는 어딘가에 저장되어야 합니다. 더 느리고 더 많은 메모리를 사용합니다.


Glenn Maynard

이것은 실제로 기본값과 아무 관련이 없습니다. 변경 가능한 기본값을 사용하여 함수를 작성할 때 예기치 않은 동작이 자주 발생한다는 점만 빼면 말입니다.

 >>> def foo(a): a.append(5) print a >>> a = [5] >>> foo(a) [5, 5] >>> foo(a) [5, 5, 5] >>> foo(a) [5, 5, 5, 5] >>> foo(a) [5, 5, 5, 5, 5]

이 코드에는 기본값이 표시되지 않지만 정확히 동일한 문제가 발생합니다.

문제는 foo 가 호출자가 이것을 기대하지 않을 때 호출자로부터 전달된 변경 가능한 변수를 수정하고 있다는 것입니다. append_5 와 같은 것으로 호출되면 이와 같은 코드는 괜찮을 것입니다. 그러면 호출자는 전달한 값을 수정하기 위해 함수를 호출하고 동작이 예상됩니다. 그러나 그러한 함수는 기본 인수를 사용할 가능성이 매우 낮고 아마도 목록을 반환하지 않을 것입니다(호출자가 이미 해당 목록에 대한 참조를 가지고 있기 때문에 방금 전달한 것).

원래 foo , 기본 인수, 수정해서는 안 명시 적으로 전달하거나 기본값을했습니다 여부. a 컨텍스트/이름/문서에서 인수가 수정되어야 한다는 것이 분명하지 않는 한 코드는 변경 가능한 인수를 그대로 두어야 합니다. 인수로 전달된 변경 가능한 값을 로컬 임시로 사용하는 것은 파이썬에 있든 없든 기본 인수가 포함되어 있든 없든 매우 나쁜 생각입니다.

무언가를 계산하는 과정에서 로컬 임시를 파괴적으로 조작해야 하고 인수 값에서 조작을 시작해야 하는 경우 복사본을 만들어야 합니다.


Ben

이미 바쁜 주제이지만 여기에서 읽은 내용에서 다음은 내부적으로 작동하는 방식을 깨닫는 데 도움이 되었습니다.

 def bar(a=[]): print id(a) a = a + [1] print id(a) return a >>> bar() 4484370232 4484524224 [1] >>> bar() 4484370232 4484524152 [1] >>> bar() 4484370232 # Never change, this is 'class property' of the function 4484523720 # Always a new object [1] >>> id(bar.func_defaults[0]) 4484370232

Stéphane

성능 최적화입니다. 이 기능의 결과로 이 두 함수 호출 중 어느 것이 더 빠를 것 같습니까?

 def print_tuple(some_tuple=(1,2,3)): print some_tuple print_tuple() #1 print_tuple((1,2,3)) #2

내가 힌트를 줄게. 다음은 분해입니다( http://docs.python.org/library/dis.html 참조 ).

# 1

 0 LOAD_GLOBAL 0 (print_tuple) 3 CALL_FUNCTION 0 6 POP_TOP 7 LOAD_CONST 0 (None) 10 RETURN_VALUE

# 2

 0 LOAD_GLOBAL 0 (print_tuple) 3 LOAD_CONST 4 ((1, 2, 3)) 6 CALL_FUNCTION 1 9 POP_TOP 10 LOAD_CONST 0 (None) 13 RETURN_VALUE

경험이 풍부한 행동이 실제로 사용되는지 의심스럽습니다(누가 실제로 버그를 일으키지 않고 C에서 정적 변수를 사용했습니까?)

당신이 볼 수 있듯이 불변의 기본 인수를 사용하는 경우, 성능 이점이있다. 이것은 자주 호출되는 함수이거나 기본 인수를 구성하는 데 오랜 시간이 걸리는 경우 차이를 만들 수 있습니다. 또한 Python은 C가 아님을 명심하십시오. C에는 거의 무료인 상수가 있습니다. Python에서는 이러한 이점이 없습니다.


Jason Baker

Python: 변경 가능한 기본 인수

기본 인수는 함수가 함수 개체로 컴파일될 때 평가됩니다. 함수에서 사용할 때 해당 함수에서 여러 번 사용하면 동일한 객체이며 그대로 유지됩니다.

변경 가능하고 변경되면(예: 요소를 추가하여) 연속 호출에서 변경된 상태로 유지됩니다.

매번 같은 객체이기 때문에 돌연변이 상태를 유지합니다.

등가 코드:

함수 개체가 컴파일되고 인스턴스화될 때 목록이 함수에 바인딩되므로 다음과 같습니다.

 def foo(mutable_default_argument=[]): # make a list the default argument """function that uses a list"""

다음과 거의 동일합니다.

 _a_list = [] # create a list in the globals def foo(mutable_default_argument=_a_list): # make it the default argument """function that uses a list""" del _a_list # remove globals name binding

데모

다음은 데모입니다. 참조할 때마다 동일한 개체인지 확인할 수 있습니다.

  • 함수가 함수 객체에 대한 컴파일을 완료하기 전에 목록이 생성되었음을 확인하고,
  • 목록이 참조될 때마다 id가 동일한지 관찰하고,
  • 목록을 사용하는 함수가 두 번째로 호출될 때 목록이 변경된 상태로 유지되는 것을 관찰하고,
  • 소스에서 출력이 인쇄되는 순서를 관찰합니다(편리하게 번호를 매겼습니다).

example.py

 print('1. Global scope being evaluated') def create_list(): '''noisily create a list for usage as a kwarg''' l = [] print('3. list being created and returned, id: ' + str(id(l))) return l print('2. example_function about to be compiled to an object') def example_function(default_kwarg1=create_list()): print('appending "a" in default default_kwarg1') default_kwarg1.append("a") print('list with id: ' + str(id(default_kwarg1)) + ' - is now: ' + repr(default_kwarg1)) print('4. example_function compiled: ' + repr(example_function)) if __name__ == '__main__': print('5. calling example_function twice!:') example_function() example_function()

python example.py 실행:

 1. Global scope being evaluated 2. example_function about to be compiled to an object 3. list being created and returned, id: 140502758808032 4. example_function compiled: <function example_function at 0x7fc9590905f0> 5. calling example_function twice!: appending "a" in default default_kwarg1 list with id: 140502758808032 - is now: ['a'] appending "a" in default default_kwarg1 list with id: 140502758808032 - is now: ['a', 'a']

이것은 "Least Astonishment"의 원칙에 위배됩니까?

이 실행 순서는 Python의 새로운 사용자에게 자주 혼동을 줍니다. Python 실행 모델을 이해한다면 충분히 예상할 수 있습니다.

새로운 Python 사용자에 대한 일반적인 지침:

그러나 이것이 새로운 사용자에 대한 일반적인 지침이 대신 다음과 같이 기본 인수를 만드는 것입니다.

 def example_function_2(default_kwarg=None): if default_kwarg is None: default_kwarg = []

이것은 우리가 기본값 이외의 인수를 얻었는지 여부를 함수에 알려주는 센티넬 객체로 None 싱글톤을 사용합니다. 인수가 없으면 실제로 새 빈 목록 [] 기본값으로 사용하려고 합니다.

제어 흐름에 대한 자습서 섹션에서 다음과 같이 말합니다.

후속 호출 간에 기본값을 공유하지 않으려면 대신 다음과 같이 함수를 작성할 수 있습니다.

 def f(a, L=None): if L is None: L = [] L.append(a) return L

Aaron Hall

가장 짧은 대답은 아마도 "정의는 실행이다"일 것이므로 전체 주장은 엄격한 의미가 없습니다. 좀 더 인위적인 예로 다음을 인용할 수 있습니다.

 def a(): return [] def b(x=a()): print x

def 문의 실행 시 기본 인수 표현식을 실행하지 않는 것이 쉽지 않거나 이치에 맞지 않거나 둘 다라는 것을 보여주는 것으로 충분하기를 바랍니다.

그러나 기본 생성자를 사용하려고 할 때 문제가 있다는 데 동의합니다.


Baczek

이 동작은 다음을 고려하면 놀라운 일이 아닙니다.

  1. 할당 시도 시 읽기 전용 클래스 속성의 동작 및
  2. 함수는 객체입니다(허용되는 답변에 잘 설명되어 있음).

(2) 의 역할은 이 스레드에서 광범위하게 다루어졌습니다. (1) 이 동작은 다른 언어에서 올 때 "직관적"이 아니기 때문에 놀라운 원인일 수 있습니다.

(1) 은 클래스에 대한 Python 자습서에 설명되어 있습니다. 읽기 전용 클래스 속성에 값을 할당하려는 경우:

...가장 안쪽 범위 외부에서 발견된 모든 변수는 읽기 전용입니다( 이러한 변수에 쓰려는 시도는 가장 안쪽 범위에 새로운 지역 변수를 생성하고 동일한 이름의 외부 변수를 변경하지 않은 채로 둡니다 ).

원래 예를 다시 살펴보고 위의 사항을 고려하십시오.

 def foo(a=[]): a.append(5) return a

여기서 foo 는 객체이고 a foo 의 속성입니다 foo.func_defs[0] 에서 사용 가능). a 는 목록이므로 a foo 의 읽기-쓰기 속성입니다. 함수가 인스턴스화될 때 서명에 지정된 대로 빈 목록으로 초기화되며 함수 개체가 존재하는 한 읽기 및 쓰기가 가능합니다.

기본값을 재정의하지 않고 foo 를 호출 foo.func_defs 의 기본값이 사용됩니다. 이 경우, foo.func_defs[0] 위해 사용된다 함수 객체의 코드 범위. a 로 변경 변화 a foo.func_defs[0] 의 일부이며, foo 의 코드의 실행 사이에 물체와이 지속 foo .

이제 이것을 다른 언어의 기본 인수 동작 에뮬레이션에 대한 문서의 예제와 비교하여 함수가 실행될 때마다 함수 서명 기본값이 사용되도록 합니다.

 def foo(a, L=None): if L is None: L = [] L.append(a) return L

(1)(2) 를 고려하면 이것이 원하는 동작을 달성하는 이유를 알 수 있습니다.

  • foo 함수 객체가 인스턴스화될 때 foo.func_defs[0] 은 불변 객체인 None 으로 설정됩니다.
  • 함수가 기본값으로 실행될 때(함수 호출에서 L foo.func_defs[0] ( None )은 로컬 범위에서 L 로 사용할 수 있습니다.
  • L = [] foo.func_defs[0] 에서 할당이 성공할 수 없습니다. 해당 속성은 읽기 전용이기 때문입니다.
  • (1)L 이라는 이름의 새 로컬 변수가 로컬 범위에서 생성되고 나머지 함수 호출에 사용됩니다. foo.func_defs[0] foo 향후 호출에 대해 변경되지 않은 상태로 유지됩니다.

Dmitry Minkovsky

None을 사용하는 간단한 해결 방법

 >>> def bar(b, data=None): ... data = data or [] ... data.append(b) ... return data ... >>> bar(3) [3] >>> bar(3) [3] >>> bar(3) [3] >>> bar(3, [34]) [34, 3] >>> bar(3, [34]) [34, 3]

hugo24

기본 목록 값을 함수에 전달하는 대체 구조를 시연할 것입니다(사전과 동일하게 작동함).

다른 사람들이 광범위하게 언급했듯이 list 매개변수는 실행될 때와 반대로 정의될 때 함수에 바인딩됩니다. 목록과 사전은 변경 가능하므로 이 매개변수를 변경하면 이 함수에 대한 다른 호출에 영향을 줍니다. 결과적으로 함수에 대한 후속 호출은 함수에 대한 다른 호출에 의해 변경되었을 수 있는 이 공유 목록을 받게 됩니다. 설상가상으로 두 개의 매개변수가 이 함수의 공유 매개변수를 동시에 사용하여 다른 매개변수의 변경 사항을 무시하고 있습니다.

잘못된 방법(아마도...) :

 def foo(list_arg=[5]): return list_arg a = foo() a.append(6) >>> a [5, 6] b = foo() b.append(7) # The value of 6 appended to variable 'a' is now part of the list held by 'b'. >>> b [5, 6, 7] # Although 'a' is expecting to receive 6 (the last element it appended to the list), # it actually receives the last element appended to the shared list. # It thus receives the value 7 previously appended by 'b'. >>> a.pop() 7

id 를 사용하여 하나의 동일한 객체인지 확인할 수 있습니다.

 >>> id(a) 5347866528 >>> id(b) 5347866528

Brett Slatkin의 "Effective Python: 59 Specific Ways to Write Better Python", 항목 20: 동적 기본 인수를 지정하기 위해 None 및 Docstrings 사용 (p. 48)

Python에서 원하는 결과를 얻기 위한 규칙은 기본값 None 을 제공하고 docstring에서 실제 동작을 문서화하는 것입니다.

이 구현은 함수에 대한 각 호출이 기본 목록을 받거나 함수에 전달된 목록을 받도록 합니다.

선호하는 방법 :

 def foo(list_arg=None): """ :param list_arg: A list of input values. If none provided, used a list with a default value of 5. """ if not list_arg: list_arg = [5] return list_arg a = foo() a.append(6) >>> a [5, 6] b = foo() b.append(7) >>> b [5, 7] c = foo([10]) c.append(11) >>> c [10, 11]

프로그래머가 기본 목록 매개변수를 공유하도록 의도한 '잘못된 방법'에 대한 합법적인 사용 사례가 있을 수 있지만 이것은 규칙보다 예외일 가능성이 더 큽니다.


Alexander

솔루션은 다음과 같습니다.

  1. None 을 기본값(또는 nonce object )으로 사용하고 이를 켜서 런타임에 값을 생성합니다. 또는
  2. lambda 를 기본 매개변수로 사용하고 try 블록 내에서 호출하여 기본값을 가져옵니다(이것이 람다 추상화의 용도입니다).

두 번째 옵션은 함수의 사용자가 이미 존재할 수 있는 콜러블(예: type )을 전달할 수 있기 때문에 좋습니다.


Marcin

객체를 교체하여 이 문제를 해결할 수 있습니다(따라서 범위와 연결).

 def foo(a=[]): a = list(a) a.append(5) return a

추악하지만 작동합니다.


joedborg

우리가 이것을 할 때:

 def foo(a=[]): ...

... 우리는 인수 지정 a 호출자가의 값을 전달하지 않는 경우, 익명의 목록.

이 토론을 더 간단하게 하기 위해 이름 없는 목록에 임시로 이름을 지정해 보겠습니다. pavlo ?

 def foo(a=pavlo): ...

호출자가 무엇인지 알려하지 않는 경우 언제든지 a , 우리는 다시 pavlo .

pavlo 가 변경 가능하고(수정 가능) foo 가 이를 수정하면 다음에 foo a 를 지정하지 않고 호출될 때 알아차리는 효과가 있습니다.

이것이 당신이 보는 것입니다( pavlo 는 []로 초기화된다는 것을 기억하십시오):

 >>> foo() [5]

이제 pavlo 는 [5]입니다.

foo() 다시 호출 pavlo 다시 수정됩니다.

 >>> foo() [5, 5]

지정 호출 a foo() 보장하지만 pavlo 건드리지 않는다.

 >>> ivan = [1, 2, 3, 4] >>> foo(a=ivan) [1, 2, 3, 4, 5] >>> ivan [1, 2, 3, 4, 5]

따라서 pavlo 는 여전히 [5, 5] 입니다.

 >>> foo() [5, 5, 5]

Saish

나는 때때로 다음 패턴의 대안으로 이 동작을 악용합니다.

 singleton = None def use_singleton(): global singleton if singleton is None: singleton = _make_singleton() return singleton.use_me()

singleton use_singleton 에서만 사용되는 경우 다음 패턴을 대체물로 사용하는 것이 좋습니다.

 # _make_singleton() is called only once when the def is executed def use_singleton(singleton=_make_singleton()): return singleton.use_me()

외부 리소스에 액세스하는 클라이언트 클래스를 인스턴스화하고 메모화를 위한 사전 또는 목록을 만드는 데 이것을 사용했습니다.

이 패턴은 잘 알려지지 않은 것 같아서 앞으로의 오해를 방지하기 위해 짧은 코멘트를 남깁니다.


bgreen-litl

다음과 같은 사실이 사실일 수 있습니다.

  1. 누군가가 모든 언어/라이브러리 기능을 사용하고 있으며,
  2. 여기서 동작을 전환하는 것은 바람직하지 않지만

위의 두 가지 기능을 모두 유지하면서도 다른 점을 강조하는 것은 완전히 일관적입니다.

  1. 이것은 혼란스러운 기능이며 Python에서는 불행한 일입니다.

다른 답변, 또는 그 중 적어도 일부는 1과 2를 제시하지만 3은 제시하지 않거나 3을 제시하고 1과 2를 경시 합니다. 그러나 세 가지 모두 사실입니다.

여기에서 중간에 말을 바꾸는 것은 상당한 손상을 요구할 수 있으며, 스테파노의 시작 부분을 직관적으로 처리하도록 Python을 변경하면 더 많은 문제가 발생할 수 있습니다. 그리고 파이썬 내부를 잘 아는 사람이 결과의 지뢰밭을 설명할 수 있다는 것은 사실일 수도 있습니다. 하지만,

기존 동작은 Pythonic이 아니며 Python은 이 근처 에서 가장 놀라움이 거의 없다는 원칙을 위반하는 언어가 거의 없기 때문에 성공합니다. 그것을 뿌리 뽑는 것이 현명할지 여부는 실제 문제입니다. 디자인 결함입니다. 동작을 추적하여 언어를 훨씬 더 잘 이해한다면 C++가 이 모든 것 이상을 수행한다고 말할 수 있습니다. 예를 들어 미묘한 포인터 오류를 탐색하여 많은 것을 배웁니다. 그러나 이것은 Pythonic이 아닙니다. 이러한 행동에 직면하여 인내할 만큼 Python에 관심이 있는 사람들은 Python이 다른 언어보다 놀라움이 훨씬 적기 때문에 해당 언어에 끌리는 사람들입니다. Dabblers와 호기심 많은 사람들은 Python에 끌린 프로그래머의 직관에 반하는 디자인 때문이 아니라 무언가가 작동하는 데 걸리는 시간이 얼마나 짧은지 알고 놀랐을 때 Pythonistas가 되었습니다. 그냥 작동 하기 때문입니다.


Christos Hayward

이 "버그"는 나에게 많은 초과 근무 시간을 주었다! 그러나 나는 그것의 잠재적인 사용을 보기 시작했습니다(그러나 여전히 실행 시간에 그것을 원했을 것입니다)

제가 보기에 유용한 예시를 보여드리겠습니다.

 def example(errors=[]): # statements # Something went wrong mistake = True if mistake: tryToFixIt(errors) # Didn't work.. let's try again tryToFixItAnotherway(errors) # This time it worked return errors def tryToFixIt(err): err.append('Attempt to fix it') def tryToFixItAnotherway(err): err.append('Attempt to fix it by another way') def main(): for item in range(2): errors = example() print '\n'.join(errors) main()

다음을 인쇄합니다

 Attempt to fix it Attempt to fix it by another way Attempt to fix it Attempt to fix it by another way

Norfeldt

이것은 설계상의 결함이 아닙니다 . 이것에 걸려 넘어지는 사람은 뭔가 잘못하고 있는 것입니다.

이 문제가 발생할 수 있는 3가지 경우가 있습니다.

  1. 함수의 부작용으로 인수를 수정하려고 합니다. 이 경우 기본 인수를 갖는 것은 결코 의미가 없습니다. 유일한 예외는 함수 속성(예: cache={} 을 갖기 위해 인수 목록을 남용하고 실제 인수로 함수를 호출할 것으로 예상되지 않는 경우입니다.
  2. 인수를 수정하지 않은 상태로 두려고 했지만 실수로 수정했습니다. 버그입니다. 수정하세요.
  3. 함수 내부에서 사용하기 위해 인수를 수정하려고 하지만 함수 외부에서 수정 사항을 볼 수 있을 것이라고 기대하지 않았습니다. 이 경우 기본값인지 여부에 관계없이 인수 의 복사본 을 만들어야 합니다! Python은 값에 의한 호출 언어가 아니므로 사본을 만들지 않으므로 명시적으로 설명해야 합니다.

질문의 예는 범주 1 또는 3에 속할 수 있습니다. 전달된 목록을 수정하고 반환한다는 것은 이상한 일입니다. 둘 중 하나를 선택해야 합니다.


Mark Ransom

기능을 다음과 같이 변경하십시오.

 def notastonishinganymore(a = []): '''The name is just a joke :)''' a = a[:] a.append(5) return a

ytpillai

다른 모든 답변은 이것이 실제로 훌륭하고 바람직한 동작인 이유 또는 어쨌든 이것이 필요하지 않은 이유를 설명합니다. 내 것은 그 반대가 아니라 자신의 의지에 따라 언어를 구부릴 권리를 행사하려는 완고한 사람들을위한 것입니다.

기본값으로 남겨진 각 위치 인수에 대해 동일한 인스턴스를 재사용하는 대신 기본값을 복사하는 데코레이터로 이 동작을 "수정"합니다.

 import inspect from copy import copy def sanify(function): def wrapper(*a, **kw): # store the default values defaults = inspect.getargspec(function).defaults # for python2 # construct a new argument list new_args = [] for i, arg in enumerate(defaults): # allow passing positional arguments if i in range(len(a)): new_args.append(a[i]) else: # copy the value new_args.append(copy(arg)) return function(*new_args, **kw) return wrapper

이제 이 데코레이터를 사용하여 함수를 재정의해 보겠습니다.

 @sanify def foo(a=[]): a.append(5) return a foo() # '[5]' foo() # '[5]' -- as desired

이것은 여러 인수를 취하는 함수에 특히 깔끔합니다. 비교하다:

 # the 'correct' approach def bar(a=None, b=None, c=None): if a is None: a = [] if b is None: b = [] if c is None: c = [] # finally do the actual work

~와 함께

 # the nasty decorator hack @sanify def bar(a=[], b=[], c=[]): # wow, works right out of the box!

다음과 같이 키워드 인수를 사용하려고 하면 위의 솔루션이 중단된다는 점에 유의하는 것이 중요합니다.

 foo(a=[4])

데코레이터는 이를 허용하도록 조정할 수 있지만, 우리는 이것을 독자를 위한 연습으로 남겨둡니다 ;)


Przemek D

TLDR: 정의 시간 기본값은 일관되고 엄격하게 표현됩니다.


함수 정의는 함수를 포함 하는 정의 범위와 함수에 포함된 실행 범위의 두 가지 범위에 영향을 줍니다. 블록이 범위에 매핑되는 방법은 매우 분명하지만 def <name>(<args=defaults>): 속하는 위치는 다음과 같습니다.

 ... # defining scope def name(parameter=default): # ??? ... # execution scope

def name 부분은 정의 범위에서 평가 되어야 합니다 name 을 사용할 수 있기를 원합니다. 자체 내부에서만 함수를 평가하면 액세스할 수 없게 됩니다.

parameter def name 과 동시에 "평가"할 수 있습니다. 이것은 또한 베어 name(...): name(parameter=...): 와 같은 알려진 서명으로 함수를 생성하는 이점이 있습니다.

default 을 평가할 때 ?

일관성은 이미 "정의 시"라고 말합니다. def <name>(<args=defaults>): 다른 모든 것도 정의 시 가장 잘 평가됩니다. 그것의 일부를 연기하는 것은 놀라운 선택이 될 것입니다.

두 가지 선택은 동일하지 않습니다. default 이 정의 시 평가되는 경우 여전히 실행 시간에 영향을 미칠 수 있습니다. default 이 실행 시 평가되는 경우 정의 시간에 영향을 미치지 않습니다. "at definition"을 선택하면 두 경우를 모두 표현할 수 있지만 "at execution"을 선택하면 하나만 표현할 수 있습니다.

 def name(parameter=defined): # set default at definition time ... def name(parameter=default): # delay default until execution time parameter = default if parameter is None else parameter ...

MisterMiyagi

이 질문에 대한 답은 파이썬이 변수에 데이터를 전달하는 방법(값 또는 참조로 전달)이 아니라 변경 가능성이나 파이썬이 "def" 문을 처리하는 방법에 있다고 생각합니다.

간단한 소개입니다. 첫째, 파이썬에는 두 가지 유형의 데이터 유형이 있습니다. 하나는 숫자와 같은 간단한 기본 데이터 유형이고 다른 데이터 유형은 객체입니다. 둘째, 데이터를 매개변수에 전달할 때 파이썬은 기본 데이터 유형을 값으로 전달합니다. 즉, 값의 로컬 복사본을 로컬 변수로 만들지만 객체를 참조로 전달합니다. 즉, 객체에 대한 포인터입니다.

위의 두 가지 점을 인정하면서 파이썬 코드에 무슨 일이 일어났는지 설명해보자. 객체에 대한 참조로 전달하기 때문일 뿐이지만 변경 가능/불변과 관련이 없거나 "def" 문이 정의될 때 한 번만 실행된다는 사실과 관련이 없습니다.

[]는 객체이므로 파이썬은 []의 참조를 a 전달합니다. 즉, a 는 메모리에 객체로 있는 []에 대한 포인터일 뿐입니다. 그러나 많은 참조가 포함된 [] 사본이 하나만 있습니다. 첫 번째 foo()의 경우 list []는 append 메소드에 의해 1로 변경됩니다. 그러나 목록 개체의 복사본은 하나만 있으며 이 개체는 이제 1 이 됩니다. 두 번째 foo()를 실행할 때 effbot 웹 페이지가 말하는 것(항목이 더 이상 평가되지 않음)이 잘못되었습니다. a 는 목록 객체로 평가되지만 이제 객체의 내용은 1 입니다. 이것이 참조로 전달하는 효과입니다! foo(3)의 결과도 같은 방식으로 쉽게 도출할 수 있습니다.

내 대답을 더 확인하기 위해 두 개의 추가 코드를 살펴보겠습니다.

====== 2번 ========

 def foo(x, items=None): if items is None: items = [] items.append(x) return items foo(1) #return [1] foo(2) #return [2] foo(3) #return [3]

[] 는 객체이고 None 마찬가지입니다(전자는 변경 가능하지만 후자는 변경할 수 없습니다. 그러나 변경 가능성은 질문과 아무 관련이 없습니다). None은 공간 어딘가에 있지만 우리는 그것이 거기에 있다는 것을 알고 있으며 거기에는 None의 복사본이 하나만 있습니다. 따라서 foo가 호출될 때마다 항목은 (한 번만 평가된다는 답변과 달리) None으로 평가되며, 명확하게는 None의 참조(또는 주소)입니다. 그런 다음 foo에서 item은 []로 변경됩니다. 즉, 다른 주소를 가진 다른 객체를 가리킵니다.

====== 3번 =======

 def foo(x, items=[]): items.append(x) return items foo(1) # returns [1] foo(2,[]) # returns [2] foo(3) # returns [1,3]

foo(1)를 호출하면 항목이 주소가 11111111인 목록 객체 []를 가리키게 됩니다. 목록의 내용은 속편에서 foo 함수에서 1 로 변경되지만 주소는 변경되지 않고 여전히 11111111입니다. 그러면 foo(2,[])가 옵니다. foo(2,[])의 []는 foo(1)을 호출할 때 기본 매개변수 []와 내용이 같지만 주소가 다릅니다! 매개변수를 명시적으로 제공하기 때문에 items 은 이 새로운 [] 의 주소(예: 2222222)를 가져와야 하고 일부 변경 후 반환해야 합니다. 이제 foo(3)가 실행됩니다. x 만 제공되기 때문에 항목은 다시 기본값을 취해야 합니다. 기본값은 무엇입니까? foo 함수를 정의할 때 설정됩니다. 11111111에 위치한 list 객체 더. 결과적으로 3을 추가하면 items [1,3]이 됩니다.

위의 설명 에서 허용된 답변에서 권장하는 effbot 웹 페이지가 이 질문에 대한 적절한 답변을 제공하지 못했음을 알 수 있습니다. 더군다나 effbot 웹 페이지의 한 지점이 잘못되었다고 생각합니다. UI.Button에 관한 코드가 정확하다고 생각합니다.

 for i in range(10): def callback(): print "clicked button", i UI.Button("button %s" % i, callback)

i 다른 값을 표시하는 고유한 콜백 함수를 보유할 수 있습니다. 나는 이것을 보여주는 예를 제공할 수 있습니다:

 x=[] for i in range(10): def callback(): print(i) x.append(callback)

x[7]() 을 실행하면 예상대로 7을 얻을 수 있고 x[9]() i 또 다른 값을 제공합니다.


user2384994

출처 : http:www.stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument

반응형