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

Правильный способ перегрузки функций в Python

Перегрузка функций – это общий шаблон программирования, который, похоже, зарезервирован для статически набран, … Теги с Python, учебником.

Перегрузка функций – это общий шаблон программирования, который, кажется, зарезервирован для статически набранных, скомпилированных языков. Тем не менее, есть простой способ реализации его в Python с помощью Несколько диспетчеров или как это называется в Python Мультиметоды Отказ

Перегрузка

Первые вещи сначала – вы можете спросить, как мы можем реализовать метод перегрузки в Python, когда мы все знаем, что это невозможно? Ну, хотя Python – это динамически напечатанный язык, и поэтому не может иметь надлежащий перегрузку метода, как это требует, чтобы язык мог различить различные типы при совокупности времени, мы все еще можем реализовать его немного по-другому, который подходит для динамично- Напечатанные языки.

Этот подход называется Несколько диспетчеров или Мультиметоды , где интерпретатор дифференцирует между несколькими реализациями функции/метода во время выполнения на основе динамически определенных типов. Чтобы быть более точным, язык использует типы аргументов, передаваемых функции во время его вызова, чтобы динамически выбирать, какую из реализации нескольких функций для использования (или отправки).

Теперь вы можете думать: «Нам действительно нужно это, хотя? Если он не может быть реализован нормально, может быть, мы не должны использовать его в Python … » Да, действительный момент, но есть веские причины, по которым нужно захотеть реализовать некоторую форму функции/метода перегрузки в Python. Это мощный инструмент, который может сделать код более лаконичным, читаемым и минимизировать его сложность. Без мультиметод, хотя «Очевидный способ» Для этого используется проверка типа с Isinstance () Отказ Это очень уродливое, хрупкое решение, которое закрыто для расширения, и я бы назвал его анти-образцом.

