“최소한의 놀라움”과 가변적 기본 인수

Jul 16 2009

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라인은 바인딩의 일부라는 의미에서 "하이브리드"가 될 것입니다. 함수 객체)는 정의시 발생하고 일부 (기본 매개 변수 할당)는 함수 호출시 발생합니다.

실제 동작은 더 일관 적입니다. 해당 행이 실행될 때 해당 행의 모든 ​​것이 평가됩니다.

답변

1658 rob Jul 18 2009 at 04:29

사실 이것은 설계상의 결함이 아니며 내부 나 성능 때문도 아닙니다.
파이썬의 함수는 코드의 일부가 아니라 일류 객체라는 사실에서 비롯됩니다.

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

어쨌든 Effbot은 Python의 Default Parameter Values 에서이 동작에 대한 이유를 아주 잘 설명합니다 .
나는 그것이 매우 명확하다는 것을 알았고 함수 객체가 어떻게 작동하는지에 대한 더 나은 지식을 얻으려면 그것을 읽는 것이 좋습니다.

278 EliCourtwright Jul 16 2009 at 01:11

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

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의 현재 접근 방식을 좋아합니다. 기본 함수 인수는 함수가 정의 될 때 평가되고 해당 객체는 항상 기본값입니다. 나는 그들이 빈 목록을 사용하여 특별한 경우를 가질 수 있다고 생각하지만, 그런 종류의 특별한 경우는 이전 버전과 호환되지 않는 것은 말할 것도없고 훨씬 더 놀랍습니다.

244 glglgl Jul 10 2012 at 21:50

문서 의 관련 부분 :

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

def whats_on_the_telly(penguin=None):
    if penguin is None:
        penguin = []
    penguin.append("property of the zoo")
    return penguin
121 Utaal Jul 16 2009 at 06:21

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

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

a = []

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

왜해야 a=[]

def x(a=[]):

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

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

사용자, a정의하거나 실행할 때에 해당하는 datetime을 기본값으로 설정 x하시겠습니까? 이 경우 이전의 경우와 마찬가지로 기본 인수 "할당"이 함수의 첫 번째 명령 ( datetime.now()함수 호출시 호출 됨) 인 것처럼 동일한 동작을 유지합니다 . 반면에 사용자가 정의 시간 매핑을 원하면 다음과 같이 작성할 수 있습니다.

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

알아요, 알아요 : 그건 종결입니다. 또는 Python은 정의 시간 바인딩을 강제하는 키워드를 제공 할 수 있습니다.

def x(static a=b):
83 LennartRegebro Jul 16 2009 at 01:54

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

이것을 비교하십시오 :

class BananaBunch:
    bananas = []

    def addBanana(self, banana):
        self.bananas.append(banana)

이 코드는 똑같은 예상치 못한 우연을 겪습니다. bananas는 클래스 속성이므로 여기에 항목을 추가하면 해당 클래스의 모든 인스턴스에 추가됩니다. 그 이유는 똑같습니다.

그것은 단지 "작동 방식"일 뿐이며, 함수 케이스에서 다르게 작동하도록 만드는 것은 아마도 복잡 할 것이고, 클래스 케이스에서는 불가능할 것입니다. 또는 적어도 클래스 코드를 유지해야하므로 객체 인스턴스화 속도를 많이 늦추십시오. 객체가 생성 될 때 실행합니다.

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

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

69 DimitrisFasarakisHilliard Dec 09 2015 at 14:13

반성하지 그래?

정말 파이썬에서 제공하는 통찰력 성찰을 수행 (한 사람을 놀라게하지 23callables에 적용을).

func다음과 같이 정의 된 간단한 작은 함수가 주어 집니다.

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

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

따라서 함수 객체 에서 목록이 확장되는 방식을 조사하기 위해 이전과 이후에 몇 가지 introspection을 수행해 보겠습니다 . 나는 Python 3.x이것을 위해 사용 하고 있는데 , 파이썬 2의 경우에도 동일하게 적용됩니다 ( __defaults__또는 func_defaults파이썬 2에서; 예, 같은 것에 대해 두 개의 이름).

실행 전 기능 :

>>> 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의을 반환하도록 함수를 변경 하면됩니다. 그런 다음에있는 목록과 비교 (위치 에서 ) 이러한 실제로 동일한 목록 인스턴스로 다스 려하는 방법은 다음과 같이 표시됩니다ida__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가 만들어 지기 전에 호출 됩니다.

59 Brian Jul 16 2009 at 17:05

나는 런타임에 객체를 만드는 것이 더 나은 접근 방식이라고 생각했습니다. 초보자의 혼란을 막기 위해 그만한 가치가 있지만 유용한 기능을 잃어 버리기 때문에 지금은 확신이 없습니다. 이렇게하면 단점은 다음과 같습니다.

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=[]

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

55 LutzPrechelt Mar 30 2015 at 18:18

