Стандартная библиотека Python включает в себя множество отличных модулей, которые могут помочь вам сделать ваш код чище и проще и Functools
Определенно один из них. Этот модуль предлагает много полезных функций более высокого порядка, которые действуют или возвращают другие функции, которые мы можем использовать для реализации кэширования функций, перегрузки, создания декораторов и в целом, чтобы сделать наш код немного больше функциональный , Итак, давайте отправимся в тур и увидим все, что он может предложить …
Кэширование
Давайте начнем с самых простых, но довольно мощных функций Functools
модуль. Это функции кэширования (а также декораторы) – lru_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://martinheinz.dev/", "https://reddit.com/", "https://google.com/", "https://dev.to/martinheinz", "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}
В этом примере мы делаем запросы и кэшируем их результаты (до 32 кэшированных результатов), используя @lru_cache
декоратор. Чтобы увидеть, действительно ли кеширование работает, мы можем проверить информацию о кеше нашей функции, используя cache_info
Метод, который показывает количество ударов и промахов в кешах. Декоратор также предоставляет clear_cache
и cache_parameters
Методы недействительных кэшированных результатов и проверки параметров соответственно.
Если вы хотите иметь немного больше гранулированного кэширования, то вы также можете включить необязательный typed = true
Аргумент, который делает так, что аргументы разных типов кэшируются отдельно.
Еще один декоратор кэширования в Functools
Функция просто называется кэш
Анкет Это простая обертка на вершине 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, такие как <
, > =
или ==
используя __lt__
, __gt__
или __eq__
Анкет Это может быть довольно раздражающим реализовать каждый из них __eq__
, __lt__
, __le__
, __gt__
, или __ge__
хотя. К счастью, Functools
Модуль включает в себя @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 < other.value def __eq__(self, other): return self.value == other.value print(Number(20) > Number(3)) # True print(Number(1) < Number(5)) # True print(Number(15) >= Number(15)) # True print(Number(10) <= Number(2)) # False
Выше показывает, что, хотя мы реализовали только __eq__
и __lt__
Мы можем использовать все богатые операции сравнения. Наиболее очевидным преимуществом этого является удобство отсутствия необходимости писать все эти дополнительные магические методы, но, вероятно, более важным является сокращение кода и улучшенная читабельность.
Перегрузка
Вероятно, все мы учили, что перегрузка функции невозможна в Python, но на самом деле есть простой способ реализовать ее, используя две функции в Functools
Модуль – SingleDispatch
и/или SingleDispatchMethod
Анкет Эти функции помогают нам реализовать то, что мы бы назвали Несколько отправок Алгоритм, который является способом для динамически типичных языков программирования, таких как Python, чтобы различать типы во время выполнения.
Учитывая, что перегрузка функции является довольно большой темой само по себе, я посвятил отдельную статью Python’s SingleDispatch
и SingleDispatchMethod
, так что если вы хотите узнать об этом больше, то вы можете прочитать больше об этом Здесь Анкет
Частичный
Мы все работаем с различными внешними библиотеками или рамками, многие из которых предоставляют функции и интерфейсы, которые требуют от нас выполнения функций обратного вызова – например, для асинхронных операций или для слушателей событий. В этом нет ничего нового, но что, если нам нужно также передать некоторые аргументы вместе с функцией обратного вызова. Вот где functools.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()
Приведенный выше фрагмент демонстрирует, как мы могли бы использовать частично
чтобы пройти функцию ( 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")
С помощью этого простого трюка мы создали новую функцию Callable (функция), которая всегда будет передавать Файл = Sys.stderr
Аргумент ключевого слова для Печать
, позволяя нам упростить наш код, не приходилось определять аргумент ключевого слова каждый раз.
И последний пример для хорошей меры. Мы также можем использовать частично
Использовать малоизвестную особенность iter
Функция – можно создать итератор, передавая Callable и значение Sentinel к 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...
Обычно при чтении файла мы хотим итерации по строкам, но в случае бинарных данных мы могли бы вместо этого итерации по записям фиксированного размера. Это можно сделать, создав Callable, используя частично
Это считывает указанный патрон данных и передал его iter
который затем создает из этого итератор. Этот итератор затем вызывает Читать
Функция до тех пор, пока не будет достигнут конец файла, всегда принимая только указанный патрон данных ( record_size
). Наконец, когда достигнут конец файла Сентинельное значение ( b ''
) возвращается, и итерация останавливается.
Декораторы
Мы уже говорили о некоторых декораторах в предыдущих разделах, но не о декораторах для создания большего количества декораторов. Один такой декоратор – functools.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
) с внешним декоратор
функция, которая становится декоратором, которую мы можем затем прикрепить к другим функциям – например, с Приветствую
функционируйте здесь. Когда Приветствую
Функция называется вы увидите, что она печатает оба сообщения из actual_func
а также свои собственные. Все выглядит нормально, здесь нет проблем, верно? Но что, если мы попробуем следующее:
print(greet.__name__) # actual_func print(greet.__doc__) # Inner function within decorator, which does the actual work
Когда мы осматриваем имя и документ о украшенной функции, мы обнаруживаем, что оно было заменено значениями изнутри функции декоратора. Это не очень хорошо – мы не можем перезаписать все наши имена функций и документы каждый раз, когда мы используем какой -то декоратор. Итак, как мы решаем это? – С functools.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
Единственная работа Обертывания
Функция состоит в том, чтобы копировать имя, Docstring, список аргументов и т. Д. Чтобы не было перезаписано их. И учитывая, что Обертывания
также декоратор, мы можем просто пощечить его на нашем actual_func
И проблема решена!
Уменьшать
Последнее, но не менее важное в Functools
Модуль уменьшить
Анкет Вы можете знать это на других языках как склад
(Хаскелл). Что делает эта функция, это принять итерационную и уменьшить (или сложить) все его значения в одиночные. Это много разных приложений, так что вот некоторые из них:
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
Как вы можете видеть из приведенного выше кода, уменьшить
может упростить и часто сжимать код в одну строку, которая в противном случае было бы намного дольше. С учетом сказанного, чрезмерное использование этой функции только ради сокращения кода, создавая “Умный” или сделать это больше функциональный Обычно это плохая идея, так как она становится ужасной и нечитаемой очень быстро, так что, на мой взгляд, используйте ее экономно.
Также учитывая, что использование уменьшить
Как правило, производит однострочные, это идеальный кандидат на частичный
:
product = partial(reduce, operator.mul) print(product([1, 2, 3])) # 6
И, наконец, если вам нужно не только финал уменьшен Результат, но также и промежуточные, тогда вы можете использовать накапливаться
Вместо этого – функция из другого великого модуля итулс
Анкет Вот как вы можете использовать его для вычисления максимума работы:
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]
Заключительные мысли
Как вы могли видеть здесь, Functools
Особенности много полезных функций и декораторов, которые могут облегчить жизнь, но этот модуль на самом деле просто верхушка айсберга. Как я уже упоминал в начале стандартной библиотеки Python, включает в себя множество модулей, которые могут помочь вам создать лучший код, поэтому, помимо Functools
который мы исследовали здесь, вы также можете заказать другие модули, такие как оператор
или итул
(Я тоже написал статью об этом, вы можете проверить это Здесь ) или просто перейти прямо к Индекс модуля Python и нажмите на то, что привлечет ваше внимание И я уверен, что вы найдете там что -то полезное.
Оригинал: “https://dev.to/martinheinz/functools-the-power-of-higher-order-functions-in-python-3dg0”