如何创建一个像 class 这样的列表,它允许在切片上调用包含的对象方法,这些方法同时适用于 py2 和 py3

How to create a List like class which allows calling contained objects methods on slices which work with both py2 and py3

以下代码来自SCons的代码库。我们正在努力移植代码,使其适用于 Python 2.7.x 和 3.x.

下面的代码在 python 2.7.x 下工作正常,但是当 运行 在 python 3.5 下时失败如下:

python3.5 ~/tmp/blah123.py Traceback (most recent call last):
File "/home/bdbaddog/tmp/blah123.py", line 73, in print("stuff:%s"%nl[0:2].bar) AttributeError: 'list' object has no attribute 'bar'

此代码在某种程度上是 SCons 功能的核心。任何帮助将是最受欢迎的。 (在这里查看原始代码:src/engine/SCons/Util.py

from __future__ import print_function


try:
    from UserList import UserList
except ImportError as e:
    from collections import UserList


class NodeList(UserList):
    """This class is almost exactly like a regular list of Nodes
    (actually it can hold any object), with one important difference.
    If you try to get an attribute from this list, it will return that
    attribute from every item in the list.  For example:

    >>> someList = NodeList([ '  foo  ', '  bar  ' ])
    >>> someList.strip()
    [ 'foo', 'bar' ]
    """
    def __nonzero__(self):
        return len(self.data) != 0

    def __bool__(self):
        return self.__nonzero__()

    def __str__(self):
        return ' '.join(map(str, self.data))

    def __iter__(self):
        return iter(self.data)

    def __call__(self, *args, **kwargs):
        result = [x(*args, **kwargs) for x in self.data]
        return self.__class__(result)

    def __getattr__(self, name):
        result = [getattr(x, name) for x in self.data]
        return self.__class__(result)

#    def __getitem__(self, index):
#        return self.__class__(self.data[index])
#        return self.data[index]

    def __getitem__(self, index):
        """ 
        This comes for free on py2,
        but py3 slices of NodeList are returning a list
        breaking slicing nodelist and refering to 
        properties and methods on contained object
        """
#        return self.__class__(self.data[index])

        if isinstance(index, slice):
            # Expand the slice object using range()
            # to a maximum of eight items.
            return [self[x] for x in
                    range(*index.indices(8))]
        else:
            # Return one item of the tart
            return self.data[index]


class TestClass(object):
    def __init__(self, name, child=None):
        self.child = child
        self.bar = name

t1 = TestClass('t1', TestClass('t1child'))
t2 = TestClass('t2', TestClass('t2child'))
t3 = TestClass('t3')

nl = NodeList([t1, t2, t3])
print("stuff:%s"%nl[0:2].bar)
print("another:%s"%nl[1:].bar)


assert nl.bar == [ 't1', 't2', 't3' ], nl.bar
assert nl[0:2].child.bar == [ 't1child', 't2child' ], \
         nl[0:2].child.bar

for f in nl:
    print("->%s"%f.bar)

你用 slice 调用的 __getitem__ 可能应该再次 return 相同 class 的新实例。例如:

def __getitem__(self, index):
    if isinstance(index, slice):
        return self.__class__(self[x] for x in
                              range(*index.indices(len(self.data)))
    else:
        return self.data[index]

然后你的测试用例打印:

stuff:t1 t2
->t1
->t2
->t3