Многофункциональный диспетчер отправки в Python

Я написал декоратор для легкого создания нескольких функций отправки :

from functools import wraps

def multi_dispatch(for_function):
    """
    Returns a multiple dispatch version of the function.

    The returned function doesn't allow keyword arguments.

    >>> @multi_dispatch
    ... def foo(a, b):
    ...     pass
    ...
    >>> @foo.add
    ... def _foo(a: int, b: int, c : str = '33'):
    ...     return a + b*3 + len(c)
    ...
    >>> @foo.add
    ... def _foo(a: int, b: int, c = 3):
    ...     return a + b*3 - c
    ...
    >>> @foo.add
    ... def _foo(a: float, b: float):
    ...     return a*2 - b
    ...
    >>> foo(4, 5)
    16
    >>> foo(1.0, 2.0)
    0.0
    >>> foo(4, 5, 'loooong')
    26
    >>> foo(3, 4.5)
    Traceback (most recent call last):
      ...
    KeyError: (<class 'int'>, <class 'float'>)
    """
    @wraps(for_function)
    def decorated(*args):
        return decorated.registry[tuple(type(i) for i in args)](*args)

    decorated.registry = {}

    def adder(func):
        """
        Adds the supplied function to the multiple dispatch registry.
        """
        code = func.__code__
        args = code.co_varnames
        annotations = func.__annotations__
        types = tuple(annotations[a] for a in args if a in annotations)
        decorated.registry[types] = func
        return func

    decorated.add = adder
    return decorated

Я бы хотел прочитать ваши комментарии о коде (эффективность, повторное использование и т. д.), документацию и все остальное, что вы считаете нужным заметить.

Обновление: После исправления всех пунктов, перечисленных в выбранном ответе, я создал новая версия .

11 голосов | спросил Joschua 8 J000000Tuesday14 2014, 23:18:35

1 ответ


8
  1. У вас есть docstring и некоторые доктрины, и это здорово! Докшрингу требуется некоторая работа, хотя, как описано ниже.

  2. В docstring необходимо указать, что возвращаемая функция имеет метод add и объясняет, что делает этот метод.

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

  4. В документе docstring сказано: «Возвращенная функция не разрешает аргументы ключевого слова», но вы можете уточнить, что именно это означает.

  5. Пример кода в docstring не очень мотивирован. Почему я хочу захотеть написать функцию foo, как это? Это было бы очень полезно для читателя, если бы вы могли придумать пример, более близкий к реальному варианту использования.

  6. Группа многофункциональных функций - это постоянная структура данных с двумя методами (add и __call__), и поэтому было бы более ясным, если бы оно было реализовано как класс.

  7. Код интроспекции будет намного понятнее, если вы использовали inspect.signature ). Тело функции игнорируется. Это похоже на отходы: почему бы не @multi_dispatch эту функцию в реестре тоже?

  8. Если вы написали functools.update_wrapper вместо add в конце return decorated, тогда вам не придется отбирать имена экземпляров метода.

    Обновление: Что я имею в виду, так это то, что вам нужно записать экземпляры вашего метода return func, а не adder, чтобы избежать перезаписывания мульти-метода в переменной _foo. С моим предложением, вам не нужно было бы это делать.

  9. Отправка на точные типы аргументов ограничивает полезность множественной отправки. В таких языках, как Common Lisp, которые имеют множественную диспетчеризацию, диспетчеризация может выполняться в подклассах (не только в точном совпадении). Это делает более естественным резервный код, например:

    foo

    В вашей реализации нужно было бы предоставить отдельные экземпляры методов для foo, @find.add def find(needle: str, haystack: str): # When both arguments are strings, Python has a built-in efficient # search operation. return haystack.find(needle) @find.add def find(needle: Sequence, haystack: Sequence): # Otherwise, for generic sequences, use Boyer–Moore–Horspool. h = len(haystack) n = len(needle) skip = {needle[i]: n - i - 1 for i in range(n - 1)} i = n - 1 while i < h: for j in range(n): if haystack[i - j] != needle[-j - 1]: i += skip.get(haystack[i], n) break else: return i - n + 1 return -1 , (list, list) и другие комбинации типов последовательностей.

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

    (tuple, tuple)

    (При добавлении нового экземпляра метода это может привести к аннулированию некоторых кэшированных запросов, поэтому вам нужно очистить кеш в (list, tuple).)

  10. Вероятно, вам стоит взглянуть на многоточечную реализацию Guido van Rossum для других идей (например, «укладка» декораторов с несколькими мерами).

ответил Gareth Rees 9 J000000Wednesday14 2014, 14:45:27

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132