2012-02-02 11 views
10

Mam dwie klasy (nazwijmy je Working and ReturnStatement), których nie mogę zmodyfikować, ale chcę rozszerzyć obie z logowaniem. Sztuczka polega na tym, że metoda Working zwraca obiekt ReturnStatement, więc nowy obiekt MutantWorking zwraca również ReturnStatement, chyba że mogę go przesłać do MutantReturnStatement. Mówiąc o kodzie:Jak rzutować obiekt w Pythonie

# these classes can't be changed 
class ReturnStatement(object): 
    def act(self): 
     print "I'm a ReturnStatement." 

class Working(object): 
    def do(self): 
     print "I am Working." 
     return ReturnStatement() 

# these classes should wrap the original ones 
class MutantReturnStatement(ReturnStatement): 
    def act(self): 
     print "I'm wrapping ReturnStatement." 
     return ReturnStatement().act() 

class MutantWorking(Working): 
    def do(self): 
     print "I am wrapping Working." 
     # !!! this is not working, I'd need that casting working !!! 
     return (MutantReturnStatement) Working().do() 

rs = MutantWorking().do() #I can use MutantWorking just like Working 
print "--" # just to separate output 
rs.act() #this must be MutantReturnState.act(), I need the overloaded method 

Oczekiwany wynik:
ja owijania roboczych.
Pracuję.
-
Zawijam ReturnStatement.
Jestem raportem zwrotnym.

Czy można rozwiązać problem? Jestem także ciekawy, czy problem można rozwiązać również w PHP. Dopóki nie otrzymam działającego rozwiązania, nie mogę zaakceptować odpowiedzi, więc proszę napisać działający kod, aby uzyskać akceptację.

+6

"Przesyłanie" nie istnieje w języku Python. –

+0

Nie powinieneś też robić "' ReturnStatement(). Act() '" - jeśli chcesz, aby metoda aktowania była inna klasa do pracy na bieżącym obiekcie, wykonaj 'returnStatement.act (self)' - lub po prostu zaznacz to jako metoda klasy lub metoda statyczna - jeśli nie potrzebuje instancji bieżącego obiektu. – jsbueno

+0

Funkcja Working.do() zwraca wartość ReturnStatement. Chcę, aby MutantWorking.do() powrócił z MutantReturnStatement. Wiem, że casting nie istnieje w Pythonie, ale problem występuje. Czy istnieje rozwiązanie? – Visko

Odpowiedz

5

Nie ma odlewania jak inne odpowiedzi już wyjaśniono. Możesz tworzyć podklasy lub modyfikować nowe typy, korzystając z dodatkowej funkcjonalności przy użyciu dekoratorów .

Oto pełny przykład (kredyt na How to make a chain of function decorators?). Nie musisz modyfikować oryginalnych klas. W moim przykładzie oryginalna klasa nazywa się Praca.

# decorator for logging 
def logging(func): 
    def wrapper(*args, **kwargs): 
     print func.__name__, args, kwargs 
     res = func(*args, **kwargs) 
     return res 
    return wrapper 

# this is some example class you do not want to/can not modify 
class Working: 
    def Do(c): 
     print("I am working") 
    def pr(c,printit): # other example method 
     print(printit) 
    def bla(c):   # other example method 
     c.pr("saybla") 

# this is how to make a new class with some methods logged: 
class MutantWorking(Working): 
    pr=logging(Working.pr) 
    bla=logging(Working.bla) 
    Do=logging(Working.Do) 

h=MutantWorking() 
h.bla() 
h.pr("Working")             
h.Do() 

to wypisze

h.bla() 
bla (<__main__.MutantWorking instance at 0xb776b78c>,) {} 
pr (<__main__.MutantWorking instance at 0xb776b78c>, 'saybla') {} 
saybla 

pr (<__main__.MutantWorking instance at 0xb776b78c>, 'Working') {} 
Working 

Do (<__main__.MutantWorking instance at 0xb776b78c>,) {} 
I am working 

Ponadto, chciałbym zrozumieć, dlaczego nie można zmodyfikować klasę. Próbowałeś? Ponieważ, jako alternatywa dokonaniem podklasy, jeśli czujesz dynamiczny ty może prawie zawsze zmodyfikować starą klasę w lokalu:

Working.Do=logging(Working.Do) 
ReturnStatement.Act=logging(ReturnStatement.Act) 

Aktualizacja: Zastosuj rejestrowanie wszystkich metod klasy

Jak teraz wyraźnie o to prosiliście. Możesz można pętli na wszystkich członków i zastosować rejestrowanie do nich wszystkich. Ale musisz zdefiniować regułę dla jakiego rodzaju członków do modyfikacji. Poniższy przykład wyklucza jakąkolwiek metodę z __ w nazwie.

import types 
def hasmethod(obj, name): 
    return hasattr(obj, name) and type(getattr(obj, name)) == types.MethodType 

def loggify(theclass): 
    for x in filter(lambda x:"__" not in x, dir(theclass)): 
    if hasmethod(theclass,x): 
     print(x) 
     setattr(theclass,x,logging(getattr(theclass,x))) 
    return theclass 

Mając to wszystko co musisz zrobić, aby nowy zalogowany wersję klasy jest:

@loggify 
class loggedWorker(Working): pass 

lub modyfikacji istniejącego klasa w lokalu:

loggify(Working) 
+1

