如何为Python的新NamedTuple类型定义构造函数?

Kam*_*yar 0 python oop

您可能知道,这是在python中定义命名元组的最新类型:

from typing import NamedTuple


class MyType(NamedTuple):
    id: int = 0
    name: str = 0
Run Code Online (Sandbox Code Playgroud)

定义类型后,Python解释器定义一个获取ID和名称的默认构造函数,您可以使用字段实例化新对象。现在,我想使用一个字符串初始化一个新对象,并在函数中对其进行解析。如何定义另一个构造函数而不破坏良好的默认构造函数?

aba*_*ert 5

如何定义另一个构造函数而不破坏良好的默认构造函数?

你不能 Python类不能有多个__new__方法(或者,如果您要表示“初始化器”,则__init__方法)只能是一个。


但是有一种简单的方法可以解决此问题:备用构造函数惯用语:您编写了@classmethod一种提供构造实例的替代方法的。标准库中有很多示例,例如datetime.nowdatetime.utcfromtimestamp。基本内建类型中甚至还有一些示例,例如int.from_bytes

运作方式如下:

class MyType(NamedTuple):
    id: int = 0
    name: str = 0

    @classmethod
    def from_string(cls, string_to_parse):
        id, name = … your parsing code here …
        return cls(id, name)
Run Code Online (Sandbox Code Playgroud)

当然,这与collections.namedtuple子类@dataclass,或普通类有太多不同的构造方法一样。


如果确实要这样做,另一种方法是为丑陋的构造函数提供仅关键字的参数,或者根据传递的内容而具有不同含义的参数。使用NamedTuple,您将不得不以这种方式插入一个额外的类,或者在创建后对该类进行monkeypatch,因为否则,没有记录的方法可以获取默认的构造函数实现。

所以:

class _MyType(NamedTuple):
    id: int = 0
    name: str = 0

class MyType(_MyType):
    def __new__(cls, id: int=None, name: str=None, *, parseything: str=None):
        if parseything:
            if id is not None or str is not None:
                raise TypeError("don't provide both")
            id, name = … your parsing code here …
        return super().__new__(cls, id, name)
Run Code Online (Sandbox Code Playgroud)

……或者,如果您更喜欢猴子补丁:

class MyType(NamedTuple):
    id: int = 0
    name: str = 0

_new = MyType.__new__
def __new__(cls, id=None, name=None, *, parseything=None):
    if parseything:
        if id is not None or str is not None:
            raise TypeError("don't provide both")
        id, name = … your parsing code here …
    return _new(cls, id, name)
MyType.__new__ = __new__
del _new
del __new__
Run Code Online (Sandbox Code Playgroud)

…,或者,如果您想更多地使用range-style丑陋的API,可以使用以下两种方法之一:

def __new__(cls, id_or_parsey_thing: Union[int,str]=None, 
            name: str=None):
    if isinstance(id_or_parsey_thing, str):
        if name is not None:
            raise TypeError("don't provide both")
        id, name = … your parsing code here …
    else:
        id = id_or_parsey_thing
    # super().__new__ or _new here
Run Code Online (Sandbox Code Playgroud)