2012-06-18 27 views
5

Po this answer wydaje się, że klasa może metaklasa zostać zmienione po klasa został zdefiniowany za pomocą następującego *:Ustawianie klasy metaklasa użyciu dekorator

class MyMetaClass(type): 
    # Metaclass magic... 

class A(object): 
    pass 

A = MyMetaClass(A.__name__, A.__bases__, dict(A.__dict__)) 

Definiowanie funkcji

def metaclass_wrapper(cls): 
    return MyMetaClass(cls.__name__, cls.__bases__, dict(cls.__dict__)) 

pozwala mi na zastosowanie dekorator do definicji klasy jak tak,

@metaclass_wrapper 
class B(object): 
    pass 

to zobaczyć ms, że magia metaclass jest stosowana do B, jednak B nie ma atrybutu __metaclass__. Czy powyższy sposób rozsądny sposób zastosować metaclasses do definicji klas, chociaż jestem definiting i ponownie definiting klasę, albo będę lepiej po prostu pisząc

class B(object): 
    __metaclass__ = MyMetaClass 
    pass 

Przypuszczam, istnieją pewne różnice między tymi dwoma metody.


* Uwaga, pierwotna odpowiedź w połączonej pytanie MyMetaClass(A.__name__, A.__bases__, A.__dict__), zwraca TypeError:

TypeError: type() argument 3 must be a dict, not dict_proxy

Wydaje się, że atrybut A (definicja klasy) __dict__ ma typ dict_proxy, natomiast typ atrybutu __dict__ instancji z A ma typ dict. Dlaczego to? Czy jest to różnica Pythona 2.x vs. 3.x?

Odpowiedz

1

Ta klasa nie ma zestawu atrybutów __metaclass__ ... ponieważ nigdy go nie ustawiłeś!

Która metaklasa używana jest zwykle określana jako przez nazwa __metaclass__ ustawiona w bloku klasy. Atrybut __metaclass__ nie jest ustawiony przez metaclass. Więc jeśli wywołasz metaclass bezpośrednio, zamiast ustawiać __metaclass__ i pozwalając Pythonowi go rozgryźć, wtedy nie zostanie ustawiony atrybut __metaclass__.

W rzeczywistości, normalne zajęcia są wszystkie przypadki metaklasą type, więc jeśli metaklasa zawsze ustawić atrybut __metaclass__ na jego wystąpień następnie każdy klasa będzie mieć atrybut __metaclass__ (większość z nich ustawiony type).


Nie użyłbym twojego podejścia do dekoratora. Pomija fakt, że metaclass jest zaangażowany (i który z nich), jest nadal jedną linią standardowego zestawu, a po prostu jest bałagan stworzyć klasę z 3 cech definiujących (name, bases, attributes) tylko po to, aby usunąć te 3 bity z wynikowej klasy, wyrzuć klasę i utwórz nową klasę z tych samych 3 bitów!

Kiedy to zrobić w Pythonie 2.x:

class A(object): 
    __metaclass__ = MyMeta 
    def __init__(self): 
     pass 

Można by się z grubsza taki sam rezultat gdybyś napisał to:

attrs = {} 
attrs['__metaclass__'] = MyMeta 
def __init__(self): 
    pass 
attrs['__init__'] = __init__ 
A = attrs.get('__metaclass__', type)('A', (object,), attrs) 

W rzeczywistości obliczania metaklasą więcej Jest to skomplikowane, ponieważ w rzeczywistości trzeba przeszukać wszystkie bazy, aby ustalić, czy istnieje konflikt metaclasu, a jeśli jedna z baz nie ma type jako metaklasy, a attrs nie zawiera __metaclass__, wówczas domyślną metaklasą jest metaklasa przodka, a nie type. Jest to jedna z sytuacji, w której oczekuję, że twoje "rozwiązanie" dla dekoratora będzie się różnić od bezpośredniego stosowania __metaclass__. Nie jestem do końca pewien, co by się stało, gdybyś użył swojego dekoratora w sytuacji, w której użycie __metaclass__ dałoby błąd konfliktu metaklasowego, ale nie spodziewałbym się, że będzie to przyjemne.

Ponadto, jeśli są zaangażowane jakiekolwiek inne metaclasy, twoja metoda spowoduje, że zostaną uruchomione jako pierwsze (ewentualnie modyfikując nazwę, bazy i atrybuty!), A następnie wyciągając je z klasy i używając jej do utworzenia nowa klasa. Może to potencjalnie różnić się od tego, co uzyskasz używając __metaclass__.

