tags:

views:

368

answers:

3

Hi fellow Pythonistas,

I have a question concerning subtypes of built-in types and their constructors. I want a class to inherit both from tuple and from a custom class.

Let me give you the concrete example. I work a lot with graphs, meaning nodes connected with edges. I am starting to do some work on my own graph framework.

There is a class Edge, which has its own attributes and methods. It should also inherit from a class GraphElement. (A GraphElement is every object that has no meaning outside the context of a specific graph.) But at the most basic level, an edge is just a tuple containing two nodes. It would be nice syntactic sugar if you could do the following:

edge = graph.create_edge("Spam","Eggs")
(u, v) = edge

So (u,v) would contain "Spam" and "Eggs". It would also support iteration like

for node in edge: ...

I hope you see why I would want to subtype tuple (or other basic types like set).

So here is my Edge class and its init:

class Edge(GraphElement, tuple):

def __init__(self, graph, (source, target)):
 GraphElement.__init__(self, graph)
 tuple.__init__((source, target))

When i call

Edge(aGraph, (source, target))

I get a TypeError: tuple() takes at most 1 argument (2 given). What am I doing wrong?

+5  A: 

Since tuples are immutable, you need to override the __new__ method as well. See http://www.python.org/download/releases/2.2.3/descrintro/#__new__

class GraphElement:
    def __init__(self, graph):
        pass

class Edge(GraphElement, tuple):
    def __new__(cls, graph, (source, target)):
        return tuple.__new__(cls, (source, target))
    def __init__(self, graph, (source, target)):
        GraphElement.__init__(self, graph)
        tuple.__init__((source, target))
Dave
+2  A: 

You need to override __new__ -- currently tuple.__new__ is getting called (as you don't override it) with all the arguments you're passing to Edge.

Alex Martelli
+4  A: 

For what you need, I would avoid multiple inheritance and would implement an iterator using generator:

class GraphElement:
    def __init__(self, graph):
        pass

class Edge(GraphElement):
    def __init__(self, graph, (source, target)):
        GraphElement.__init__(self, graph)
        self.source = source
        self.target = target

    def __iter__(self):
        yield self.source
        yield self.target

In this case both usages work just fine:

e = Edge(None, ("Spam","Eggs"))
(s, t) = e
print s, t
for p in e:
    print p
van
I see. That would indeed give me all the syntactic niceties I want. This seems to be the better solution, thanks.
D-Bug