如何根据输入参数以不同的方式初始化NamedTuple子类?

问题描述 投票:0回答:1

我正在构建一个 typing.NamedTuple 类 (请看这里的 typing.NamedTuple docs或较老的 集合.命名的tuples docs 它继承的),可以接受不同的初始化方式。

为什么在这种情况下要用NamedTuple?我希望它是不可变的、可自动哈希的,这样它就可以成为一个字典键,而且我不需要写哈希函数。

我知道我需要使用 __new__ 而非 __init__ 由于NamedTuples是不可变的(例如。请看这个Q&A. 我已经搜索过了,那里有一些小插曲(例如 这个问题的答案,关于为一个命名的tuple设置一个自定义哈希值),但我不能让所有的东西都正常工作,我得到一个关于不能覆盖 __new__.

这是我现在的代码。

from typing import NamedTuple

class TicTacToe(NamedTuple):
    """A tic-tac-toe board, each character is ' ', 'x', 'o'"""
    row1: str = '   '
    row2: str = '   '
    row3: str = '   '

    def __new__(cls, *args, **kwargs):
        print(f'Enter __new__ with {cls}, {args}, {kwargs}')
        if len(args) == 1 and args[0] == 0:
            new_args = ('   ', '   ', '   ')
        else:
            new_args = args
        self = super().__new__(cls, *new_args, *kwargs)
        return self

if __name__ == '__main__':
    a = TicTacToe(('xo ', 'x x', 'o o'))
    print(a)
    b = TicTacToe(0)
    print(b)

但我得到了以下的错误。

Traceback (most recent call last):
  File "c:/Code/lightcc/OpenPegs/test_namedtuple.py", line 4, in <module>
    class TicTacToe(NamedTuple):
  File "C:\Dev\Python37\lib\typing.py", line 1384, 
in __new__
    raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
AttributeError: Cannot overwrite NamedTuple attribute __new__

我无法创建一个单独的 __new__ 继承自NamedTuple的子类的函数?从消息来看,它似乎在试图覆盖 __new__ 直接用于NamedTuple,而不是TicTacToe类。

这到底是怎么回事?

python inheritance constructor immutability namedtuple
1个回答
1
投票

你可以避免需要定义 __new__() 通过定义 classmethod. 在下面的示例代码中,我简单地把它命名为 make().

这是一种为任何类提供替代构造函数的常见方法。

请注意,我还修改了函数的第一次调用,这样它就能将参数正确地传递给了 make() 方法。

from typing import NamedTuple

class TicTacToe(NamedTuple):
    """A tic-tac-toe board, each character is ' ', 'x', 'o'."""
    row1: str = '   '
    row2: str = '   '
    row3: str = '   '

    @classmethod
    def make(cls, *args, **kwargs):
        print(f'Enter make() with {cls}, {args}, {kwargs}')
        if len(args) == 1 and args[0] == 0:
            new_args = ('   ', '   ', '   ')
        else:
            new_args = args
        self = cls(*new_args, *kwargs)
        return self

if __name__ == '__main__':
#    a = TicTacToe.make(('xo ', 'x x', 'o o'))
    a = TicTacToe.make('xo ', 'x x', 'o o')
    print(a)
    b = TicTacToe.make(0)
    print(b)

输出。

Enter make() with <class '__main__.TicTacToe'>, ('xo ', 'x x', 'o o'), {}
TicTacToe(row1='xo ', row2='x x', row3='o o')
Enter make() with <class '__main__.TicTacToe'>, (0,), {}
TicTacToe(row1='   ', row2='   ', row3='   ')

更新。

一个替代的变通方法,可以解决无法重载 NamedTuple 子类 __new__() 方法是将派生类拆分成两个类,一个是公有类,一个是私有类,这样前者就不再是直接的子类了 NamedTuple.

这样做的一个好处是,不再需要使用类似于 make() 以上。

我的意思是这样的。

from typing import NamedTuple

class _BaseBoard(NamedTuple):
    """Private base class for tic-tac-toe board."""
    row1: str = '   '
    row2: str = '   '
    row3: str = '   '


class TicTacToe(_BaseBoard):
    """A tic-tac-toe board, each character is ' ', 'x', 'o'."""

    __slots__ = ()  # Prevent creation of a __dict__.

    @classmethod
    def __new__(cls, *args, **kwargs):
        print(f'Enter __new__() with {cls}, {args}, {kwargs}')
        if len(args) == 1 and args[0] == 0:
            new_args = ('   ', '   ', '   ')
        else:
            new_args = args
        self = super().__new__(*new_args, *kwargs)
        return self


if __name__ == '__main__':

    a = TicTacToe('xo ', 'x x', 'o o')
    print(a)
    assert getattr(a, '__dict__', None) is None  # Verify not being created.
    b = TicTacToe(0)
    print(b)

请注意,这种方法是应用安德鲁-柯尼希的... 软件工程基本定理意思是: "我们可以通过引入一个额外的间接层次来解决任何问题",对问题。

© www.soinside.com 2019 - 2024. All rights reserved.