Jeśli chodzi o __dict__ nie dając prawdziwego słownika, to tylko szczegół wdrożenia; Zgaduję, ze względu na wydajność. Wątpię, aby istniała jakaś specyfikacja, która mówi, że __dict__ instancji (bez klasy) musi być tego samego typu co klasa __dict__ klasy (która jest również instancją btw, po prostu instancją metaclass). Atrybut __dict__ klasy jest "dictproxy", który pozwala na wyszukiwanie kluczy atrybutów tak, jakby były one dict, ale nadal nie jest to dict. type jest wybredny o typie trzeciego argumentu; chce prawdziwego dyktatu, a nie tylko obiektu typu "dyktującego" (wstyd za to, że psuje pisanie z kaczką). To nie jest rzecz 2.x vs 3.x; Python 3 zachowuje się w ten sam sposób, chociaż zapewnia ładniejszą reprezentację ciągów znaków dictproxy. Python 2.4 (który jest najstarszym 2.x, który mam łatwo dostępny) ma również obiekty dictproxy dla obiektów klasy .

1

Moje podsumowanie twojego pytania: "Próbowałem nowego, trudnego sposobu robienia rzeczy, i to nie działało. Czy powinienem użyć prostej metody?"

Tak, powinieneś zrobić to w prosty sposób. Nie powiedziałeś, dlaczego jesteś zainteresowany wynajdywaniem nowego sposobu na zrobienie tego.

+0

Po prostu zastanawiałem się, czy istnieje dekorator równoważny ustawieniu atrybutu '__metaclass__' w definicji klasy. Sądzę, że inną częścią mojego pytania jest to, że mój "dekorator" * działał *, ale zauważyłem, że dekorowana klasa nie ma atrybutu "__metaclass__", więc wyraźnie widać różnicę między tymi dwiema metodami; dlaczego to? – Chris

3

Wprawdzie trochę się spóźniłem na imprezę. Jednak upadłem, to było warte dodania.

Jest to całkowicie wykonalne. Biorąc to pod uwagę, istnieje wiele innych sposobów osiągnięcia tego samego celu. Jednak rozwiązanie dekoracyjne w szczególności pozwala na opóźnioną ocenę (obj = dec(obj)), która przy użyciu klasy __metaclass__ nie działa. W typowym stylu dekoratora moje rozwiązanie znajduje się poniżej.

Istnieje pewna trudna sprawa, którą możesz napotkać, jeśli po prostu skonstruujesz klasę bez zmiany słownika lub skopiowania jego atrybutów. Wszelkie atrybuty, które klasa miała wcześniej (przed dekorowaniem) wydają się być nieobecne. Tak więc, absolutnie niezbędne jest skopiowanie ich, a następnie ich poprawienie, tak jak w moim rozwiązaniu.

Osobiście lubię być w stanie śledzić, jak obiekt był opakowany. Dlatego dodałem atrybut __wrapped__, który nie jest bezwzględnie potrzebny. To również sprawia, że ​​bardziej przypomina functools.wraps w Pythonie 3 dla klas. Jednak może być pomocne przy introspekcji. Dodaje się także __metaclass__, aby działać bardziej jak normalny przypadek użycia metaklasy.

def metaclass(meta): 
    def metaclass_wrapper(cls): 
     __name = str(cls.__name__) 
     __bases = tuple(cls.__bases__) 
     __dict = dict(cls.__dict__) 

     for each_slot in __dict.get("__slots__", tuple()): 
      __dict.pop(each_slot, None) 

     __dict["__metaclass__"] = meta 

     __dict["__wrapped__"] = cls 

     return(meta(__name, __bases, __dict)) 
    return(metaclass_wrapper) 

Dla trywialnego przykładu, wykonaj następujące czynności.

class MetaStaticVariablePassed(type): 
    def __new__(meta, name, bases, dct): 
     dct["passed"] = True 

     return(super(MetaStaticVariablePassed, meta).__new__(meta, name, bases, dct)) 

@metaclass(MetaStaticVariablePassed) 
class Test(object): 
    pass 

To daje piękny efekt ...

|1> Test.passed 
|.> True 

Korzystanie z dekorator w mniej zwykle, ale identyczny sposób ...

class Test(object): 
    pass 

Test = metaclass_wrapper(Test) 

... plony, zgodnie z oczekiwaniami , ten sam niezły wynik.

|1> Test.passed 
|.> True