Рубрики
Без рубрики

Functools – Сила функций высшего порядка в Python

Стандартная библиотека Python включает в себя множество отличных модулей, которые помогут вам сделать ваш код очиститель и … Tagged with Python, учебник.

Стандартная библиотека 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”