2025, Nov 29 03:02

Как привязать общие утилиты к функциям модуля в Python

Разбираем, почему в Python нет наследования функций, и как решить задачу: классы с делегированием или functools.partial. Примеры и как избежать дублирования.

Когда вы разбиваете библиотеку Python на несколько модулей, возникает желание держать каждый файл предельно сфокусированным на одной ключевой функции, а затем представить вокруг неё группу родственных вспомогательных средств. Трудность появляется, когда хочется определить эти помощники один раз, но автоматически привязывать их к «главной» функции конкретного файла, чтобы пользователи вызывали их так, будто они родные для каждого модуля. На словах это похоже на «наследование функций» плюс частичное применение — в одном решении. В Python такая комбинация не возникает сама по себе, и понимание причин сэкономит время и избавит от дублирования.

Минимальная конфигурация, из-за которой возникает вопрос

Представьте два модуля, в каждом — по одной базовой функции, и отдельную утилиту, которая умножает результат переданной функции на коэффициент:

# module_one.py

def add_pair(u, v):
    res = u + v
    return res
# module_two.py

def diff_pair(u, v):
    res = u - v
    return res
# utils.py

def scale_apply(fn, x, y, z):
    return fn(x, y) * z

Цель — избежать повторения «клеящего» кода, но при этом позволить вызовы вроде module_one.scale_apply(x, y, z) и module_two.scale_apply(x, y, z), где первый аргумент для scale_apply неявно берётся из собственной ключевой функции модуля.

Почему это кажется сложнее, чем должно быть

Первый порыв — смешать две идеи: наследование и частичную привязку аргументов функции. Обе существуют, но автоматически не сочетаются. Наследование в Python — это механизм только для классов. Если пытаться спроектировать это исключительно функциями, не будет иерархии, откуда «наследоваться». С функциями можно частично связывать аргументы, чтобы не повторяться, но такую привязку всё равно приходится выполнять явно.

Два пути решения

Первый путь — использовать классы. Вы определяете базовый класс с общим методом, который делегирует работу методу, определённому в подклассе. Каждый подкласс предоставляет операцию для конкретного файла. Это даёт нужное наследование, только через классы, а не функции:

class CoreBase:
    def mul_then(self, p, q, r):
        return self.core_op(p, q) * r

class AddOps(CoreBase):
    def core_op(self, p, q):
        return p + q

class SubOps(CoreBase):
    def core_op(self, p, q):
        return p - q

adder = AddOps()
subber = SubOps()

print(adder.mul_then(1, 2, 3))
print(subber.mul_then(1, 2, 3))

Второй путь — остаться в рамках функционального подхода. Вы сохраняете единственную реализацию вспомогательной функции и создаёте для каждого модуля вызываемые объекты, частично привязывая первый параметр к функции этого модуля. Наследования здесь нет, значит, ничего не произойдёт автоматически — вы один раз явно выполняете привязку:

def sum_two(a, b):
    return a + b

def minus_two(a, b):
    return a - b

def apply_factor(g, a, b, c):
    return g(a, b) * c

from functools import partial

apply_with_sum = partial(apply_factor, sum_two)
apply_with_diff = partial(apply_factor, minus_two)

print(apply_with_sum(1, 2, 3))
print(apply_with_diff(1, 2, 3))

Что выбрать и почему это важно

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

Практические выводы

В Python нет встроенного механизма «наследования функций». Нужна семантика наследования — берите классы и дайте базовому методу вызывать реализацию в подклассе. Предпочитаете функции — используйте functools.partial, чтобы привязать «ядро» модуля к общей вспомогательной функции. В обоих случаях вы централизуете поведение, минимизируете повтор и сохраняете единообразный публичный интерфейс между файлами.