bdbaddog bdbaddog -4 years ago 78
Python Question

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)

Answer Source

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
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download