파이썬 가변 기본 인수 : 왜?


20

기본 인수는 함수가 호출 될 때마다가 아니라 함수 초기화시 작성됩니다. 다음 코드를 참조하십시오.

def ook (item, lst=[]):
    lst.append(item)
    print 'ook', lst

def eek (item, lst=None):
    if lst is None: lst = []
    lst.append(item)
    print 'eek', lst

max = 3
for x in xrange(max):
    ook(x)

for x in xrange(max):
    eek(x)

내가 얻지 못하는 것은 이것이 왜 이런 식으로 구현되었는지입니다. 이 동작은 각 통화 시간마다 초기화에 비해 어떤 이점을 제공합니까?


이것은 이미 논의 놀라운 스택 오버플로에 세부 사항 : stackoverflow.com/q/1132941/5419599
와일드 카드를

답변:


14

그 이유는 구현의 단순성이라고 생각합니다. 자세히 설명하겠습니다.

함수의 기본값은 평가해야 할 표현식입니다. 귀하의 경우 클로저에 의존하지 않는 간단한 표현이지만 자유 변수가 포함 된 것일 수 있습니다 def ook(item, lst = something.defaultList()). 파이썬을 디자인하려면 선택해야합니다. 함수가 정의 될 때 또는 함수가 호출 될 때마다 한 번씩 평가합니까? 파이썬은 첫 번째 옵션을 선택합니다 (두 번째 옵션과 함께 사용되는 Ruby와 달리).

이것에 대한 몇 가지 이점이 있습니다.

먼저 속도와 메모리가 향상됩니다. 대부분의 경우 변경 불가능한 기본 인수가 있으며 Python은 모든 함수 호출 대신 한 번만 구성 할 수 있습니다. 이것은 (일부) 메모리와 시간을 절약합니다. 물론 변경 가능한 값으로는 잘 작동하지 않지만 어떻게 해결할 수 있는지 알고 있습니다.

또 다른 이점은 단순성입니다. 표현식이 평가되는 방식을 이해하는 것은 매우 쉽습니다. 함수가 정의 될 때 어휘 범위를 사용합니다. 다른 방법으로 진행하면 어휘 범위가 정의와 호출간에 변경되어 디버깅하기가 약간 더 어려워 질 수 있습니다. 이런 경우 파이썬은 매우 간단 해집니다.


3
흥미로운 점-일반적으로 파이썬에서 가장 놀랍지 않은 원칙입니다. 어떤 것들은 모델의 복잡한 복잡성에서 단순하지만 명백하고 놀라운 것은 아니며, 이것이 중요하다고 생각합니다.
Steve314

1
여기서 가장 놀라운 점은 다음과 같습니다. 모든 통화 평가 시맨틱이있는 경우 두 함수 호출간에 클로저가 변경되면 오류가 발생할 수 있습니다 (매우 가능함). 한 번 평가된다는 것을 아는 것과는 달리 이는 더욱 놀라운 일입니다. 물론, 당신은 다른 언어에서 왔을 때, 당신은 각각의 평가 시맨틱을 제외하고는 놀랍지 만, 그것이 두 가지 방식으로 어떻게 진행되는지 볼 수 있다고 주장 할 수 있습니다 :)
Stefan Kanev

범위에 대한 좋은 지적
0xc0de

나는 범위가 실제로 더 중요한 비트라고 생각합니다. 기본값에 대한 상수로 제한되지 않으므로 호출 사이트에서 범위를 벗어나는 변수가 필요할 수 있습니다.
Mark Ransom

5

그것을 넣는 한 가지 방법 은 매개 변수를 변경 lst.append(item) 하지 않는 것lst 입니다. lst여전히 동일한 목록을 참조합니다. 해당 목록의 내용이 변경되었을뿐입니다.

기본적으로 파이썬은 상수 또는 불변 변수를 전혀 가지고 있지 않지만 불변의 상수 유형을 가지고 있습니다. 정수 값은 수정할 수 없으며 바꿀 수만 있습니다. 그러나 목록의 내용을 바꾸지 않고 수정할 수 있습니다.

정수와 마찬가지로 참조를 수정할 수 없으며 바꿀 수만 있습니다. 그러나 참조중인 객체의 내용을 수정할 수 있습니다.

기본 객체를 한 번 생성하는 경우 객체 생성 및 가비지 수집 오버 헤드를 줄이기 위해 대부분 최적화라고 생각합니다.


정확히 +1 변수 이 아니라는 간접 계층을 이해하는 것이 중요 합니다. 대신 값을 참조 합니다. 변수를 변경하려면, 값이 될 수있다 교환 , 또는 돌연변이 (가 가변 인 경우).
Joonas Pulakka

파이썬에서 변수와 관련된 까다로운 문제에 직면했을 때 "="를 "이름 바인딩 연산자"로 간주하면 도움이됩니다. 바인딩하는 것이 새로운 것 (신규 객체 또는 불변 유형 인스턴스)인지 여부에 관계없이 이름은 항상 리바운드됩니다.
StarWeaver

4

이 동작은 각 통화 시간마다 초기화에 비해 어떤 이점을 제공합니까?

예제에서 설명한대로 원하는 동작을 선택할 수 있습니다. 따라서 기본 인수를 변경할 수 없게하려면 변경 불가능한 값 (예 : None또는)을 사용하십시오 1. 기본 인수를 변경 가능하게하려면 다음과 같이 변경 가능한 것을 사용하십시오 []. 인정할지라도, 그것은 단지 융통성입니다. 모르는 경우에는 물릴 수 있습니다.


2

