2012-01-21 10 views
6

mam co jest zasadniczo następujące w Pythonie:Częściowo Ocenianie Pythona classmethod podstawie gdzie jest dostępny z

class X(object): pass 

class Y(object): 
    @classmethod 
    def method(cls, x_inst, *args, **kwargs): 
    #do some work here that requires an instance of x 
    pass 

Co chciałbym zrobić to dodać właściwość dynamiczną do wszystkich wystąpień X umożliwiających dojazd do Y który domyślnie wypełnia pierwszy parametr z method z podaną instancją. Np chciałbym następujący kod do pracy identycznie:

# current 
x = X() 
result = Y.method(x, 1, 2, 3) 

# desired 
x = X() 
x.Y.method(1, 2, 3) 

Istnieje kilka metod na kilka podklas, które chciałbym zaimplementować to zachowanie za. To, co zrobiłem obecnie, to stworzyć klasę YProxy, którą faktycznie zwraca X, a następnie umieścić w niej część kodu. Wydaje się raczej nieeleganckie i trudne do utrzymania jednakże:

class X(object): 
    @property 
    def Y(self): 
    return YProxy(self) 

class Y(object): 
    @classmethod 
    def method(cls, x_inst, *args, **kwargs): 
    #do some work here that requires an instance of x 
    pass 

class YProxy(object): 
    def __init__(self, x_inst): 
    self.x_inst = x_inst 

    def method(self, *args, **kwargs): 
    return Y.method(self.x_inst, *args, **kwargs) 

Czy istnieje jakiś sposób, aby warunkowo częściowo ocenić classmethods na obiekcie?

+0

W 'x(). Y.foo' będzie 'foo' zawsze metoda klasy "Y", która musi mieć swój pierwszy argument Nt wypełniony? A może chcesz mieć dostęp do dowolnych atrybutów 'Y' z instancji' X'? –

+0

@benw będą metody na Y, które nie powinny mieć x_inst jako pierwszego parametru, choć obecnie są to wszystkie metody instancji, a nie metody klasy. –

Odpowiedz

2

To można zrobić z obiektem Descriptor + reklamy klasy otoki jednoznacznie stwierdzającej klas musisz owinąć na tej drodze w swojej klasie docelowej.

Obiektem deskryptora jest każdy obiekt definiujący metodę __get__, który pozwala dostosować atrybut do pobierania, gdy deskryptor jest częścią klasy. W tym przypadku chcemy, aby atrybut ten, który jest klasą "Y", był pobierany z instancji, gdy metoda jest pobierana z tej klasy, instancja jest wstawiana na listę parametrów.

Wymaga to, aby pobrany atrybut sam w sobie był klasą "proxy" z niestandardowym dostępem do atrybutu, aby umożliwić zanotowanie dynamiczne.

Przełożenie to wszystko w Pythonie, mamy:

import types 
from functools import partial 

class APIWrapper(object): 
    def __init__(self, apicls, instance): 
     self._apicls = apicls 
     self._instance = instance 
    def __getattribute__(self, attr): 
     apicls = object.__getattribute__(self, "_apicls") 
     instance = object.__getattribute__(self,"_instance") 
     obj = getattr(apicls, attr) 
     if isinstance(obj, types.MethodType): 
      return partial(obj,instance) 
     return obj 


class APIProperty(object): 
    def __init__(self, cls): 
     self.cls = cls 
    def __get__(self, instance, cls): 
     return APIWrapper(self.cls, instance) 

class Y(object): 
    @classmethod 
    def method(cls, x, *args): 
     print cls, x, args 

class X(object): 
    Y = APIProperty(Y) 

#Example usage: 
x = X() 
x.Y.method(1,2,3) 

(drukuje <class '__main__.Y'> <__main__.X object at 0x18ad090> (1, 2, 3) gdy bieg)

Ale przypuszczam, że nie chcesz, aby trzeba pisać

