Maison > Article > développement back-end > Comment définir la surcharge de plusieurs méthodes de constructeur et les méthodes génériques dans les classes Python
Une méthode composée de plusieurs méthodes qui implémentent la même opération pour différents types.
Il existe désormais une exigence qui vous oblige à créer une classe de date personnalisée (CustomDate
) de la manière suivante : 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__
Année, mois, jour (tuple contenant trois entiers)
🎜Datetime
Implémentation générale de la classe 🎜 🎜🎜🎜🎜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🎜 Remarque : Nous ne discuterons pas de la question de savoir si l'horodatage entrant est légal ou non, mais nous porterons seulement un jugement approximatif sur le type. 🎜🎜Y a-t-il une meilleure façon ? 🎜🎜Nous pouvons diviser différentes méthodes de construction en plusieurs méthodes et utiliser le décorateur
singledispatchmethod
dans functools
pour décider quelle méthode appeler en fonction du type de paramètres transmis. 🎜rrreee🎜De cette façon, nous pouvons séparer l'initialisation de chaque type de paramètre en méthodes distinctes. 🎜🎜Inconvénients🎜singledispatchmethod
est une expédition unique. Autrement dit, seul le premier paramètre sera pris en compte. C’est loin d’être suffisant dans la réalité des affaires. 🎜if
/else
pour déterminer le type d'éléments dans un tuple. Autrement dit, nous ne pouvons pas utiliser typing.Tuple[int, int, int]
. 🎜🎜À titre de compromis, nous pouvons peut-être définir une classe ThreeIntTuple
pour la limiter et isoler ces jugements de la classe CustomDate
. 🎜🎜Voici juste une idée pour votre référence, je ne la mettrai pas en œuvre (car nous avons une meilleure façon xD). 🎜🎜Alternative : bibliothèque multiméthode🎜🎜Cette bibliothèque ne fait pas partie des bibliothèques standards et doit être installée via pip : 🎜rrreee🎜Avantages🎜🎜multiméthode
utilise un algorithme multi-dispatch, qui peut mieux répondre scène d’exigences plus complexes. De plus, la bibliothèque prend bien en charge les types en typing
. 🎜🎜Meilleure pratique🎜🎜Retour à la question ci-dessus, nous pouvons l'améliorer comme ceci :🎜multimethod
singledispatchmethod
; 🎜🎜Tuple[int, int, int]
pour remplacer tuple
, vous n'avez plus besoin de vérifier manuellement les tuples. La longueur et type d'élément de ) : 🎜rrreee🎜Que produira le code ci-dessus ? Ou est-ce que cela générera une erreur ? 🎜🎜Sortie 2
. 🎜🎜En Python, si des méthodes portant le même nom sont définies, la dernière méthode écrasera la méthode précédente. 🎜🎜Mais vous ne savez peut-être pas que nous pouvons changer ce comportement via la métaclasse : 🎜rrreee🎜Dans les lignes 7 et 8, nous renommeons la méthode a
du même nom en b
et l'appelle avec succès sur la ligne 22. 🎜🎜Les responsables de multimethod
en ont fait bon usage et ont traité des méthodes avec des noms en double pour obtenir un "effet spécial". 🎜🎜Revenant au sujet, nous pouvons apporter les améliorations suivantes : 🎜multimethod.multidata
sur CustomDate
Métaclasse de la classe ; 🎜🎜__init__
. 🎜🎜🎜rrreee🎜En termes d'effet, c'est exactement la même chose que la surcharge de méthodes de langage statique ! 🎜Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!