2011-07-14 18 views
11

Próbuję napisać klasę dekorator dekorator, które odnoszą się do wszystkich metod klasy:Pisanie dekorator klasy, które odnoszą dekorator do wszystkich metod

import inspect 


def decorate_func(func): 
    def wrapper(*args, **kwargs): 
     print "before" 
     ret = func(*args, **kwargs) 
     print "after" 
     return ret 
    for attr in "__module__", "__name__", "__doc__": 
     setattr(wrapper, attr, getattr(func, attr)) 
    return wrapper 


def decorate_class(cls): 
    for name, meth in inspect.getmembers(cls, inspect.ismethod): 
     setattr(cls, name, decorate_func(meth)) 
    return cls 


@decorate_class 
class MyClass(object): 

    def __init__(self): 
     self.a = 10 
     print "__init__" 

    def foo(self): 
     print self.a 

    @staticmethod 
    def baz(): 
     print "baz" 

    @classmethod 
    def bar(cls): 
     print "bar" 


obj = MyClass() 
obj.foo() 
obj.baz() 
MyClass.baz() 
obj.bar() 
MyClass.bar() 

To prawie działa, ale @classmethod S potrzebujesz specjalne traktowanie:

$ python test.py 
before 
__init__ 
after 
before 
10 
after 
baz 
baz 
before 
Traceback (most recent call last): 
    File "test.py", line 44, in <module> 
    obj.bar() 
    File "test.py", line 7, in wrapper 
    ret = func(*args, **kwargs) 
TypeError: bar() takes exactly 1 argument (2 given) 

Czy istnieje sposób, aby ładnie poradzić sobie z tym problemem? Sprawdziłem metody zdobione w stylu @classmethod, ale nie widzę niczego, co odróżniałoby je od innych "typów" metod.

Aktualizacja

Oto kompletne rozwiązanie dla rekordu (przy użyciu deskryptorów obsłużyć @staticmethod S i @classmethod S ładnie i podstęp AIX do wykrywania @classmethod S VS normalne metody):

import inspect 


class DecoratedMethod(object): 

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

    def __get__(self, obj, cls=None): 
     def wrapper(*args, **kwargs): 
      print "before" 
      ret = self.func(obj, *args, **kwargs) 
      print "after" 
      return ret 
     for attr in "__module__", "__name__", "__doc__": 
      setattr(wrapper, attr, getattr(self.func, attr)) 
     return wrapper 


class DecoratedClassMethod(object): 

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

    def __get__(self, obj, cls=None): 
     def wrapper(*args, **kwargs): 
      print "before" 
      ret = self.func(*args, **kwargs) 
      print "after" 
      return ret 
     for attr in "__module__", "__name__", "__doc__": 
      setattr(wrapper, attr, getattr(self.func, attr)) 
     return wrapper 


def decorate_class(cls): 
    for name, meth in inspect.getmembers(cls): 
     if inspect.ismethod(meth): 
      if inspect.isclass(meth.im_self): 
       # meth is a classmethod 
       setattr(cls, name, DecoratedClassMethod(meth)) 
      else: 
       # meth is a regular method 
       setattr(cls, name, DecoratedMethod(meth)) 
     elif inspect.isfunction(meth): 
      # meth is a staticmethod 
      setattr(cls, name, DecoratedClassMethod(meth)) 
    return cls 


@decorate_class 
class MyClass(object): 

    def __init__(self): 
     self.a = 10 
     print "__init__" 

    def foo(self): 
     print self.a 

    @staticmethod 
    def baz(): 
     print "baz" 

    @classmethod 
    def bar(cls): 
     print "bar" 


obj = MyClass() 
obj.foo() 
obj.baz() 
MyClass.baz() 
obj.bar() 
MyClass.bar() 
+0

Twoje klasy DecoratedClassMethod i DecoratedMethod są dokładnie takie same. edytuj, aby umieścić prawidłowe rozwiązanie. –

+0

Są różne: DecoratedMethod przekazuje instancję obiektu, podczas gdy DecoratedClassMethod nie. –

+1

Są one na tyle podobne, że musi istnieć możliwość ich połączenia, aby uniknąć powielania. Pomyśl wzdłuż linii 'self.func (cls lub obj, * args, ** kwargs)'. Wiem, że to nie to samo, ale prosta instrukcja "if" z odpowiednim testem ostatecznie uratuje cię przed posiadaniem tych dwóch niemal identycznych klas. – robru

Odpowiedz

11

inspect.isclass(meth.im_self) powinien powiedzieć, czy meth jest metodą klasy:

def decorate_class(cls): 
    for name, meth in inspect.getmembers(cls, inspect.ismethod): 
     if inspect.isclass(meth.im_self): 
      print '%s is a class method' % name 
      # TODO 
     ... 
    return cls 
+1

+1 pokonałeś mnie do tego. – SingleNegationElimination

+0

Udało się, dzięki! Zaktualizowałem moje pytanie kompletnym rozwiązaniem. –

1

(zbyt długi dla komentarzu)

Pozwoliłem sobie dodawania możliwość określenia, jakie metody powinien dostać urządzone rozwiązania:

def class_decorator(*method_names): 

    def wrapper(cls): 

     for name, meth in inspect.getmembers(cls): 
      if name in method_names or len(method_names) == 0: 
       if inspect.ismethod(meth): 
        if inspect.isclass(meth.im_self): 
         # meth is a classmethod 
         setattr(cls, name, VerifyTokenMethod(meth)) 
        else: 
         # meth is a regular method 
         setattr(cls, name, VerifyTokenMethod(meth)) 
       elif inspect.isfunction(meth): 
        # meth is a staticmethod 
        setattr(cls, name, VerifyTokenMethod(meth)) 

     return cls 

    return wrapper 

Zastosowanie:

@class_decorator('some_method') 
class Foo(object): 

    def some_method(self): 
     print 'I am decorated' 

    def another_method(self): 
     print 'I am NOT decorated' 
0

powyższych odpowiedzi nie odnoszą się bezpośrednio do python3. Na podstawie innych świetnych odpowiedzi udało mi się wymyślić następujące rozwiązanie:

import inspect 
import types 
import networkx as nx 


def override_methods(cls): 
    for name, meth in inspect.getmembers(cls): 
     if name in cls.methods_to_override: 
      setattr(cls, name, cls.DecorateMethod(meth)) 
    return cls 


@override_methods 
class DiGraph(nx.DiGraph): 

    methods_to_override = ("add_node", "remove_edge", "add_edge") 

    class DecorateMethod: 

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

     def __get__(self, obj, cls=None): 
      def wrapper(*args, **kwargs): 
       ret = self.func(obj, *args, **kwargs) 
       obj._dirty = True # This is the attribute I want to update 
       return ret 
      return wrapper 

    def __init__(self): 
     super().__init__() 
     self._dirty = True 

Teraz w każdej chwili metoda w krotce methods_to_override nazywa, brudna flaga jest ustawiona. Oczywiście można tam też wszystko inne. Nie jest konieczne uwzględnianie klasy DecorateMethod w klasie, której metody muszą zostać zastąpione. Jednak jako DecorateMehod używa określonych atrybutów do klasy, wolę utworzyć atrybut klasy.