Jak urządzić klasę?

W Pythonie 2.5, czy istnieje sposób na stworzenie dekoratora, który dekoruje klasę? W szczególności chcę użyć dekoratora, aby dodać członek do klasy I zmienić konstruktor, aby przyjąć wartość dla tego członka.

Szukam czegoś takiego (co ma błąd składni w ' class Foo:':

def getId(self): return self.__id

class addID(original_class):
    def __init__(self, id, *args, **kws):
        self.__id = id
        self.getId = getId
        original_class.__init__(self, *args, **kws)

@addID
class Foo:
    def __init__(self, value1):
        self.value1 = value1

if __name__ == '__main__':
    foo1 = Foo(5,1)
    print foo1.value1, foo1.getId()
    foo2 = Foo(15,2)
    print foo2.value1, foo2.getId()

Myślę, że tak naprawdę chodzi mi o sposób na zrobienie czegoś w rodzaju interfejsu C# w Pythonie. Chyba muszę zmienić swój paradygmat.

Author: martineau, 2009-03-25

8 answers

Chciałbym poprzeć pogląd, że może chciałbyś rozważyć podklasę zamiast podejścia, które przedstawiłeś. Jednak nie znając konkretnego scenariusza, YMMV: -)

Myślisz o metaklasie. Funkcja __new__ w metaklasie przekazuje pełną proponowaną definicję klasy, którą może przepisać przed utworzeniem klasy. W tym czasie możesz zastąpić konstruktora nowym.

Przykład:

def substitute_init(self, id, *args, **kwargs):
    pass

class FooMeta(type):

    def __new__(cls, name, bases, attrs):
        attrs['__init__'] = substitute_init
        return super(FooMeta, cls).__new__(cls, name, bases, attrs)

class Foo(object):

    __metaclass__ = FooMeta

    def __init__(self, value1):
        pass

Zastąpienie konstruktora jest być może trochę dramatyczne, ale język zapewnia wsparcie dla tego rodzaju głębokiej introspekcji i dynamicznej modyfikacji.

 81
Author: Jarret Hardie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-07 20:51:35

Poza pytaniem, czy dekoratorzy klas są właściwym rozwiązaniem Twojego problemu:

W Pythonie 2.6 i wyższych, istnieją dekoratory klas ze składnią@, więc możesz napisać:

@addID
class Foo:
    pass

W starszych wersjach można to zrobić w inny sposób:

class Foo:
    pass

Foo = addID(Foo)

Zauważ jednak, że działa to tak samo jak w przypadku dekoratorów funkcji i że dekorator powinien zwrócić nową (lub zmodyfikowaną oryginalną) klasę, co nie jest tym, co robisz w przykładzie. Dekorator addID wyglądałby Tak:

def addID(original_class):
    orig_init = original_class.__init__
    # Make copy of original __init__, so we can call it without recursion

    def __init__(self, id, *args, **kws):
        self.__id = id
        self.getId = getId
        orig_init(self, *args, **kws) # Call the original __init__

    original_class.__init__ = __init__ # Set the class' __init__ to the new one
    return original_class

Możesz następnie użyć odpowiedniej składni dla swojej wersji Pythona, jak opisano powyżej.

Ale Zgadzam się z innymi, że dziedziczenie jest lepsze, jeśli chcesz zastąpić __init__.

 209
Author: Steven,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 13:23:34

Nikt nie wyjaśnił, że można dynamicznie definiować klasy. Więc możesz mieć dekorator, który definiuje (i zwraca) podklasę:

def addId(cls):

    class AddId(cls):

        def __init__(self, id, *args, **kargs):
            super(AddId, self).__init__(*args, **kargs)
            self.__id = id

        def getId(self):
            return self.__id

    return AddId

Który może być użyty w Pythonie 2 (komentarz od Blckknghta, który wyjaśnia, dlaczego powinieneś dalej to robić w 2.6+) w następujący sposób:

class Foo:
    pass

FooId = addId(Foo)

I w Pythonie 3 tak (ale uważaj, aby używać super() w swoich klasach):

@addId
class Foo:
    pass

Więc możesz mieć swoje ciastoi je jeść - dziedziczenie i dekoratorzy!

 20
Author: andrew cooke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 13:30:55

To nie jest dobra praktyka i nie ma mechanizmu, aby to zrobić z tego powodu. Dobrym sposobem na osiągnięcie tego, czego chcesz, jest dziedziczenie.

Zajrzyj do dokumentacji klasy .

Mały przykład:

class Employee(object):

    def __init__(self, age, sex, siblings=0):
        self.age = age
        self.sex = sex    
        self.siblings = siblings

    def born_on(self):    
        today = datetime.date.today()

        return today - datetime.timedelta(days=self.age*365)


class Boss(Employee):    
    def __init__(self, age, sex, siblings=0, bonus=0):
        self.bonus = bonus
        Employee.__init__(self, age, sex, siblings)

W ten sposób szef ma wszystko Employee ma, a także swoje __init__ metody i członków własnych.

 14
Author: mpeterson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-21 15:40:44

Zgadzam się, że dziedziczenie lepiej pasuje do stawianego problemu.

Znalazłem to pytanie bardzo przydatne choć na zajęciach dekorowania, dzięki wszystkim.

Oto kilka przykładów, opartych na innych odpowiedziach, w tym jak dziedziczenie wpływa na rzeczy w Pythonie 2.7, (i @wraps, który utrzymuje oryginalny docstring funkcji, itp.):

