2015-12-22 15 views
6

Mi piacerebbe essere in grado di inviare diverse implementazioni di una funzione, basate non solo sul tipo del primo parametro, ma sulla base di predicati arbitrari. Attualmente ho di farlo in questo modo:Corrispondenza modello/predicato funzione in 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.') 

Ecco qualcosa nello spirito di quello che mi piacerebbe essere in grado di fare:

@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 

E 'simile a Python 3 di singledispatch, tuttavia singledispatch supporta solo l'invio su tipi, non su predicati arbitrari.

TL; DR: Esiste una libreria che consente l'invio basato su predicati di una funzione basata su predicati arbitrari (non solo il tipo del parametro)?

risposta

2

Grazie a i repulsori Dopo aver fatto questa domanda sembrava che non esistesse alcun modulo esistente che faccia esattamente questo. Così ho scritto il mio :) È ispirato dal suggerimento di @ Elazar.

Non esitate a dare un'occhiata. It's on PyPI ed è possibile installarlo utilizzando:

pip install genericfuncs 

E 'anche hosted on GitHub e ho intenzione di continuare lo sviluppo e aggiungere funzioni durante il tentativo di mantenere il semplice API. I contributi sono ben accetti

-1

è possibile utilizzare isintance e combinato con un ABC per controllare la caratteristica di ingresso in questo modo:

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.') 

L'ABC si dirà di che tipo di interfaccia del param ha, in modo da poter utilizzare l'apposito a seconda di cosa fai se non ti interessa se si tratta di un tipo particolare o no, così definito che param può essere un set, list o tuple di stringhe e sempre supererà il secondo controllo in modo da poterlo elaborare di conseguenza. C'è anche e ABC per numbers vuoi essere generale anche in questo caso.

1

Non conosco una libreria, ma ecco uno scheletro di base per l'implementazione. Il vero problema che impedisce a questa soluzione di essere una soluzione pratica, secondo me, è che non ho idea di come far funzionare qui le risoluzioni specializzate . Quando questo è il caso, probabilmente porterà a molte difficoltà di manutenzione.

#!/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]: Il problema con la risoluzione è che non è facile da ottenere. Ecco alcune alternative da considerare, tutte rigorosamente carenti di buone ragioni per implementare e utilizzare.

  1. Specializzata predicati che si applicano possono essere complesse, e probabilmente sarà meglio posto nelle mani degli utenti, in modo da definire manualmente ranghi/pesi per ciascuna. Questo è maldestro, e in genere un mal di testa di manutenzione/caldaia che non vale il fascino iniziale di questo meccanismo.
  2. L'utente può sempre aggiungere più sovraccarichi mentre il programma viene eseguito (Python viene interpretato) e ciò può causare un comportamento temporale sorprendente. Diffondere questo nella base di codice è compiacente. Quando non è diffuso, perché non solo if/else e fatto?
  3. In qualche modo è possibile limitare l'utilizzo e applicarlo in modo che solo un predicato restituisca True per una determinata chiamata. Questo è allo stesso tempo strano, inefficiente e inutile in molti modi, ad es. cosa succede se vuoi catturare tutte le istanze di A o delle sue sottoclassi, ma trattare la sottoclasse C in un modo speciale? O se si desidera ulteriormente specializzare un predicato con una condizione aggiuntiva. Come hai intenzione di classificare questo tipo di modello?
1

Ecco una soluzione modificata da @ Yam's per adattarsi alla sintassi e per essere utilizzata come libreria.La decisione (che è comune) è che il primo predicato vince: il codice

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) 

utente:

@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' 

Cercando di esso, si ottiene qualcosa di simile:

>>> 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. In che modo una risposta completa, dimostrabile in modo accurato, viene svalutata? Mi chiedo. – Elazar