Використання комбінаторних функцій у модулі itertools, Python, Програмування, статті

Девід Мертц (David Mertz)

Функціональне програмування на Python стало відкладеним

В Python 2.2 були введені прості генератори, а стандартні цикли перепродумани в термінах ітераторів. В Python 2.3 генератори стають стандартними (немає необхідності в _future_), А новий модуль itertools доданий для гнучкої роботи з ітераторами. Модуль itertools, По суті, це набір комбінаторних функцій вищого порядку, які, однак, працюють з ітераторами з відкладеним обчисленням, а не з кінцевими списками. У цій статті Девід розглядає цей новий модуль, показуючи виразну силу, що з'явилася з комбінаторними ітераторами.

Пояснення нової концепції

Поняття ітераторів було введено в Python версії 2.2. Хоча це не зовсім вірно; натяки на цю ідею були присутні вже в більш ранній функції xrange() й у файловому методі .xreadlines(). Python 2.2 узагальнив це поняття в більшій частині своїх внутрішніх реалізацій і значно спростив програмування ітераторів, визначених користувачем, увівши ключове слово yield (Присутність yield перетворює функцію в генератор, який у свою чергу повертає ітератор).

Привабливість ітераторів пояснюється двома причинами. Робота з даними як послідовностями – часто найбільш простий підхід, а послідовність, що обробляється в лінійному порядку, насправді часто не повинна існувати вся відразу.

Попередження x*() надають очевидні приклади цих принципів. Якщо ви хочете зробити що-небудь мільярд разів, ваша програма, ймовірно, буде виконуватися який час, проте, загалом не потрібно вимагати для неї багато пам'яті. Подібним чином для багатьох типів файлів обробка може виконуватися порядково, і немає необхідності зберігати в пам'яті весь файл. Усі види інших послідовностей також можна найкращим чином піддати відкладеного обчисленню, вони могли б покладатися на дані, що надходять у вигляді приростів по каналу, або на обчислення, що виконуються крок за кроком.

Велику частину часу ітератор використовується в циклі for точно так само, як і справжня послідовність. Ітератори надають метод .next(), Який може бути явно запущений, але в 99% часу те, що ви побачите – це щось на кшталт:

Цей цикл завершується, коли закулісний звернення до iterator.next() збуджує виключення StopIteration. Між іншим, справжня послідовність може бути перетворена в ітератор викликом iter(seq) – Це анітрохи не збереже пам'ять, але може бути корисно у функціях, що обговорюються нижче.

Пітоновское прогресуюче роздвоєння особистості

Щодо Python до функціонального програмування є щось шизофренічне. З одного боку, багато розробників Python недооцінюють традиційні функції функціонального програмування: map(), filter() і reduce() – Зазвичай рекомендуючи використовувати замість них спискові включення (list comprehensions). Але весь модуль itertools складений з функцій точно такого ж виду і просто оперує над "відкладеними послідовностями" (ітераторами), а не над закінченими послідовностями (списками, кортежами). Більше того, в Python 2.3 відсутній синтаксис для "ітераторних включень" (iterator comprehensions), які здавалося б мають ті ж мотиви, що і спискові включення.

Я підозрюю, що Python в кінцевому рахунку розвине якусь форму ітераторних включень, але це залежить від знаходження відповідного природного синтаксису для них. Між тим, у нас є ряд зручних комбінаторних функцій в модулі itertools. Взагалі кожна з цих функцій приймає деякі параметри (звичайно включаючи деякі базові ітератори) і повертає новий ітератор. Наприклад, функції ifilter(), imap() і izip() повністю еквівалентні відповідним вбудованим функціям, у яких відсутній початкове i.

Відсутні еквіваленти

В itertools немає ireduce(), Хоча це могло б здатися природним; можлива Пітоновская реалізація така:

Лістинг 1. Приклад реалізації ireduce ()

Випадок використання ireduce() подібний варіанту з reduce(). Наприклад, припустимо, що ви хочете підсумувати список чисел, що знаходяться у великому файлі, але зупинитися, коли виконується умова. Ви могли б контролювати поточний підсумок за допомогою:

Лістинг 2. Додавання списку чисел і підбиття підсумку

Більш реальний приклад, ймовірно, щось на зразок застосування потоку подій до об'єкта, що підтримує внутрішній стан, як, наприклад, до елементу управління графічного інтерфейсу користувача. Але навіть простий вищенаведений приклад демонструє ітераторние комбінатори в стилі функціонального програмування.

Фабрики базових ітераторів

Всі функції в модулі itertools можуть бути легко реалізовані на чистому Python як генератори. Основний зміст цього модуля в Python 2.3 + – забезпечити стандартну поведінку та імена для деяких корисних функцій. Хоча програмісти могли б написати свої власні версії, на практиці кожен створив би злегка несумісні варіації. Крім цього, ще одна причина – ефективна реалізація ітераторних комбінаторів на С. Використання функцій itertools буде помітно швидше, ніж написання своїх власних комбінаторів. У стандартній документації показані еквівалентні реалізації на чистому Python для кожної функції itertools, Так що немає необхідності повторювати їх у цій статті.

Функції в модулі itertools настільки базові – і досить чітко зазначені – що, ймовірно, має сенс імпортувати всі імена з цього модуля. Функція enumerate(), Наприклад, цілком могла б існувати в itertools, Але замість цього є вбудованою функцією в Python 2.3 +. Зокрема, ви можете легко висловити enumerate() в термінах функцій itertools:

Давайте розглянемо декілька функцій itertools, Які НЕ використовують інші ітератори в якості базису, а просто створюють ітератори "з нуля". times() повертає ітератор, який видає ідентичний об'єкт безліч разів; сама по собі ця можливість досить зручна, але по-справжньому вона хороша при надмірному використанні xrange() і індексної змінної для простого повторення дії. Тобто замість:

