Functools -Сила функцій вищого порядку в Python

Вихідний вузол: 1865357

Ця стаття була опублікована як частина Блогатон науки про дані

Вступ

Стандартна бібліотека Python має багато чудових модулів, які допоможуть у збереженні чистоти коду, його простоти та функціональності безумовно, один з

кешування

Почнемо з деяких з найпростіших, але потужних функцій функціональних модулів модуля. Почнемо з функцій кешування (а також декораторів) - lru_cache,cache та cached_property. Перший з них - lru_cache надає кеш останніх результатів виконання функцій, або іншими словами, запам'ятовує результат їх роботи:

from functools import lru_cache
import requests @lru_cache(maxsize=32)
def get_with_cache(url): try: r = requests.get(url) return r.text except: return "Not Found" for url in ["https://google.com/", "https://reddit.com/", "https://google.com/", "https://google.com/"]: get_with_cache(url) print(get_with_cache.cache_info())
# CacheInfo(hits=2, misses=4, maxsize=32, currsize=4)
print(get_with_cache.cache_parameters())
# {'maxsize': 32, 'typed': False}

У цьому прикладі ми робимо запити GET і кешуємо їх результати (до 32 результатів) за допомогою декоратора @lru_cache. Щоб перевірити, чи справді працює кешування, можна перевірити інформацію кешу функцій за допомогою методу cache_infoщо показує кількість звернень кешу та звернень. Декоратор також надає методи clear_cacheта cache_parametersдля скасування кешованих результатів та параметрів тестування відповідно.

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

Ще один декоратор для кешування у функціональних інструментах - це функція, яка називається просто cache. Це проста обгортка lru_cacheщо опускає аргумент max_size, зменшуючи його, і не видаляє старі значення.

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

from functools import cached_property class Page: @cached_property def render(self, value): # Do something with supplied value... # Long computation that renders HTML page... return html

Цей простий приклад показує. Як можна використовувати, властивість кешування, наприклад, для кешування відображеної сторінки HTML, яку потрібно показувати користувачеві знову і знову. Те саме можна зробити для певних запитів до бази даних або тривалих математичних розрахунків.

Ще одна красуня cached_propertyполягає в тому, що він працює лише під час пошуку, тому дозволяє нам змінювати значення атрибута. Після зміни атрибута раніше кешоване значення не зміниться, натомість нове значення буде обчислено та кешоване. Ви також можете очистити кеш, і все, що вам потрібно зробити, це видалити атрибут.

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

Порівняння та упорядкування

Напевно, ви вже знаєте, що в Python можна реалізувати оператори порівняння, такі як <, >=or ==, С lt, gtor eq. Однак усвідомлення кожного з них може бути досить неприємним eq, lt, le, gtor ge. На щастя, у функціональних інструментів є декоратор @total_orderingщо може допомогти нам у цьому, тому що все, що нам потрібно - це реалізувати eqодин із решти методів, а решта декоратора буде створено автоматично.

from functools import total_ordering @total_ordering
class Number: def __init__(self, value): self.value = value def __lt__(self, other): return self.value Number(3))
# True
print(Number(1) = Number(15))
# True
print(Number(10) <= Number(2))
# False

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

Перевантаження

Напевно, всіх нас вчили, що в Python немає перевантаження, але насправді існує простий спосіб реалізувати це за допомогою двох функцій із функтуолів, а саме методу єдиного диспетчеризації та/або єдиного диспетчеризації. Ці функції допомагають нам реалізувати алгоритм множинної відправки, що дозволяє динамічно набраним мовам програмування, таким як Python, розрізняти типи під час виконання.

Частковий

Ми всі працюємо з різними зовнішніми бібліотеками або фреймворками, багато з яких забезпечують функції та інтерфейси, які вимагають від нас передачі зворотного виклику, наприклад, для асинхронних операцій або прослуховування подій. Це нічого нового, але що, якщо нам також потрібно передати деякі аргументи разом із зворотним викликом. Тут він стане в нагоді у функціональних інструментах.partial. Може бути використаний partialзаморозити деякі (або всі) аргументи функції шляхом створення нового об’єкта зі спрощеним підписом функції. Збентежений? Давайте розглянемо деякі практичні приклади:

def output_result(result, log=None): if log is not None: log.debug(f"Result is: {result}") def concat(a, b): return a + b import logging
from multiprocessing import Pool
from functools import partial logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("default") p = Pool()
p.apply_async(concat, ("Hello ", "World"), callback=partial(output_result, log=logger))
p.close()
p.join()

Наведений вище код показує, як ви можете ним користуватися partialпередати функцію ( output_result) разом з аргументом ( log=logger) як зворотний дзвінок. У цьому випадку ми будемо використовувати multiprocessing.apply_async, який асинхронно обчислює результат функції ( concat) і повертає результат зворотного дзвінка. Однак, apply_asyncвін завжди передаватиме результат як перший аргумент, і якщо ми хочемо включити будь -які додаткові аргументи, як у випадку з log = logger, нам потрібно використовувати частковий.

Ми розглянули досить розширений варіант використання, і більш простим прикладом може бути звичайне створення функції, яка пише в stderr замість stdout:

import sys
from functools import partial print_stderr = partial(print, file=sys.stderr)
print_stderr("This goes to standard error output")

За допомогою цього простого трюку ми створили нову функцію, що викликається, яка завжди пройде file=sys.stderrяк іменований аргумент для виведення, що дозволяє нам спростити наш код і не потрібно щоразу вказувати значення іменованого аргументу.

І останній хороший приклад. Ми можемо використовувати partialу поєднанні з маловідомою функцією iterстворити ітератор, передавши об’єкт, що викликається, та sentinelin iter, які можна застосувати так:

