"최소한의 놀라움"과 변하기 쉬운 기본 주장


2593

파이썬으로 오랫동안 땜질을하는 사람은 다음과 같은 문제로 물렸거나 조각났습니다.

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

파이썬 초보자는이 함수가 항상 하나의 요소로만 목록을 반환 할 것으로 기대합니다 [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있는 것을 의미합니다. 함수 객체)는 정의시 발생하고 함수 호출시 부분 (기본 매개 변수 할당)이 발생합니다.

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



4
나는 변덕스러운 주장이 보통 사람에게는 최소한 놀랍게도되는 원칙을 위반한다는 것을 의심하지 않으며, 초보자들이 그곳에 들어서서 메일 링리스트를 메일 링 튜플로 영웅적으로 대체하는 것을 보았습니다. 그럼에도 불구하고 변경 가능한 인수는 여전히 Python Zen (Pep 20)과 일치하며 "네덜란드어에게는 명백하다"(하드 코어 Python 프로그래머가 이해 / 탐색) 조항에 해당합니다. doc 문자열에 대한 권장 해결 방법은 최고이지만 doc 문자열에 대한 내성이며 오늘날 (작성된) 문서는 그리 일반적이지 않습니다. 개인적으로 나는 데코레이터를 선호합니다 (예 : @fixed_defaults).
서지

5
이 문제에 대한 나의 주장은 "왜 선택적으로 변경 가능한 변수 일 수있는 변경 가능한 변수를 반환하는 함수를 만들어야합니까? 함수에 전달할 것입니까? 코드 하나에 세 줄을 추가하지 않고 인터프리터를 다시 작성해야하는 이유는 무엇입니까? " 여기서 인터프리터가 함수 정의와 호출을 처리하는 방식을 다시 작성하는 것에 대해 이야기하고 있기 때문입니다. 간신히 필요한 사용 사례를 위해해야 ​​할 일이 많습니다.
Alan Leuthard 2016 년

12
"Python 초보자는이 함수가 항상 하나의 요소 만있는 목록을 반환 할 것으로 기대합니다 [5]." 나는 파이썬 초보자 해요, 분명히 때문에,이를 기대하지 않을 것이다 foo([1])반환 [1, 5]하지 [5]. 당신이 말하고자하는 것은 초보자는 매개 변수없이 호출 된 함수 가 항상 리턴 할 것이라고 예상한다는 것 [5]입니다.
symplectomorphic

2
이 질문은 "왜이 방법이 잘못 구현 되었습니까?" "올바른 방법은 무엇입니까?"라고 묻지 않습니다. [ arg = None을 사용하면 왜 파이썬의 가변 기본 인수 문제를 해결합니까? ] * ( stackoverflow.com/questions/10676729/… ). 신규 사용자는 거의 항상 전자에 관심이없고 후자는 훨씬 더 관심이 많기 때문에 인용 할 때 매우 유용한 링크 / 속임수입니다.
smci

답변:


1612

실제로 이것은 디자인 결함이 아니며 내부 또는 성능 때문이 아닙니다.
파이썬의 함수는 코드가 아니라 일류 객체라는 사실에서 비롯됩니다.

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

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


80
위의 답변을 읽는 사람에게는 링크 된 Effbot 기사를 읽는 것이 좋습니다. 다른 유용한 정보뿐만 아니라이 언어 기능을 결과 캐싱 / 메모리에 사용하는 방법에 대한 부분은 매우 편리합니다.
Cam Jackson

85
비록 그것이 일류 객체 일지라도, 각각의 디폴트 값에 대한 코드 가 객체와 함께 저장되고 함수가 호출 될 때마다 재평가 되는 디자인을 여전히 구상 할 수있다 . 나는 그것이 더 좋을 것이라고 말하지 않고 단지 일류 객체 인 함수가 그것을 완전히 배제하지는 않습니다.
gerrit

312
죄송하지만 "Python에서 가장 큰 WTF"로 간주 되는 것은 디자인 결함 입니다. 이것은 처음에는 그 행동을 기대하는 사람이 없기 때문에 어느 시점 에서든 모두에게 버그의 원인이 됩니다. 나는 그들이 어떤 농구 대를 뛰어 넘 었는지 상관하지 않고 기본 인수가 비 정적이되도록 Python을 설계 해야합니다 .
BlueRaja-대니 Pflughoeft

192
그것이 디자인 결함인지 여부에 관계없이, 당신의 대답은 함수가 일류 객체이며 단순히 그렇지 않다는 것을 감안할 때이 행동이 어떻게 든 자연스럽고 명백하다는 것을 암시하는 것처럼 보입니다. 파이썬에는 클로저가 있습니다. 기본 인수를 함수의 첫 번째 행에 대한 지정으로 바꾸면 각 호출의 표현식을 평가합니다 (잠재적 범위에 선언 된 이름을 사용하여). 함수가 정확히 같은 방식으로 호출 될 때마다 기본 인수를 평가하는 것이 불가능하거나 합리적이지 않을 이유가 전혀 없습니다.
Mark Amery

