Python类中怎么定义多个构造器方法重载与泛方法

其他教程   发布日期:2023年10月13日   浏览次数:176

这篇文章主要介绍“Python类中怎么定义多个构造器方法重载与泛方法”,在日常操作中,相信很多人在Python类中怎么定义多个构造器方法重载与泛方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Python类中怎么定义多个构造器方法重载与泛方法”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

    什么是“泛方法”?

    由多个方法组成的方法,这些方法为不同的类型实现相同的操作。

    举个栗子

    现在有个需求,需要你通过以下几种方式创建一个自定义的日期类(

    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

    这样一来,我们便能将每种参数类型的初始化独立成一个个的方法了。

    缺点

    #1 单分派

    在调用期间应该使用哪个方法实现由分派算法决定。如果该算法只基于单个参数的类型来决定使用哪个方法实现,则称其为单分派。

    singledispatchmethod
    就是就是单分派的。也就是说,只有第一个参数会作为考量。这在实际业务中是远远不足的。

    #2 不支持 typing

    然而,如上,对元组中元素类型判断还是需要我们用

    if
    /
    else
    实现。也就是说,我们不能使用
    typing.Tuple[int, int, int]

    作为一种折中的方案,或许我们可以定义一个

    ThreeIntTuple
    类来对其进行限定,将这些判断从
    CustomDate
    类中隔离开来。

    这里仅提供一个思路让大家参考,我就不实现了(因为我们有更好的方式 xD)。

    替代方案:multimethod 库

    这个库不是标准库之一,需要通过 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__
    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

    从效果上来看,这完全和静态语言的方法重载一模一样!

    以上就是Python类中怎么定义多个构造器方法重载与泛方法的详细内容,更多关于Python类中怎么定义多个构造器方法重载与泛方法的资料请关注九品源码其它相关文章!