[python] 가변 함수 인수 기본값에 대한 좋은 사용?

파이썬에서 가변 객체를 함수의 인수 기본값으로 설정하는 것은 흔한 실수입니다. 다음 은 David Goodger의 훌륭한 글 에서 가져온 예입니다 .

>>> def bad_append(new_item, a_list=[]):
        a_list.append(new_item)
        return a_list
>>> print bad_append('one')
['one']
>>> print bad_append('two')
['one', 'two']

이것이 일어나는 이유에 대한 설명은 여기에 있습니다 .

그리고 이제 내 질문에 대해 : 이 구문에 대한 좋은 사용 사례가 있습니까?

내 말은, 모든 사람이 같은 실수를하고, 디버그하고, 문제를 이해하고, 그로부터 그것을 피하려고 시도한다면, 그러한 구문은 어떤 용도로 사용됩니까?



답변

이를 사용하여 함수 호출간에 값을 캐시 할 수 있습니다.

def get_from_cache(name, cache={}):
    if name in cache: return cache[name]
    cache[name] = result = expensive_calculation()
    return result

그러나 일반적으로 이러한 종류의 작업은 캐시 등을 지우는 추가 속성을 가질 수 있으므로 클래스를 사용하면 더 잘 수행됩니다.


답변

정식 답변은이 페이지입니다 : http://effbot.org/zone/default-values.htm

또한 변경 가능한 기본 인수에 대한 3 가지 “좋은”사용 사례를 언급합니다.

  • 콜백에서 지역 변수를 외부 변수의 현재 값 에 바인딩
  • 캐시 / 메모리
  • 전역 이름의 로컬 리 바인딩 (고도로 최적화 된 코드 용)


답변

아마도 변경 가능한 인수를 변경하지 않지만 변경 가능한 인수를 기대할 수 있습니다.

def foo(x, y, config={}):
    my_config = {'debug': True, 'verbose': False}
    my_config.update(config)
    return bar(x, my_config) + baz(y, my_config)

(예, config=()이 특별한 경우에 사용할 수 있다는 것을 알고 있지만 덜 명확하고 덜 일반적입니다.)


답변

import random

def ten_random_numbers(rng=random):
    return [rng.random() for i in xrange(10)]

random기본 난수 생성기로 효과적으로 변경 가능한 싱글 톤 인 모듈을 사용합니다 .


답변

편집 (설명) : 변경 가능한 기본 인수 문제는 더 깊은 설계 선택의 증상입니다. 즉, 기본 인수 값이 함수 개체의 속성으로 저장된다는 것입니다. 이 선택이 이루어진 이유를 물어볼 수 있습니다. 언제나 그렇듯이 그러한 질문은 제대로 대답하기 어렵습니다. 그러나 확실히 좋은 용도가 있습니다.

성능 최적화 :

def foo(sin=math.sin): ...

변수 대신 클로저에서 객체 값을 가져옵니다.

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


답변

변경 가능한 기본 인수 값의 좋은 사용에 대한 질문에 대한 대답으로 다음 예제를 제공합니다.

변경 가능한 기본값은 사용하기 쉽고 직접 만든 가져 오기 가능한 명령을 프로그래밍하는 데 유용 할 수 있습니다. 변경 가능한 기본 메서드는 첫 번째 호출에서 초기화 할 수있는 (클래스와 매우 유사) 함수에 전용 정적 변수를 포함하지만 전역에 의존 할 필요가없고 래퍼를 사용하지 않고 인스턴스화 할 필요도 없습니다. 가져온 클래스 개체입니다. 당신이 동의하기를 바라는 것처럼 그것은 나름대로 우아합니다.

다음 두 가지 예를 고려하십시오.

def dittle(cache = []):

    from time import sleep # Not needed except as an example.

    # dittle's internal cache list has this format: cache[string, counter]
    # Any argument passed to dittle() that violates this format is invalid.
    # (The string is pure storage, but the counter is used by dittle.)

     # -- Error Trap --
    if type(cache) != list or cache !=[] and (len(cache) == 2 and type(cache[1]) != int):
        print(" User called dittle("+repr(cache)+").\n >> Warning: dittle() takes no arguments, so this call is ignored.\n")
        return

    # -- Initialize Function. (Executes on first call only.) --
    if not cache:
        print("\n cache =",cache)
        print(" Initializing private mutable static cache. Runs only on First Call!")
        cache.append("Hello World!")
        cache.append(0)
        print(" cache =",cache,end="\n\n")
    # -- Normal Operation --
    cache[1]+=1 # Static cycle count.
    outstr = " dittle() called "+str(cache[1])+" times."
    if cache[1] == 1:outstr=outstr.replace("s.",".")
    print(outstr)
    print(" Internal cache held string = '"+cache[0]+"'")
    print()
    if cache[1] == 3:
        print(" Let's rest for a moment.")
        sleep(2.0) # Since we imported it, we might as well use it.
        print(" Wheew! Ready to continue.\n")
        sleep(1.0)
    elif cache[1] == 4:
        cache[0] = "It's Good to be Alive!" # Let's change the private message.

