다른 유형에 대해 동일한 작업을 구현하는 여러 메서드로 구성된 메서드입니다.
이제 다음과 같은 방법으로 사용자 정의 날짜 클래스(CustomDate
)를 생성해야 하는 요구 사항이 있습니다. 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__
년, 월, 일(3개의 정수를 포함하는 튜플)
🎜Datetime</code > 클래스의 일반 구현 🎜🎜 🎜🎜🎜<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">로그인 후 복사</div></div>🎜 참고: 들어오는 날짜/시간 스탬프가 합법적인지 여부에 대해서는 논의하지 않으며 유형에 대해서만 대략적인 판단을 내립니다. 🎜🎜더 좋은 방법이 있나요? 🎜🎜다양한 빌드 방법을 여러 메서드로 분할하고 <code>functools
의 singledispatchmethod
데코레이터를 사용하여 전달된 매개변수 유형에 따라 호출할 메서드를 결정할 수 있습니다. 🎜rrreee🎜이런 방식으로 각 매개변수 유형의 초기화를 별도의 메소드로 분리할 수 있습니다. 🎜🎜단점🎜singledispatchmethod
는 단일 디스패치입니다. 즉, 첫 번째 매개변수만 고려됩니다. 이는 실제 비즈니스에서는 충분하지 않습니다. 🎜if
/else
를 사용해야 합니다. 튜플. 즉, typing.Tuple[int, int, int]
를 사용할 수 없습니다. 🎜🎜절충안으로 ThreeIntTuple
클래스를 정의하여 이를 제한하고 이러한 판단을 CustomDate
클래스에서 분리할 수 있습니다. 🎜🎜여기에는 참고용 아이디어일 뿐이며 구현하지는 않겠습니다(더 나은 방법이 있기 때문입니다). 🎜🎜대안: 멀티메소드 라이브러리🎜🎜이 라이브러리는 표준 라이브러리 중 하나가 아니며 pip를 통해 설치해야 합니다. 🎜rrreee🎜Advantages🎜🎜멀티메서드
는 다중 디스패치 알고리즘을 사용하므로 더 나은 요구 사항을 충족할 수 있습니다. 더 복잡한 요구 사항 장면. 또한 라이브러리는 입력
의 유형을 잘 지원합니다. 🎜🎜더 나은 연습🎜🎜위의 질문으로 돌아가서 다음과 같이 개선할 수 있습니다.🎜다중 메서드
메서드를 사용하세요. >singledispatchmethod; 🎜🎜Tuple
을 대체하려면 Tuple[int, int, int]
를 사용하세요. 더 이상 튜플을 수동으로 확인할 필요가 없습니다. )의 요소 유형: 🎜rrreee🎜위 코드는 무엇을 출력합니까? 아니면 오류가 발생할까요? 🎜🎜2
를 출력하세요. 🎜🎜Python에서는 동일한 이름의 메소드가 정의되면 마지막 메소드가 이전 메소드를 덮어씁니다. 🎜🎜하지만 메타클래스를 통해 이 동작을 변경할 수 있다는 사실을 모르실 수도 있습니다. 🎜rrreee🎜 7행과 8행에서는 동일한 이름의 a
메서드 이름을 b
로 바꿉니다. 22번 라인에서 성공적으로 호출합니다. 🎜🎜다중 메서드
의 관리자는 이를 잘 활용하여 "특수 효과"를 달성하기 위해 중복된 이름을 가진 메서드를 처리했습니다. 🎜🎜주제로 돌아가서 다음과 같은 개선 사항을 적용할 수 있습니다. 🎜multimethod.multidata
를 CustomDate
로 설정 > 클래스의 메타클래스입니다. 🎜🎜__init__
로 지정합니다. 🎜🎜🎜rrreee🎜효과적으로 보면 이는 정적 언어 메서드 오버로딩과 정확히 같습니다! 🎜위 내용은 Python 클래스에서 여러 생성자 메서드 오버로드 및 일반 메서드를 정의하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!