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, чтобы привязать «ядро» модуля к общей вспомогательной функции. В обоих случаях вы централизуете поведение, минимизируете повтор и сохраняете единообразный публичный интерфейс между файлами.