Y = APIWrapper(Y) 

dla każdą z klas, którą chcesz owinąć w ten sposób. (I niech te klasy zostaną zdefiniowane po zawiniętej klasie, tak, że Y już zostało sparsowane, gdy X body jest parsowane).

Można to zrobić za pomocą metaclasses, dekoratorów klas, które należy zdefiniować dla każdej klasy, do której chcesz zastosować metody - zamiast tego stworzyłem funkcję, która ma zostać wywołana na końcu definicji modułu , gdzie definiujesz swoją klasę "X" - ta funkcja doda pożądane klasy jako atrybuty dla każdej zdefiniowanej klasy (w moim przykładzie chcę, aby klasa była oznaczona atrybutem "auto_api" - ale odpowiadaj sobie) - . funkcja "auto_api" definicja klas x i Y, jak to się (przy użyciu tego samego APIProperty i APIWrapper jak wyżej)

def auto_api(api_classes, glob_dict): 
    for key, value in glob_dict.items(): 
     if isinstance(value, type) and hasattr(value, "auto_api"): 
      for api_class in api_classes: 
       setattr(value, api_class.__name__, APIProperty(api_class)) 


class X(object): 
    auto_api = True 

class Y(object): 
    @classmethod 
    def method(cls, x, *args): 
     print cls, x, args 

auto_api((Y,), globals()) 

#Example 

x = X() 
x.Y.method(1,2,3) 
+0

Metoda "__get__"! Świetnie, zapomniałem o tym. To zrobiło to dla mnie. zadeklaruj wszystkie potencjalne właściwości klas z góry, istnieje ich predefiniowany zestaw i nie ma ich wiele, przypuszczam, że gdybym naprawdę chciał, mogłem wykonać introspekcję mojego modułu przez metaklass, ale to będzie wymagało więcej kodu niż ja ' Być oszczędnym i wyglądać okropnie magicznie –

+0

Czytanie innych odpowiedzi teraz, rozwiązanie @ ben-w jest bardzo podobne do tego - ale tutaj idę o krok dalej n, które pozwalam ci przekazać klasy, które chcesz zawijać jako parametry, zamiast konieczności ich kodowania. – jsbueno

+0

AFAICT to zrobi coś złego w przypadku metod, które albo (a) nie są metodami klasy lub (b) nie oczekują wystąpienia X jako ich pierwszego argumentu --- niezbędna jest jakaś inspekcja, wykraczająca poza zapewnienie atrybutu Y to metoda upewnienia się, że wychodzi dobrze. (Chyba, że ​​* wszystkie * Y metody są metodami klasowymi oczekującymi instancji X). (Ale przyznaję, że jazz "auto_api" jest fajny.) –

1
import inspect 

def callthing(): 
    my_caller = inspect.stack()[1] 
    args, varargs, keywords, locals = inspect.getargvalues(my_caller) 
    print args[0] 

Gdy wyżej callthing jest wywoływana, to dostać argumenty jego rozmówcy (The [1] bycia „ramka stosu powyżej obecnego jednego”), drukując pierwszą. Możesz nawet użyć nazwanych argumentów, aby "uzyskać wartość argumentu o nazwie x_inst". Być może @classmethod powinien zwrócić funkcję zawiniętą w wywołanie do czegoś takiego jak callthing?

Można również zrobić rozgałęzienie wydarzy się w dekoratora sama stosując inspect.getargspec(my_function), pamiętając, że rzeczywista wartośćargument jest podana nie jest dostępny w tym momencie, ponieważ funkcja jest skonstruowany zamiast nazywa.

+1

W pythonie 2 lepiej jest użyć 'inspect.currentframe (1)' zamiast 'inspect.stack() [1]' - później zajmuje wiek do przetworzenia, do tego stopnia, że ​​zwykle nie nadaje się do użycia w kod produkcyjny. – jsbueno

+0

