2011-12-14 13 views
21

Próbujesz znaleźć sposób na oczyszczenie mojego kodu.Może "rodzaj" monada w pytonie

Więc mam coś takiego w moim kodu Pythona:

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) 

Po przeczytaniu tutorial monad Haskell (w szczególności może), zastanawiałem się, czy to możliwe, aby napisać w inny sposób.

Odpowiedz

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

Jest to jednoznaczna poprawna odpowiedź dla tego konkretnego przypadku. Cały cel "Być może" jako monady polega na jednoznacznym modelowaniu podejścia EAFP jako bytu pierwszej klasy. W Pythonie jest to zarówno domniemana, jak i idiomatyczna w tej formie, więc używaj jej! –

+0

Niestety, muszę "wiedzieć", która z osób lub firm jest Brakująca. – drozzy

+2

@drozzy: Jeśli musisz warunkowo wykonać różne fragmenty kodu, w zależności od tego, które zmienne są "Brak", to oczywiste jest, że potrzebujesz warunkowych. – 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

Powiedziałbym, że na odwrót "company = Company.find (person.companyID), jeśli inna osoba nie ma". Usuwa 'jest Brak', a normalny przypadek jest pierwszy, a nie wyjątkowy. –

15

Python nie posiada szczególnie piękny składnię monad. To powiedziawszy, jeśli chcesz ograniczyć się do używania czegoś podobnego do monady Maybe (Znaczy to, że będziesz mógł korzystać tylko z Maybe, nie będziesz w stanie tworzyć ogólnych funkcji związanych z monadą), możesz użyć następujące podejście:

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 

Następnie dokonaj wszystkich metod, które obecnie wracają None zwrotu albo Just(value) lub Nothing zamiast. To pozwala ci napisać ten kod:

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

Możesz oczywiście dostosować lambda do przechowywania wyników pośrednich w zmiennych; od ciebie zależy, jak to zrobić właściwie.

+0

Innym problemem, który tu wpadłem, jest to, że nie otrzymuję wartości "pośrednich" - na przykład "osoba" i "firma". To daje mi tylko Może kraju. – drozzy

+0

Jeśli chcesz uzyskać wszystkie wyniki, musisz zawijać swoje lambdas w następujący sposób: 'Person.find (id = 12345). I Then (lambda osoba: Company.find (person.companyId). I Then (firma lambda: Country. find (company.countryId). i Then (kraj lambda: Just ((osoba, firma, kraj))))). Zwróć uwagę na śmieszną ilość parens; nie można ich uniknąć, jeśli chcesz programować w tak funkcjonalny sposób jak ten. – dflemstr

+0

@dflemstr Więc ostatnie "i To" jest zasadniczo tam, aby zwrócić wynik? Ciekawy. – drozzy

19

Exploit zachowanie zwarciem i że zwyczaj obiekt jest prawda domyślnie None jest fałszywe:

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

Więcej „pythonowy” niż próbuje wdrożyć inny paradygmat (nie, to nie jest ciekawe i fajnie) byłoby dodawanie inteligencji do twoich obiektów, aby mogły same znaleźć swoje cechy (i to, czy w ogóle istnieją).

Osłona to przykład klasy bazowej, która wykorzystuje metodę "znajdowania" i korelację nazw atrybutów Id oraz nazw klas do pracy z przykładem - umieszczam minimalne klasy Person i Company w celu wyszukania firmy do pracy:

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 

A na konsoli, po wklejeniu powyższy kod:

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

Dzięki tej Base kodzie powyżej może być tylko:

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

Hm ... Myślę, że mnie źle zrozumiałeś. Find faktycznie ma zwrócić obiekt "Person", z atrybutami takimi jak "firstName, lastName" itp. Nie powinien po prostu zwracać identyfikatora. A może brakuje mi sensu? – drozzy

+0

Zrozumiałem cię - to jest tylko moja implementacja 'find'ta zwraca ciąg znaków, aby odróżnić go od numeru identyfikacyjnego (zakodowany jako 5) - co jest tutaj nowe, to' __getattr__'- zachowałbyś to samo metoda, którą teraz masz. – jsbueno

+0

Przepraszam, ale nie mam pojęcia, co ta linia robi: 'globals() [attr.title()]. ​​Find (getattr (self, attr +" Id "))' – drozzy

2

Sprawdziłeś PyMonad?

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

Obejmuje ona nie tylko Maybe monady, ale także listę monady, funktora i aplikacyjnych klas funktor. Monoidy i więcej.

W twoim przypadku to byłoby coś takiego:

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

łatwiejsze do zrozumienia i czystsze niż EAFP.

1

myślę, że jest idealnym rozwiązaniem dla:

getattr(object, name[, default])

znajdę getattr niezbędne przy pracy z obiektami i atrybutami. Jest to odpowiednik dict.get(key[, default]).

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