Sublassing tuple works, subclassing list does not

F

Frank Millman

Hi all

I needed something similar to, but not quite the same as,
collections.namedtuple.

The differences are that namedtuple requires the 'names' to be provided at
creation time, and then lends itself to creating multiple instances of
itself. I wanted a more generic class where I could supply the 'names' and
'values' at instantiation time.

I came up with a simple solution that seems to work -
.... def __new__(cls, names, values):
.... for name, value in zip(names, values):
.... setattr(cls, name, value)
.... return tuple.__new__(cls, values)
....
names = ['A', 'B', 'C']
values = ['a', 'b', 'c']

tup = MyTuple(names, values)

print tup ('a', 'b', 'c')

print tup[0] a

print tup.B b

Then I had a need to add elements after the tuple had been created. As
tuples are immutable, I thought it would be easy to change it to a list.
However, it does not work -
.... def __new__(cls, names, values):
.... for name, value in zip(names, values):
.... setattr(cls, name, value)
.... return list.__new__(cls, values)
....
names = ['A', 'B', 'C']
values = ['a', 'b', 'c']

lst = MyList(names, values)
Traceback (most recent call last):

I can find a workaround, but I would be interested to know the reason why it
does not work.

Version is 2.6.2.

Thanks

Frank Millman
 
L

lbolla

Hi all

I needed something similar to, but not quite the same as,
collections.namedtuple.

The differences are that namedtuple requires the 'names' to be provided at
creation time, and then lends itself to creating multiple instances of
itself. I wanted a more generic class where I could supply the 'names' and
'values' at instantiation time.

I came up with a simple solution that seems to work -

...   def __new__(cls, names, values):
...     for name, value in zip(names, values):
...       setattr(cls, name, value)
...     return tuple.__new__(cls, values)
...
names = ['A', 'B', 'C']
values = ['a', 'b', 'c']
tup = MyTuple(names, values)
print tup
('a', 'b', 'c')
b

Then I had a need to add elements after the tuple had been created. As
tuples are immutable, I thought it would be easy to change it to a list.
However, it does not work -

...   def __new__(cls, names, values):
...     for name, value in zip(names, values):
...       setattr(cls, name, value)
...     return list.__new__(cls, values)
...>>> names = ['A', 'B', 'C']
values = ['a', 'b', 'c']
lst = MyList(names, values)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: list() takes at most 1 argument (2 given)



I can find a workaround, but I would be interested to know the reason why it
does not work.

Version is 2.6.2.

Thanks

Frank Millman

When subclassing immutable types, you need to override __new__;
otherwise you need to override __init__.
Here is an in-depth explanation: http://www.python.org/download/releases/2.2/descrintro/#metaclasses

Here is some code:

class MyTuple(tuple):
def __new__(cls, names, values):
for name, value in zip(names, values):
setattr(cls, name, value)
return tuple.__new__(cls, values)

class MyList(list):
def __init__(self, names, values):
list.__init__(self, values)
for name, value in zip(names, values):
setattr(self, name, value)

names = ['A', 'B', 'C']
values = ['a', 'b', 'c']

tup = MyTuple(names, values)
print tup
print tup[0]
print tup.B

lst = MyList(names, values)
print lst
print lst[0]
print lst.B


L.
 
B

Bruno Desthuilliers

lbolla a écrit :
class MyList(list):
def __init__(self, names, values):
list.__init__(self, values)
for name, value in zip(names, values):
setattr(self, name, value)

names = ['A', 'B', 'C']
values = ['a', 'b', 'c']

lst = MyList(names, values)
print lst
print lst[0]
print lst.B
>>> lst[0] = "foobar"
>>> lst.A 'a'>>> lst.B = 42
>>> lst[1] 'b'
>>> lst.D="duh"
>>> lst[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
 
C

Carl Banks

Frank Millman wrote in (e-mail address removed) in comp.lang.python:
I came up with a simple solution that seems to work -
class MyTuple(tuple):
...   def __new__(cls, names, values):
...     for name, value in zip(names, values):
...       setattr(cls, name, value)
...     return tuple.__new__(cls, values)
...
names = ['A', 'B', 'C']
values = ['a', 'b', 'c']
tup = MyTuple(names, values)

Are you aware you are adding attributes to the class here, IOW:

        MyTuple.C == 'c'


If you want to add attibutes to the instance:

class MyTuple(tuple):
  def __new__(cls, names, values):
    r =  tuple.__new__(cls, values)
    for name, value in zip(names, values):
      setattr(r, name, value)
    return r

names = ['A', 'B', 'C']
values = ['a', 'b', 'c']

tup = MyTuple(names, values)

assert tup[0] == 'a'
assert tup.B == 'b'

try:
  MyTuple.C
except AttributeError:
  pass
else:
  assert False

Careful, this adds the new attributes as to the object's __dict__, not
to the tuple item slots. Which works ok if you don't care if that
someone can mutate the attributes, and if they do the attributes no
longer match the items. Personally I do mind.

tup = MyTuple(names,values)
assert tup.A == 'a'
assert tup[0] == 'a'
tup.A = 'd' # doesn't fail but should
assert tup.A == 'd'
assert tup[0] == 'd' # fails but, granted you allow mutabilty,
shouldn't



The best way to do what the OP wanted (originally) is this, no
subclassing necessary:

def my_named_tuple(names,values):
return namedtuple('ad_hoc_named_tuple',names)(*values)


As for the OP's second problem, to append named items, I'd first
consider whether I'm thinking about the problem correctly, and if so,
go with a subclass of list and overriding __getattr__. Probably one
of the rare cases I would override __getattr__ other than a proxy
class.


Carl Banks
 
H

Hrvoje Niksic

Frank Millman said:
... def __new__(cls, names, values):
... for name, value in zip(names, values):
... setattr(cls, name, value)
... return list.__new__(cls, values)

Did you really mean to setattr the class here? If I'm guessing
your intentions correctly, it should be:

def __new__(cls, names, values):
self = list.__new__(cls, values)
for name, value in zip(names, values):
setattr(self, name, value)
return self
Traceback (most recent call last):


I can find a workaround, but I would be interested to know the reason
why it does not work.

Because you didn't define __init__, so MyList inherited the one from
list, and it has a different signature to what you're calling it with.
Simply add an __init__ with the proper signature, and the proper upcall
to list.__init__:

def __init__(self, names, values):
list.__init__(self, values)

But if you're doing that, you don't need __new__ at all. Simply
override __init__ and place your setattr loop there:
.... def __init__(self, names, values):
.... for name, value in zip(names, values):
.... setattr(self, name, value)
.... list.__init__(self, values)
....
1
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,012
Latest member
RoxanneDzm

Latest Threads

Top