迭代类对象

2 投票
4 回答
1344 浏览
提问于 2025-04-16 19:52

这不是一个真实的程序,但我想知道为什么这样做不行。

我在考虑 numpy.r_ 这个对象,试着做一些类似的事情,但只是创建一个类,并且没有实例化它

一个简单的代码(有一些缺陷)用于整数可以是:

class r_:
    @classmethod
    def __getitem__(clc, sl):
        try:
            return range(sl)
        except TypeError:
            sl = sl.start, sl.stop, sl.step
            return range(*(i for i in sl if i is not None))

但是当我尝试使用 r_[1:10] 时,我收到了 TypeError: 'type' object is not subscriptable 的错误。

当然,代码可以用 r_.__getitem__(slice(1,10)) 来运行,但这不是我想要的。

在这种情况下,我有什么办法可以做到,而不是使用 r_()[1:10] 呢?

4 个回答

1

这种行为的原因在于特殊方法,比如 __getitem__() 是如何被查找的。

当我们查找属性时,首先会在对象的 __dict__ 中找,如果那里没有,再去类的 __dict__ 中找。这就是为什么下面的代码可以正常工作的原因:

>>> class Test1(object):
...     x = 'hello'
...
>>> t = Test1()
>>> t.__dict__
{}
>>> t.x
'hello'

在类体内定义的方法会存储在类的 __dict__ 中:

>>> class Test2(object):
...     def foo(self):
...         print 'hello'
...
>>> t = Test2()
>>> t.foo()
hello
>>> Test2.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method foo() must be called with Test2 instance as first argument (got nothing
instead)

到这里为止没有什么特别的。不过,当涉及到特殊方法时,Python 的行为就有点(或者说非常)不同了:

>>> class Test3(object):
...     def __getitem__(self, key):
...         return 1
...
>>> t = Test3()
>>> t.__getitem__('a key')
1
>>> Test3['a key']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'type' object is unsubscriptable

错误信息也很不同。对于 Test2,Python 报告的是未绑定的方法调用错误,而对于 Test3,它则抱怨无法下标。

如果你尝试通过使用相关的操作符来调用一个特殊方法,在一个对象上,Python 不会在对象的 __dict__ 中查找,而是直接去查找 __dict__ 所在类的,如果这个对象本身是一个类,那么 就是元类。所以你需要在这里定义它:

>>> class Test4(object):
...     class __metaclass__(type):
...         def __getitem__(cls, key):
...             return 1
...
>>> Test4['a key']
1

没有其他办法。引用一下 PEP20 的话:应该有一种——而且最好只有一种——明显的方法来做到这一点。

2

你想做的事情就像是 list[1:5]set[1:5] 这样的操作。特别的 __getitem__ 方法只适用于类的实例。

通常的做法是创建一个类的单一实例,也就是“单例”:

class r_class(object):
    ...

r_ = r_class()

现在你可以这样做:

r_[1:5]

你也可以使用元类,但这可能会有点复杂,不一定需要。

"不,我的问题是关于类里的 getitem,而不是实例里的"

那么你确实需要元类。

class r_meta(type):
    def __getitem__(cls, key):
        return range(key)
class r_(object, metaclass=r_meta):
    pass

示例:

>>> r_[5]
range(0, 5)

如果你传入 r_[1:5],你会得到一个 slice 对象。想了解更多信息,可以输入 help(slice);你可以像这样访问值:key.stop if isinstance(key,slice) else key

4

处理 obj[index] 这种写法时,系统会先去找 obj 的类型里面有没有 __getitem__ 这个方法,而不是直接在 obj 本身上找这个方法。通常情况下,如果 obj 没有这个方法,系统会再去找它的类型里有没有。

这个过程其实很简单,可以很容易地验证。

>>> class Foo(object):
    pass

>>> def __getitem__(self, index):
    return index

>>> f = Foo()
>>> f.__getitem__ = __getitem__
>>> f[3]
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    f[3]
TypeError: 'Foo' object does not support indexing
>>> Foo.__getitem__ = __getitem__
>>> f[3]
3

我不太清楚为什么会这样,但我猜这其中的原因之一就是为了防止你想做的事情。如果每个定义了 __getitem__ 的类都能让它的实例被索引,那就太奇怪了。大多数情况下,试图对一个类进行索引的代码都是错误的,所以如果 __getitem__ 方法恰好能返回一些东西,那就会很糟糕,因为这个错误没有被发现。

为什么不把这个类换个名字,然后把它的一个实例绑定到 r_ 这个名字上呢?这样你就可以用 r_[1:10] 了。

撰写回答