24
디자인은에서 직접 이어지지 않습니다 functions are objects. 패러다임에서는 함수의 기본값을 속성이 아닌 속성으로 구현하는 것이 좋습니다.
bukzor

273

다음 코드가 있다고 가정하십시오.

fruits = ("apples", "bananas", "loganberries")

def eat(food=fruits):
    ...

내가 먹는 선언을 볼 때 가장 놀라운 것은 첫 번째 매개 변수가 주어지지 않으면 튜플과 같다고 생각하는 것입니다. ("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의 현재 접근 방식을 좋아합니다. 기본 함수 인수는 함수가 정의되고 해당 객체가 항상 기본값 일 때 평가됩니다. 나는 그들이 빈 목록을 사용하여 특별한 경우를 할 수 있다고 생각하지만, 그런 종류의 특별한 케이싱은 더 이상 놀랍게 만들지 않을 것입니다.


30
나는 그것이 논쟁의 문제라고 생각합니다. 전역 변수로 작업하고 있습니다. 전역 변수와 관련된 코드의 모든 위치에서 수행 된 모든 평가는 이제 "올바로"( "블루 베리", "망고")를 참조합니다. 기본 매개 변수는 다른 경우와 유사 할 수 있습니다.
Stefano Borini

47
사실, 나는 당신의 첫 번째 예에 동의하지 않는다고 생각합니다. 처음에는 초기화를 수정하는 아이디어가 마음에 들지 않지만, 그렇게하면 기본값을로 변경하여 설명대로 정확하게 작동 할 것으로 기대합니다 ("blueberries", "mangos").
벤 블랭크

12
기본 매개 변수 다른 경우와 같습니다. 예기치 않은 것은 매개 변수가 전역 변수이며 로컬 변수가 아니라는 것입니다. 이는 코드가 호출이 아닌 함수 정의에서 실행되기 때문입니다. 일단 당신이 그것을 얻었고, 수업에도 똑같이 적용되면, 그것은 분명합니다.
Lennart Regebro

17
나는 예가 현명하지 않고 오도하는 것을 발견했다. 경우 some_random_function()추가의 정보는 다음의 제품에 fruits대신에 할당하는,의 동작 eat() 것이다 변경합니다. 현재 훌륭한 디자인을 위해 너무 많은. 다른 곳에서 참조되는 기본 인수를 사용하고 함수 외부에서 참조를 수정하면 문제가 발생합니다. 실제 WTF는 사람들이 새로운 기본 인수 (목록 리터럴 또는 생성자에 대한 호출)를 정의하고 여전히 비트를 얻는 경우입니다.
Alexis

13
global튜플을 명시 적으로 선언 하고 재 지정 eat했습니다. 그 후에 다르게 작동 해도 놀라운 것은 없습니다 .
user3467349

241

문서 의 관련 부분 :

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

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

180
"이것은 일반적으로 의도 된 것이 아니다"라는 문구와 "이 문제를 해결하는 방법"은 디자인 결함을 문서화하는 것처럼 냄새가납니다.
bukzor

4
@ Matthew : 나는 잘 알고 있지만 함정의 가치가 없습니다. 일반적으로 이러한 이유로 스타일 가이드와 린터가 변경 가능한 기본값을 무조건 플래그로 표시합니다. 똑같은 일을하는 명시적인 방법은 함수에 속성을 채우 function.data = []거나 객체를 만드는 것입니다.
bukzor

6
@bukzor : 함정에 대해 기록하고 문서화해야하므로이 질문이 좋으며 많은 찬사를 받았습니다. 동시에 함정을 제거 할 필요는 없습니다. 얼마나 많은 Python 초보자가 목록을 수정 한 함수에 목록을 전달했으며 원래 변수에 변경 사항이 표시되는 것에 충격을 받았습니까? 그러나 변경 가능한 객체 유형은 사용 방법을 이해할 때 훌륭합니다. 이 함정에 대한 의견으로 귀결됩니다.
Matthew

33
"이것은 일반적으로 의도 된 것이 아닙니다"라는 문구는 "프로그래머가하지 말아야 할 것이 아니라", "프로그래머가 실제로 원하는 것을하지 않는 것"을 의미합니다.
holdenweb 2014

4
@holdenweb 와우, 나는 파티에 초대형입니다. 문맥을 감안할 때 bukzor는 완전히 옳습니다. 언어가 함수 정의를 실행해야한다고 결정할 때 의도하지 않은 행동 / 결과를 문서화하고 있습니다. 의도하지 않은 디자인 선택의 결과이기 때문에 디자인 결함입니다. 디자인 결함이 아니라면 "이 문제를 해결하는 방법"을 제공 할 필요도 없습니다.
code_dredd

118

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

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

a = []

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

왜해야 a=[]

def x(a=[]):

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

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

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

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

나는 알고있다 : 그것은 폐쇄이다. 또는 파이썬은 정의 시간 바인딩을 강제하는 키워드를 제공 할 수 있습니다.

def x(static a=b):

11
def x (a = None) : a가 None이면 a = datetime.datetime.now ()
Anon

20
감사합니다. 나는 왜 이것이 끝이 없는지에 손가락을 넣을 수 없었다. 당신은 최소한의 퍼지와 혼란으로 아름답게했습니다. 누군가가 C ++로 시스템 프로그래밍을하고 언어 특성을 순진하게 "번역"하는 것처럼,이 잘못된 친구는 클래스 속성처럼 머릿속에서 큰 시간을 보냈습니다. 나는 왜 이런 일이 일어나는지 이해하지만, 긍정적 인 결과가 나오더라도 도움을 줄 수는 없지만 싫어합니다. 적어도 그것은 내 경험과는 정반대로, 아마 (희망스럽게도) 결코 잊지 못할 것입니다 ...
AndreasT

5
@Andreas 일단 Python을 오랫동안 사용하면 파이썬이 사물을 클래스 속성으로 해석하는 것이 얼마나 논리적인지 알기 시작합니다 .C ++ (및 Java 및 C # ...) class {}블록의 내용이 인스턴스에 속하는 것으로 해석되는 것이 의미 가 있음을 알 수 있습니다. 그러나 클래스가 일류 객체 인 경우 분명히 내용은 메모리에 내용이 반영되는 것이 분명합니다. (코드로).
Karl Knechtel

6
내 책에서 규범 적 구조는 당연하거나 제한되지 않습니다. 나는 그것이 서투르고 추악 할 수 있다는 것을 알고 있지만, 그것을 무언가의 "정의"라고 부를 수 있습니다. 역동적 인 언어는 나에게 무정부주의자처럼 보입니다. 모두가 자유롭지 만 누군가가 쓰레기를 비우고 도로를 포장하도록 구조가 필요합니다. 내가
늙었다 고 생각한다

4
기능 정의 는 모듈로드시 실행됩니다. 함수 본문 은 함수 호출시 실행됩니다. 기본 인수는 함수 본문이 아닌 함수 정의의 일부입니다. (내포 된 함수의 경우 더 복잡해집니다.)
Lutz Prechelt

84

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

이것을 비교하십시오 :

class BananaBunch:
    bananas = []

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

이 코드는 정확히 예상치 못한 상황이 발생합니다. bananas는 클래스 속성이므로 항목을 추가하면 해당 클래스의 모든 인스턴스에 추가됩니다. 그 이유는 정확히 같습니다.

그것은 단지 "어떻게 작동 하는가"일 뿐이며, 함수 케이스에서 다르게 작동하게 만드는 것은 아마도 복잡 할 것이고 클래스 케이스에서는 불가능할 것입니다. 객체가 생성되면 실행합니다.

그렇습니다. 그러나 페니가 떨어지면 파이썬의 일반적인 작동 방식과 완벽하게 맞습니다. 사실, 그것은 훌륭한 교육 보조 자료이며, 왜 이런 일이 발생했는지 이해하면 파이썬을 훨씬 더 잘 이해할 것입니다.

그것은 훌륭한 파이썬 튜토리얼에서 두드러지게 등장해야한다고 말했습니다. 언급했듯이 모든 사람이 조만간이 문제에 부딪칩니다.


클래스의 각 인스턴스마다 다른 클래스 속성을 어떻게 정의합니까?
Kieveli

19
각 인스턴스마다 다르면 클래스 속성이 아닙니다. 클래스 속성은 CLASS의 속성입니다. 따라서 이름입니다. 따라서 모든 인스턴스에서 동일합니다.
Lennart Regebro

1
클래스의 각 인스턴스마다 다른 클래스에서 속성을 어떻게 정의합니까? (파이썬 명명 규칙에 익숙하지 않은 사람이 클래스의 일반 멤버 변수에 대해 묻는 것일 수 있음을 결정할 수없는 사람들을 위해 재정의되었습니다).
Kieveli

@ Kivieli : 클래스의 일반적인 멤버 변수에 대해 이야기하고 있습니다. :-) 모든 메소드에서 self.attribute = value를 말하여 인스턴스 속성을 정의합니다. 예를 들어 __init __ ()입니다.
Lennart Regebro

@Kieveli : 두 가지 답변 : 클래스 수준에서 정의한 것은 클래스 속성이되고 해당 속성에 액세스하는 모든 인스턴스는 동일한 클래스 속성에 액세스하므로 할 수 없습니다. propertys-실제로는 일반 속성처럼 작동하지만 클래스 대신 인스턴스에 속성을 저장하는 클래스 수준 함수 인 s 를 사용하여 / sort of /를 수행 할 수 있습니다 ( self.attribute = valueLenart가 말한대로).
Ethan Furman

66

왜 당신은 내성하지?

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

func다음과 같이 정의 된 간단한 작은 함수가 있습니다.

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

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

이제 함수 객체 에서 목록이 어떻게 확장되는지 조사하기 전과 후에 약간의 내부 검사를 해 봅시다 . 나는 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

내성의 힘을 가진 모든 것!


* 함수 컴파일 중 파이썬이 기본 인수를 평가하는지 확인하려면 다음을 실행하십시오.

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

아시다시피 input()함수를 빌드하고 이름에 바인딩하는 프로세스 전에 호출 bar됩니다.


1
되어 id(...)마지막 검증에 필요한, 또는 것 is운영자는 같은 질문에 대답?
das-g

1
@ das-g is는 잘 작동 id(val)할 것입니다. 더 직관적이라고 생각하기 때문에 방금 사용 했습니다.
Dimitris Fasarakis Hilliard

None기본값으로 사용 하면 __defaults__내부 검사 의 유용성이 심각하게 제한 되므로 __defaults__작업 방식을 방어하는 데 효과적이라고 생각 하지 않습니다. 지연 평가는 기능 기본값을 양쪽에서 유용하게 유지하기 위해 더 많은 작업을 수행합니다.
Brilliand

58

나는 런타임에 객체를 만드는 것이 더 나은 방법이라고 생각했습니다. 몇 가지 유용한 기능을 잃어 버렸으므로 초보자는 혼란스럽지 않습니다. 그렇게하는 단점은 다음과 같습니다.

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

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


1
각각에 대해 값 대신 기본 인수를 작성하는 기능이 있으면 내부 검사를 수행 할 수 있습니다. inspect 모듈은 해당 함수를 호출합니다.
yairchu

@SilentGhost : 행동을 재현하기 위해 변경되었는지에 대해 이야기하고 있습니다. 한 번 만드는 것이 현재 행동이며 변경 가능한 기본 문제가있는 이유입니다.
Brian

1
@ yairchu : 그것은 안전하다고 가정합니다 (즉, 부작용이 없음). 인수를 조사하는 것은 아무 것도 하지 않아야 하지만 임의의 코드를 평가하면 결과가 영향을 줄 수 있습니다.
Brian

1
다른 언어 디자인은 종종 물건을 다르게 쓰는 것을 의미합니다. 첫 번째 예제는 다음과 같이 쉽게 작성할 수 있습니다. _expensive = expensive (); def foo (arg = _expensive), 구체적으로 재평가 하지 않으려는 경우.
Glenn Maynard

@Glenn-이것이 "변수를 외부 적으로 캐시"로 언급 한 것입니다. 좀 더 장황하며 네임 스페이스에 추가 변수가 생깁니다.
Brian

55

파이썬 방어에서 5 포인트

  1. 단순성 :이 동작은 다음과 같은 의미에서 간단합니다. 대부분의 사람들은이 함정에 몇 번만 빠지지 않습니다.

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

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

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

  5. 메타 학습 : 함정에 빠지는 것은 실제로 매우 유용한 순간입니다 (적어도 반사 학습자 인 경우). 위의 "일관성"점을 더 잘 이해하고 파이썬에 대해 많은 것을 가르쳐 줄 것이기 때문입니다.


18
이 동작이 프로덕션에서 내 코드를 망쳐 놓고 우연히이 디자인 결함에 부딪 칠 때까지 완전한 기능을 제거하는 데 1 년이 걸렸습니다. 장고를 사용하고 있습니다. 준비 환경에는 요청이 많지 않았으므로이 버그는 QA에 영향을 미치지 않았습니다. 우리가 실시간으로 많은 동시 요청을 받았을 때-일부 유틸리티 기능이 서로의 매개 변수를 덮어 쓰기 시작했습니다! 보안 허점, 버그 등을 만듭니다.
oriadam

7
@oriadam, 위법은 없지만 이전에 이것을 보지 않고 어떻게 파이썬을 배웠는지 궁금합니다. 나는 지금 파이썬을 배우고 있으며이 가능한 함정은 공식 파이썬 튜토리얼 에서 기본 인수에 대한 첫 번째 언급과 함께 언급되었습니다. (이 답변의 4 점에서 언급 한 바와 같이) 나는 도덕이 생산적인 소프트웨어를 만드는 데 사용 하는 공식 언어 문서 를 읽는 것보다는 비대칭 적이라고 생각합니다 .
와일드 카드

또한 내가 만들고있는 함수 호출 외에도 알 수없는 복잡한 함수가 호출되면 놀랍습니다.
Vatine

52

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

  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 변경-동일한 객체에서 작업이 수행되고 인쇄됩니다.

(실제로 add 는 좋지 않은 예이지만 정수는 여전히 불변입니다.)
Anon

b를 []로 설정하면 b .__ add __ ([1])은 [1]을 반환하지만 목록이 변경 가능하더라도 b는 여전히 []로 남아 있는지 확인한 후 내 chagrin에 인식했습니다. 내 잘못이야.
Anon

@ANon :이 __iadd__있지만 int에서는 작동하지 않습니다. 물론이야. :-)
Veky

