2015-12-22 3 views
6

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

def f(param): 
    try: 
     if param > 0: 
      # do something 
    except TypeError: 
     pass 
    try: 
     if all(isinstance(item, str) for item in param): 
      # do something else 
    except TypeError: 
     raise TypeError('Illegal input.') 

Вот что-то в духе того, что я хотел бы быть в состоянии сделать:

@generic 
def f(param): 
    raise TypeError('Illegal input.') # default 

@f.when(lambda param: param > 0) 
def f_when_param_positive(param): 
    # do something 

@f.when(lambda param: all(isinstance(item, str) for item in param)) 
def f_when_param_iterable_of_strings(param): 
    # do something else 

Он похож на Python 3-х singledispatch, однако singledispatch поддерживает только отправку по типам, а не по произвольным предикатам.

TL; DR: Существует ли библиотека, которая позволяет отправлять на основе предикатов функцию, основанную на произвольных предикатах (а не только на тип параметра)?

ответ

2

Благодаря ответчиков. Задав этот вопрос, казалось, что не существует существующего модуля, который делает именно это. Поэтому я написал свой собственный :) Это вдохновлено предложением Элазара.

Пожалуйста, не стесняйтесь проверить это. It's on PyPI и вы можете установить его с помощью:

pip install genericfuncs 

Это также hosted on GitHub и я планирую продолжить развитие и добавлять новые функции, пытаясь сохранить API просто. Взносы приветствуются.

-1

вы можете использовать isintance и соединил его с ABC проверить характеристику входа, как это:

from collections.abc import Iterable 

def foo(param): 
    if isinstance(param,int) and param > 0: 
     #do something 
    elif isinstance(param,Iterable) and all(isinstance(item, str) for item in param): 
     # do something else 
    else: 
     raise TypeError('Illegal input.') 

АВС сказать вам, какой интерфейс параметр может есть, так что вы можете использовать соответствующий один в зависимости от того, что вы делаете, если вам все равно, является ли он конкретным типом или нет, поэтому такой параметр, как этот параметр, может быть set, list или tuple строк и всегда будет проходить мимо второй проверки, чтобы вы могли обработать ее соответствующим образом. Существует также и ABC для numbers вы хотите быть вообще в этом случае тоже.

1

Я не знаю об библиотеке, но вот базовый скелет реализации. Реальная проблема, которая мешает этому быть практическим решением, на мой взгляд, заключается в том, что я понятия не имею, как сделать специализированное разрешение здесь . Когда это произойдет, это, вероятно, приведет к большим трудностям в обслуживании.

#!/usr/bin/python3 

class when(object): 
    funcs = {} 

    def __init__(self, pred): 
    self.pred = pred 

    def __call__(self, func): 
    if func.__qualname__ not in when.funcs: 
     when.funcs[func.__qualname__] = {} 

    when.funcs[func.__qualname__][self.pred] = func 

    return lambda *args, **kwargs: when.__match(func, *args, **kwargs) 

    @staticmethod 
    def __match(f, *args, **kwargs): 
    for pred, func in when.funcs[f.__qualname__].items(): 
     if pred(*args, **kwargs): 
      return func(*args, **kwargs) 
    raise NotImplementedError() 


@when(lambda x: x < 0) 
def my_func(x): 
    return "smaller!" 

@when(lambda x: x > 0) 
def my_func(x): 
    return "greater!" 


print(my_func(-123)) 
print(my_func(123)) 

[1]: Проблема с разрешением в том, что это не так просто получить права. Вот несколько альтернатив для рассмотрения, у всех которых нет серьезных оснований для внедрения и использования.

  1. Специализирующиеся предикаты, которые применяются, могут быть сложными и, вероятно, лучше всего укладываться в руки пользователя, чтобы вручную определять ранги/веса для каждого. Это неуклюжие и, как правило, головная боль, поддерживающая/головная боль, которая не стоит первоначального очарования этого механизма.
  2. Пользователь всегда может добавить больше перегрузок при запуске программы (интерпретация Python), и это может вызвать удивительное временное поведение. Распространение этого в вашей кодовой базе является самодовольным. Когда он не распространяется вокруг, почему бы не просто if/else и не сделать?
  3. Вы можете как-то ограничить использование и применить его так, чтобы только один предикат должен был вернуть True для данного вызова. Это странно, неэффективно и бесполезно во многих отношениях, например. что, если вы хотите поймать все экземпляры A или его подклассов, но обработать подкласс C особым образом? Или если вы хотите дополнительно специализировать предикат с дополнительным условием. Как вы собираетесь классифицировать такую ​​модель?
1

Вот решение, отрегулированное с @ Yam, чтобы соответствовать вашему синтаксису и использоваться в качестве библиотеки.Решение (которое является общим) является то, что первый предикат выигрывает: код

class guarded: 
    def __init__(self, default): 
     self.funcs = [] 
     self.default = default 

    def when(self, pred): 
     def add(func): 
      self.funcs.append((pred, func)) 
      return func 
     return add 

    def __call__(self, *args, **kwargs): 
     for pred, func in self.funcs: 
      try: 
       match = pred(*args, **kwargs) 
      except Exception: 
       match = False 
      if match: 
       return func(*args, **kwargs) 
     return self.default(*args, **kwargs) 

Пользователь:

@guarded 
def f(param): 
    raise TypeError('Illegal input') 

@f.when(lambda param: param > 0) 
def f_when_param_positive(param): 
    return 'param_positive' 

@f.when(lambda param: all(isinstance(item, str) for item in param)) 
def f_when_param_iterable_of_strings(param): 
    return 'param_iterable_of_strings' 

Trying, мы получаем что-то вроде:

>>> print(f(123)) 
param_positive 
>>> print(f(['a', 'b'])) 
param_iterable_of_strings 
>>> print(f(-123)) 
Traceback (most recent call last): 
... 
TypeError: Illegal input 
+0

Oooh. Как явно-точный, полный ответ получает downvoted? Интересно. – Elazar