Proxying or subclassing base classes?

75 views Asked by At

This is perhaps a too general question, still it bothers me. In general, what's a better practice (and why): proxying or subclassing a base class? By base I mean one of the standard classes, which usually not even implemented in Python.

A more concrete question is as follows: I wish to create an object for graph edges; a graph edge is essentially a pair of two vertices. A vertex can be any hashable object. I wish that an edge will expose many more methods than a frozenset exposes. So I wonder whether I should

class Edge(frozenset):
    def my_method(self, *args):
        return 3

or

class Edge(object):
    def __init__(self, *args):
        self._frozenset = frozenset(*args)

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

    def ...

The benefits of the first method is that I have to write less (since I don't have to duplicate all the original methods). The second method looks, however, safer in some sense. It is also more flexible, as it allows me to avoid some methods which frozenset exposes (such as difference), if I wish.

The first method also introduces an issue with the number of arguments passed. I probably have to overwrite frozenset.__new__, if I wish to control that. On the other hand, the first method will probably be faster in general, since the proxying creates some overhead.

I don't know if it matters, but I usually write for Python 2.7.

1

There are 1 answers

4
bgusach On

The question you have to ask is: my class is or my class has?, i.e. the typical question inheritance vs composition. In other words, is your Edge a frozenset or just happens to have one for its internal use?

Think about it as a third-party user of your class. You get an Edge object. You take a look at its API. What do you want to see there? Probably methods that allow you to interact with the Edge logic domain, like, uhmm... .do_what_edges_do(), and not things like .union() or .isdisjoint(),

In this case it is clear to me that you should go for composition. When in doubt, go for composition.

However, you might want to expose some methods of the frozenset directly in your API. Python has great mechanisms to do so, but you have to separate between normal y and magic methods.

Normal methods:

Just declare a list of methods you would like to expose, and then use __getattr__. For instance, let's say you want to expose the methods 'abc' and 'cde' (they don't exist in the frozenset, this is just for learning purposes)

class Edge(object):
  ..

  _exposed_methods = ['abc', 'cde']

  def __getattr__(self, item):
    if item in self._exposed_methods:
      return getattr(self, item)

    raise AttributeError

Magic Methods:

The previous mechanism does not work for magic methods, Python skips it for performance. In this case you have to declare them explictly. For instance, a useful one __len__

class Edge(object):
  ..

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

EDIT AFTER READING COMMENTS

You have to make a semantic decision. I don't know what objects an Edge should contain, so let's say Item objs. Now the question is, what kind of method do you want to offer in the API of your code?

  • something like get_items? this emphazises the concept of Item. In this case it is perfectly right to return a collection like a set populated with Items. Since you say that you want to add more methods to this collection, this is not probably what you want to do.

  • or get_edge? This gives more importance to the concept of Edge. In this case you should return an instance of your own Edge class. If all the methods exposed by frozenset are meaningful to your Edge concept, go for inheritance. Otherwise go for composition and expose only what makes sense (use the mechanism described above).