35

당신이 묻는 것은 이것이 왜 그런지입니다.

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

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

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


6
클로저 일 필요는 없습니다. 더 나은 생각은 바이트 코드가 기본값을 코드의 첫 줄로 만드는 것입니다. 어쨌든 그 시점에서 본문을 컴파일 한 후에는 코드간에 실제 차이가 없습니다 본문의 논쟁과 코드에서.
Brian

10
사실, 그것은 여전히 ​​파이썬을 느리게 할 것입니다. 클래스 정의에 대해 똑같이하지 않으면 실제로 놀랍습니다. 수업. 언급 한 바와 같이, 수정은 문제보다 더 놀라운 것입니다.
Lennart Regebro

Lennart와 동의했습니다. Guido는 모든 언어 기능 또는 표준 라이브러리에 대해 그것을 좋아하는 사람 이 있습니다.
Jason Baker

6
지금 바꾸는 것은 광기 일 것입니다. 우리는 그것이 왜 그런지 탐험하고 있습니다. 기본 평가가 늦게 시작했다면 반드시 놀라운 것은 아닙니다. 구문 분석의 이러한 핵심이 언어 전체에 영향을 미치며 어쩌면 많은 모호한 영향을 미쳤을 것입니다.
Glenn Maynard

35

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]

이 함수가 반환 한 인스턴스의 public 속성에 저장해서는 안됩니다 . ( 인스턴스의 프라이빗 속성은 규칙에 따라이 클래스 또는 서브 클래스 외부에서 수정해서는 안된다고 가정합니다 . 즉 _var1, 프라이빗 속성입니다)

