Функциональная переменная

[Вдохновлен этим вопросом]

Предположим, у меня есть два списка:

list1 = ['tom', 'mary', 'frank', 'joe', 'john', 'barry']
list2 = [1, 2, 3, 4]

Я хочу сопоставить каждое имя в list1 с числом в list2. Поскольку в list2 есть четыре числа, я хотел бы соединить каждое из первых четырех имен с соответствующим числом в list2 и назначить случайные числа оставшимся именам в list1.

Я знаю, что могу решить эту проблему, используя цикл for с enumerate и random.choice. Действительно, я предоставил такое решение исходной проблемы.
Однако я хотел бы знать, можно сделать что-то вроде этого:

for name, number in itertools.izip_longest(list1, list2, fillvalue=MAGIC):
    print name, number

Первоначально я думал использовать что-то вроде этого:

MAGIC = random.choice(list1)

Однако сначала выполняется random.choice(list1), а затем используется ответ как fillvalue для операции архивирования. Это непривлекательно, так как не выбирает новое случайное значение для каждой пары значений, которые он сжимает. Поэтому ясно, что itertools.izip_longest требует для своего fillvalue чего-то, что само по себе имеет значение, чего оно не вызывает. В самом деле, если бы я предоставил ему функцию, это дало бы пару, состоящую из имени и вызываемой функции, что также не требуется. По этой причине функции lambda не являются возможными решениями.

Как мне создать переменную, которая вызывает некоторую функцию, когда она вызывается? Как itertools.izip_longest использует переменную fillvalue? Вызывается ли __repr__ этой переменной? Если да, могу ли я создать класс с __repr__, который вызывает функцию внутри него?


person inspectorG4dget    schedule 06.02.2014    source источник
comment
Как мне создать переменную, которая вызывает некоторую функцию, когда она вызывается? - ты бы не стал. Вы бы вызвали функцию.   -  person user2357112 supports Monica    schedule 07.02.2014
comment
Вы можете получить что-то вроде того, что вы хотите для атрибутов объекта, используя свойства, а в Python 3 вы можете получить то, что хотите, в области класса, используя неприятную магию метакласса, но для обычных переменных вы не можете этого сделать.   -  person user2357112 supports Monica    schedule 07.02.2014
comment
@ user2357112: Да, но экземпляры многих классов показывают свои __repr__ значения, когда вызывается, когда . Thus, if __repr__` должен был вызвать какую-то внешнюю функцию, а затем внезапно у вас есть переменная, которая содержит экземпляр, значение которого определяется функцией . В остальном я действительно знаком с разницей между переменной и функцией.   -  person inspectorG4dget    schedule 07.02.2014
comment
izip_longest не звонит repr. Он просто выплевывает объект напрямую.   -  person user2357112 supports Monica    schedule 07.02.2014
comment
@ InspectorG4dget: Вы действительно не хотите, чтобы __repr__ возвращала другое значение при каждом вызове. Кроме того, даже если бы это было так, подход izip_longets все равно не работал бы, поскольку значение fillvalue определяется при вызове функции, а не при каждом ее заполнении.   -  person Claudiu    schedule 07.02.2014
comment
Предположим, есть какое-то магическое значение, которое вы можете передать как fillvalue, которое будет одно, когда вы посмотрите на него один раз, и другое, когда вы посмотрите на него снова. Откуда izip_longest знать, что эта переменная должна просматриваться только определенное количество раз? Что, если бы предполагалось, что значение будет одинаковым каждый раз, а какой-то инвариант в коде C, управляющем izip_longest, сломался и вызвал segfault?   -  person user2357112 supports Monica    schedule 07.02.2014


Ответы (2)


Кажется, самым простым подходом здесь было бы создать генератор, который выдает все значения заполнителя для вечности, а затем связать это с получением значений в list2:

def inf_gen(f):
    while True:
        yield f()

Обратите внимание, что inf_gen(f) на самом деле эквивалентно iter(f, None), учитывая, что f никогда не возвращается. iter с двумя аргументами вызывает первый аргумент, пока не вернет второй аргумент.

Затем используйте его как таковой:

>>> from itertools import izip, chain
>>> for name, number in izip(list1,chain(list2, iter(lambda: random.choice(list2), None))):
    print name, number

tom 1
mary 2
frank 3
joe 4
john 1
barry 4

Вы также можете сделать это только с помощью itertools, не определяя отдельную функцию, используя понимание генератора с itertools.count:

>>> from itertools import izip, chain, count
>>> for name, number in izip(list1, chain(list2, (random.choice(list2) for _ in count()))):
    print name, number
person Claudiu    schedule 06.02.2014
comment
Форма с двумя аргументами iter по сути является тем, чем является inf_gen. - person chepner; 07.02.2014
comment
@chepner: о, хорошо! Совсем забыл про иттер. Обновлю ответ - person Claudiu; 07.02.2014

Если вы хотите, чтобы он «масштабировался» на несколько итераторов, вы можете сделать это с небольшой модификацией кода, приведенного в http://docs.python.org/2.7/library/itertools.html?highlight=izip_longest#itertools.izip_longest :

from itertools import repeat, chain, count

class ZipExhausted(Exception):
    pass

def izip_longest2(*args, **kwds):
    fillvalue = kwds.get('fillvalue')
    fillgen = kwds.get('fillgen', repeat(fillvalue))  # added
    counter = [len(args) - 1]
    def sentinel():
        if not counter[0]:
            raise ZipExhausted
        counter[0] -= 1
        yield next(fillgen)  # modified
    iterators = [chain(it, sentinel(), fillgen) for it in args]  # modified
    try:
        while iterators:
            yield tuple(map(next, iterators))
    except ZipExhausted:
        pass


a = ['bob', 'mary', 'paul']
b = ['eggs', 'spam']
c = ['a', 'b', 'c', 'd']

for x in izip_longest2(a, b, c, fillgen=count()):
    print '\t'.join(map(str, x))

# output:
# bob   eggs    a
# mary  spam    b
# paul  0   c
# 1 2   d
person Brave Sir Robin    schedule 06.02.2014
comment
Пожалуйста, исправьте отступ? - person inspectorG4dget; 07.02.2014
comment
РЕДАКТИРОВАТЬ: исправлен отступ, спасибо, что указали на это, @ InspectorG4dget - person Brave Sir Robin; 07.02.2014
comment
Это на самом деле очень гениально! Спасибо :) - person inspectorG4dget; 07.02.2014