def dec(klass):
    old_foo = klass.foo
    @wraps(klass.foo)
    def decorated_foo(self, *args ,**kwargs):
        print('@decorator pre %s' % msg)
        old_foo(self, *args, **kwargs)
        print('@decorator post %s' % msg)
    klass.foo = decorated_foo
    return klass

@dec  # No parentheses
class Foo...

Często chcesz dodać parametry do dekoratora:

from functools import wraps

def dec(msg='default'):
    def decorator(klass):
        old_foo = klass.foo
        @wraps(klass.foo)
        def decorated_foo(self, *args ,**kwargs):
            print('@decorator pre %s' % msg)
            old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)
        klass.foo = decorated_foo
        return klass
    return decorator

@dec('foo decorator')  # You must add parentheses now, even if they're empty
class Foo(object):
    def foo(self, *args, **kwargs):
        print('foo.foo()')

@dec('subfoo decorator')
class SubFoo(Foo):
    def foo(self, *args, **kwargs):
        print('subfoo.foo() pre')
        super(SubFoo, self).foo(*args, **kwargs)
        print('subfoo.foo() post')

@dec('subsubfoo decorator')
class SubSubFoo(SubFoo):
    def foo(self, *args, **kwargs):
        print('subsubfoo.foo() pre')
        super(SubSubFoo, self).foo(*args, **kwargs)
        print('subsubfoo.foo() post')

SubSubFoo().foo()

Wyjścia:

@decorator pre subsubfoo decorator
subsubfoo.foo() pre
@decorator pre subfoo decorator
subfoo.foo() pre
@decorator pre foo decorator
foo.foo()
@decorator post foo decorator
subfoo.foo() post
@decorator post subfoo decorator
subsubfoo.foo() post
@decorator post subsubfoo decorator

I ' ve użyłem dekoratora funkcji, ponieważ uważam je za bardziej zwięzłe. Oto Klasa do dekoracji klasy:

class Dec(object):

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

    def __call__(self, klass):
        old_foo = klass.foo
        msg = self.msg
        def decorated_foo(self, *args, **kwargs):
            print('@decorator pre %s' % msg)
            old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)
        klass.foo = decorated_foo
        return klass

Bardziej solidna wersja, która sprawdza te nawiasy i działa, jeśli metody nie istnieją na dekorowanej klasie: {17]}

from inspect import isclass

def decorate_if(condition, decorator):
    return decorator if condition else lambda x: x

def dec(msg):
    # Only use if your decorator's first parameter is never a class
    assert not isclass(msg)

    def decorator(klass):
        old_foo = getattr(klass, 'foo', None)

        @decorate_if(old_foo, wraps(klass.foo))
        def decorated_foo(self, *args ,**kwargs):
            print('@decorator pre %s' % msg)
            if callable(old_foo):
                old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)

        klass.foo = decorated_foo
        return klass

    return decorator

assert sprawdza, czy dekorator nie został użyty bez nawiasów. Jeśli tak, to dekorowana klasa jest przekazywana do parametru msg dekoratora, który podnosi AssertionError.

@decorate_if stosuje się tylko decorator, Jeśli condition ocenia do True.

The getattr, callable test i @decorate_if są używane tak, aby dekorator nie pękł, jeśli metoda foo() nie istnieje na dekorowanej klasie.

 8
Author: Chris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 13:34:39

Jest tu całkiem dobra implementacja dekoratora klasy:

Https://github.com/agiliq/Django-parsley/blob/master/parsley/decorators.py

Myślę, że to całkiem ciekawa realizacja. Ponieważ podklasuje klasę, którą dekoruje, będzie zachowywać się dokładnie tak jak ta klasa w rzeczach takich jak isinstance checks.

Ma dodatkową zaletę: często zdarza się, że __init__ w niestandardowej formie django dokonuje modyfikacji lub dodatki do self.fields więc lepiej, aby zmiany do self.fields zaszły po Wszystkie __init__ wystartowały do danej klasy.

Bardzo sprytne.

Jednak w twojej klasie chcesz, aby Dekoracja zmieniła konstruktor, co nie wydaje mi się dobrym przypadkiem użycia dla dekoratora klasy.

 4
Author: Jordan Reiter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-28 15:05:42

Oto przykład, który odpowiada na pytanie o zwracanie parametrów klasy. Co więcej, nadal respektuje łańcuch dziedziczenia, tzn. zwracane są tylko parametry samej klasy. Funkcja get_params jest dodawana jako prosty przykład, ale inne funkcje mogą być dodawane dzięki modułowi inspect.

import inspect 

class Parent:
    @classmethod
    def get_params(my_class):
        return list(inspect.signature(my_class).parameters.keys())

class OtherParent:
    def __init__(self, a, b, c):
        pass

class Child(Parent, OtherParent):
    def __init__(self, x, y, z):
        pass

print(Child.get_params())
>>['x', 'y', 'z']

 0
Author: Patricio Astudillo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-12-20 10:16:09

Django mA method_decorator który jest dekoratorem, który zamienia dowolny dekorator w dekorator metody, możesz zobaczyć jak jest zaimplementowany w django.utils.decorators:

Https://github.com/django/django/blob/50cf183d219face91822c75fa0a15fe2fe3cb32d/django/utils/decorators.py#L53

Https://docs.djangoproject.com/en/3.0/topics/class-based-views/intro/#decorating-the-class

 0
Author: Boris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-27 16:33:34