결론 :
입력 매개 변수 개체는 수정하거나 변경하지 않아야하며 함수에서 반환 한 개체에 바인딩해서는 안됩니다. (부작용이없는 프로그래밍을 선호하는 경우 강력히 권장됩니다. "부작용"에 대한 위키를 참조하십시오 (이 문맥에서 처음 두 단락은 관련됨).)

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

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


5
파이썬이 함수형 프로그래밍 언어 가 아님을 알고 있기를 바랍니다 .
Veky

6
예, 파이썬은 몇 가지 기능적 특징을 가진 다중 언어 언어입니다. ( "해머가 있다고해서 모든 문제를 못처럼 보이게하지는 마십시오." 파이썬에는 흥미로운 하우투 기능 프로그래밍 기능이 있습니다. 여기에 언급되지 않은 다른 기능으로는 클로저 및 카레가 있습니다.
hynekcer 2016 년

1
또한이 후기 단계에서 Python의 할당 의미 체계는 필요한 경우 데이터 복사를 피하도록 명시 적으로 설계되었으므로 복사본 (특히 딥 카피)을 만들면 런타임 및 메모리 사용량에 부정적인 영향을 미칩니다. 따라서 필요할 때만 사용해야하지만 신입생은 종종 그 시점을 이해하기가 어렵습니다.
holdenweb

1
@holdenweb 동의합니다. 임시 사본은 가장 일반적인 방법이며 때로는 원본 가변 데이터를 잠재적으로 수정하는 외부 함수로부터 보호하는 유일한 방법입니다. 다행스럽게도 데이터를 불합리하게 수정하는 기능은 버그로 간주되므로 일반적이지 않습니다.
hynekcer

이 답변에 동의합니다. 그리고 나는 def f( a = None )당신이 정말로 다른 것을 의미 할 때 그 구조가 권장되는 이유를 이해하지 못합니다 . 복사는 괜찮습니다. 인수를 변경해서는 안됩니다. 그리고 당신이 할 때 if a is None: a = [1, 2, 3]어쨌든 목록을 복사합니다.
koddo

30

이것은 변경 가능한 기본값으로 함수를 작성할 때 종종 예기치 않은 동작으로 나타나는 것 외에는 기본값과 아무 관련이 없습니다.

>>> 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은 명시 적으로 전달되었는지 또는 기본값을 얻었는지 수정해서는 안됩니다 . 문맥 / 이름 / 문서에서 인수가 수정되어야한다는 것이 명확하지 않으면 코드는 가변 인수 만 남겨 두어야합니다. 지역 임시로 인수로 전달 된 가변 값을 사용하는 것은 파이썬에 있든 없든 기본 인수가 관련되어 있는지 여부에 관계없이 매우 나쁜 생각입니다.

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


7
관련이 있지만, 이는 "제자리에서" append변경 될 것으로 예상되는 고유 한 동작이라고 생각합니다 a. 각 호출 에서 기본 변경 가능 변수가 다시 인스턴스화되지 않는다는 것은 적어도 "나에게 예상치 못한"비트입니다. :)
Andy Hayden

