«Ленивая» оценка элементов списка в Python

1

Из-за отсутствия лучших слов я пошел с этим названием.

Я хочу иметь возможность сделать что-то вроде этого:

>>> from random import randint
>>> fruits = [
...     "Orange",
...     "Banana",
...     f"{randint(2,5)} Cherries",
... ]

>>> fruits[2]
'3 Cherries'

>>> fruits[2]
'5 Cherries'

>>> fruits[2]
'2 Cherries'

Но вместо этого буквенное выражение внутри строки вычисляется один раз при создании списка и дает один и тот же результат каждый раз, когда я к нему обращаюсь.

Мне было интересно, есть ли более простой/умный способ приблизиться к этому, чем написание какой-то сложной обработки крайних случаев (в конце концов, мы программисты; кому не нравится писать хороший код и быть элегантным и причудливым?). Я говорю об обработке краевого случая, потому что только 6 из моих 49 строк требуют такого "особого" поведения.

Пока я пытался сделать лямбда-функцию из вызова randint, но это не помогло; тот же результат. Может быть, это случай для ленивой оценки, но мне нужно небольшое руководство о том, как (или нужно ли?) Использовать его со списком.

Теги:
lazy-evaluation
lambda
f-string

4 ответа

6

Если вы хотите, чтобы некоторые из ваших элементов были буквальными, а некоторые вызывались, вы можете создать свою собственную версию list:

from random import randint

class LazyList(list):
    'Like a list, but invokes callables'
    def __getitem__(self, key):
        item = super().__getitem__(key)
        if callable(item):
            item = item()
        return item

fruits = LazyList([
    "Orange",
    "Banana",
    lambda: f"{randint(2,5)} Cherries",
])

print(fruits[2])
print(fruits[2])
  • 2
    Это намного приятнее, чем у o11c .. Вам нужно перегружать множество других методов, чтобы сделать его более удобным при использовании итерации, нарезки, печати в формате repr, str и т. Д.? или все они проходят через __getitem__ per se? как насчет сортировки?
  • 0
    @PatrickArtner нет, и это почти гарантированно не будет работать со встроенным типом в CPython.
3

Вы на правильном пути, желая лямбда:

from random import randint

fruits = [
        lambda: "Orange",
        lambda: "Banana",
        lambda: f"{randint(2, 5)} Cherries",
]

print(fruits[2]())
print(fruits[2]())
print(fruits[2]())

Есть способы исключить () но это, вероятно, не стоит.

0

Отказ от ответственности: я автор библиотеки

Вы можете использовать seqtools для ленивого отображения, оно поддерживает итерации и нарезки:

>>> fruits = seqtools.smap(lambda x: x if isinstance(x, str) else x(), 
...                        fruits)
>>> fruits[0]
'Orange'
>>> fruits[-1]
'3 Cherries'

Если вы не хотите переоценивать элементы функции при каждом вызове, вы можете добавить кеш:

fruits = seqtools.add_cache(fruits)

Назначение предметов не работает через сопоставления, очевидно, поэтому вам придется обдумать это.

Сортировка в конечном итоге оценит все элементы в списке, поэтому вы можете сделать это заранее и вызвать обычный метод list.sort().

0

Помещение лямбды в список поначалу звучит неплохо, но затем вы должны вызвать этот специальный (или все) индексы вашего списка. Вы не можете перебрать список, вы не можете отсортировать список,...

Имхо проще создать функцию, которая предоставляет вам списки:

from random import randint

def gimme_fruits ():
    return [ "Orange", "Banana", f"{randint(2,5)} Cherries",]

print(gimme_fruits())

Выход:

['Orange', 'Banana', '3 Cherries']

['Orange', 'Banana', '4 Cherries']

['Orange', 'Banana', '2 Cherries']

Каждый список "фиксирован" по количеству вишен - но вы можете сделать это:

for f in gimme_fruits() + gimme_fruits() + gimme_fruits():
    print(f)

Чтобы получить ваши витамины:

Orange
Banana
3 Cherries
Orange
Banana
3 Cherries
Orange
Banana
3 Cherries

Ещё вопросы

Сообщество Overcoder
Наверх
Меню