파이썬 방어를위한 5 가지 포인트

  1. 단순성 : 행동은 다음과 같은 의미에서 간단합니다. 대부분의 사람들은이 함정에 몇 번이 아니라 한 번만 빠집니다.

  2. 일관성 : 파이썬은 항상 이름이 아닌 객체를 전달합니다. 기본 매개 변수는 분명히 함수 제목의 일부입니다 (함수 본문이 아님). 따라서 함수 호출 시가 아니라 모듈로드시 (중첩되지 않는 한 모듈로드시에만) 평가되어야합니다.

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

  4. 충분한 문서 : 가장 기본적인 Python 문서 인 튜토리얼에서이 문제는 "More on Defining Functions" 섹션의 첫 번째 하위 섹션 에서 "중요 경고" 로 큰 소리로 발표됩니다 . 경고는 굵은 글씨도 사용하며 제목 외부에는 거의 적용되지 않습니다. RTFM : 훌륭한 매뉴얼을 읽으십시오.

  5. 메타 학습 : 함정에 빠지는 것은 실제로 매우 도움이되는 순간입니다 (적어도 당신이 성찰적인 학습자라면). 나중에 위의 "일관성"을 더 잘 이해하고 파이썬에 대해 많은 것을 배울 수 있기 때문입니다.

53 ymv Jul 16 2009 at 02:15

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

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

그래서:

def x(a=0, b=[], c=[], d=0):
    a = a + 1
    b = b + [1]
    c.append(1)
    print a, b, c
  1. a 변경되지 않음-모든 할당 호출은 새로운 int 객체를 생성합니다-새로운 객체가 인쇄됩니다
  2. b 변경되지 않음-새 배열이 기본값에서 빌드되고 인쇄됩니다.
  3. c 변경-동일한 개체에 대해 작업이 수행되고 인쇄됩니다.
35 GlennMaynard Jul 16 2009 at 03:18

당신이 묻는 것은 왜 이것이 냐는 것입니다.

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)을 호출하는 경우를 제외하고는 무시합니다.

즉, 기본 매개 변수를 평가하는 대신 각 매개 변수를 저장하고 함수가 호출 될 때 평가하는 것이 어떻습니까?

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

35 hynekcer Nov 23 2012 at 01:09

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]

이 함수에 의해 반환 된 인스턴스의 공개 속성에 저장되어서는 안됩니다 . ( 인스턴스의 개인 속성이 관례에 따라이 클래스 또는 하위 클래스 외부에서 수정되어서는 안된다고 가정합니다 . 즉 _var1, 개인 속성입니다)

결론 :
입력 매개 변수 객체는 제자리에서 수정 (변형)하거나 함수가 반환하는 객체에 바인딩되어서는 안됩니다. (강하게 권장되는 부작용없는 프로그래밍을 선호하는 경우 "부작용"에 대한 Wiki를 참조하십시오 (이 문맥에서 처음 두 단락은 관련이 있습니다.)).

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

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

31 Ben May 23 2011 at 11:24

이것은 실제로 기본값과 관련이 없습니다. 변경 가능한 기본값을 사용하여 함수를 작성할 때 종종 예기치 않은 동작으로 나타나는 것 외에는 다릅니다.

>>> 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은 명시 적으로 전달되었는지 또는 기본값을 얻었는지 여부를 수정해서는 안됩니다 . 컨텍스트 / 이름 / 문서에서 인수가 수정되어야한다는 것이 명확하지 않은 경우 코드는 가변 인수를 그대로 두어야합니다. 인수로 전달 된 가변 값을 로컬 임시로 사용하는 것은 우리가 Python을 사용하는지 여부와 관련 기본 인수가 있는지 여부에 관계없이 매우 나쁜 생각입니다.

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

27 Stéphane Mar 27 2015 at 06:14

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

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
25 JasonBaker Jul 16 2009 at 06:18

성능 최적화입니다. 이 기능의 결과로이 두 함수 호출 중 어느 것이 더 빠르다고 생각합니까?

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에서는 이러한 이점이 없습니다.

25 AaronHall May 01 2016 at 23:20

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']

이것이 "최소한 경악"의 원칙에 위배됩니까?

이 실행 순서는 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
24 Baczek Jul 16 2009 at 19:19

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

def a(): return []

def b(x=a()):
    print x

def명령문 실행시 기본 인수 표현식을 실행 하지 않는 것이 쉽지 않거나 말이되지 않거나 둘 다임을 보여주는 것으로 충분 합니다.

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

21 DmitryMinkovsky Apr 25 2012 at 02:43

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

  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.
20 hugo24 Feb 28 2013 at 18:10

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

>>> 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]
19 Alexander Sep 12 2015 at 13:00

기본 목록 값을 함수에 전달하는 대체 구조를 보여 드리겠습니다 (사전과 동일하게 작동 함).

다른 사람들이 광범위하게 주석을 달았 듯이 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의 "효과적인 Python : 더 나은 Python을 작성하는 59 가지 특정 방법", 항목 20 : None동적 기본 인수를 지정하는 데 사용 및 독 스트링 (p. 48)

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

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

선호하는 방법 :

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]

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

17 Marcin Mar 21 2012 at 00:22

