Iterating class object

2019-05-19 17:03发布

问题:

It's not a real world program but I would like to know why it can't be done.

I was thinking about numpy.r_ object and tried to do something similar but just making a class and not instantiating it.

a simple code (has some flaws) for integers could be:

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))

but as I try to do r_[1:10] i receive TypeError: 'type' object is not subscriptable.

Of course the code works with r_.__getitem__(slice(1,10)) but that's not what I want.

Is there something I can do in this case instead of using r_()[1:10]?

回答1:

The protocol for resolving obj[index] is to look for a __getitem__ method in the type of obj, not to directly look up a method on obj (which would normally fall back to looking up a method on the type if obj didn't have an instance attribute with the name __getitem__).

This can be easily verified.

>>> 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

I don't know why exactly it works this way, but I would guess that at least part of the reason is exactly to prevent what you're trying to do; it would be surprising if every class that defined __getitem__ so that its instances were indexable accidentally gained the ability to be indexed itself. In the overwhelming majority of cases, code that tries to index a class will be a bug, so if the __getitem__ method happened to be able to return something, it would be bad if that didn't get caught.

Why don't you just call the class something else, and bind an instance of it to the name r_? Then you'd be able to do r_[1:10].



回答2:

What you are trying to do is like list[1:5] or set[1:5] =) The special __getitem__ method only works on instances.

What one would normally do is just create a single ("singleton") instance of the class:

class r_class(object):
    ...

r_ = r_class()

Now you can do:

r_[1:5]

You can also use metaclasses, but that may be more than is necessary.

"No, my question was about getitem in the class, not in the instance"

Then you do need metaclasses.

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

Demo:

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

If you pass in r_[1:5] you will get a slice object. Do help(slice) for more info; you can access values like key.stop if isinstance(key,slice) else key.



回答3:

Define __getitem__() as a normal method in r_'s metaclass.



回答4:

The reason for this behavior lies in the way how special methods like __getitem__() are lookup up.

Attributes are looked up first in the objects __dict__, and, if not found there, in the class __dict__. That's why e.g. this works:

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

Methods that are defined in the class body are stored in the class __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)

So far there's nothing surprising here. When it comes to special methods however, Python's behavior is a little (or very) different:

>>> 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

The error messages are very different. With Test2, Python complains about an unbound method call, whereas with Test3 it complains about the unsubscriptability.

If you try to invoke a special method - by way of using it's associated operator - on an object, Python doesn't try to find it in the objects __dict__ but goes straight to the __dict__ of the object's class, which, if the object is itself a class, is a metaclass. So that's where you have to define it:

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

There's no other way. To quote PEP20: There should be one-- and preferably only one --obvious way to do it.



标签: python range