9

Possible Duplicate:
Subclassing Python tuple with multiple __init__ arguments

I want to define a class which inherits from tuple, and I want to be able to instantiate it using a syntax not supported by tuple. For a simple example, let's say I want to define a class MyTuple which inherits from tuple, and which I can instantiate by passing two values, x and y, to create the (my) tuple (x, y). I've tried the following code:

class MyTuple(tuple):
    def __init__(self, x, y):
        print("debug message")
        super().__init__((x, y))

But when I tried, for example, MyTuple(2, 3) I got an error: TypeError: tuple() takes at most 1 argument (2 given). It seems my __init__ function was not even called (based on the error I got and on the fact my "debug message" was not printed).

So what's the right way to do this?

I'm using Python 3.2.

Community
  • 1
  • 1
Tom
  • 4,910
  • 5
  • 33
  • 48
  • 3
    You might want to take a peek at [``collections.namedtuple()``](http://docs.python.org/dev/library/collections.html#collections.namedtuple). – Gareth Latty Sep 29 '12 at 12:27
  • 2
    Please check [this original SO question](http://stackoverflow.com/questions/1565374/subclassing-python-tuple-with-multiple-init-arguments) for a thorough answer on (1) why you need to use `__new__` instead of `__init__` (2) what other steps you need to follow. – Pierre GM Sep 29 '12 at 12:45

2 Answers2

13
class MyTuple(tuple):
    def __new__(cls, x, y):
        return tuple.__new__(cls, (x, y))

x = MyTuple(2,3)
print(x)
# (2, 3)

One of the difficulties of using super is that you do not control which classes's method of the same name is going to be called next. So all the classes' methods have to share the same call signature -- at least the same number of items. Since you are changing the number of arguments sent to __new__, you can not use super.


Or as Lattyware suggests, you could define a namedtuple,

import collections
MyTuple = collections.namedtuple('MyTuple', 'x y')

p = MyTuple(2,3)
print(p)
# MyTuple(x=2, y=3)
print(p.x)
# 2
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
1

another approach would be to encapsulate a tuple rather than inheriting from it:

>>> class MyTuple(object):
    count = lambda self, *args: self._tuple.count(*args)
    index = lambda self, *args: self._tuple.index(*args)
    __repr__ = lambda self: self._tuple.__repr__()
    # wrap other methods you need, or define them yourself,
    # or simply forward all unknown method lookups to _tuple
    def __init__(self, x, y):
        self._tuple = x,y


>>> x = MyTuple(2,3)
>>> x
(2, 3)
>>> x.index(3)
1

How practical this is, depends on how many capabilities and modifications you need, and wheter you need to have isinstance(MyTuple(2, 3), tuple).

ch3ka
  • 11,792
  • 4
  • 31
  • 28
  • Interesting. Took me some time to understand what looks like field assignments and the lambdas. Any particular reason why you didn't use the normal `def` construction? Also, how do you "forward all unknown method lookups to `_tuple`"? – Tom Sep 29 '12 at 16:07
  • no particular reason for the lambdas, I just like to "bind" functions like that. `count = lambda:...` says to me "count IS ASSIGNED TO BE the count of _tuple". – ch3ka Sep 29 '12 at 18:20
  • 2
    You can forward unknown method lookups with `def __getattr__(self, attr): return self._tuple.__getattribute__(attr)` – ch3ka Sep 29 '12 at 18:21
  • @ch3ka your forward unknown method lookups works well with the old-style class not the new-style class. For new-style class, magic methods, such as `__iter__`, `__contains__`, must be defined or `AttributeError` will be raised. New-style class makes these magic methods as class's attributes, search only in that class and not call `__getattr__()` – dragon2fly Oct 12 '16 at 16:53