Python 如何清晰地编写uu getitem uuu?

Python 如何清晰地编写uu getitem uuu?,python,python-3.x,Python,Python 3.x,在Python中,当实现序列类型时,我经常(相对而言)发现自己在编写这样的代码: class FooSequence(collections.abc.Sequence): # Snip other methods def __getitem__(self, key): if isinstance(key, int): # Get a single item elif isinstance(key, slice):

在Python中,当实现序列类型时,我经常(相对而言)发现自己在编写这样的代码:

class FooSequence(collections.abc.Sequence):
    # Snip other methods

    def __getitem__(self, key):
        if isinstance(key, int):
            # Get a single item
        elif isinstance(key, slice):
            # Get a whole slice
        else:
            raise TypeError('Index must be int, not {}'.format(type(key).__name__))
代码使用
isinstance()
显式检查其参数的类型。这是在Python社区中实现的。我如何避免它

  • 我不能使用
    functools.singledispatch
    ,因为这与方法不兼容(它将尝试在
    self
    上进行调度,这是完全无用的,因为我们已经通过OOP多态性在
    self
    上进行调度)。它与
    @staticmethod
    一起工作,但是如果我需要从
    self
    中获取内容,该怎么办
  • 强制转换到
    int()
    ,然后捕获
    TypeError
    ,检查一个片段,并可能重新提升,这仍然很难看,尽管可能稍微不那么难看
  • 将整数转换为一个元素片段并用相同的代码处理这两种情况可能会更简洁,但这也有其自身的问题(返回
    0
    [0]
    ?)

尽管这看起来很奇怪,但我怀疑你的方式是处理事情的最佳方式。模式的存在通常是为了包含常见的用例,但这并不意味着当遵循这些模式会使生活变得更加困难时,就应该将它们视为福音。PEP 443给出的在显式类型检查时犹豫不决的主要原因是它“脆弱且接近扩展”。但是,这主要适用于在任何时候采用多种不同类型的自定义函数。从:

对于序列类型,接受的键应该是整数和切片对象。请注意,负索引的特殊解释(如果类希望模拟序列类型)取决于u_getitem_u;()方法。如果键的类型不合适,可能会引发TypeError;如果某个值超出序列的索引集(在对负值进行任何特殊解释之后),则应引发IndexError。对于映射类型,如果缺少键(不在容器中),则应引发KeyError

Python文档明确说明了应该接受的两种类型,以及如果提供了不属于这两种类型的项,该怎么办。由于这些类型是由文档本身提供的,所以不太可能发生更改(这样做会破坏比您的实现多得多的实现),因此可能不值得费心编写针对Python本身可能发生更改的代码

如果你打算避免显式的打字检查,我会告诉你。它包含一个简明的
@methdispatch
装饰器实现(不是我的名字,但我会使用它),它允许
@singledispatch
通过强制它检查
args[1]
(arg)而不是
args[0]
(self)来使用方法。使用它应该允许您在
\uuu getitem\uuu
方法中使用自定义的单一分派


你是否认为这些“Python”取决于你,但请记住,虽然Python的禅指出“特殊情况并不特别足以打破规则”,但它立刻注意到“实用性胜过纯粹性”。在这种情况下,仅检查文档明确指出的两种类型是

\uuu getitem\uuuu
应该支持的唯一内容,对我来说似乎是一种切实可行的方法。

我不知道有什么方法可以避免这样做一次。这只是以这种方式使用动态类型语言的折衷。然而,这并不意味着你必须一次又一次地这样做。我将通过创建一个带有拆分方法名的抽象类来解决这个问题,然后从该类继承,而不是直接从
序列继承,如:

class UnannoyingSequence(collections.abc.Sequence):

    def __getitem__(self, key):
        if isinstance(key, int):
            return self.getitem(key)
        elif isinstance(key, slice):
            return self.getslice(key)
        else:
            raise TypeError('Index must be int, not {}'.format(type(key).__name__))

    # default implementation in terms of getitem
    def getslice(self, key):
        # Get a whole slice

class FooSequence(UnannoyingSequence):
    def getitem(self, key):
        # Get a single item

    # optional efficient, type-specific implementation not in terms of getitem
    def getslice(self, key):
        # Get a whole slice

这清理了
FooSequence
,如果我只有一个派生类,我甚至可以这样做。我有点惊讶标准库并没有这样工作。

为了保持pythonic,您必须处理语义而不是对象的类型。所以,如果您有一些参数作为序列的访问器,就这样使用它。尽可能长时间地使用参数的抽象。如果需要一组用户标识符,请不要使用一组,而是使用方法
add
的一些数据结构。如果需要一些文本,不要使用
unicode
对象,而是使用
encode
decode
方法的字符容器

通常,我假设您希望执行类似“使用基本实现的行为,除非提供了某些特殊值。如果您希望实现
\uuuu getitem\uuuu
,则可以使用案例区分,如果提供了一个特殊值,则会发生不同的情况。我将使用以下模式:

class FooSequence(collections.abc.Sequence):
    # Snip other methods

    def __getitem__(self, key):
        try:
            if key == SPECIAL_VALUE:
                return SOMETHING_SPECIAL
            else:
                return self.our_baseclass_instance[key]
        except AttributeError:
            raise TypeError('Wrong type: {}'.format(type(key).__name__))
如果您想区分单个值(在perl术语“标量”中)和序列(在Java术语“集合”中),那么确定是否实现了迭代器就可以从pythonical角度进行判断。您可以使用try-catch模式或
hasattr
,就像我现在所做的那样:

>>> a = 42
>>> b = [1, 3, 5, 7]
>>> c = slice(1, 42)
>>> hasattr(a, "__iter__")
False
>>> hasattr(b, "__iter__")
True
>>> hasattr(c, "__iter__")
False
>>>
适用于我们的示例:

class FooSequence(collections.abc.Sequence):
    # Snip other methods

    def __getitem__(self, key):
        try:
            if hasattr(key, "__iter__"):
                return map(lambda x: WHATEVER(x), key)
            else:
                return self.our_baseclass_instance[key]
        except AttributeError:
            raise TypeError('Wrong type: {}'.format(type(key).__name__))

python和ruby等动态编程语言使用duck类型。duck是一种动物,走路像鸭子,游泳像鸭子,嘎嘎叫像鸭子。这不是因为有人叫它“duck”。

反模式是让普通用户代码进行类型检查,特别是通过使用
type()
函数1

在处理内部事务时,2类型检查是必要的,而
isinstance()
是首选方法

换句话说,您的代码完全是Pythonic的,它唯一的问题是错误消息(它没有提到
slice
s)


披露:我是一名Python核心开发人员


1当绝对需要时,
isinstance()
是更好的选择


2我同意像
\uuu getitem\uuu

这样的特殊方法,但是getitem的文档明确指出“只允许int和slice类型的对象”