가변 함수 인수 기본값에 대한 유용한 사용?
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
모듈, 사실상 가변 싱글톤을 기본 난수 생성기로 사용합니다.
이것이 오래된 것이라는 것은 알고 있지만, 그냥 참고로 이 스레드에 사용 사례를 추가하고 싶습니다.저는 정기적으로 TensorFlow/Keras에 대한 사용자 정의 함수와 계층을 작성하고, 서버에 스크립트를 업로드하고, 그곳에서 (사용자 정의 개체로) 모델을 교육한 다음 모델을 저장하고 다운로드합니다.이러한 모델을 로드하려면 사용자 지정 개체가 모두 포함된 사전을 제공해야 합니다.
나와 같은 상황에서 할 수 있는 것은 다음과 같은 사용자 지정 개체를 포함하는 모듈에 코드를 추가하는 것입니다.
custom_objects = {}
def custom_object(obj, storage=custom_objects):
storage[obj.__name__] = obj
return obj
그런 다음 사전에 있어야 하는 클래스/함수를 모두 장식할 수 있습니다.
@custom_object
def some_function(x):
return 3*x*x + 2*x - 2
또한 사용자 정의 Keras 계층과는 다른 사전에 사용자 정의 손실 함수를 저장하고 싶다고 가정합니다.functools.partial을 사용하면 새 데코레이터에 쉽게 액세스할 수 있습니다.
import functools
import tf
custom_losses = {}
custom_loss = functools.partial(custom_object, storage=custom_losses)
@custom_loss
def my_loss(y, y_pred):
return tf.reduce_mean(tf.square(y - y_pred))
EDIT(명확성):가변 기본 인수 문제는 더 심층적인 설계 선택, 즉 기본 인수 값이 함수 개체에 속성으로 저장되는 증상입니다.왜 이런 선택을 했는지 물을 수도 있습니다. 항상 그렇듯이, 그러한 질문들은 제대로 대답하기 어렵습니다.하지만 확실히 좋은 용도가 있습니다.
성능 최적화:
def foo(sin=math.sin): ...
변수 대신 폐쇄에서 객체 값을 가져옵니다.
callbacks = []
for i in range(10):
def callback(i=i): ...
callbacks.append(callback)
호출 코드에서 실제로 사용되지 않는 가변 기본 인수를 사용하여 센티널 값을 만들 수 있습니다.내장된 Python 딥 복사본은 이를 수행합니다.
변수 인수는 값이 해당 함수에 고유한지 확인하는 데 사용됩니다. 다음과 같은 경우 새 목록을 만들어야 하기 때문입니다.deepcopy
컴파일되고 액세스할 수 없는 경우 개체는 다른 곳에 표시될 수 없습니다.불변의 객체는 내부에 포함되는 경향이 있으며, 빈 목록은 만들기 쉽습니다.일반적으로 이와 같은 sentinel 개체는 명시적으로 별도로 생성되지만, 이렇게 하면 네임스페이스 오염(선행 밑줄 이름에서도)을 피할 수 있습니다.
가변 기본 인수 값에 대한 유용한 사용에 대한 질문에 대한 답변으로 다음과 같은 예를 제시합니다.
가변 기본값은 사용하기 쉽고 사용자가 만든 가져오기 가능한 명령을 프로그래밍하는 데 유용할 수 있습니다.mutable default 메서드는 첫 번째 호출에서 초기화할 수 있지만(클래스와 매우 유사) 전역에 의존하지 않고, 래퍼를 사용하지 않고, 가져온 클래스 개체를 인스턴스화할 필요 없이 기능에 개인 정적 변수를 갖는 것과 같습니다.당신이 동의하기를 바라지만, 그것은 그 나름대로 우아합니다.
다음 두 가지 예를 생각해 보십시오.
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()
이제 그것은 가능한 한 매끄럽고 깨끗하지 않습니까?이러한 변동 가능한 기본값은 실제로 유용할 수 있습니다.
========================
한동안 제 대답을 곰곰이 생각해 본 결과, 저는 변동 가능한 기본 방식을 사용하는 것과 같은 일을 수행하는 일반적인 방식을 사용하는 것 사이에 차이를 분명히 했는지 확신할 수 없습니다.
일반적인 방법은 클래스 개체를 래핑하고 전역을 사용하는 가져올 수 있는 함수를 사용하는 것입니다.비교를 위해, 여기서 클래스 기반 방법은 변형 가능한 기본 방법과 동일한 작업을 시도합니다.
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()
두 가지 방법을 비교하면 함수에서 가변 기본값을 사용할 때의 이점이 더 명확해질 것입니다.가변 기본 메서드는 전역이 필요하지 않으므로 내부 변수를 직접 설정할 수 없습니다.그리고 변형 가능한 방법은 단일 주기에 대해 지식이 풍부한 전달 인수를 수락하고 이를 무시했지만, 클래스 방법은 내부 변수가 외부에 직접 노출되기 때문에 영구적으로 변경되었습니다.프로그래밍하기 쉬운 방법은 무엇입니까?그것은 방법과 목표의 복잡성에 대한 당신의 편안함 수준에 달려 있다고 생각합니다.
언급URL : https://stackoverflow.com/questions/9158294/good-uses-for-mutable-function-argument-default-values
'programing' 카테고리의 다른 글
IE9 jQuery AJAX with CORS에서 "Access is denied"를 반환합니다. (0) | 2023.08.12 |
---|---|
멀티스레드에서 스프링 트랜잭션을 사용하는 방법 (0) | 2023.08.12 |
MariaDB: 항목당 월평균 선택(Pivot 테이블) (0) | 2023.08.12 |
PowerShell에서 표준 오류 출력을 억제하려면 어떻게 해야 합니까? (0) | 2023.08.12 |
단일 Git 커밋에 대해 구성된 사용자 재정의 (0) | 2023.08.12 |