Использование атрибутов функции для хранения результатов для отложенной (потенциальной) обработки

Я занимаюсь определением столкновений и очень хотел бы использовать одну и ту же функцию в двух разных контекстах. В одном контексте я хотел бы, чтобы это было что-то вроде

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

и в обоих случаях я не делаю больше обработки, чем мне нужно.

Я понимаю, что это больше кода, чем первые две функции, но он также имеет преимущество:

  1. Сохранение моего интерфейса для обнаружения столкновений в виде дерева с узлом на верхнем уровне, а не на среднем уровне. Это кажется проще.

  2. Подключить себя с удобной функцией заглянуть. Если я использую это в другой раз, то на самом деле я пишу меньше кода. (В ответ на 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() так или иначе, а также доступен для использования в другом месте.

Другие вопросы по тегам