Myślę, że zrozumiałeś mój problem i tak daleko to wydaje się być odpowiedź. Dziękuję Ci! – Visko

+0

Po moim początkowym entuzjazmie ze smutkiem zdałem sobie sprawę, że to nie jest rozwiązanie mojego problemu. Nie mogę zmodyfikować klasy Working za pomocą dekoratorów, aby zwrócić MutantReturnStatement zamiast ReturnStatement, ponieważ był to warunek początkowy (Working nie może być modyfikowany). – Visko

+0

@Visko, myślę, że błędnie przeczytałeś moją odpowiedź. Moja "loghello" to dokładnie "MutantWorking", o którą prosiłeś. Nie musisz modyfikować "Praca" tak jak nie zmieniłem "cześć". Jeśli uważasz, że to wciąż jest niejasne lub nie zgadzasz się, mogę spróbować zaktualizować moją odpowiedź. –

2

W Pythonie nie ma "rzucania". Każda podklasa klasy jest uważana za przykład jej rodziców. Pożądane zachowanie można osiągnąć poprzez odpowiednie wywołanie metod nadklasy i przez nadpisanie atrybutów klas.

Co można zrobić na przykład, ma mieć inicjator podklasy, który odbiera superklasę i kopiuje jej odpowiednie atrybuty - tak, Twój MutantReturnstatement można zapisać w następujący sposób:

class MutantReturnStatement(ReturnStatement): 
    def __init__(self, previous_object=None): 
     if previous_object: 
      self.attribute = previous_object.attribute 
      # repeat for relevant attributes 
    def act(self): 
     print "I'm wrapping ReturnStatement." 
     return ReturnStatement().act() 

a następnie zmień swoje MutantWorking klasa:

class MutantWorking(Working): 
    def do(self): 
     print "I am wrapping Working." 
     return MutantReturnStatement(Working().do()) 

Istnieje pythonic sposoby na nie o wiele self.attr = other.attr linii na metodzie __init__ jeśli istnieje wiele (jak ponad 3 :-)) atrybuty chcesz skopiować - najbardziej leniwym z nich jest po prostu skopiowanie atrybutu drugiej instancji o wartości __dict__.

Alternatywnie jeśli wiesz co robisz, można też po prostu zmienić atrybut swojego obiektu docelowego __class__ do wybranej klasy - ale to może być mylące i prowadzić do subtelnych błędów (metoda z __init__ podklasa nie zostanie wywołana, nie będzie działać na klasach innych niż python i innych możliwych problemach), nie zalecam tego podejścia - nie jest to "rzutowanie", jest to wykorzystanie introspekcji do brutalnej zmiany obiektu i jest tylko zawarte na utrzymanie pełnej odpowiedzi:

class MutantWorking(Working): 
    def do(self): 
     print "I am wrapping Working." 
     result = Working.do(self) 
     result.__class__ = MutantReturnStatement 
     return result 

Again - to powinno działać, ale nie rób tego - użyj FO metoda rmera.

Nawiasem mówiąc, nie mam zbyt dużego doświadczenia z innymi językami OO, które pozwalają na casting - ale rzuca się do podklasy, nawet w jakimkolwiek języku? Czy jest sens? Sądzę, że casting jest dozwolony tylko dla klas nadrzędnych.

+0

Czy możesz wstawić odpowiedź w kodzie? W przeciwnym razie nie sądzę, byś odpowiedział na moje pytanie. – Visko

+0

@jsbueno, tak * możesz * zdefiniować rzutowanie z dowolnego obiektu na dowolny obiekt, na przykład C++. –

+0

Niekoniecznie znam atrybuty klas niezmutowanych. Powiedzmy, że są to biblioteki C++ importowane do Pythona. – Visko

1

Nie ma bezpośredniego sposobu.

Można zdefiniować MutantReturnStatement za Init tak:

def __init__(self, retStatement): 
    self.retStatement = retStatement 

a następnie używać go tak:

class MutantWorking(Working): 
    def do(self): 
     print "I am wrapping Working." 
     # !!! this is not working, I'd need that casting working !!! 
     return MutantReturnStatement(Working().do()) 

I należy pozbyć dziedziczeniu ReturnStatement w opakowaniu jednostkowym, jak to

class MutantReturnStatement(object): 
    def act(self): 
     print "I'm wrapping ReturnStatement." 
     return self.retStatement.act() 
+0

Jestem nowy w Pythonie, mogą występować pewne błędy w kodzie, ale pokazuje pomysł – Jurlie

+0

Podoba mi się Twoja odpowiedź! Jeśli nikt nie powie innej (lepszej) techniki, zaakceptuję to :). – Visko

+0

+1. @Visko, +1 jeśli coś Ci się podoba. (możesz to zrobić więcej niż jeden raz.) Z dekoratorami możesz to zrobić bez potrzeby tworzenia takiego bloku kodu dla każdej metody. –

0

You don” t potrzebujesz tu castingu. Potrzebujesz tylko

class MutantWorking(Working): 
def do(self): 
    print "I am wrapping Working." 
    Working().do() 
    return MutantReturnStatement() 

To oczywiście zapewni prawidłowy powrót i pożądany wydruk.

+0

To nie jest dobre rozwiązanie, ponieważ muszę zwrócić z tym samym obiektem stanu (właściwości nie są zawarte w moim przykładzie, aby zachować prostotę). Zwraca to nowe wystąpienie, które nie jest tym, czego chciałem. – Visko

Powiązane problemy