2
함수가되는 경우 @AndyHayden 예상 인수를 수정, 왜 기본을 가지고 감각을 만들 것?
마크 랜섬

@MarkRansom 내가 생각할 수있는 유일한 예는 cache={}입니다. 그러나,이 "최소한의 놀랍게도"가 나타나는 것은 인수를 변경하기 위해 호출하는 함수를 기대 하지 않거나 원하지 않을 때 입니다.
Andy Hayden

1
@AndyHayden 나는 그 정서의 확장과 함께 여기에 내 자신의 대답을 남겼습니다. 당신이 무슨 생각을하는지 제게 알려주세요. cache={}완벽을 기하기 위해 귀하의 예를 추가 할 수 있습니다 .
Mark Ransom

1
@AndyHayden 내 대답의 요점은 실수로 인수의 기본값을 변경하여 놀랐다면 다른 버그가 있다는 것 입니다. 기본값 사용 하지 않을 때 코드가 실수로 호출자의 값을 변경시킬 수 있다는 것 입니다. Nonearg가 None 문제를 해결하지 못하면 실제 기본값 을 사용 하고 할당하는 것이 좋습니다 (그 이유 때문에 반 패턴으로 간주합니다). 기본값이 있는지 여부에 관계없이 인수 값을 변경하지 않고 다른 버그를 수정하면이 "놀라운"동작에 대해 신경 쓰거나 신경 쓰지 않을 것입니다.
Ben

