2011-12-14 4 views
21

Essayer de trouver un moyen de nettoyer une partie de mon code.Peut-être monade "kind-of" en python

J'ai donc quelque chose comme ça dans mon code 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) 

Après avoir lu un tutoriel sur les monades de Haskell (en peut-être particulier), je me demandais s'il est possible d'écrire d'une autre manière.

Répondre

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

Ceci est sans équivoque la réponse correcte pour ce cas spécifique. Tout le but de 'Maybe 'en tant que monade est de modéliser l'approche EAFP explicitement comme une entité de première classe. En Python, il est à la fois implicite et idiomatique sous cette forme, alors utilisez-le! –

+0

Malheureusement, je dois réellement "savoir" qui de la personne ou de l'entreprise sont None. – drozzy

+2

@drozzy: Si vous avez besoin d'exécuter de manière conditionnelle différentes parties de code en fonction des variables 'None', alors évidemment vous avez besoin de conditions. – 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

En fait, j'écrirais que dans l'autre sens 'company = Company.find (person.companyID) si personne d'autre None'. Il supprime le 'est Aucun» et le cas normal est le premier, plutôt que l'exceptionnel. –

15

Python n'a pas une syntaxe particulièrement agréable pour les monades. Cela étant dit, si vous voulez vous limiter à utiliser quelque chose comme la monade Maybe (ce qui signifie que vous ne serez en mesure d'utiliser Maybe, vous ne serez pas en mesure de faire des fonctions génériques qui traitent toute monade), vous pouvez utiliser l'approche suivante:

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 

Ensuite, faire toutes les méthodes qui retournent actuellement None retour soit Just(value) ou Nothing à la place. Cela vous permet d'écrire ce code:

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

Vous pouvez bien sûr adapter les lambdas pour stocker les résultats intermédiaires dans les variables; C'est à vous de voir comment le faire correctement.

+0

En outre, un autre problème que j'ai rencontré ici, c'est que je ne reçois pas les valeurs "intermédiaires" - comme "personne", et "entreprise" à la fin. Cela me donne seulement un peu de pays. – drozzy

+0

Si vous voulez obtenir tous les résultats, vous devez envelopper vos lambdas comme ceci: 'Person.find (id = 12345) .etThen (personne lambda: Company.find (person.companyId) .etThen (société lambda: Pays. trouver (company.countryId) .et then (pays lambda: Just ((personne, compagnie, pays))))) '. Notez la quantité ridicule de parens; ils ne peuvent pas être évités si vous voulez programmer dans un style fonctionnel comme celui-ci. – dflemstr

+0

@dflemstr Donc le dernier "andThen" est là essentiellement juste pour retourner le résultat? Intéressant. – drozzy

19

Exploit le comportement court-circuit et qu'un objet personnalisé est vrai par défaut et None est faux:

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

Plus « Pythonic » que d'essayer de mettre en œuvre un paradigme différent (pas qu'il est pas intéressant et cool) serait d'ajouter de l'intelligence à vos objets afin qu'ils puissent trouver leurs attributs (et s'ils existent), par eux-mêmes. Ci-dessous un exemple d'une classe de base qui utilise votre méthode "find" et la corrélation des noms d'attributs Id et des noms de classes pour travailler avec votre exemple - Je mets des classes Person et Company minimales pour une recherche de l'entreprise au travail:

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 

Et sur la console, après avoir collé le code ci-dessus:

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

Avec ce Base au-dessus de votre code pourrait juste être:

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

Hm ... Je pense que vous m'avez mal compris. Find est en fait censé renvoyer un objet "Person", avec des attributs comme "firstName, lastName" etc ... Il n'est pas censé retourner l'identifiant. Ou peut-être que je manque le point? – drozzy

+0

Je vous ai bien compris - c'est juste mon implémentation de 'find'qui retourne la chaîne, pour la différencier du numéro d'identification (codé en dur comme 5) - ce qui est nouveau ici est le' __getattr__'- vous garderiez exactement la même méthode que vous avez maintenant. – jsbueno

+0

Désolé, mais je n'ai aucune idée de ce que cette ligne fait: 'globals() [attr.title()]. ​​Find (getattr (auto, attr +" Id "))' – drozzy

2

Avez-vous vérifié PyMonad?

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

Il comprend non seulement peut-être monade, mais aussi une monade liste, un cours de foncteurs foncteurs et Applicative. Monoïdes et plus encore.

Dans votre cas, il serait quelque chose comme:

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

plus facile à comprendre et plus propre que EAFP.

1

Je pense que cela convient parfaitement à:

getattr(object, name[, default])

je trouve getattr essentiel lorsque l'on travaille avec des objets et des attributs. C'est à peu près l'équivalent de dict.get(key[, default]).

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