Klassendekorateure in Python: praktische Anwendungsfälle

9

Ich suche nach praktischen und nicht synthetischen Anwendungsfällen von Python-Klassendekorateuren. Bisher war der einzige Fall, der für mich sinnvoll war, die Registrierung einer Klasse in einem Publisher-Subscriber-System, z. B. Plugins oder Events, wie z.

@register
class MyPlugin(Plugin):
    pass

oder

@recieves_notifications
class Console:
    def print(self, text):
        ...

Alle anderen vernünftigen Fälle, an die ich gedacht habe, könnten auf Vererbung, Metaklassen oder Dekorationsmethoden aufbauen. Könnten Sie bitte gute (oder schlechte!) Beispiele für die Verwendung von Klassendekorateuren nennen?

Vielen Dank!

Zaur Nasibov
quelle
Für den Anfang sind Dekorateure konzeptionell viel einfacher als Metaklassen. Es ist viel einfacher, einen Dekorateur als eine Metaklasse zu schreiben, und es muss möglicherweise nicht herausgefunden werden, wie mehrere Metaklassen kombiniert werden können, ohne etwas zu beschädigen.
Amon
@amon Wenn eine Metaklasse von einem erfahrenen Entwickler verwendet wird, bedeutet dies normalerweise, dass andere Optionen gründlich in Betracht gezogen wurden und Metaklasse bislang der beste Weg ist, um das Problem zu lösen. Darüber hinaus ist explizit besser als implizit - das könnte der Grund sein, warum wir (als Beispiel) ABCMetakeinen @abstractclassKlassendekorateur haben.
Zaur Nasibov
2
Ich bin ein Python-Programmierer und sehe ehrlich gesagt keine Notwendigkeit für sie. Nur weil eine Sprachfunktion vorhanden ist, heißt das nicht, dass Sie sie verwenden müssen oder sollten. Designer haben alle möglichen verrückten Funktionen entwickelt, die sich im Nachhinein als unnötig oder sogar schädlich herausstellen. In der Tat haben Sie zwei andere solche Funktionen in Ihrer Frage erwähnt :)
Gardenhead
1
Nicht etwas, ohne das ich nicht leben könnte, aber etwas, das ich als nützlich empfunden habe, ist die Verwendung eines Klassendekorators, um alle Ein- und Ausgaben aller Methoden der Klasse zu protokollieren.
Bgusach

Antworten:

8

Beim Schreiben von Komponententests mit dem @unittest.skipIfund @unittest.skipUnlesskönnen entweder einzelne Methoden oder eine gesamte unittest.TestCaseKlassendefinition verwendet werden. Dies erleichtert das Schreiben von Tests für plattformspezifische Probleme erheblich.

Beispiel aus asyncio/test_selectors

# Some platforms don't define the select.kqueue object for these tests.
# On those platforms, this entire grouping of tests will be skipped.

@unittest.skipUnless(hasattr(selectors, 'KqueueSelector'),
                 "Test needs selectors.KqueueSelector)")
class KqueueSelectorTestCase(BaseSelectorTestCase, ScalableSelectorMixIn):
...
Sethmlarson
quelle
2

Klassendekorateure wurden explizit erstellt, um einige Dinge, die bereits über Metaklassen ausgedrückt werden konnten, aus dem PEP angenehmer zu machen :

Der motivierende Anwendungsfall bestand darin, bestimmte Konstrukte leichter auszudrücken und weniger von Implementierungsdetails des CPython-Interpreters abhängig zu machen. Während es möglich ist, Klassendekorator-ähnliche Funktionen mithilfe von Metaklassen auszudrücken, sind die Ergebnisse im Allgemeinen unangenehm und die Implementierung sehr fragil. Darüber hinaus werden Metaklassen vererbt, während dies bei Klassendekoratoren nicht der Fall ist, sodass Metaklassen für einige klassenspezifische Verwendungen von Klassendekoratoren ungeeignet sind. Die Tatsache, dass große Python-Projekte wie Zope diese wilden Verrenkungen durchliefen, um so etwas wie Klassendekorateure zu erreichen, überzeugte die BDFL.

Quodlibetor
quelle
Vielen Dank, dass Sie PEP-3129 zitiert haben. Die Frage ist jedoch nicht, warum es Klassendekorateure gibt, was Guido von ihnen hält und wie Zope oder IronPython sie hätten verwenden können, bevor sie aufgetaucht sind. Die Frage ist über ihre praktische Verwendung heute, im Jahr 2016.
Zaur Nasibov
1
Richtig, der Punkt ist, dass die ursprüngliche Frage Metaklassen als etwas hervorhebt, das verwendet werden kann, um klassendekoratorähnliche Funktionen zu implementieren, aber der ganze Punkt von Dekoratoren ist, dass sie einfacher zu verwenden und offensichtlicher als Metaklassen sind. Die Antwort auf diesen Teil der Frage lautet also: "Wenn Sie zwischen Dekorateuren oder Metaklassen hin- und hergerissen sind, sollten Sie Dekorateure verwenden, da diese für andere leichter zu verstehen sind."
Quodlibetor
Sie hinterlassen weitere Fragen, die beantwortet werden. "... der ganze Sinn von Dekorateuren ist, dass sie einfacher zu bedienen und offensichtlicher sind als Metaklassen" - In welchen Fällen? Dann "... sollten Sie Dekorateure verwenden, weil sie für andere leichter zu verstehen sind" Wirklich? Für eine lange Zeit der Sucht nach Python habe ich Tonnen von Metaklassen gesehen, einige hässlich, einige schön. Habe aber überhaupt keine Klassendekorateure entdeckt.
Zaur Nasibov
1

Aus dieser Stapelüberlauffrage:

def singleton(class_):
  instances = {}
  def getinstance(*args, **kwargs):
    if class_ not in instances:
        instances[class_] = class_(*args, **kwargs)
    return instances[class_]
  return getinstance

@singleton
class MyClass(BaseClass):
  pass
Jared Smith
quelle
Ja, aber das Gegenteil ist in derselben SO-Frage richtig angegeben: Die Metaklasse ist ein besserer Ansatz.
Zaur Nasibov
1

Die Suche nach Anwendungsfällen, die mit Dekorateuren in Klassen durchgeführt werden können, ist zwecklos - alles , was mit Dekorateuren in Klassen durchgeführt werden kann, kann mit Metaklassen durchgeführt werden. Sogar Ihr Beispiel für die Registrierung. Um dies zu beweisen, ist hier eine Metaklasse, die einen Dekorateur anwendet:

Python 2:

def register(target):
    print 'Registring', target
    return target


class ApplyDecorator(type):
    def __new__(mcs, name, bases, attrs):
        decorator = attrs.pop('_decorator')
        cls = type(name, bases, attrs)
        return decorator(cls)

    def __init__(cls, name, bases, attrs, decorator=None):
        super().__init__(name, bases, attrs)


class Foo:
    __metaclass__ = ApplyDecorator
    _decorator = register

Python 3:

def register(target):
    print('Registring', target)
    return target


class ApplyDecorator(type):
    def __new__(mcs, name, bases, attrs, decorator):
        cls = type(name, bases, attrs)
        return decorator(cls)

    def __init__(cls, name, bases, attrs):
        super().__init__(name, bases, attrs)


class Foo(metaclass=ApplyDecorator, decorator=register):
    pass
Idan Arye
quelle
1
Wie Sie sagen - mit Dekorateur kann alles gemacht werden. Die Frage ist, was die Fälle sind , die über Klassendekorateure und nicht über Metaklassen durchgeführt werden sollten.
Zaur Nasibov