27

이미 바쁜 주제이지만 여기서 읽은 내용을 통해 다음과 같은 내용이 내부에서 어떻게 작동하는지 알 수있었습니다.

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

2
실제로 이것은 a = a + [1]과부하 로 신규 이민자에게는 약간 혼란 스러울 수 있습니다 a...로 변경 b = a + [1] ; print id(b)하고 라인을 추가하는 것을 고려 하십시오 a.append(2). 따라서 +두 목록에서 항상 새 목록 (에 할당 됨 b)을 작성하는 반면 수정 된 항목 a은 여전히 ​​동일한 것을 가질 수 있습니다 id(a).
Jörn Hees

25

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

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에서 정적 변수를 실제로 사용 했습니까?)

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


24

파이썬 : 가변 기본 인수

기본 인수는 함수가 함수 객체로 컴파일 될 때 평가됩니다. 함수가 해당 함수에 의해 여러 번 사용될 때 그것들은 같은 객체로 남아 있습니다.

변경 가능한 경우 (예 : 요소를 추가하여) 변경하면 연속 호출시 변경 상태를 유지합니다.

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

동등한 코드 :

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

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 사용자에게 일반적인 지침 :

그러나 이것이 새로운 사용자에게 일반적인 지침이 대신 다음과 같은 기본 인수를 작성하는 이유입니다.

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

가장 짧은 대답은 아마도 "정의는 실행"일 것이므로 전체 논증은 엄격하지 않습니다. 더 고안된 예로서, 당신은 이것을 인용 할 수 있습니다 :

def a(): return []

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

def명령문 실행 시간에 기본 인수 표현식을 실행 하지 않는 것이 쉽지 않거나 이해가되지 않거나 둘 다임을 나타내면 충분 합니다.

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


20

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]

19

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

  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. 객체의 일부이며 코드 실행 사이에 지속 되는 achange로 변경합니다 .foo.func_defs[0]foofoo

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

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.

19

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

다른 사람들이 광범위하게 언급했듯이 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의 "유효한 파이썬 : 더 나은 파이썬을 작성하는 59 가지 방법", 항목 20 : None동적 기본 인수를 지정하기 위해 및 Docstring 사용 (p. 48)

파이썬에서 원하는 결과를 얻기위한 규칙은 기본값을 제공 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]

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


17

해결책은 다음과 같습니다.

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

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


16

우리가 이것을 할 때 :

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

때때로이 패턴을 다음 패턴의 대안으로 활용합니다.

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

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

나는이 패턴이 잘 알려져 있지 않다고 생각하기 때문에, 미래의 오해를 막기 위해 짧은 의견을 제시합니다.


2
메모를 위해 데코레이터를 추가하고 메모 리 캐시를 함수 객체 자체에 넣는 것을 선호합니다.
스테파노 보리 니

이 예제는 _make_singleton기본 인수 예제에서는 def 시간에 호출하지만 전역 예제에서는 호출 시간에 호출 하기 때문에보다 복잡한 패턴을 대체하지 않습니다 . 진정한 대체는 기본 인수 값에 일종의 변경 가능한 상자를 사용하지만 인수를 추가하면 대체 값을 전달할 수 있습니다.
얀 베르니에

15

객체 (따라서 스코프와의 넥타이)를 바꾸면이 문제를 해결할 수 있습니다.

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

추악하지만 작동합니다.


3
이 기능은 자동 문서 생성 소프트웨어를 사용하여 함수에 필요한 인수 유형을 문서화하는 경우에 유용한 솔루션입니다. a = None을 입력 한 다음 a가 None 인 경우 a를 []로 설정해도 독자가 예상 한 내용을 한눈에 파악하는 데 도움이되지 않습니다.
Michael Scott Cuthbert

멋진 아이디어 : 그 이름을 리 바인드하면 절대로 수정할 수 없습니다. 정말 좋아합니다.
holdenweb