Ви можете тепер використовувати більш нейтральне:

Якщо другий аргумент не передається в times(), Вона просто повторно видає None. Функція repeat() подібна times(), Але необмежено повертає той же самий об'єкт. Цей ітератор зручний або якщо у циклу є незалежне умова break, Або в комбінатора, як izip() і imap().

Функція count() схожа на помісь repeat() і range(). count() необмежено повертає послідовно йдуть цілі (починаючи з факультативного аргумента). Проте, з урахуванням того, що зараз count() коректно не підтримує автоматичне перетворення до long при переповненні, ви могли б з однаковим успіхом як і раніше використовувати xrange(n,sys.maxint); Вона не є необмеженою буквально, але для більшості цілей призводить до того ж. Як і repeat(), count() особливо зручна в інших ітераторних комбінатора.

Комбінаторні функції

Кілька реальних комбінаторних функцій в itertools вже були побіжно згадані. ifilter(), izip() і imap() поводяться саме так, як слід було б очікувати від відповідних функцій над послідовностями. ifilterfalse() – Допоміжна функція, так що вам не потрібно інвертувати предикатних функцію в lambda і def (І це значно економить накладні витрати на виклик функції). Але функціонально ви могли б визначити ifilterfalse() (Приблизно, ігноруючи предикат None), Зокрема:

Опції dropwhile() і takewhile() поділяють ітератор предикатом. Перша ігнорує повертаються елементи, поки предикат не виконано, а друга видає, поки предикат виконується. dropwhile пропускає невизначений число первинних елементів ітератора, щоб він зміг почати ітерації тільки після затримки. takewhile() запускається негайно, але завершує ітератор, якщо переданий предикат стає true.

Функція islice(), По суті, просто ітераторная версія зрізу списку. Ви можете задати початок, останов і крок, як з регулярними зрізами. Якщо початок задано, ряд елементів відкидається, поки переданий ітератор не досягне необхідного елемента. Це ще один випадок, коли, як я думаю, можливо удосконалити Python – найкраще для ітераторів було б просто розпізнати розрізи, як роблять списки (як синонім того, що робить islice()).

Остання функція starmap() – Це легка варіація imap(). Якщо функція, яка передається як аргумент, приймає набір аргументів, переданий ітератор повинен видавати кортежі належного розміру. По суті, це те ж саме, що й imap() з кількома переданими ітеріруемимі аргументами, тільки з набором ітеріруемих аргументів, попередньо комбінованих izip().

Більше ніж основи

Функції, включені в itertools – Запорука гарного початку. Як мінімум, вони стимулюють програмістів на Python до використання та комбінування ітераторів. Загалом, широке використання ітераторів безумовно важливо для майбутнього Python. Але, крім уже включених, є й інші функції, які я б рекомендував для майбутніх редакцій цього модуля. Ними можна скористатися негайно – зрозуміло, якщо вони потім будуть включені, частина імен і інтерфейсів може відрізнятися.

Одна категорія, яка могла б у загальному здатися корисною-це функції, що приймають набір ітеріруемих аргументів, потім видають окремі елементи з кожного ітеріруемого аргументу. На противагу цього, izip() видає кортежі елементів, а imap() видає значення, обчислені з основних елементів. Два очевидних рішення, на мою думку, це chain() і weave(). Перша функція, по суті, подібна конкатенації послідовностей (доречно відкладеною). Тобто де для простих послідовностей ви використовували б, наприклад:

для звичайних ітеріруемих аргументів ви могли б використовувати:

Пітоновская реалізація така:

Лістинг 3. Приклад реалізації chain ()

Ви також могли б комбінувати ітератори, перемішуючи їх. Вбудований синтаксис, щоб зробити те ж саме з послідовностями, відсутня, однак сама weave() також прекрасно працює для кінцевих послідовностей. Можлива реалізація приведена нижче (Магнус Лай Хетленд (Magnus Lie Hetland) призвів схожу функцію на comp.lang.python):

Лістинг 4. Приклад реалізації weave ()

Дозвольте мені проілюструвати поведінку weave(), Оскільки воно може бути не відразу очевидно з цієї реалізації:

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

Я запропоную ще одну можливу функцію itertools. Підхід до проблеми в ній багато в чому запозичений з функціонального програмування. icompose() володіє певною симетрією з розглянутим вище ireduce(). Однак, якщо ireduce() передає в функцію (відкладену) послідовність величин і видає кожен результат, icompose() застосовує послідовність функцій до повертається величиною кожної попередньої функції. Ймовірне використання ireduce() – Передати послідовність подій в довгоживучий об'єкт. icompose() може замість цього послідовно передавати об'єкт функцій-мутатори, кожна з яких повертає новий об'єкт. Якщо перша – досить традиційний для об'єктно-орієнтованого програмування спосіб подання подій, друга більш характерна для підходів функціонального програмування.

Нижче – можлива реалізація icompose():

Лістинг 5. Приклад реалізації icompose ():

Висновок

Ітератори, що подаються як відкладені послідовності, потужна концепція, що відкриває нові стилі Пітоновского програмування. Хоча є тонка різниця між поданням ітератора як джерела даних і його поданням до послідовному стилі. Жоден спосіб представлення сам по собі не правильніше іншого, але другий відкриває шлях до комбінаторного скорочення для маніпулювання програмними подіями. Комбінаторні функції в itertools (І особливо деякі, які він може розвинути, як ті, що я пропоную) близько підходять до декларативного стилю програмування. На мою думку, ці декларативні стилі менш схильні до помилок і більше потужні.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*