Чи yield
є хороша ідея з мовою для генератора, наприклад ?
Я хотів би відповісти на це з точки зору Python з виразним так, це чудова ідея .
Почну з розгляду деяких питань та припущень у вашому запитанні, а потім продемонструю розповсюдженість генераторів та їх необґрунтовану корисність у Python пізніше.
За допомогою звичайної негенераторної функції ви можете викликати її, і якщо їй подати один і той же вхід, він поверне той самий вихід. З врожайністю він повертає різний вихід, виходячи з внутрішнього стану.
Це помилково. Методи на об'єктах можна розглядати як самі функції, із власним внутрішнім станом. У Python, оскільки все є об'єктом, ви можете фактично отримати метод від об'єкта і пройти навколо цього методу (який пов'язаний з об'єктом, з якого він прийшов, тому він пам'ятає його стан).
Інші приклади включають свідомо випадкові функції, а також методи введення, такі як мережа, файлова система та термінал.
Як така функція вписується в мовну парадигму?
Якщо мовна парадигма підтримує такі речі, як функції першого класу, а генератори підтримують інші мовні функції, такі як протокол Iterable, то вони легко вписуються.
Чи насправді це порушує якісь умовності?
Ні. Оскільки він викладений на мові, конвенції побудовані навколо і включають (або вимагають!) Використання генераторів.
Чи повинні компілятори / перекладачі мови програмування виключати будь-які умовності для здійснення такої функції
Як і будь-яка інша функція, компілятор просто повинен бути розроблений для підтримки функції. У разі Python функції вже є об'єктами зі станом (наприклад, аргументи за замовчуванням та анотації до функцій).
чи потрібна мова для впровадження багатопотокової передачі, щоб ця функція працювала, чи це можна зробити без технології нарізки?
Факт забави: реалізація Python за замовчуванням взагалі не підтримує нитку. Він оснащений глобальним блоком інтерпретаторів (GIL), тому нічого насправді не працює одночасно, якщо ви не запустили другий процес для запуску іншого примірника Python.
Примітка: приклади наведені в Python 3
Поза вихід
Хоча yield
ключове слово можна використовувати в будь-якій функції, щоб перетворити його в генератор, це не єдиний спосіб зробити його. У Python представлені генераторні вирази, потужний спосіб чітко виразити генератор за допомогою іншого ітерабельного (включаючи інших генераторів)
>>> pairs = ((x,y) for x in range(10) for y in range(10) if y >= x)
>>> pairs
<generator object <genexpr> at 0x0311DC90>
>>> sum(x*y for x,y in pairs)
1155
Як бачите, синтаксис є не тільки чистим і читабельним, але і вбудованими функціями, такими як sum
генератори прийому.
З
Ознайомтеся з пропозицією щодо удосконалення Python для оператора With . Це зовсім інше, ніж ви могли б очікувати від заяви With з інших мов. За невеликої допомоги стандартної бібліотеки генератори Python прекрасно працюють як контекстні менеджери для них.
>>> from contextlib import contextmanager
>>> @contextmanager
def debugWith(arg):
print("preprocessing", arg)
yield arg
print("postprocessing", arg)
>>> with debugWith("foobar") as s:
print(s[::-1])
preprocessing foobar
raboof
postprocessing foobar
Звичайно, друк речей - це найсумніше, що ви можете зробити тут, але це показує видимі результати. Більш цікаві варіанти включають автоматичне керування ресурсами (відкриття та закриття файлів / потоків / мережевих з'єднань), блокування для одночасності, тимчасове завершення або заміна функції та декомпресія та повторне стиснення даних. Якщо функція виклику нагадує введення коду у ваш код, то з операторами подібно до загортання частин коду в інший код. Однак ви його використовуєте, це надійний приклад легкої гачки мовної структури. Генератори на основі урожаю - це не єдиний спосіб створення контекстних менеджерів, але, безумовно, є зручним.
За часткове виснаження
Для циклів у Python цікавий спосіб працювати. Вони мають такий формат:
for <name> in <iterable>:
...
По-перше, вираз, який я викликав <iterable>
, оцінюється, щоб отримати ітерабельний об'єкт. По-друге, ітерабел __iter__
закликав його, і отриманий ітератор зберігається поза кадром. Згодом __next__
викликається ітератор, щоб отримати значення, яке прив'язується до введеного вами імені <name>
. Цей крок повторюється, поки заклик __next__
кинути a StopIteration
. Виняток проковтується циклом for, і звідти триває виконання.
Повернення до генераторів: коли ви викликаєте __iter__
генератор, він просто повертається.
>>> x = (a for a in "boring generator")
>>> id(x)
51502272
>>> id(x.__iter__())
51502272
Це означає, що ви можете відокремити ітерацію над чим-небудь від того, що ви хочете зробити з цим, і змінити таку поведінку на середині. Нижче зауважте, як один і той же генератор використовується в двох петлях, а в другій він починає виконувати з того місця, де він зупинився від першого.
>>> generator = (x for x in 'more boring stuff')
>>> for letter in generator:
print(ord(letter))
if letter > 'p':
break
109
111
114
>>> for letter in generator:
print(letter)
e
b
o
r
i
n
g
s
t
u
f
f
Ледача оцінка
Одне з нижчих сторін для генераторів порівняно зі списками - це єдине, до чого можна отримати доступ у генераторі, це наступне, що виходить із нього. Ви не можете повернутися назад і щодо попереднього результату, або перейти до більш пізнього, не проходячи проміжні результати. Верхня частина цього генератора може займати майже не пам'ять у порівнянні з його еквівалентним списком.
>>> import sys
>>> sys.getsizeof([x for x in range(10000)])
43816
>>> sys.getsizeof(range(10000000000))
24
>>> sys.getsizeof([x for x in range(10000000000)])
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
sys.getsizeof([x for x in range(10000000000)])
File "<pyshell#10>", line 1, in <listcomp>
sys.getsizeof([x for x in range(10000000000)])
MemoryError
Генератори також можуть бути ліниво прикованими.
logfile = open("logs.txt")
lastcolumn = (line.split()[-1] for line in logfile)
numericcolumn = (float(x) for x in lastcolumn)
print(sum(numericcolumn))
Перший, другий та третій рядки просто визначають генератор кожен, але не виконують жодної реальної роботи. Коли викликається останній рядок, сума запитує числовий стовпець для значення, числовий стовпець потребує значення з останнього стовпця, останній стовпець запитує значення з logfile, яке потім фактично зчитує рядок з файлу. Цей стек розгортається, поки сума не отримає перше ціле число. Потім процес повторюється для другого рядка. На даний момент сума має два цілі числа, і вона додає їх разом. Зауважте, що третій рядок ще не було прочитано з файлу. Потім сума надходить на запит значень з числового стовпчика (повністю не звертаючи уваги на решту ланцюга) та додавання їх, поки числовий стовпець не вичерпається.
Дійсно цікава частина тут полягає в тому, що рядки читаються, споживаються та відкидаються окремо. У жодному разі весь файл не запам'ятовується відразу. Що станеться, якщо цей файл журналу є, скажімо, терабайт? Це просто працює, тому що читає лише один рядок.
Висновок
Це не повний огляд усіх застосувань генераторів в Python. Помітно, я пропустив нескінченні генератори, стан машини, передаючи значення назад і їх взаємозв'язок з супровідними програмами.
Я вважаю, що достатньо продемонструвати, що ви можете мати генератори як чітко інтегровану корисну мову.
yield
по суті є двигуном держави. Це не означає щоразу повертати один і той же результат. Що це буде робити з абсолютною впевненістю, це повернути наступний товар у безлічі кожного разу, коли він буде викликаний. Нитки не потрібні; вам потрібно закрити (більш-менш), щоб зберегти поточний стан.