여기에 해결책은 다음과 같습니다.

  1. 사용 None기본 값 (또는 비표로 object런타임에 당신의 가치를 창출하는에), 및 스위치; 또는
  2. a lambda를 기본 매개 변수로 사용하고 try 블록 내에서 호출하여 기본값을 가져옵니다 (이는 람다 추상화가 사용하는 일종의 것입니다).

함수의 사용자는 호출 가능한 전달할 수 있기 때문에, 두 번째 옵션은 이미 좋은 (예로서 존재 될 수있는 type)

16 joedborg Jan 15 2013 at 18:02

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

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

못 생겼지 만 작동합니다.

16 Saish Sep 12 2014 at 05:05

우리가 이것을 할 때 :

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

... 호출자가 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]
16 bgreen-litl Feb 06 2015 at 04:44

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

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

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

이 패턴은 잘 알려져 있지 않다고 생각하기 때문에 향후 오해를 방지하기 위해 짧은 댓글을 달았습니다.

13 ChristosHayward Jul 17 2009 at 02:17

다음이 사실 일 수 있습니다.

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

위의 두 기능을 모두 유지하면서 또 다른 요점을 만드는 것은 전적으로 일관성이 있습니다.

  1. 혼란스러운 기능이며 Python에서는 불행합니다.

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

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

기존의 동작은 Pythonic이 아니며 Python은 성공한 이유는 언어에 대한 거의 모든 것이이 근처의 어느 곳에서나 최소한의 경악의 원칙을 위반하기 때문 입니다. 근절하는 것이 현명하든 아니든 그것은 진정한 문제입니다. 디자인 결함입니다. 동작을 추적하여 언어를 훨씬 더 잘 이해한다면 C ++이이 모든 것을 처리한다고 말할 수 있습니다. 예를 들어 미묘한 포인터 오류를 탐색하여 많은 것을 배웁니다. 그러나 이것은 Pythonic이 아닙니다. Python이 다른 언어보다 훨씬 적은 놀라움을 가지고 있기 때문에 이러한 행동에 대해 인내 할 수있을만큼 Python에 관심이있는 사람들은 언어에 끌리는 사람들입니다. Dabblers와 호기심 많은 사람들은 Python에 끌리는 프로그래머의 직관에 반하는 디자인 fl 때문이 아니라 숨겨진 논리 퍼즐 때문이 아니라 작업을 수행하는 데 걸리는 시간이 얼마나 적은지에 놀랐을 때 Pythonistas가됩니다. 그냥 작동 하기 때문 입니다.

10 MarkRansom Oct 18 2017 at 00:38

이것은 설계상의 결함이 아닙니다 . 이 문제를 겪는 사람은 누구나 뭔가 잘못하고 있습니다.

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

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

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

9 Norfeldt Jul 22 2013 at 14:35

이 "버그"는 나에게 많은 초과 근무 시간을 주었다! 그러나 나는 그것의 잠재적 인 사용을보기 시작했다 (그러나 나는 그것이 실행 시점에 있었으면 좋았을 것이다.)

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

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
8 ytpillai May 26 2015 at 06:04

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

def notastonishinganymore(a = []): 
    '''The name is just a joke :)'''
    a = a[:]
    a.append(5)
    return a
7 user2384994 Aug 22 2013 at 12:58

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

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

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

[]은 객체이므로 파이썬은 []의 참조를에 전달합니다 a. 즉, a객체로 메모리에있는 []에 대한 포인터 일뿐입니다. 그러나 많은 참조가있는 []의 복사본이 하나뿐입니다. 첫 번째 foo ()의 경우, 목록 []은 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 복사본이 하나뿐입니다. 따라서 foo가 호출 될 때마다 항목은 없음 (한 번만 평가되는 일부 답변과 달리)이 없음, 즉 없음의 참조 (또는 주소)로 평가됩니다. 그런 다음 foo에서 항목이 []로 변경됩니다. 즉, 다른 주소를 가진 다른 객체를 가리 킵니다.

====== No. 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)가 실행됩니다. only x가 제공되므로 항목은 다시 기본값을 가져와야합니다. 기본값은 무엇입니까? 11111111에 위치한 목록 객체 인 foo 함수를 정의 할 때 설정됩니다. 따라서 항목은 요소 1이있는 주소 11111111로 평가됩니다. 2222222에 위치한 목록은 하나의 요소 2도 포함하지만 항목에 의해 가리켜지지 않습니다. 더. 따라서 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]()의지의 또 다른 값을 9 제공합니다 i.

6 MisterMiyagi Dec 15 2018 at 19:09

TLDR : 정의 시간 기본값은 일관되고 엄격하게 표현력이 뛰어납니다.


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

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

def name부분이 있어야 정의하는 범위에서 평가 - 우리가 원하는 name모든 후, 거기에 사용할 수. 내부에서만 함수를 평가하면 액세스 할 수 없게됩니다.

parameter은 상수 이름 이므로 와 동시에 "평가"할 수 있습니다 def name. 이것은 또한 name(parameter=...):베어 대신으로 알려진 시그니처를 사용하여 함수를 생성하는 이점이 있습니다 name(...):.

이제 언제 평가 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
    ...
4 PrzemekD Jan 03 2019 at 14:38

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

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

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!

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

foo(a=[4])

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