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.
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.
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__
.
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!
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.
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.
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 jakisinstance
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.
Jednak w twojej klasie chcesz, aby Dekoracja zmieniła konstruktor, co nie wydaje mi się dobrym przypadkiem użycia dla dekoratora klasy.
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']
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://docs.djangoproject.com/en/3.0/topics/class-based-views/intro/#decorating-the-class
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