이것이 바로 그렇게하는 방법입니다. 파이썬은 매개 변수의 복사본을 만들지 않으므로 명시 적으로 복사본을 만드는 것은 사용자의 책임입니다. 사본이 있으면 예상치 못한 부작용없이 원하는대로 수정할 수 있습니다.
마크 랜섬

13

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

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

위의 두 기능을 모두 유지하면서 여전히 다른 점을 지적하는 것이 전적으로 일관됩니다.

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

다른 답변 또는 적어도 일부는 포인트 1과 2를 만들지 만 3은 만들지 않거나 포인트 3과 다운 플레이 포인트 1과 2를 만듭니다. 그러나 세 가지 모두 사실입니다.

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

기존의 행동은 Pythonic이 아니며 Python은 언어에 대해 거의 거의 모든 곳 에서 가장 놀랍지 않은 원칙을 위반하기 때문에 성공적입니다.이것은 나쁘다. 뿌리 뽑는 것이 현명한 지 아닌지에 따라 실제 문제입니다. 디자인 결함입니다. 동작을 추적하여 언어를 훨씬 더 잘 이해한다면 C ++이이 모든 것 이상을 수행한다고 말할 수 있습니다. 예를 들어 미묘한 포인터 오류를 탐색하여 많은 것을 배웁니다. 그러나 이것은 파이썬이 아닙니다. 파이썬이 다른 언어보다 놀라움이 훨씬 적기 때문에이 행동에 직면 할 정도로 파이썬에 관심이있는 사람들은 언어에 관심이있는 사람들입니다. 다 블러와 호기심은 파이썬에 이끌 리는 프로그래머의 직관을 가로막는 디자인 fl-즉 숨겨진 논리 퍼즐 때문이 아니라 작동하는 데 걸리는 시간이 얼마 남지 않았을 때 Pythonistas가됩니다. 그냥 작동 하기 때문 입니다.


6
-1 있지만 정당성 관점이 아닌 대답, 그리고 나는 그것으로 동의하지 않는다. 너무 많은 특수 예외가 자신의 코너 케이스를 낳습니다.
Marcin

3
그렇다면 파이썬에서 함수가 호출 될 때마다 []의 기본 인수가 []로 유지되는 것이 더 의미가 있다고 말하는 것은 "놀랍게도 무지하다"고 말하는가?
Christos Hayward

3
그리고 불행한 관용구로 기본 인수를 None으로 설정 한 다음 argument == None : argument = []? 인 경우 함수 설정의 본문에서 고려하는 것은 무지합니다. f (argument = [])를 할당하면 인수가 자동으로 []의 값으로 설정된다는 순진한 이민자가 기대하는 것을 종종 사람들이 원하기 때문에이 관용구를 불행하게 생각하는 것은 무지한가?
Christos Hayward

3
그러나 파이썬에서 언어의 정신의 일부는 너무 많은 심해 다이빙을 할 필요가 없다는 것입니다. array.sort ()는 정렬, big-O 및 상수에 대해 이해하는 정도에 관계없이 작동합니다. 셀 수없이 많은 예제 중 하나를 제공하기 위해 배열 정렬 메커니즘에서 파이썬의 장점은 내부에 대해 자세히 살펴볼 필요가 없다는 것입니다. 그리고 다르게 말하면, 파이썬의 장점은 보통 Just Works가있는 것을 얻기 위해 구현에 대해 깊이 들어 가지 않아도된다는 것입니다. 그리고 해결 방법 (... if argument == None : argument = []), FAIL이 있습니다.
Christos Hayward

3
독립형으로서이 명령문 x=[]은 "빈 목록 오브젝트를 작성하고 이름 'x'를 바인드합니다."를 의미합니다. 따라서에서 def f(x=[])빈 목록도 생성됩니다. 항상 x에 바인딩되지는 않으므로 기본 대리에 바인딩됩니다. 나중에 f ()가 호출되면 기본값이 제거되어 x에 바인딩됩니다. 빈 목록 자체가 다람쥐이기 때문에 그 목록에 붙어있는 것이 있든 없든 동일한 목록이 x에 바인딩 할 수있는 유일한 것입니다. 그렇지 않으면 어떻게 될 수 있습니까?
Jerry B

10

이것은 디자인 결함이 아닙니다 . 이것을 넘어가는 사람은 무언가 잘못하고 있습니다.

이 문제가 발생할 수있는 곳은 3 가지입니다.

  1. 함수의 부작용으로 인수를 수정하려고합니다. 이 경우 기본 인수를 갖는 것은 의미없습니다 . 인수 목록을 남용하여 함수 속성을 갖는 경우는 예외입니다. 예를 들어 cache={}실제 인수를 사용하여 함수를 전혀 호출하지 않을 수도 있습니다.
  2. 인수를 수정 하지 않고 그대로 두려고했지만 실수로 수정했습니다. 그것은 버그입니다.
  3. 함수 내에서 사용하기 위해 인수를 수정하려고하지만 함수 외부에서 수정할 수있는 것으로 기대하지 않았습니다. 이 경우 기본 여부에 상관없이 인수 의 사본 을 작성해야합니다 ! 파이썬은 값별 호출 언어가 아니기 때문에 사본을 만들지 않으므로 명시 적이어야합니다.

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