# =================== MAIN ======================        
if __name__ == "__main__":

    for cnt in range(2):dittle() # Calls can be loop-driven, but they need not be.

    print(" Attempting to pass an list to dittle()")
    dittle([" BAD","Data"])

    print(" Attempting to pass a non-list to dittle()")
    dittle("hi")

    print(" Calling dittle() normally..")
    dittle()

    print(" Attempting to set the private mutable value from the outside.")
    # Even an insider's attempt to feed a valid format will be accepted
    # for the one call only, and is then is discarded when it goes out
    # of scope. It fails to interrupt normal operation.
    dittle([" I am a Grieffer!\n (Notice this change will not stick!)",-7])

    print(" Calling dittle() normally once again.")
    dittle()
    dittle()

이 코드를 실행하면 dittle () 함수가 첫 번째 호출에서 내부화되지만 추가 호출에서는 내부화되지 않으며 호출 사이의 내부 정적 저장소에 개인 정적 캐시 (변경 가능한 기본값)를 사용하고 하이재킹 시도를 거부합니다. 정적 저장소는 악의적 인 입력에 대해 탄력적이며 동적 조건 (여기서는 함수가 호출 된 횟수)에 따라 작동 할 수 있습니다.

가변 기본값을 사용하는 열쇠는 메모리에서 변수를 재 할당하는 작업을 수행하지 않고 항상 제자리에서 변수를 변경하는 것입니다.

이 기술의 잠재적 인 힘과 유용성을 확인하려면이 첫 번째 프로그램을 “DITTLE.py”라는 이름으로 현재 디렉토리에 저장 한 후 다음 프로그램을 실행하십시오. 기억하기위한 단계를 거치거나 점프 할 농구대를 프로그래밍하지 않고도 새로운 dittle () 명령을 가져 와서 사용합니다.

두 번째 예가 있습니다. 이것을 새 프로그램으로 컴파일하고 실행하십시오.

from DITTLE import dittle

print("\n We have emulated a new python command with 'dittle()'.\n")
# Nothing to declare, nothing to instantize, nothing to remember.

dittle()
dittle()
dittle()
dittle()
dittle()

이제 가능한 한 매끄럽고 깨끗하지 않습니까? 이러한 변경 가능한 기본값은 실제로 유용 할 수 있습니다.

========================

잠시 내 대답을 생각해 본 후, 변경 가능한 기본 방법을 사용하는 것과 동일한 작업을 수행하는 일반적인 방법의 차이를 명확하게 만들지 못했습니다.

일반적인 방법은 Class 객체를 감싸고 전역을 사용하는 임포트 가능한 함수를 사용하는 것입니다. 따라서 비교를 위해 여기에는 변경 가능한 기본 메서드와 동일한 작업을 시도하는 클래스 기반 메서드가 있습니다.

from time import sleep

class dittle_class():

    def __init__(self):

        self.b = 0
        self.a = " Hello World!"

        print("\n Initializing Class Object. Executes on First Call only.")
        print(" self.a = '"+str(self.a),"', self.b =",self.b,end="\n\n")

    def report(self):
        self.b  = self.b + 1

        if self.b == 1:
            print(" Dittle() called",self.b,"time.")
        else:
            print(" Dittle() called",self.b,"times.")

        if self.b == 5:
            self.a = " It's Great to be alive!"

        print(" Internal String =",self.a,end="\n\n")

        if self.b ==3:
            print(" Let's rest for a moment.")
            sleep(2.0) # Since we imported it, we might as well use it.
            print(" Wheew! Ready to continue.\n")
            sleep(1.0)

cl= dittle_class()

def dittle():
    global cl

    if type(cl.a) != str and type(cl.b) != int:
        print(" Class exists but does not have valid format.")

    cl.report()

# =================== MAIN ====================== 
if __name__ == "__main__":
    print(" We have emulated a python command with our own 'dittle()' command.\n")
    for cnt in range(2):dittle() # Call can be loop-driver, but they need not be.

    print(" Attempting to pass arguments to dittle()")
    try: # The user must catch the fatal error. The mutable default user did not. 
        dittle(["BAD","Data"])
    except:
        print(" This caused a fatal error that can't be caught in the function.\n")

    print(" Calling dittle() normally..")
    dittle()

    print(" Attempting to set the Class variable from the outside.")
    cl.a = " I'm a griefer. My damage sticks."
    cl.b = -7

    dittle()
    dittle()

이 클래스 기반 프로그램을 현재 디렉토리에 DITTLE.py로 저장 한 후 다음 코드를 실행하십시오 (이전과 동일).

from DITTLE import dittle
# Nothing to declare, nothing to instantize, nothing to remember.

dittle()
dittle()
dittle()
dittle()
dittle()

두 가지 방법을 비교하면 함수에서 가변 기본값을 사용하는 이점이 더 명확 해집니다. 변경 가능한 기본 메서드에는 전역이 필요하지 않으며 내부 변수를 직접 설정할 수 없습니다. 그리고 가변 메소드가 단일주기 동안 전달 된 인수를 받아 들인 다음이를 무시했지만, Class 메소드는 내부 변수가 외부에 직접 노출되기 때문에 영구적으로 변경되었습니다. 어떤 방법이 프로그래밍하기 더 쉬운가요? 나는 그것이 당신의 목표의 방법과 복잡성에 대한 당신의 편안함 수준에 달려 있다고 생각합니다.


답변