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.
The question you have to ask is:
my class is
ormy class has
?, i.e. the typical question inheritance vs composition. In other words, is yourEdge
afrozenset
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 theEdge
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 thefrozenset
, this is just for learning purposes)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__
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 ofItem
. In this case it is perfectly right to return a collection like a set populated withItem
s. 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 ofEdge
. In this case you should return an instance of your ownEdge
class. If all the methods exposed byfrozenset
are meaningful to yourEdge
concept, go for inheritance. Otherwise go for composition and expose only what makes sense (use the mechanism described above).