如何创建一个类似List的类,它允许在与py2和py3一起使用的片上调用包含对象的方法

问题描述:

下面的代码来自SCons的代码库。我们正在努力移植代码,以便它可以在Python 2.7.x和3.x上使用。如何创建一个类似List的类,它允许在与py2和py3一起使用的片上调用包含对象的方法

下面的代码工作正常的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) 

__getitem__调用一个slice或许应该再次回到同一类的新实例。例如:

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

谢谢!这涵盖了完整测试套件中的几乎所有测试。一个单元测试仍然失败。如果我可以追踪并简化,我将添加到上面的示例代码。 – bdbaddog

+0

在上面添加更新测试请参阅“另一个”。 Fix实际上是删除上面index.indices(8)中的最大值8。并用index.indices(len(self.data))替换。如果你在上面编辑,我会标记为答案。 – bdbaddog

+0

@bdbaddog太好了!我很高兴它的工作:) – MSeifert