迭代类对象
这不是一个真实的程序,但我想知道为什么这样做不行。
我在考虑 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 个回答
这种行为的原因在于特殊方法,比如 __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 的话:应该有一种——而且最好只有一种——明显的方法来做到这一点。
你想做的事情就像是 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
。
处理 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]
了。