В Python распространенной ошибкой является установка изменяемого объекта в качестве значения аргумента функции по умолчанию. Вот пример из прекрасной статьи Дэвида Гуджера :
>>> 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']
Объяснение, почему это происходит, здесь .
А теперь вопрос: есть ли хороший вариант использования этого синтаксиса?
Я имею в виду, что если каждый, кто сталкивается с этим, совершает одну и ту же ошибку, устраняет ее, понимает проблему и, следовательно, пытается ее избежать, какой смысл в таком синтаксисе?
python
arguments
default-value
mutable
Джонатан
источник
источник
__init__
функции класса, который устанавливается в переменную экземпляра; это совершенно правильная вещь, и все идет ужасно неправильно с изменяемым значением по умолчанию. stackoverflow.com/questions/43768055/…Ответы:
Вы можете использовать его для кеширования значений между вызовами функций:
def get_from_cache(name, cache={}): if name in cache: return cache[name] cache[name] = result = expensive_calculation() return result
но обычно такие вещи лучше делать с классом, так как тогда у вас могут быть дополнительные атрибуты для очистки кеша и т. д.
источник
@functools.lru_cache(maxsize=None)
lru_cache
недоступен, если у вас есть нехэшируемые значения.Канонический ответ - это страница: 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
модуль, фактически изменяемый синглтон, в качестве генератора случайных чисел по умолчанию.источник
random
один раз за вызов функции». Оба в конечном итоге используют один и тот же объект.РЕДАКТИРОВАТЬ (пояснение): проблема изменяемого аргумента по умолчанию является симптомом более глубокого выбора дизайна, а именно, что значения аргументов по умолчанию хранятся как атрибуты в объекте функции. Вы можете спросить, почему был сделан этот выбор; как всегда, на такие вопросы сложно ответить должным образом. Но он, безусловно, имеет хорошее применение:
Оптимизация для производительности:
def foo(sin=math.sin): ...
Получение значений объекта в замыкании вместо переменной.
callbacks = [] for i in range(10): def callback(i=i): ... callbacks.append(callback)
источник
Я знаю, что это старый, но, черт возьми, я хотел бы добавить в этот поток вариант использования. Я регулярно пишу собственные функции и слои для 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))
источник
В ответ на вопрос о том, как правильно использовать изменяемые значения аргументов по умолчанию, я предлагаю следующий пример:
Изменяемое значение по умолчанию может быть полезно для программирования простых в использовании, импортируемых команд, созданных вами. Изменяемый метод по умолчанию сводится к наличию частных статических переменных в функции, которую вы можете инициализировать при первом вызове (очень похоже на класс), но без необходимости прибегать к глобальным переменным, без использования оболочки и без необходимости создавать экземпляр объект класса, который был импортирован. Он по-своему элегантен, надеюсь, вы согласитесь.
Рассмотрим эти два примера:
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 был навсегда изменен, потому что его внутренняя переменная напрямую открыта извне. А какой метод проще программировать? Я думаю, это зависит от вашего уровня комфорта с методами и сложности ваших целей.
источник