вторник, 19 мая 2009 г.

Перегрузка функций в python

Так уж случилось что пришел я к python'у из мира С++, Как и многие "эмигранты" в начале своего изучения языка, я пытался писать на нем как на С++. Именно тогда мне не хватало перегрузки функций, особенно это чувствовалось в конструкторах. Позже, освоившись с дизайном языка, в таких случаях я стал использовать именованные параметры (keyword parameters) или просто начал давать разные имена функциям. Разберёмся с ситуацией на примере.

 Задача - нам нужно написать функцию inspect_object, которая бы печатала различные данные об объекте переданном ей в первом аргументе. Например, если мы знаем, что аргументом является функция, дополнительно мы можем вывести её сигнатуру. У нас есть 2 очевидных варианта реализации, засунуть проверку на тип внутрь функции (куча if`ов) или же написать по функции на объект.

1-вый вариант:
import types, inspect

def inspect_object(obj):
   print "repr:", repr(obj)

   if isinstance(obj, types.FunctionType):
       print "sign:", (obj.__name__ + inspect.formatargspec(
                       *inspect.getargspec(obj)))

   elif isinstance(obj, types.ModuleType):
       print "file:", obj.__file__

И 2-ой:
import inspect

def inspect_object(obj):
   print "repr:", repr(obj)

def inspect_func(obj):
   print "repr:", repr(obj)
   print "sign:", (obj.__name__ + inspect.formatargspec(
                   *inspect.getargspec(obj)))

def inspect_module(obj):
   print "repr:", repr(obj)
   print "file:", obj.__file__
   


Сразу скажу, оба они ужасны. Но первый хоть и плохо расширяем, но всё же более предпочтителен, так как не требует знания типа объекта при вызове функции.
Вот тут то и не хватает перегрузки функций, которая элегантным образом решила бы проблему. В Python 3.0 собирались ввести стандартный модуль overloading но дело не заладилось (подробности). Судя по PEP`у, в модуле должно было быть реализовано много вкусностей (обобщенные функции, перегрузка, интерфейсы и т.д.). Библиотека должна была использовать аннотации функций появившиеся в новой версии python'а, а смотрелось бы это на нашем примере так:
import types, inspect
from overloading import overload

# обобщенная функция
def inspect_object(obj):
   print "repr:", repr(obj)

@overload
def inspect_object(module: types.ModuleType):
   print "repr:", repr(module)
   print "file:", module.__file__

@overload
def inspect_object(function: types.FunctionType):
   print "repr:", repr(function)
   print "sign:", (function.__name__ + inspect.formatargspec(
       *inspect.getargspec(function)))


Этот код понятен и легко расширяем, ведь легче написать новую специализированную функцию чем рыться во всех этих if`ах. (более аргументированные объяснения, чем подход с специализированными функциями лучше, ручного выбора с помощью if`ов, можно почитать в любой хорошей книжке по C++ или в том же PEP-3107)
Но к сожалению библиотеку так и не реализовали до базового функционала.

Мне давно была интересна эта тема и после того как я узнал что PEP всё таки отложили на неопределённое время, я решился на написание собственного велосипеда реализации. В ней нет и половины возможностей и расширяемости первоисточника, но я писал её скорее для фана. В моём представлении она должна выполнять свою основную задачу, добавлять в язык перегрузку функций, основанную на типах аргументов, максимально близкую к реализации оной из C++. Т.е. от идеи включения в библиотеку реализации интерфейсов и прочего функционала, я отказался, сосредоточившись на перегрузке. Так как писал я библиотеку на чистом python`е и поиск функций происходит в runtime, то старался максимально сократить время поиска среди кандидатов, подходящей функции (Phillip J. Eby, автор PEP`а и библиотеки PEAK для оптимизации использовал Bytecode Assembler, а это сильная магия:)). Всё таки Python 3.0 еще будущее, а настоящим сейчас является ветка 2.x, в которой нет аннотаций для функций. Так что синтаксис немного видоизменился, тип аргумента передаётся декоратору overload в качестве параметра, первый тип ассоциируется с первым параметром декорируемой функции, второй со вторым, и т.д.
Наш пример, использующий библиотеку, выглядит так:
import types, inspect
from overloading import overload

# обобщенная функция
def inspect_object(obj):
    print "repr:", repr(obj)

@overload(types.ModuleType)
def inspect_object(module):
    print "repr:", repr(module)
    print "file:", module.__file__

@overload(types.FunctionType)
def inspect_object(function):
    print "repr:", repr(function)
    print "sign:", (function.__name__ + inspect.formatargspec(
        *inspect.getargspec(function)))


В случае если вы не хотите типизировать один из параметров функции, можно поставить в место типа object, ведь библиотека поддерживает полиморфизм.

Не буду пересказывать все достоинства и недостатки библиотеки, так как всё это разжевано в документации, для заинтересовавшихся ссылка на страницу проекта

2 комментария:

  1. Спасибо, я тоже из С++ пришвартовался даже перешел по запросу "перегрузка вычитания в python"

    ОтветитьУдалить
  2. Спасибо наконец-то уяснил про перегрузку, а то везде как-то пишут запутано, а тут понятно.

    ОтветитьУдалить