Использование атрибутов функции для хранения результатов для отложенной (потенциальной) обработки
Я занимаюсь определением столкновений и очень хотел бы использовать одну и ту же функцию в двух разных контекстах. В одном контексте я хотел бы, чтобы это было что-то вроде
def detect_collisions(item, others):
return any(collides(item, other) for other in others)
а в другом я бы хотел чтобы это было
def get_collisions(item, others):
return [other for other in others if collides(item, other)]
Я действительно ненавижу идею написания двух функций здесь. Простота сохранения их имен - это одно отклонение, а усложнение интерфейса для обнаружения столкновений - другое. так я думал
def peek(gen):
try:
first = next(gen)
except StopIteration:
return False
else:
return it.chain((first,), gen)
def get_collisions(item, others):
get_collisions.all = peek(other for other in others if collides(item, other))
return get_collisions.all
Теперь, когда я просто хочу сделать проверку, я могу сказать:
if get_collisions(item, others):
# aw snap
или же
if not get_collisions(item, others):
# w00t
и в другом контексте, где я на самом деле хочу их изучить, я могу сделать:
if get_collisions(item, others):
for collision in get_collisions.all:
# fix it
и в обоих случаях я не делаю больше обработки, чем мне нужно.
Я понимаю, что это больше кода, чем первые две функции, но он также имеет преимущество:
Сохранение моего интерфейса для обнаружения столкновений в виде дерева с узлом на верхнем уровне, а не на среднем уровне. Это кажется проще.
Подключить себя с удобной функцией заглянуть. Если я использую это в другой раз, то на самом деле я пишу меньше кода. (В ответ на YAGNI, если он у меня будет, я буду)
Так. Если бы вы были маньяком-убийцей, который знает, где я живу, ожидал бы я от вас визита, если бы написал вышеприведенный код? Если так, как бы вы подошли к этой ситуации?
2 ответа
Просто сделай get_collisions
вернуть генератор:
def get_collisions(item, others):
return (other for other in others if collides(item, other))
Затем, если вы хотите сделать проверку:
for collision in get_collisions(item, others):
print 'Collision!'
break
else:
print 'No collisions!'
Это очень похоже на то, что мы обсуждали "питонским способом переписать присваивание в операторе if", но эта версия обрабатывает только один позиционный или один аргумент ключевого слова на вызов, так что всегда можно получить реально кэшированное значение (а не просто Истинное значение в логическом смысле Pythonian или нет).
Я никогда не обращал внимания на ваше предложение принять несколько ключевых слов и иметь функции с разными именами в зависимости от того, хотите ли вы, чтобы все результаты были представлены через any()
или же all()
- но понравилась идея использования ключевых аргументов, которые позволили бы использовать одну функцию в двух или более местах одновременно. Вот чем я закончил:
# can be called with a single unnamed value or a single named value
def cache(*args, **kwargs):
if len(args)+len(kwargs) == 1:
if args:
name, value = 'value', args[0] # default attr name 'value'
else:
name, value = kwargs.items()[0]
else:
raise NotImplementedError('"cache" calls require either a single value argument '
'or a name=value argument identifying an attribute.')
setattr(cache, name, value)
return value
# add a sub-function to clear the cache attributes (optional and a little weird)
cache.clear = lambda: cache.func_dict.clear()
# you could then use it either of these two ways
if get_collisions(item, others):
# no cached value
if cache(collisions=get_collisions(item, others)):
for collision in cache.collisions:
# fix them
Помещая все уродливые детали в отдельную функцию, это не влияет на код в get_collisions()
так или иначе, а также доступен для использования в другом месте.