Alternativa alla proprietà sulla funzione in Python?

0

Ho una collezione di oggetti Python, li chiamiamo frutti. Alcuni di questi frutti potrebbero essere difettosi. Per verificare questi errori, ho un numero di funzioni che esegue controlli diversi e restituisce un elenco di frutti che non ha superato il controllo:

def not_delicious(fruits, limit):
    return fruit for fruit in fruits if fruit.tastyness < limit

def not_ripe(fruits):
    return fruit for fruit in fruits if not fruit.ripe

Ora voglio avere un metodo che mi permetta di registrare in modo ordinato il risultato di questi controlli. Un approccio ingenuo assomiglia a questo:

def log_not_delicious(fruits, limit):
    for fruit in not_delicious(fruits, limit):
        log("The fruit {name} was not delicious enough.".format(**fruit))

def log_not_ripe(fruits):
    for fruit in not_ripe(fruits):
        log("The fruit {name} was not ripe.".format(**fruit))

Ecco un bel po 'di piastre. Per semplificare, vorrei una funzione log_check che prenda l'assegno che voglio eseguire come input e quindi esegua il logging per me. Quindi posso usarlo in questo modo:

log_check(not_delicious, fruits, 7)

Tale funzione non dovrebbe essere così difficile da scrivere. Qualcosa in questo senso:

def log_check(check, fruit, *args, **kwargs):
    for fruit in check(fruit, *args, **kwargs):
        log(message.format(**fruit))

C'è solo un problema. In che modo log_check sa cosa message usare? Ho pensato a due soluzioni a questo, ma non mi piace nessuno di loro:

  1. Inseriscilo come una proprietà sulla funzione check , quindi ho ad esempio:

    not_delicious.message = "The fruit {name} was not delicious enough."
    

    Questo non sembra molto Pythonic - sembra più come JavaScript.

  2. Utilizza invece una classe come funzione per check . Questo mi sembra un po 'eccessivo - mi è stato pensato che ho solo bisogno di usare una classe se ho uno stato interno. E non c'è stato qui - eseguo il controllo, e poi ho finito.

C'è una terza alternativa, o qual è il mio approccio migliore qui?

    
posta Anders 15.05.2017 - 13:31
fonte

4 risposte

3

Questo sembra un lavoro per il polimorfismo.

Si desidera associare controlli e messaggi senza collegarli al codice log_check . Questa è una buona idea.

Sei riluttante a usare una classe perché la classe sarebbe senza stato. A quello dico: Feh! Gli oggetti stateless stanno perfettamente bene.

Un oggetto è un sacco di funzioni che si muovono insieme. Lo stato è solo qualcosa che cambia anche il comportamento di quelle funzioni insieme.

Gli oggetti apolidi hanno avuto una cattiva reputazione in OO perché alcuni amici di pallacanestro hanno pensato che i metodi stateless dovevano essere statici. No, è indietro.

log_check( fruits = fruits, ripe(), delicious(7) )

Qualsiasi motivo non dovrebbe essere così semplice?

    
risposta data 15.05.2017 - 14:37
fonte
3

Se ad es. non voglio riscrivere un gruppo di funzioni esistenti solo per allegare messaggi a loro, puoi farlo esternamente:

CHECKS = {
  (check_ripeness, 'This ain\'t no ripe enough'),
  (check_taste, 'deliciousness does not conform to the standards'),
  # etc
}

# somewhere in your method
for (predicate, message) in CHECKS:
  if not predicate(fruit):
    log(message)

L'utilizzo di classi con __call__ otterrebbe lo stesso risultato, solo in modo meno diretto.

    
risposta data 15.05.2017 - 15:13
fonte
2

Crea un tipo che contiene il messaggio e la funzione di controllo. Usa namedtuple o una classe.

from collections import namedtuple
Check = namedtuple('Check', ('message', 'checker'))

Ora puoi creare oggetti che contengono sia il messaggio sia una funzione per controllare la frutta.

ripe = Check(message = "is not ripe", lambda fruit: fruit.tastiness < 5)

Quindi scrivi una funzione generica

def log_fruits(fruits, checker):
    for fruit in fruits:
        if checker(fruit):
            print("Fruit: ", fruit, checker.message)
    
risposta data 15.05.2017 - 16:40
fonte
2

Usa una classe. Anche il comportamento fa parte dello stato se si utilizza il polimorfismo, ma in questo caso potrebbe non essere necessario il polimorfismo "formale": basta utilizzare una classe per tutti i controlli, con il messaggio e il predicato come parametri.

In aggiunta:

  • Dato che stai usando una classe, può essere una buona idea passare un predicato che controlli un singolo frutto e lasciare che la classe gestisca il controllo di un elenco di essi.
  • Per rendere la sintassi più gradevole, puoi utilizzare un decoratore.

Quindi, avrei fatto qualcosa di simile:

log = print


class Fruit(object):
    def __init__(self, name, **kwargs):
        self.name = name
        for k, v in kwargs.items():
            setattr(self, k, v)

# Single class, receives predicate as parameter
class Check(object):
    def __init__(self, predicate, message):
        self.predicate = predicate
        self.message = message

    def check(self, fruits, *args, **kwargs):
        """ Run the predicate on a list of checks """
        for fruit in fruits:
            if self.predicate(fruit, *args, **kwargs):
                yield fruit

    def log(self, fruits, *args, **kwargs):
        """ Check a list of fruits, logging the results """
        for fruit in self.check(fruits, *args, **kwargs):
            log(self.message.format(**fruit.__dict__))


def check(message):
    """ Helper decorator to ease the creationg of Check objects """
    def wrapper(func):
        return Check(func, message)
    return wrapper


@check(message="The fruit {name} was not delicious enough.")
def not_delicious(fruit, limit):
    return fruit.tastyness < limit


@check(message="The fruit {name} was not ripe")
def not_ripe(fruit):
    return not fruit.ripe


fruits = [Fruit("Yellow Banana", ripe=True, tastyness=10),
          Fruit("Green Banana", ripe=False, tastyness=0),
          Fruit("Grapefruit", ripe=True, tastyness=-float('inf'))]


not_delicious.log(fruits, limit=7)
not_ripe.log(fruits)
    
risposta data 15.05.2017 - 17:14
fonte

Leggi altre domande sui tag