from functools import partial RECORD_SIZE = 64 # Read binary file...
with open("file.data", "rb") as file: records = iter(partial(file.read, RECORD_SIZE), b'') for r in records: # Do something with the record...

Зазвичай під час читання файлу ми хочемо перебирати рядки, але у випадку двійкових даних нам може знадобитися перебирати записи фіксованого розміру. Ви можете зробити це, створивши об'єкт, що викликається, за допомогою partialякий зчитує зазначену частину даних і передає її iterдля створення ітератора. Потім цей ітератор викликає функцію читання до тих пір, поки вона не досягне кінця файлу, завжди приймаючи лише вказаний розмір шматка ( RECORD_SIZE). Нарешті, коли кінець файлу досягається, значення sentinel(б ») повертається і ітерація припиняється.

декоратори

Ми вже говорили про деяких декораторів у попередніх розділах, але не про декораторів, щоб створити більше декораторів. Одним з таких декораторів є функціональні інструменти.wraps. Щоб зрозуміти, навіщо це потрібно, давайте просто розглянемо приклад:

def decorator(func): def actual_func(*args, **kwargs): """Inner function within decorator, which does the actual work""" print(f"Before Calling {func.__name__}") func(*args, **kwargs) print(f"After Calling {func.__name__}") return actual_func @decorator
def greet(name): """Says hello to somebody""" print(f"Hello, {name}!") greet("Martin")
# Before Calling greet
# Hello, Martin!
# After Calling greet

У цьому прикладі показано, як можна реалізувати простий декоратор. Ми обгортаємо функцію, яка виконує певне завдання ( actual_func) із зовнішнім декоратором, і він стає самим декоратором, який потім може бути застосований до інших функцій, наприклад, як у випадку greet. Коли ви викликаєте функцію, greet ви побачите, що він роздруковує повідомлення як з actual_funcі самостійно. Виглядає нормально, чи не так? Але що станеться, якщо ми зробимо це:

print(greet.__name__)
# actual_func
print(greet.__doc__)
# Inner function within decorator, which does the actual work

Коли ми називаємо назву та документацію прикрашеної функції, ми розуміємо, що вони замінені значеннями з функції декоратора. Це погано, оскільки ми не можемо переписати всі назви функцій та документацію, коли ми використовуємо якийсь декоратор. Як можна вирішити цю проблему? Звичайно, з функціональними інструментами.wraps:

from functools import wraps def decorator(func): @wraps(func) def actual_func(*args, **kwargs): """Inner function within decorator, which does the actual work""" print(f"Before Calling {func.__name__}") func(*args, **kwargs) print(f"After Calling {func.__name__}") return actual_func @decorator
def greet(name): """Says hello to somebody""" print(f"Hello, {name}!") print(greet.__name__)
# greet
print(greet.__doc__)
# Says hello to somebody

Єдине призначення функції wrapsполягає в тому, щоб скопіювати ім'я, документацію, список аргументів тощо, щоб запобігти перезапису. Враховуючи це wrapsце також декоратор, ви можете просто додати його до нашої фактичної_функції, і проблема вирішена!

Зменшити

І останнє, але не менш важливе у функціональних модулях модуля - це це зменшення. Можливо, з інших мов ви знаєте це як fold(Хаскелл). Ця функція бере ітерабельність і складає (додає) всі свої значення в одне. Для цього існує безліч програм, наприклад:

from functools import reduce
import operator def product(iterable): return reduce(operator.mul, iterable, 1) def factorial(n): return reduce(operator.mul, range(1, n)) def sum(numbers): # Use `sum` function from standard library instead return reduce(operator.add, numbers, 1) def reverse(iterable): return reduce(lambda x, y: y+x, iterable) print(product([1, 2, 3]))
# 6
print(factorial(5))
# 24
print(sum([2, 6, 8, 3]))
# 20
print(reverse("hello"))
# olleh

Як видно з коду, reduce може спростити або згустити код в один рядок, що в іншому випадку було б набагато довшим. Враховуючи це, зазвичай поганою ідеєю є зловживання цією функцією лише заради зменшення коду, роблячи його «розумнішим», оскільки він швидко стає страшним і нечитабельним. З цієї причини, на мою думку, його слід використовувати помірно.

І якщо ви це пам’ятаєте reduce часто скорочує все до одного рядка, з ним можна ідеально поєднувати partial:

product = partial(reduce, operator.mul) print(product([1, 2, 3]))
# 6

І, нарешті, якщо вам потрібно більше, ніж просто остаточний «згорнутий» результат, ви можете скористатися accumulate- з іншого чудового модуля itertools. Для розрахунку максимуму його можна використати таким чином:

from itertools import accumulate data = [3, 4, 1, 3, 5, 6, 9, 0, 1] print(list(accumulate(data, max)))
# [3, 4, 4, 4, 5, 6, 9, 9, 9]

Висновок

Як ви бачите, функціональні інструменти мають багато корисних функцій та декораторів, які можуть полегшити ваше життя, але це лише вершина айсберга. Як я вже говорив на початку, у стандартній бібліотеці Python є багато функцій, які допомагають писати кращий код, тому на додаток до функцій, які ми розглянули тут, можна звернути увагу на інші модулі, такі як operatoror itertool. Для будь -яких запитів ви можете натиснути вікно моїх коментарів. Я постараюся вирішити ваші внески і сподіваюся дати вам бажані результати. Ви також можете зв’язатися зі мною у LinkedIn:-

https://www.linkedin.com/in/shivani-sharma-aba6141b6/

Носії, показані в цій статті, не належать Analytics Vidhya і використовуються на розсуд автора.

Джерело: https://www.analyticsvidhya.com/blog/2021/08/functools-the-power-of-higher-order-functions-in-python/

Часова мітка:

Більше від Аналітика Vidhya