Eine Methode, die aus mehreren Methoden besteht, die denselben Vorgang für verschiedene Typen implementieren.
Jetzt gibt es eine Anforderung, die erfordert, dass Sie eine benutzerdefinierte Datumsklasse (CustomDate
) auf folgende Weise erstellen: CustomDate
):
时间戳
年、月、日(包含三个整数的元组)
ISO 格式的字符串
Datetime
类
from datetime import date, datetime class CustomDate: def __init__(self, arg): if isinstance(arg, (int, float)): self.__date = date.fromtimestamp(arg) elif isinstance(arg, tuple) and len(arg) == 3 and all(map(lambda x: isinstance(x, int), arg): self.__date = date(*arg) elif isinstance(arg, str): self.__date = date.fromisoformat(arg) elif isinstance(arg, datetime): self.__date = datetime.date() else: raise TypeError("could not create instance from " + type(arg).__name__) @property def date(): return self.__date
注:这里暂不讨论传入的日期/时间戳合不合法,仅仅只对类型做大致判断。
我们可以将不同的构建方式拆分为多个方法,并利用 functools
中的 singledispatchmethod
装饰器来根据传入的参数类型决定调用哪个方法。
from datetime import date, datetime from functools import singledispatchmethod class CustomDate: @singledispatchmethod def __init__(self, arg): raise TypeError("could not create instance from " + type(arg).__name__) @__init__.register(int) @__init__.register(float) def __from_timestamp(self, arg): self.__date = date.fromtimestamp(arg) @__init__.register(tuple) def __from_tuple(self, arg): if len(arg) == 3 and all(map(lambda x: isinstance(x, int), arg)): self.__date = date(*arg) else: raise ValueError("could not create instance from a malformed tuple") @__init__.register(str) def __from_isoformat(self, arg): self.__date = date.fromisoformat(arg) @__init__.register(datetime) def __from_datetime(self, arg): self.__date = arg.date() @property def date(self): return self.__date
这样一来,我们便能将每种参数类型的初始化独立成一个个的方法了。
在调用期间应该使用哪个方法实现由分派算法决定。如果该算法只基于单个参数的类型来决定使用哪个方法实现,则称其为单分派。
singledispatchmethod
就是就是单分派的。也就是说,只有第一个参数会作为考量。这在实际业务中是远远不足的。
然而,如上,对元组中元素类型判断还是需要我们用 if
/else
实现。也就是说,我们不能使用 typing.Tuple[int, int, int]
。
作为一种折中的方案,或许我们可以定义一个 ThreeIntTuple
类来对其进行限定,将这些判断从 CustomDate
类中隔离开来。
这里仅提供一个思路让大家参考,我就不实现了(因为我们有更好的方式 xD)。
这个库不是标准库之一,需要通过 pip 安装:
pip install multimethod
multimethod
采用的是多分派算法,能更好地满足更复杂的场景。此外,该库对 typing
中的类型也有不错的支持。
回到上面的问题,我们可以这么改进:
使用 multimethod
方法来替代 singledispatchmethod
;
使用 Tuple[int, int, int]
来替代 tuple
,不再需要手动校验元组的长度和元素类型了;
from datetime import date, datetime from typing import Tuple, Union from multimethod import multimethod class CustomDate: @multimethod def __init__(self, arg): raise TypeError("could not create instance from " + type(arg).__name__) @__init__.register def __from_timestamp(self, arg: Union[int, float]): self.__date = date.fromtimestamp(arg) @__init__.register def __from_tuple(self, arg: Tuple[int, int, int]): self.__date = date(*arg) @__init__.register def __from_isoformat(self, arg: str): self.__date = date.fromisoformat(arg) @__init__.register def __from_datetime(self, arg: datetime): self.__date = arg.date() @property def date(self): return self.__date
在此之前,先问大家一个简单的问题(这跟我们之后的内容有很大的联系):
class A: def a(self): print(1) def a(self): print(2) A().a()
以上这段代码会输出什么?还是会抛出错误?
输出 2
。
在 Python 中,如果定义了重名的方法,最后一个方法是会覆盖掉之前的方法的。
但你或许不知,我们可以通过元类(metaclass)来改变这一行为:
class MetaA(type): class __prepare__(dict): def __init__(*args): pass def __setitem__(self, key, value): if self.get('a'): # Line 7 super().__setitem__('b', value) # Line 8 else: super().__setitem__(key, value) class A(metaclass=MetaA): def a(self): print(1) def a(self): print(2) A().a() # => 1 A().b() # => 2 # Line 22
在第 7 和第 8 行,我们将重名的 a
方法改名为 b
,并在第 22 行成功地调用它了。
multimethod
的维护者们很好地运用了这一点,对重名的方法进行了处理,以达到一种“特殊的效果”。
回到正题,我们可以做出如下改进:
将 multimethod.multidata
设置为 CustomDate
类的元类;
将所有方法命名为 __init__
Jahr, Monat, Tag (Tupel mit drei ganzen Zahlen)
🎜Datetime</code > Allgemeine Implementierung der Klasse 🎜 🎜🎜🎜🎜<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:py;">from datetime import date, datetime
from typing import Tuple, Union
from multimethod import multimeta
class CustomDate(metaclass=multimeta):
def __init__(self, arg: Union[int, float]):
self.__date = date.fromtimestamp(arg)
def __init__(self, arg: Tuple[int, int, int]):
self.__date = date(*arg)
def __init__(self, arg: str):
self.__date = date.fromisoformat(arg)
def __init__(self, arg: datetime):
self.__date = arg.date()
def __init__(self, arg):
raise TypeError("could not create instance from " + type(arg).__name__)
@property
def date(self):
return self.__date</pre><div class="contentsignin">Nach dem Login kopieren</div></div>🎜 Hinweis: Wir werden nicht diskutieren, ob der eingehende Datums-/Zeitstempel legal ist oder nicht, sondern nur eine grobe Beurteilung des Typs vornehmen. 🎜🎜Gibt es einen besseren Weg? 🎜🎜Wir können verschiedene Erstellungsmethoden in mehrere Methoden aufteilen und den <code>singledispatchmethod
-Dekorator in functools
verwenden, um basierend auf dem Typ der übergebenen Parameter zu entscheiden, welche Methode aufgerufen werden soll. 🎜rrreee🎜Auf diese Weise können wir die Initialisierung jedes Parametertyps in separate Methoden aufteilen. 🎜🎜Nachteile🎜singledispatchmethod
ist Einzelversand. Das heißt, es wird nur der erste Parameter berücksichtigt. Das reicht in der Praxis bei weitem nicht aus. 🎜if
/else
verwenden, um den Typ der Elemente in zu bestimmen ein Tupel. Das heißt, wir können typing.Tuple[int, int, int]
nicht verwenden. 🎜🎜Als Kompromiss können wir vielleicht eine ThreeIntTuple
-Klasse definieren, um sie einzuschränken und diese Urteile von der CustomDate
-Klasse zu isolieren. 🎜🎜Hier ist nur eine Idee als Referenz, ich werde sie nicht umsetzen (weil wir einen besseren Weg haben xD). 🎜🎜Alternative: Multimethod-Bibliothek🎜🎜Diese Bibliothek gehört nicht zu den Standardbibliotheken und muss über pip installiert werden: 🎜rrreee🎜Vorteile🎜🎜multimethod
verwendet einen Multi-Dispatch-Algorithmus, der besser erfüllen kann komplexere Anforderungsszene. Darüber hinaus bietet die Bibliothek eine gute Unterstützung für Typen in typing
. 🎜🎜Bessere Vorgehensweise🎜🎜Zurück zur obigen Frage, wir können sie folgendermaßen verbessern:🎜multimethod
-Methode anstelle von singledispatchmethod
; 🎜🎜Tuple[int, int, int]
, um tuple
zu ersetzen. Die Länge und die Länge von Tupeln müssen nicht mehr manuell überprüft werden Elementtyp von): 🎜rrreee🎜Was wird der obige Code ausgeben? Oder wird es einen Fehler auslösen? 🎜🎜Ausgabe 2
. 🎜🎜Wenn in Python Methoden mit demselben Namen definiert sind, überschreibt die letzte Methode die vorherige Methode. 🎜🎜Aber Sie wissen vielleicht nicht, dass wir dieses Verhalten durch die Metaklasse ändern können: 🎜rrreee🎜In den Zeilen 7 und 8 benennen wir die gleichnamige Methode a
in b
um und ruft es erfolgreich in Zeile 22 auf. 🎜🎜Die Betreuer von multimethod
haben dies sinnvoll genutzt und Methoden mit doppelten Namen verarbeitet, um einen „Spezialeffekt“ zu erzielen. 🎜🎜Zurück zum Thema, wir können folgende Verbesserungen vornehmen: 🎜multimethod.multidata
auf CustomDate
Metaklasse der Klasse; 🎜🎜__init__
. 🎜🎜🎜rrreee🎜In Bezug auf die Wirkung ist dies genau das Gleiche wie das Überladen statischer Sprachmethoden! 🎜Das obige ist der detaillierte Inhalt vonSo definieren Sie die Überladung mehrerer Konstruktormethoden und generische Methoden in Python-Klassen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!