"무언가를한다"는 진단입니다. 즉, = None 패턴이 유용한 시간이 있다고 생각하지만 일반적으로 해당 경우에 변경 가능 변수를 전달하면 수정하고 싶지 않습니다 (2). cache={}패턴은 정말 당신이 아마 원하는 실제 코드의 인터뷰 전용 솔루션입니다 @lru_cache!
Andy Hayden

9

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

유용한 예라고 생각하는 것을 알려 드리겠습니다.

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

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

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

7

이 질문에 대한 답은 파이썬이 데이터를 매개 변수 (값 또는 참조로 전달)가 아닌 가변성으로 전달하는 방법이나 파이썬이 "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, 전자는 변경 가능하지만 후자는 변경 불가능하지만 변경 가능성은 질문과 관련이 없습니다. 공간 어딘가에 아무도 없지만 거기에 있으며 없음 사본이 하나만 있다는 것을 알고 있습니다. 따라서 foo가 호출 될 때마다 항목은 (한 번만 평가된다는 대답과 달리) 없음, 명확함, 없음의 참조 (또는 주소)로 평가됩니다. 그런 다음 foo에서 항목이 []로 변경됩니다. 즉, 주소가 다른 다른 객체를 가리 킵니다.

====== 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)을 호출 할 때 기본 매개 변수 []와 내용이 동일하지만 주소가 다릅니다! 매개 변수를 명시 적으로 제공 하므로이 itemsnew의 주소 ( []예 : 2222222) 를 가져 와서 약간 변경 한 후에 반환해야합니다. 이제 foo (3)이 실행됩니다. 오직 이후x항목이 제공되면 기본값을 다시 가져와야합니다. 기본값은 무엇입니까? foo 함수를 정의 할 때 설정됩니다. 11111111에있는 목록 오브젝트입니다. 따라서 항목은 요소 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.


5
마지막 요점이 잘못되었습니다. 그것을 시도하고 당신은 것을 볼 수 x[7]()있습니다 9.
Duncan

2
"파이썬은 기본 데이터 유형을 값으로 전달합니다. 즉, 값을 로컬 변수에 로컬 복사본으로 만드는 것"이 ​​완전히 잘못되었습니다. 누군가 파이썬을 아주 잘 알 수는 있지만 펀더멘털에 대한 끔찍한 오해가 있습니다. :-(
Veky

6

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실행시 평가 되면 정의 시간에 영향을 줄 수 없습니다 . "정의"를 선택하면 두 경우 모두를 표현할 수 있으며 "실행시"를 선택하면 다음 중 하나만 표현할 수 있습니다.

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
    ...

"일관성은 이미"정의 "로되어 있습니다. 그 밖의 모든 것은 def <name>(<args=defaults>):정의에서도 가장 잘 평가됩니다." 나는 결론이 전제에서 나온다고 생각하지 않습니다. 두 가지가 같은 줄에 있다고해서 동일한 범위에서 평가되어야한다는 의미는 아닙니다. default나머지 줄과는 다른 것입니다. 그것은 표현입니다. 식을 평가하는 것은 함수를 정의하는 것과는 매우 다른 프로세스입니다.
LarsH

@LarsH 기능 정의되어 있습니다 파이썬으로 평가했다. 그것이 명령문 ( def) 또는 표현식 ( lambda) 에서 온 것인지 여부 는 함수 생성이 평가, 특히 서명의 의미를 의미한다는 것을 변경하지 않습니다. 그리고 기본값은 함수 서명의 일부입니다. 즉, 기본값 즉시 평가 해야 한다는 의미 는 아닙니다. 예를 들어, 유형 힌트는 그렇지 않을 수 있습니다. 그러나 그럴만한 이유가없는 한 반드시해야한다고 제안합니다.
MisterMiyagi

좋습니다, 함수를 만드는 것은 어떤 의미에서 평가를 의미하지만, 그 정의 내의 모든 표현이 정의 시점에 평가된다는 의미는 아닙니다. 대부분 그렇지 않습니다. 함수 본문이 "평가"(적절한 표현으로 구문 분석 됨)보다 정의 시간에 서명이 특히 "평가"되는 의미가 명확하지 않습니다. 함수 본문의 표현은 전체 의미에서 명확하게 평가되지 않습니다. 이러한 관점에서 일관성은 서명의 표현식을 "완전히"평가해서는 안된다고 말합니다.
LarsH

나는 당신이 틀렸다는 것을 의미하는 것이 아니라, 당신의 결론이 일관성만으로는 따르지 않는다는 것입니다.
LarsH

@LarsH 기본값은 본문의 일부가 아니며 일관성이 유일한 기준이라고 주장하지 않습니다. 답을 명확하게하는 방법을 제안 할 수 있습니까?
MisterMiyagi

3

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

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

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

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.