2011-12-14 14 views
21

Cercando di trovare un modo per ripulire parte del mio codice.Forse "kind-of" monad in python

così ho qualcosa di simile nel mio codice python:

company = None 
country = None 

person = Person.find(id=12345) 
if person is not None: # found   
    company = Company.find(person.companyId) 

    if company is not None: 
     country = Country.find(company.countryId) 

return (person, company, country) 

aver letto un tutorial su monadi Haskell (in particolare forse), mi chiedevo se è possibile scrivere in un altro modo.

risposta

36
company = country = None 
try: 
    person = Person.find(id=12345) 
    company = Company.find(person.companyId) 
    country = Country.find(company.countryId) 
except AttributeError: 
    pass # `person` or `company` might be None 

EAFP

+6

Questa è inequivocabilmente la risposta corretta per questo caso specifico. L'intero scopo di 'Maybe' come monade è di modellare esplicitamente l'approccio EAFP come entità di prima classe. In Python, è sia implicito che idiomatico in questa forma, quindi usalo! –

+0

Purtroppo devo effettivamente "sapere" quale persona o azienda sono None. – drozzy

+2

@drozzy: Se è necessario eseguire in modo condizionale parti di codice diverse in base a quali variabili sono "Nessuna", allora è necessario disporre di condizionali. – katrielalex

3
person = Person.find(id=12345) 
company = None if person is None else Company.find(person.companyId) 
country = None if company is None else Country.find(company.countryId) 

return (person, company, country) 
+4

In realtà scrivo al contrario 'company = Company.find (person.companyID) se Person else None'. Rimuove 'is None' e il caso normale è il primo, piuttosto che il caso eccezionale. –

15

Python non hai particolarmente piacevole sintassi per monadi. Detto questo, se vuoi limitarti ad usare qualcosa come la monade Maybe (il che significa che sarai in grado di usare solo Maybe, non sarai in grado di fare funzioni generiche che gestiscono qualsiasi monade), puoi usare il seguente approccio:

class Maybe(): 
    def andThen(self, action): # equivalent to Haskell's >>= 
     if self.__class__ == _Maybe__Nothing: 
      return Nothing 
     elif self.__class__ == Just: 
      return action(self.value) 

    def followedBy(self, action): # equivalent to Haskell's >> 
     return self.andThen(lambda _: action) 

class _Maybe__Nothing(Maybe): 
    def __repr__(self): 
     return "Nothing" 

Nothing = _Maybe__Nothing() 

class Just(Maybe): 
    def __init__(self, v): 
     self.value = v 
    def __repr__(self): 
     return "Just(%r)" % self.value 

Poi, fanno tutti i metodi che restituiscono attualmente None ritorno sia Just(value) o Nothing invece. Questo permette di scrivere questo codice:

Person.find(id=12345).andThen(lambda person: Company.find(person.companyId)).andThen(lambda company: Country.find(company.countryId)) 

Naturalmente, è possibile adattare le lambda per memorizzare i risultati intermedi delle variabili; tocca a te come farlo correttamente.

+0

Inoltre, un altro problema a cui mi sono imbattuto qui è che non ottengo i valori "intermedi" - come "persona" e "società" alla fine. Questo mi dà solo un po 'di paese. – drozzy

+0

Se vuoi ottenere tutti i risultati, devi avvolgere i tuoi lambda in questo modo: 'Person.find (id = 12345). E Then (lambda person: Company.find (person.companyId). E Then (lambda company: Country. find (company.countryId) .andThen (lambda country: Just ((persona, società, nazione))))) '. Si noti la quantità ridicola di parents; non possono essere evitati se si desidera programmare in uno stile funzionale come questo. – dflemstr

+0

@dflemstr Quindi l'ultimo "andThen" è essenzialmente lì solo per restituire il risultato? Interessante. – drozzy

19

sfruttare il comportamento di corto circuito e che un oggetto personalizzato è vero per impostazione predefinita e None è falso:

person = Person.find(id=12345) 
company = person and person.company 
country = company and company.country 
0

Più "Pythonic" che cercare di attuare un diverso paradigma (non che non sia interessante e cool) sarebbe quello di aggiungere intelligenza ai tuoi oggetti in modo che possano trovare i loro attributi (e se esistano del tutto), da soli.

Bellow è un esempio di una classe base che utilizza il metodo "trova" e la correlazione dei nomi degli attributi Id e dei nomi delle classi per funzionare con il tuo esempio - Ho inserito classi minime Persona e Società per una ricerca per l'azienda al lavoro:

class Base(object): 
    def __getattr__(self, attr): 
     if hasattr(self, attr + "Id"): 
      return globals()[attr.title()].find(getattr(self, attr + "Id")) 
     return None 
    @classmethod 
    def find(cls, id): 
     return "id %d " % id 

class Person(Base): 
    companyId=5 

class Company(Base): 
    pass 

E sulla console, dopo aver incollato il codice di cui sopra:

>>> p = Person() 
>>> p.company 
'id 5 ' 

Con questo Base tuo codice di cui sopra potrebbe essere solo:

person = Person.find(id=12345) 
company = person.company 
country = company and company.country 
+0

Hm ... Penso che tu mi abbia frainteso. Find in realtà dovrebbe restituire un oggetto "Person", con attributi come "firstName, lastName" ecc ... Non si suppone che restituisca semplicemente l'id. O forse mi manca il punto? – drozzy

+0

Ti ho capito - è solo la mia implementazione di 'find' che restituisce la stringa, per differenziarla dal numero id (hardcoded come 5) - ciò che è nuovo qui è il' __getattr__'- che manterrai la stessa identica find metodo che hai ora. – jsbueno

+0

Mi dispiace, ma non ho idea di cosa faccia questa linea: 'globals() [attr.title()]. ​​Find (getattr (self, attr +" Id "))' – drozzy

2

Avete controllato PyMonad?

https://pypi.python.org/pypi/PyMonad/

Essa non solo include una monade, ma anche una monade Forse lista, un funtore e applicativi classi funtore. Mono e altro

Nel tuo caso sarebbe qualcosa di simile:

country = Person.find(id=12345)   >> (lambda company: 
      Company.find(person.companyId) >> (lambda country: 
      Country.find(company.countryId)) 

facile da capire e più pulito di EAFP.

1

credo che questo sia una misura perfetta per:

getattr(object, name[, default])

trovo getattr essenziale quando si lavora con gli oggetti e gli attributi. È praticamente l'equivalente a dict.get(key[, default]).

person = Person.find(id=12345) 
company = person and getattr(person, 'company', None) 
country = company and getattr(company, 'country', None)