AdrianO
AdrianO

Reputation: 175

How to implement a list of references in python?

I'm trying to model a collection of objects in python (2). The collection should make a certain attribute (an integer, float or any immutable object) of the objects available via a list interface.

(1)

>>> print (collection.attrs)
[1, 5, 3]
>>> collection.attrs = [4, 2, 3]
>>> print (object0.attr == 4)
True

I especially expect this list interface in the collection to allow for reassigning a single object's attribute, e.g.

(2)

>>> collection.attrs[2] = 8
>>> print (object2.attr == 8)
True

I am sure this is a quite frequently occurring situation, unfortunately I was not able to find a satisfying answer on how to implement it on stackoverflow / google etc.

Behind the scenes, I expect the object.attr to be implemented as a mutable object. Somehow I also expect the collection to hold a "list of references" to the object.attr and not the respectively referenced (immutable) values themselves.

I ask for your suggestion how to solve this in an elegant and flexible way.

A possible implementation that allows for (1) but not for (2) is

class Component(object):
    """One of many components."""
    def __init__(self, attr):
        self.attr = attr

class System(object):
    """One System object contains and manages many Component instances.
    System is the main interface to adjusting the components.
    """
    def __init__(self, attr_list):
        self._components = []
        for attr in attr_list:
            new = Component(attr)
            self._components.append(new)

    @property
    def attrs(self):
        # !!! this breaks (2):
        return [component.attr for component in self._components] 
    @attrs.setter
    def attrs(self, new_attrs):
        for component, new_attr in zip(self._components, new_attrs):
            component.attr = new_attr

The !!! line breaks (2) because we create a new list whose entries are references to the values of all Component.attr and not references to the attributes themselves.

Thanks for your input.

TheXMA

Upvotes: 1

Views: 538

Answers (2)

AdrianO
AdrianO

Reputation: 175

@filmor thanks a lot for your answer, this solves the problem perfectly! I made it a bit more general:

class _ListProxy(object):
    """Is a list of object attributes. Accessing _ListProxy entries
    evaluates the object attributes each time it is accessed,
    i.e. this list "proxies" the object attributes.
    """
    def __init__(self, list_of_objects, attr_name):
        """Provide a list of object instances and a name of a commonly
        shared attribute that should be proxied by this _ListProxy
        instance.
        """
        self._list_of_objects = list_of_objects
        self._attr_name = attr_name

    def __getitem__(self, index):
        return getattr(self._list_of_objects[index], self._attr_name)

    def __setitem__(self, index, value):
        setattr(self._list_of_objects[index], self._attr_name, value)

    def __repr__(self):
        return repr(list(self))

    def __len__(self):
        return len(self._list_of_objects)

Are there any important list methods missing?

And what if I want some of the components (objects) to be garbage collected? Do I need to use something like a WeakList to prevent memory leakage?

Upvotes: 0

filmor
filmor

Reputation: 32182

Just add another proxy inbetween:

class _ListProxy:
    def __init__(self, system):
        self._system = system

    def __getitem__(self, index):
        return self._system._components[index].attr

    def __setitem__(self, index, value):
        self._system._components[index].attr = value


class System:
    ...
    @property
    def attrs(self):
        return _ListProxy(self)

You can make the proxy fancier by implementing all the other list methods, but this is enough for your use-case.

Upvotes: 2

Related Questions