Nie sądzę, żeby to działało - instancja "x" nie jest faktycznie używana jako parametr w niczym w wyrażeniu takim jak "xYmethod()" - "Y" jest pobierane jako atrybut instancji x, ale ponieważ Y nie jest metodą, argument "ja" nigdy nie jest przekazywany. – jsbueno

0

Dlaczego łączysz takie klasy? Z pewnością

>>> class X(object): 
...  def __init__(self, y): 
...    self.y = y 
...  
...  def method(self, *args, **kwargs): 
...    self.y.method(self, *args, **kwargs) 

>>> class Y(object): 
...  @classmethod 
...  def method(cls, x, *args): 
...    print cls, x, args 
... 
>>> y = Y() 
>>> x = X(y) 
>>> x.method(Ellipsis) 
<class '__main__.Y'> <__main__.X object at 0xb727612c> (Ellipsis,) 

będzie działać dobrze?


To, o co prosiłeś, jest nieco sprzeczne. Jeśli x.Y naprawdę jest tylko obiektem klasy Y, to z pewnością nie ma tej automatycznej właściwości wiązania argumentów, którą chcesz. Tak więc x.Y musi być jakimś obiektem proxy, który wykonuje wiązanie dla ciebie.

Ale Python już to robi na przykładach metod klas już! Jeśli masz instancję X i wywołasz na niej metodę, ta metoda otrzyma self jako pierwszy argument; wiesz to. Czy na pewno bardziej sensowne będzie użycie tego do związania argumentów?

+0

Y to tylko jedna z klas, do których chcę uzyskać dostęp. Istnieje również A, B, C itd. Zasadniczo X jest klasą API, a wszystkie pozostałe klasy są obiektowymi reprezentacjami zasobów zwracanych przez api. Chciałbym móc powiedzieć "api.ResourceA.retrieve (id_)" lub "api.ResourceB.all() " –

+0

A te zasoby same potrzebują instancji klasy API? – katrielalex

+0

Dostęp do zasobów musi być możliwy poprzez obiekt api (X), który zawiera szczegóły uwierzytelniania i inne ogólne metody komunikacji ze zdalnym serwerem. być podklasą czegoś innego (na przykład APIObject), ale zachowaj odwołanie do instancji interfejsu API, z której pochodzą. –

2

Nie wiem, czy to jest to, co chcesz, ale niektóre dyscypliny introspekcja/varname może Ci gdzieś:

import functools 

class X(object): 
    @property 
    def Y(self): 
     return YProxy(self) 

class Y(object): 
    @classmethod 
    def method(cls, x_inst, *a): 
     print x_inst, a 

instancemethod = type(Y.method) 

class YProxy(object): 
    def __init__(self, x_inst): 
     self.x_inst = x_inst 
    def __getattr__(self, k): 
     obj = getattr(Y, k) 
     if isinstance(obj, instancemethod) and obj.func_code.co_varnames[0] == 'cls': 
      return functools.partial(obj, self.x_inst) 
     return obj 

Korzystanie cls jako nazwy dla pierwszej arg do metody klasy jest ogólnie przyjęte styl, ja myśleć. self nie jest właściwe.

ETA: można również sprawdzić nazwę zmiennej o nazwie "x_inst" lub coś podobnego, jeśli istnieją inne metody klas, lub użyć dekoratora w odpowiednich klasach, aby ustawić właściwość metod, & c. Chodzi o to, że możesz zrobić to, czego potrzebujesz w __getattr__, jeśli masz jakiś programowy sposób rozróżniania, które metody muszą być dostarczone z instancją, a które nie.

0

Czy można zamiast tego zrobić coś takiego?

class X(object): 
def __init__(self): 
    self.Y = Y(self) 

class Y(object): 
def __init__(self, x): 
    self.x = x 

def method1(self, *args, **kwargs): 
    pass 

x = X() 
x.Y.method1() 
Powiązane problemy