Wenn ein Dekorateur mit einer Klasse definiert, wie transferiere ich automatisch über __name__
, __module__
und __doc__
? Normalerweise würde ich den @wraps Dekorator von functools verwenden. Folgendes habe ich stattdessen für eine Klasse getan (dies ist nicht ganz mein Code):
class memoized:
"""Decorator that caches a function's return value each time it is called.
If called later with the same arguments, the cached value is returned, and
not re-evaluated.
"""
def __init__(self, func):
super().__init__()
self.func = func
self.cache = {}
def __call__(self, *args):
try:
return self.cache[args]
except KeyError:
value = self.func(*args)
self.cache[args] = value
return value
except TypeError:
# uncacheable -- for instance, passing a list as an argument.
# Better to not cache than to blow up entirely.
return self.func(*args)
def __repr__(self):
return self.func.__repr__()
def __get__(self, obj, objtype):
return functools.partial(self.__call__, obj)
__doc__ = property(lambda self:self.func.__doc__)
__module__ = property(lambda self:self.func.__module__)
__name__ = property(lambda self:self.func.__name__)
Gibt es einen Standarddekorateur, der die Erstellung von Namensmodulen und Dokumenten automatisiert? Auch um die get-Methode zu automatisieren (ich nehme an, das dient zum Erstellen gebundener Methoden?) Gibt es fehlende Methoden?
__name__
und__doc__
werden auf die Instanz gesetzt , aber nicht auf die Klasse, die immer von verwendet wirdhelp(instance)
. Um dies zu beheben, kann keine klassenbasierte Decorator-Implementierung verwendet werden. Stattdessen muss der Decorator als Funktion implementiert werden. Weitere Informationen finden Sie unter stackoverflow.com/a/25973438/1988505 .fibonacci?
sowohl das Dokument aus dem Wrapper als auch die gespeicherte Klasse an, sodass Sie beide erhaltenIch bin mir solcher Dinge in stdlib nicht bewusst, aber wir können unsere eigenen erstellen, wenn wir müssen.
So etwas kann funktionieren:
from functools import WRAPPER_ASSIGNMENTS def class_wraps(cls): """Update a wrapper class `cls` to look like the wrapped.""" class Wrapper(cls): """New wrapper that will extend the wrapper `cls` to make it look like `wrapped`. wrapped: Original function or class that is beign decorated. assigned: A list of attribute to assign to the the wrapper, by default they are: ['__doc__', '__name__', '__module__', '__annotations__']. """ def __init__(self, wrapped, assigned=WRAPPER_ASSIGNMENTS): self.__wrapped = wrapped for attr in assigned: setattr(self, attr, getattr(wrapped, attr)) super().__init__(wrapped) def __repr__(self): return repr(self.__wrapped) return Wrapper
Verwendung:
@class_wraps class memoized: """Decorator that caches a function's return value each time it is called. If called later with the same arguments, the cached value is returned, and not re-evaluated. """ def __init__(self, func): super().__init__() self.func = func self.cache = {} def __call__(self, *args): try: return self.cache[args] except KeyError: value = self.func(*args) self.cache[args] = value return value except TypeError: # uncacheable -- for instance, passing a list as an argument. # Better to not cache than to blow up entirely. return self.func(*args) def __get__(self, obj, objtype): return functools.partial(self.__call__, obj) @memoized def fibonacci(n): """fibonacci docstring""" if n in (0, 1): return n return fibonacci(n-1) + fibonacci(n-2) print(fibonacci) print("__doc__: ", fibonacci.__doc__) print("__name__: ", fibonacci.__name__)
Ausgabe:
<function fibonacci at 0x14627c0> __doc__: fibonacci docstring __name__: fibonacci
BEARBEITEN:
Und wenn Sie sich fragen, warum dies nicht in der stdlib enthalten war, können Sie Ihren Klassendekorator in einen Funktionsdekorator einwickeln und
functools.wraps
wie folgt verwenden :def wrapper(f): memoize = memoized(f) @functools.wraps(f) def helper(*args, **kws): return memoize(*args, **kws) return helper @wrapper def fibonacci(n): """fibonacci docstring""" if n <= 1: return n return fibonacci(n-1) + fibonacci(n-2)
quelle
__get__
Methode ist?class_wraps
wird darin bestehen, eine Wrapper-Klasse so zu aktualisieren, dass sie wie die verpackte aussieht. nicht weniger nicht mehr :)__get__
alle Dekorateure der "Callable Class" außer Kraft setzen wollen ? 2. Warum verwenden wir,functools.partial
anstatt eine gebundene Methode mit zurückzugebentypes.MethodType(self.__call__, obj)
?_get__
Methode für Klassendekorateure zu implementieren , um keine seltsamen Probleme zu haben nach :) 2. Ich denke, es ist nur eine Frage der Präferenzthe beauty is in the eye of the beholder
richtig, ich bevorzugefunctools.partial
in Fällen wie diesem und meistens verwende ichtypes.*
, um die Arten eines Objekts zu testen. Ich hoffe, ich beantworte Ihre Fragen :)Ich brauchte etwas, das sowohl Klassen als auch Funktionen umschließt, und schrieb Folgendes:
def wrap_is_timeout(base): '''Adds `.is_timeout=True` attribute to objects returned by `base()`. When `base` is class, it returns a subclass with same name and adds read-only property. Otherwise, it returns a function that sets `.is_timeout` attribute on result of `base()` call. Wrappers make best effort to be transparent. ''' if inspect.isclass(base): class wrapped(base): is_timeout = property(lambda _: True) for k in functools.WRAPPER_ASSIGNMENTS: v = getattr(base, k, _MISSING) if v is not _MISSING: try: setattr(wrapped, k, v) except AttributeError: pass return wrapped @functools.wraps(base) def fun(*args, **kwargs): ex = base(*args, **kwargs) ex.is_timeout = True return ex return fun
quelle
.is_timeout=True
Redewendung zu verwenden, um Ihre durch Timeout verursachten Fehler zu markieren und diese API aus anderen Paketen zu akzeptieren.Alles, was wir wirklich tun müssen, ist, das Verhalten des Dekorateurs so zu ändern, dass es "hygienisch" ist, dh Attribute bewahrt.
#!/usr/bin/python3 def hygienic(decorator): def new_decorator(original): wrapped = decorator(original) wrapped.__name__ = original.__name__ wrapped.__doc__ = original.__doc__ wrapped.__module__ = original.__module__ return wrapped return new_decorator
Das ist alles was du brauchst. Im Allgemeinen. Die Signatur wird nicht beibehalten, aber wenn Sie dies wirklich möchten, können Sie dazu eine Bibliothek verwenden. Ich habe auch den Memoization-Code neu geschrieben, damit er auch mit Schlüsselwortargumenten funktioniert. Es gab auch einen Fehler, bei dem das Nichtkonvertieren in ein Hash-Tupel dazu führte, dass es in 100% der Fälle nicht funktionierte.
Demo des umgeschriebenen
memoized
Dekorateurs mit@hygienic
Änderung seines Verhaltens.memoized
ist jetzt eine Funktion, die die ursprüngliche Klasse umschließt, obwohl Sie (wie die andere Antwort) stattdessen eine Umbruchklasse schreiben können, oder noch besser, etwas, das erkennt, ob es sich um eine Klasse handelt, und wenn ja, die__init__
Methode umschließt .@hygienic class memoized: def __init__(self, func): self.func = func self.cache = {} def __call__(self, *args, **kw): try: key = (tuple(args), frozenset(kw.items())) if not key in self.cache: self.cache[key] = self.func(*args,**kw) return self.cache[key] except TypeError: # uncacheable -- for instance, passing a list as an argument. # Better to not cache than to blow up entirely. return self.func(*args,**kw)
In Aktion:
@memoized def f(a, b=5, *args, keyword=10): """Intact docstring!""" print('f was called!') return {'a':a, 'b':b, 'args':args, 'keyword':10} x=f(0) #OUTPUT: f was called! print(x) #OUTPUT: {'a': 0, 'b': 5, 'keyword': 10, 'args': ()} y=f(0) #NO OUTPUT - MEANS MEMOIZATION IS WORKING print(y) #OUTPUT: {'a': 0, 'b': 5, 'keyword': 10, 'args': ()} print(f.__name__) #OUTPUT: 'f' print(f.__doc__) #OUTPUT: 'Intact docstring!'
quelle
AttributeError: 'function' object has no attribute 'level'
beim Versuch,decoratorclassname.level += 1
innerhalb des__call__
Eine andere Lösung mit Vererbung:
import functools import types class CallableClassDecorator: """Base class that extracts attributes and assigns them to self. By default the extracted attributes are: ['__doc__', '__name__', '__module__']. """ def __init__(self, wrapped, assigned=functools.WRAPPER_ASSIGNMENTS): for attr in assigned: setattr(self, attr, getattr(wrapped, attr)) super().__init__() def __get__(self, obj, objtype): return types.MethodType(self.__call__, obj)
Und Verwendung:
class memoized(CallableClassDecorator): """Decorator that caches a function's return value each time it is called. If called later with the same arguments, the cached value is returned, and not re-evaluated. """ def __init__(self, function): super().__init__(function) self.function = function self.cache = {} def __call__(self, *args): try: return self.cache[args] except KeyError: value = self.function(*args) self.cache[args] = value return value except TypeError: # uncacheable -- for instance, passing a list as an argument. # Better to not cache than to blow up entirely. return self.function(*args)
quelle
__init__
Methode der übergeordneten Klassen aufrufen müssen (nicht unbedingt nursuper()
; Sie sollten nach googelnmethod resolution order python
).__init__
Methode der anderen übergeordneten Klassen aufzurufen ?__init__
Methoden aufgerufen werden, solange dies jeder tut, von dem ich erbe. Leider habe ich festgestellt, dass PyQt-Klassen dies nicht tun. Ich dachte wirklich, dass kooperative Vererbung so funktionieren muss, aber nach dem, was Sie sagen, klingt es so, als wäre ich der einzige!