나는 진정한 대답은 다음과 같다고 생각한다 : 파이썬은 절차 적 언어로 작성되었고 오직 기능적인 측면만을 채택했다. 당신이 찾고있는 것은 매개 변수 기본값을 클로저로 수행하는 것이며 Python의 클로저는 실제로 절반에 불과합니다. 이 시도의 증거를 위해 :

a = []
for i in range(3):
    a.append(lambda: i)
print [ f() for f in a ]

이것은 [2, 2, 2]당신이 진정한 폐쇄를 기대할 수있는 곳 을 제공합니다 [0, 1, 2].

파이썬이 매개 변수 기본값을 클로저로 묶을 수있는 기능을 원한다면 상당히 많은 것들이 있습니다. 예를 들면 다음과 같습니다.

def foo(a, b=a.b):
    ...

매우 유용하지만 함수 정의 시간에 "a"는 범위 내에 있지 않으므로 그렇게 할 수 없으며 대신 어수선한 작업을 수행해야합니다.

def foo(a, b=None):
    if b is None:
        b = a.b

거의 같은 것입니다 ... 거의.



1

큰 이점은 메모입니다. 이것은 표준 예입니다.

def fibmem(a, cache={0:1,1:1}):
    if a in cache: return cache[a]
    res = fib(a-1, cache) + fib(a-2, cache)
    cache[a] = res
    return res

그리고 비교를 위해 :

def fib(a):
    if a == 0 or a == 1: return 1
    return fib(a-1) + fib(a-2)

ipython에서 시간 측정 :

In [43]: %time print(fibmem(33))
5702887
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 200 µs

In [43]: %time print(fib(33))
5702887
CPU times: user 1.44 s, sys: 15.6 ms, total: 1.45 s
Wall time: 1.43 s

0

설명 코드를 실행하여 Python 컴파일이 수행되기 때문에 발생합니다.

만약 한 사람이

def f(x = {}):
    ....

매번 새로운 배열을 원한다는 것은 분명합니다.

그러나 내가 말하면 어떻게됩니까?

list_of_all = {}
def create(stuff, x = list_of_all):
    ...

여기서는 다양한 목록으로 물건을 만들고 싶습니다. 목록을 지정하지 않으면 하나의 전역 catch-all이 있습니다.

그러나 컴파일러는 이것을 어떻게 추측합니까? 왜 해봐? 우리는 이것이 이름을 지 었는지 여부에 의존 할 수 있으며 때로는 도움이 될 수 있지만 실제로는 추측 일뿐입니다. 동시에 일관성을 시도하지 않는 좋은 이유가 있습니다.

그대로, 파이썬은 코드를 실행합니다. list_of_all 변수에는 이미 객체가 할당되어 있으므로 객체를 호출하면 여기에 이름이 지정된 로컬 객체에 대한 참조를 얻는 것과 같은 방식으로 x를 기본값으로하는 코드에 객체가 참조로 전달됩니다.

명명되지 않은 케이스와 명명되지 않은 케이스를 구별하려면 컴파일시 코드가 런타임에 실행되는 것과 크게 다른 방식으로 할당을 수행해야합니다. 그래서 우리는 특별한 경우를 만들지 않습니다.


-5

이것은 파이썬의 함수가 일류 객체 이기 때문에 발생 합니다 .

함수 정의가 실행될 때 기본 매개 변수 값이 평가됩니다. 이는 함수가 정의 될 때 표현식이 한 번 평가 되고 각 호출대해 동일한 "사전 계산 된"값이 사용됨을 의미 합니다 .

계속해서 매개 변수 값을 편집하면 후속 호출에 대한 기본값이 수정되며 함수 본문에서 명시 적 테스트를 수행하여 없음을 기본값으로 사용하는 간단한 솔루션 만 있으면 놀랄 일이 없습니다.

def foo(l=[]), 호출 될 때 해당 함수의 인스턴스가되고 추가 호출에 재사용됩니다. 함수 매개 변수는 객체의 속성과 분리되는 것으로 생각하십시오.

프로는 이것을 활용하여 클래스에 C와 같은 정적 변수를 갖도록 할 수 있습니다. 따라서 기본값 None을 선언하고 필요에 따라 초기화하는 것이 가장 좋습니다.

class Foo(object):
    def bar(self, l=None):
        if not l:
            l = []
        l.append(5)
        return l

f = Foo()
print(f.bar())
print(f.bar())

g = Foo()
print(g.bar())
print(g.bar())

수율 :

[5] [5] [5] [5]

예기치 않은 대신 :

[5] [5, 5] [5, 5, 5] [5, 5, 5, 5]


5
아니요. 각 호출에 대해 기본 인수 표현식을 다시 평가하기 위해 함수를 일등석으로 정의 할 수 있습니다. 그리고 그 이후의 모든 것, 즉 답의 대략 90 %는 완전히 질문 옆에 있습니다. -1

1
그런 다음 각 호출에 대한 기본 인수를 평가하는 함수를 정의하는 방법에 대한 지식을 우리와 공유하십시오 .Python Docs가 권장 하는 것보다 간단한 방법을 알고 싶습니다 .
반전

2
언어 디자인 수준에서 나는 의미합니다. 파이썬 언어 정의는 현재 기본 인수가 원래대로 취급된다고 말합니다. 기본 인수가 다른 방식으로 처리된다고 똑같이 말할 수 있습니다. IOW 당신은 ​​"왜 그런지 이유입니다"라는 질문에 "그것이 어떻게되는지"에 대답하고 있습니다.

파이썬은 Coffeescript가하는 것과 비슷한 기본 매개 변수를 구현했을 수 있습니다. 누락 된 매개 변수를 확인하기 위해 바이트 코드를 삽입하고 누락 된 경우 표현식을 평가합니다.
Winston Ewert
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.