Кроме того, там уже есть метод перегрузки в Python для операторов и методов, таких как Лен () или новый() Использование так называемого Dunder или волшебство Методы (см. Документы здесь Несомненно И мы все используем это довольно часто, так почему бы не использовать правильную перегрузку для все Функция, верно?

Итак, теперь мы знаем, что мы можем добраться до реализации перегрузки в Python, так как именно мы это делаем?

Одиночная отправка

Выше мы говорили о Несколько диспетчеров , но Python не поддерживает это из коробки, или другими словами Несколько диспетчеров не является особенностью стандартной библиотеки Python. Что доступно для нас, однако, называется Одиночная диспетчерская Так что давайте начнем с этого проще всего первого чечка.

Единственная фактическая разница между мульти и одинокий Отправка – это количество аргументов, которые мы можем перегружать. Итак, для этой реализации в стандартной библиотеке это только одна.

Функция (и декоратор), которая обеспечивает эту функцию, называется Singledispatch и можно найти в Functools модуль Отказ

Вся эта концепция лучше всего объяснена с некоторыми примерами. Есть много «Академический» Примеры функций перегрузки (геометрические фигуры, добавление, вычитание …) Что мы, наверное, все это уже видели. Вместо того, чтобы пройти через это, давайте посмотрим на некоторые практические примеры. Итак, вот первый пример для Singledispatch Форматировать даты, время и даты данных:

from functools import singledispatch
from datetime import date, datetime, time

@singledispatch
def format(arg):
    return arg

@format.register
def _(arg: date):
    return f"{arg.day}-{arg.month}-{arg.year}"

@format.register
def _(arg: datetime):
    return f"{arg.day}-{arg.month}-{arg.year} {arg.hour}:{arg.minute}:{arg.second}"

@format.register(time)
def _(arg):
    return f"{arg.hour}:{arg.minute}:{arg.second}"

print(format("today"))
# today
print(format(date(2021, 5, 26)))
# 26-5-2021
print(format(datetime(2021, 5, 26, 17, 25, 10)))
# 26-5-2021 17:25:10
print(format(time(19, 22, 15)))
# 19:22:15

Мы начинаем с определения базы формат Функция, которая будет перегружен. Эта функция украшена @singlededispatch и обеспечивает базовую реализацию, которая используется, если нет лучших вариантов. Далее мы определяем отдельные функции для каждого типа, которые мы хотим перегружать – в этом случае Дата , datetime и время – Каждый из них имеет имя _ (подчеркивает), потому что они будут называться (отправлены) через формат Способ в любом случае, поэтому нет необходимости давать им полезные имена. Каждый из них также украшен @ Формат. Регистрация что придает их к ранее упомянутым формат функция. Затем, чтобы позволить различить дифференцировать между типами, у нас есть два варианта – мы можем использовать аннотации типа – как продемонстрированы в первых двух случаях или явно добавляют тип в декоратор как с последним из примера.

В некоторых случаях может иметь смысл использовать ту же реализацию для нескольких типов – например, для типов номеров, таких как int и плавать – Для этих ситуаций разрешено укладку декоратора, что означает, что вы можете список (стек) несколько @ Формат. Регистрация (тип) Линии для связывания функции со всеми допустимыми типами.

Помимо возможности перегружать основные функции, functools Модуль содержит также SingledispatchMethod Это может быть применено к способам класса. Пример этого может быть следующим:

from functools import singledispatchmethod
from datetime import date, time

class Formatter:
    @singledispatchmethod
    def format(self, arg):
        raise NotImplementedError(f"Cannot format value of type {type(arg)}")

    @format.register
    def _(self, arg: date):
        return f"{arg.day}-{arg.month}-{arg.year}"

    @format.register
    def _(self, arg: time):
        return f"{arg.hour}:{arg.minute}:{arg.second}"

f = Formatter()
print(f.format(date(2021, 5, 26)))
# 26-5-2021
print(f.format(time(19, 22, 15)))
# 19:22:15

Несколько диспетчеров

Часто Одиночная диспетчерская не будет достаточно, и вам может понадобиться правильный Несколько диспетчеров Функциональность. Это доступно от Multledispatch Модуль, который можно найти здесь и может быть установлен с PIP Установите Multiledispatch Отказ

Этот модуль и это декоратор – @Dispatch , ведет себя очень похожи на @singlededispatch в стандартной библиотеке. Только фактическое отличие состоит в том, что она может принимать несколько типов в качестве аргументов:

from multipledispatch import dispatch

@dispatch(list, str)
def concatenate(a, b):
    a.append(b)
    return a

@dispatch(str, str)
def concatenate(a, b):
    return a + b

@dispatch(str, int)
def concatenate(a, b):
    return a + str(b)


print(concatenate(["a", "b"], "c"))
# ['a', 'b', 'c']
print(concatenate("Hello", "World"))
# HelloWorld
print(concatenate("a", 1))
# a1

Вышеуказанный фрагмент показывает, как мы можем использовать @Dispatch Декоратор для перегрузки нескольких аргументов, например, для реализации конкатенации различных типов. Как вы, вероятно, заметили, с Multledispatch Библиотека Нам не нужно было определить и регистрировать базовую функцию, скорее мы создали несколько функций с же именем. Если мы хотели обеспечить базовую реализацию, мы могли бы использовать @dispatch (объект, объект) который будет ловить любые неспецифические типы аргументов.

Предыдущие примеры показывают доказательство концепции, но если мы хотели действительно реализовать такие ConcateNate Функция, нам нужно было бы сделать его гораздо более общемысленным. Это может быть решено с использованием профсоюзных типов. В этом конкретном примере мы могли бы изменить первую функцию следующим образом:

@dispatch((list, tuple), (str, int))
def concatenate(a, b):
    return list(a) + [b]

print(concatenate(["a", "b"], "c"))
# ['a', 'b', 'c']
print(concatenate(("a", "b"), 1))
# ['a', 'b', 1]

Это сделает это так, чтобы первый аргумент функции может быть любой из список или корпус , в то время как второй будет ул или int. . Это уже намного лучше, чем предыдущее решение, но он может быть дополнительно улучшен с использованием абстрактных типов. Вместо того, чтобы перечислить все возможные последовательности, мы можем использовать Последовательность абстрактный тип (предполагая, что наша реализация может справиться с ней), которая охватывает такие вещи, как список , кортеж или Диапазон :

from collections.abc import Sequence

@dispatch(Sequence, (str, int))
def concatenate(a, b):
    return list(a) + [b]

Если вы хотите взять этот подход, то приятно взглянуть на коллекции .abc Модуль и посмотрите, какой тип данных контейнера наилучшим образом подходит для ваших потребностей. В основном, чтобы убедиться, что ваша функция сможет обрабатывать все типы, которые попадают в выбранный контейнер.

Все это смешивание и сопоставление типов аргументов удобно, но также могут вызывать неясности при выборе подходящей функции для определенного определенного набора параметров. К счастью, Multledispatch Обеспечивает Двусмысленность Что поднята, если возможен неоднозначное поведение:

test_multipledispatch:10: AmbiguityWarning:
Ambiguities exist in dispatched function some_func

The following signatures may result in ambiguous behavior:
    [str, object], [object, str]


Consider making the following additions:

@dispatch(str, str)
def some_func(...)

Закрытие мыслей

В этой статье мы проходили простую, но мощную концепцию, которую я редко вижу, используемую в Python, что позор считает, что он может значительно улучшить читаемость кода и избавиться от осмотра Anti-Patters, как тип проверки типа Isinstance () Отказ Кроме того, я надеюсь, что вы согласитесь, что этот подход к перегрузке функций следует учитывать «Очевидный способ» И я надеюсь, что вы будете использовать его, когда это необходимо.

Если вы хотите погрузиться глубже в эту тему и грязные руки вы можете реализовать мультиметоды, как показано в Статья Гвидо – Это может быть хорошим упражнением, чтобы понять, как работает несколько диспетчеров.

Наконец, я также должен также отметить, что эта статья пропускает примеры известных Оператор перегрузки который я упомянул в начале, а также некоторые подходы к перегрузке конструкторов, например, использование заводы . Итак, в случае, если вы ищете, посмотрите эти ссылки/ресурсы, которые дают хороший обзор там темы.

Оригинал: “https://dev.to/martinheinz/the-correct-way-to-overload-functions-in-python-hlm”