简体   繁体   中英

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

The code below is from SCons's code base. We're working on porting the code such that it will work with both Python 2.7.x and 3.x.

The code below works fine under python 2.7.x, but when run under python 3.5 fails as follows:

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'

This code is somewhat core to SCons's functionality. Any help would be most welcome. (See the original code here: 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)

Your __getitem__ called with a slice should probably return a new instance of the same class again. For example:

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]

Then your test case prints:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM