Differences creating tuples and collections.namedtuples

J

John Reid

Hi,

I was hoping namedtuples could be used as replacements for tuples in all instances. There seem to be some differences between how tuples and namedtuples are created. For example with a tuple I can do:

a=tuple([1,2,3])

with namedtuples I get a TypeError:

from collections import namedtuple
B=namedtuple('B', 'x y z')
b=B([1,2,3])

TypeError: __new__() takes exactly 4 arguments (2 given)
<ipython-input-23-d1da2ef851fb>(3)<module>()
1 from collections import namedtuple
2 B=namedtuple('B', 'x y z')
----> 3 b=B([1,2,3])

I'm seeing this problem because of the following code in IPython:

def canSequence(obj):
if isinstance(obj, (list, tuple)):
t = type(obj)
return t([can(i) for i in obj])
else:
return obj

where obj is a namedtuple and t([can(i) for i in obj]) fails with the TypeError. See http://article.gmane.org/gmane.comp.python.ipython.user/10270 for more info.

Is this a problem with namedtuples, ipython or just a feature?

Thanks,
John.
 
O

Oscar Benjamin

Hi,

I was hoping namedtuples could be used as replacements for tuples in all instances.

namedtuples are not really intended to serves as tuples anywhere. They
are intended to provide lightweight, immutable, hashable objects with
*named* (rather than numbered) values.
There seem to be some differences between how tuples and namedtuples are created. For example with a tuple I can do:

a=tuple([1,2,3])

with namedtuples I get a TypeError:

from collections import namedtuple
B=namedtuple('B', 'x y z')
b=B([1,2,3])

For a namedtuple you need to unpack the arguments

b = B(*[1, 2, 3])

or

b = B(1, 2, 3)
TypeError: __new__() takes exactly 4 arguments (2 given)
<ipython-input-23-d1da2ef851fb>(3)<module>()
1 from collections import namedtuple
2 B=namedtuple('B', 'x y z')
----> 3 b=B([1,2,3])

I'm seeing this problem because of the following code in IPython:

def canSequence(obj):
if isinstance(obj, (list, tuple)):
t = type(obj)
return t([can(i) for i in obj])
else:
return obj

What is the point of the code above? If obj is a list or a tuple you
create a new list or tuple with the same data and then return it
otherwise you just return the object. What about:

def canSequence(obj):
return obj

Or is it that you want to copy the object (but only when it is a tuple
or list). Then how about

def canSequence(obj):
if isinstance(obj, (list, tuple)):
return obj[:]
else:
return obj

Note that this turns namedtuples into tuples. It might be better to
catch TypeError rather than special casing the types:

def canSequence(obj):
try:
return obj[:]
except TypeError:
return obj

Or perhaps it would be better to use the copy module (assuming that
the purpose is to copy the object).
where obj is a namedtuple and t([can(i) for i in obj]) fails with the TypeError. See http://article.gmane.org/gmane.comp.python.ipython.user/10270 for more info.

Is this a problem with namedtuples, ipython or just a feature?

I think that it is a problem with the canSequence function.


Oscar
 
O

Oscar Benjamin

I'm seeing this problem because of the following code in IPython:

def canSequence(obj):
if isinstance(obj, (list, tuple)):
t = type(obj)
return t([can(i) for i in obj])
else:
return obj

What is the point of the code above? If obj is a list or a tuple you
create a new list or tuple with the same data and then return it
otherwise you just return the object. What about:

Sorry, I didn't read this properly. I see that you want apply can() to
all the elements. What is the reason for wanting to preserve the type
of the sequence?


Oscar
 
D

Dave Angel

Hi,

I was hoping namedtuples could be used as replacements for tuples in all instances. There seem to be some differences between how tuples and namedtuples are created. For example with a tuple I can do:

a=tuple([1,2,3])

with namedtuples I get a TypeError:

from collections import namedtuple
B=namedtuple('B', 'x y z')
b=B([1,2,3])

You are passing a single list to the constructor, but you specified that
the namedtuple was to have 3 items. So you need two more.
TypeError: __new__() takes exactly 4 arguments (2 given)
<ipython-input-23-d1da2ef851fb>(3)<module>()
1 from collections import namedtuple
2 B=namedtuple('B', 'x y z')
----> 3 b=B([1,2,3])

I'm seeing this problem because of the following code in IPython:

def canSequence(obj):
if isinstance(obj, (list, tuple)):
t = type(obj)
return t([can(i) for i in obj])
else:
return obj

where obj is a namedtuple and t([can(i) for i in obj]) fails with the TypeError. See http://article.gmane.org/gmane.comp.python.ipython.user/10270 for more info.

Is this a problem with namedtuples, ipython or just a feature?

Thanks,
John.

If you want one item (list or tuple) to act like 3 separate arguments,
you could use the "*" operator:

b = B( *[1,2,3] )

or in your canSequence function, if you want a namedTuple
return t(*[can(i) for i in obj])
 
J

John Reid

Hi,

I was hoping namedtuples could be used as replacements for tuples in
all instances. There seem to be some differences between how tuples
and namedtuples are created. For example with a tuple I can do:

a=tuple([1,2,3])

with namedtuples I get a TypeError:

from collections import namedtuple
B=namedtuple('B', 'x y z')
b=B([1,2,3])

You are passing a single list to the constructor, but you specified that
the namedtuple was to have 3 items. So you need two more.

I'm aware how to construct the namedtuple and the tuple. My point was
that they use different syntaxes for the same operation and this seems
to break ipython. I was wondering if this is a necessary design feature
or perhaps just an oversight on the part of the namedtuple author or
ipython developers.
 
O

Oscar Benjamin

If they are not supposed to be tuples then calling them namedtuples and
inheriting from tuple seems a little odd.

You can use namedtuple instances in places that expect tuples.
Inheriting from tuples enables them to be all the things I said:
lightweight, immutable and hashable. The type object itself has a
different interface for the constructor, though.


Oscar
 
O

Oscar Benjamin

Hi,

I was hoping namedtuples could be used as replacements for tuples in
all instances. There seem to be some differences between how tuples
and namedtuples are created. For example with a tuple I can do:

a=tuple([1,2,3])

with namedtuples I get a TypeError:

from collections import namedtuple
B=namedtuple('B', 'x y z')
b=B([1,2,3])

You are passing a single list to the constructor, but you specified that
the namedtuple was to have 3 items. So you need two more.

I'm aware how to construct the namedtuple and the tuple. My point was
that they use different syntaxes for the same operation and this seems
to break ipython. I was wondering if this is a necessary design feature
or perhaps just an oversight on the part of the namedtuple author or
ipython developers.

I would say that depending on isinstance(obj, tuple) was the error. I
can't offer a suggestion as you haven't clarified what the purpose of
this code in canSequence() is or what constraints it is expected to
satisfy.


Oscar
 
J

John Reid

On 02/18/2013 06:47 AM, John Reid wrote:
Hi,

I was hoping namedtuples could be used as replacements for tuples in
all instances. There seem to be some differences between how tuples
and namedtuples are created. For example with a tuple I can do:

a=tuple([1,2,3])

with namedtuples I get a TypeError:

from collections import namedtuple
B=namedtuple('B', 'x y z')
b=B([1,2,3])

You are passing a single list to the constructor, but you specified that
the namedtuple was to have 3 items. So you need two more.

I'm aware how to construct the namedtuple and the tuple. My point was
that they use different syntaxes for the same operation and this seems
to break ipython. I was wondering if this is a necessary design feature
or perhaps just an oversight on the part of the namedtuple author or
ipython developers.

I would say that depending on isinstance(obj, tuple) was the error. I
can't offer a suggestion as you haven't clarified what the purpose of
this code in canSequence() is or what constraints it is expected to
satisfy.

Like I said it is not my code. I'm hoping the IPython developers can
help me out here. That said it would be nice to know the rationale for
namedtuple.__new__ to have a different signature to tuple.__new__. I'm
guessing namedtuple._make has a similar interface to tuple.__new__. Does
anyone know what the rationale was for this design?
 
J

John Reid

Because namedtuples use names for the arguments in the constructor:

2
That's a good point. I haven't used __new__ much before but wouldn't
something like this __new__() cater for both uses? (Example taken from
namedtuple docs
http://docs.python.org/2/library/collections.html#collections.namedtuple).
Point = namedtuple('Point', ['x', 'y'], verbose=True)
class Point(tuple):
'Point(x, y)'

__slots__ = ()

_fields = ('x', 'y')

def __new__(_cls, *args, **kwds):
'Create a new instance of Point(x, y)'
if args:
return _tuple.__new__(_cls, args)
else:
return _tuple.__new__(_cls, (kwds[f] for f in _fields))

...


Perhaps I could subclass namedtuple so that my namedtuples have the
correct signature for __new__.
 
T

Terry Reedy

I was hoping namedtuples could be used as replacements for tuples
in all instances.

This is a mistake in the following two senses. First, tuple is a class
with instances while namedtuple is a class factory that produces
classes. (One could think of namedtuple as a metaclass, but it was not
implemented that way.) Second, a tuple instance can have any length and
different instances can have different lengths. On the other hand, all
instances of a particular namedtuple class have a fixed length. This
affects their initialization. So does the fact that Oscar mentioned,
that fields can be initialized by name.
There seem to be some differences between how tuples and namedtuples
are created. For example with a tuple I can do:

a=tuple([1,2,3])

But no sensible person would ever do that, since it creates an
unnecessary list and is equivalent to

a = 1,2,3

The actual api is tuple(iterable). I presume you know that, but it gets
to the question you ask about 'why the difference?'. The only reason to
use an explicit tuple() call is when you already have an iterable,
possibly of unknown length, rather than the individual field objects. In
the latter case, one should use a display.
with namedtuples I get a TypeError:

from collections import namedtuple
B=namedtuple('B', 'x y z')
b=B([1,2,3])

There is no namedtuple B display, so one *must* use an explicit call
with the proper number of args. The simplest possibility is B(val0,
val1, val2). Initializaing a namedtuple from an iterable is unusual, and
hence gets the longer syntax. In other words, the typical use case for a
namedtuple class is to replace statements that have tuple display.

return a, b, c
to
return B(a, b, c)

or
x = a, b, c
to
x = B(a, b, c)

It is much less common to change tuple(iterable) to B(iterable).
def canSequence(obj):
if isinstance(obj, (list, tuple)):
t = type(obj)
return t([can(i) for i in obj])
else:
return obj

The first return could also be written t(map(can, obj)) or, in Python 3,
t(can(i) for i in obj).
where obj is a namedtuple and t([can(i) for i in obj]) fails with the TypeError. See http://article.gmane.org/gmane.comp.python.ipython.user/10270 for more info.

Is this a problem with namedtuples, ipython or just a feature?

With canSequence. If isinstance was available and the above were written
before list and tuple could be subclassed, canSequence was sensible when
written. But as Oscar said, it is now a mistake for canSequence to
assume that all subclasses of list and tuple have the same
initialization api.

In fact, one reason to subclass a class is to change the initialization
api. For instance, before python 3, range was a function that returned a
list. If lists had always been able to be subclasses, it might instead
have been written as a list subclass that attached the start, stop, and
step values, like so:

# python 3
class rangelist(list):
def __init__(self, *args):
r = range(*args)
self.extend(r)
self.start = r.start
self.stop = r.stop
self.step = r.step

r10 = rangelist(10)
print(r10, r10.start, r10.stop, r10.step)[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 10 1

However, define can() and canSequence(r10) will raise a TypeError, just
as with a namedtuple B instance.

TypeError: 'list' object cannot be interpreted as an integer

So, while your question about the namedtuple api is a legitimate one,
your problem with canSequence is not really about namedtuples, but about
canSequence making a bad assumption.
 
S

Steven D'Aprano

Terry said:
This is a mistake in the following two senses. First, tuple is a class
with instances while namedtuple is a class factory that produces
classes. (One could think of namedtuple as a metaclass, but it was not
implemented that way.)


I think you have misunderstood. I don't believe that John wants to use the
namedtuple factory instead of tuple. He wants to use a namedtuple type
instead of tuple.

That is, given:

Point3D = namedtuple('Point3D', 'x y z')

he wants to use a Point3D instead of a tuple. Since:

issubclass(Point3D, tuple)

holds true, the Liskov Substitution Principle (LSP) tells us that anything
that is true for a tuple should also be true for a Point3D. That is, given
that instance x might be either a builtin tuple or a Point3D, all of the
following hold:

- isinstance(x, tuple) returns True
- len(x) returns the length of x
- hash(x) returns the hash of x
- x returns item i of x, or raises IndexError
- del x raises TypeError
- x + a_tuple returns a new tuple
- x.count(y) returns the number of items equal to y

etc. Basically, any code expecting a tuple should continue to work if you
pass it a Point3D instead (or any other namedtuple).

There is one conspicuous exception to this: the constructor:

type(x)(args)

behaves differently depending on whether x is a builtin tuple, or a Point3D.

The LSP is about *interfaces* and the contracts we make about those
interfaces, rather than directly about inheritance. Inheritance is just a
mechanism for allowing types to automatically get the same interface as
another type. Another way to put this, LSP is about duck-typing. In this
case, if we have two instances:

x = (1, 2, 3)
y = Point3D(4, 5, 6)


then x and y:

- quack like tuples
- swim like tuples
- fly like tuples
- walk like tuples
- eat the same things as tuples
- taste very nice cooked with orange sauce like tuples

etc., but y does not lay eggs like x. The x constructor requires a single
argument, the y constructor requires multiple arguments.

You can read more about LSP here:

http://en.wikipedia.org/wiki/Liskov_substitution_principle

although I don't think this is the most readable Wikipedia article, and the
discussion of mutability is a red-herring. Or you can try this:

http://c2.com/cgi/wiki?LiskovSubstitutionPrinciple

although even by c2 wiki standards, it's a bit of a mess. These might help
more:

http://blog.thecodewhisperer.com/2013/01/08/liskov-substitution-principle-demystified/

http://lassala.net/2010/11/04/a-good-example-of-liskov-substitution-principle/

Second, a tuple instance can have any length and
different instances can have different lengths. On the other hand, all
instances of a particular namedtuple class have a fixed length.

This is a subtle point. If your contract is, "I must be able to construct an
instance with a variable number of items", then namedtuples are not
substitutable for builtin tuples. But I think this is an *acceptable*
violation of LSP, since we're deliberately restricting a namedtuple to a
fixed length. But within the constraints of that fixed length, we should be
able to substitute a namedtuple for any tuple of that same length.

This
affects their initialization. So does the fact that Oscar mentioned,
that fields can be initialized by name.

Constructing namedtuples by name is not a violation, since it *adds*
behaviour, it doesn't take it away. If you expect a tuple, you cannot
construct it with:

t = tuple(spam=a, ham=b, eggs=c)

since that doesn't work. You have to construct it from an iterable, or more
likely a literal:

t = (a, b, c)

Literals are special, since they are a property of the *interpreter*, not
the tuple type. To put it another way, the interpreter understands (a,b,c)
as syntax for constructing a tuple, the tuple type does not. So we cannot
expect to use (a,b,c) syntax to construct a MyTuple instance, or a Point3D
instance instead.

If we hope to substitute a subclass, we have to use the tuple constructor
directly:

type_to_use = tuple
t = type_to_use([a, b, c])

Duck-typing, and the LSP, tells us that we should be able to substitute a
Point3D for this:

type_to_use = namedtuple('Point3D', 'x y z')
t = type_to_use([a, b, c])

but we can't. And that is an important violation of LSP.

There could be three fixes to this, none of them practical:

1) tuple could accept multiple arguments, tuple(a, b, c) => (a, b, c) but
that conflicts with the use tuple(iterable). If Python had * argument
unpacking way back in early days, it might have been better to give tuples
the signature tuple(*args), but it didn't and so it doesn't and we can't
change that now.

2) namedtuples could accept a single iterable argument like tuple does, but
that conflicts with the desired signature pt = Point3D(1, 2, 3).

3) namedtuples should not claim to be tuples, which is probably the
least-worst fix. Backwards-compatibility rules out making this change, but
even if it didn't, namedtuples quack like tuples, swim like tuples, and
walk like tuples, so even if they aren't a subclass of tuple it would still
be reasonable to want them to lay eggs like tuples.

So I don't believe there is any good solution to this, except the ad-hoc one
of overriding the __new__ constructor when needed.

There seem to be some differences between how tuples and namedtuples
are created. For example with a tuple I can do:

a=tuple([1,2,3])

But no sensible person would ever do that, since it creates an
unnecessary list and is equivalent to

a = 1,2,3

Well, no, not as given. But it should be read as just an illustration. In
practise, code like this is not uncommon:

a = tuple(some_iterable)


[...]
It is much less common to change tuple(iterable) to B(iterable).

Less common or not, duck-typing and the LSP tells us we should be able to do
so. We cannot.

With canSequence. If isinstance was available and the above were written
before list and tuple could be subclassed, canSequence was sensible when
written. But as Oscar said, it is now a mistake for canSequence to
assume that all subclasses of list and tuple have the same
initialization api.

No, it is not a mistake. It is a problem with namedtuples that they violate
the expectation that they should have the same constructor signature as
other tuples. After all, namedtuples *are* tuples, they should be
constructed the same way. But they aren't, so that violates a reasonable
expectation.

Is the convenience of being able to write Point3D(1, 2, 3) more important
than LSP-purity? Perhaps. I suspect that will be the answer Raymond
Hettinger might give. I'm 85% inclined to agree with this answer.

In fact, one reason to subclass a class is to change the initialization
api.

That might be a reason that people give, but it's a bad reason from the
perspective of interface contracts, duck-typing and the LSP.

Of course, these are not the *only* perspectives. There is no rule that
states that one must always obey the interface contracts of one's parent
class. But if you don't, you will be considered an "ill-behaved" subclass
for violating the promises made by your type.
 
R

Roy Smith

Terry Reedy said:
Initializaing a namedtuple from an iterable is unusual, and
hence gets the longer syntax. I

I quick look through our codebase agrees with that. I found 27
namedtuple classes. 21 were initialized with MyTuple(x, y, z) syntax.
Three used MyTuple(*data).

Most interesting were the three that used MyTuple(**data). In all three
cases, data was a dictionary returned by re.match.groupdict(). The
definition of the namedtuple was even built by introspecting the regex
to find all the named groups!
 
O

Oscar Benjamin

Terry said:
On 2/18/2013 6:47 AM, John Reid wrote: [snip]
Is this a problem with namedtuples, ipython or just a feature?

With canSequence. If isinstance was available and the above were written
before list and tuple could be subclassed, canSequence was sensible when
written. But as Oscar said, it is now a mistake for canSequence to
assume that all subclasses of list and tuple have the same
initialization api.

No, it is not a mistake. It is a problem with namedtuples that they violate
the expectation that they should have the same constructor signature as
other tuples. After all, namedtuples *are* tuples, they should be
constructed the same way. But they aren't, so that violates a reasonable
expectation.

It is a mistake. A namedtuple class instance provides all of the
methods/operators provided by a tuple. This should be sufficient to
fill the tuplishness contract. Requiring that obj satisfy a contract
is one thing. When you get to the point of requiring that type(obj)
must do so as well you have gone beyond duck-typing and the normal
bounds of poly-morphism.

It's still unclear what the purpose of canSequence is, but I doubt
that there isn't a better way that it (and its related functions)
could be implemented that would not have this kind of problem.


Oscar
 
A

alex23


One quick workaround would be to use a tuple where required and then
coerce it back to Result when needed as such:

def sleep(secs):
import os, time, parallel_helper
start = time.time()
time.sleep(secs)
return tuple(parallel_helper.Result(os.getpid(), time.time() -
start))

rc = parallel.Client()
v = rc.load_balanced_view()
async_result = v.map_async(sleep, range(3, 0, -1), ordered=False)
for ar in async_result:
print parallel_helper.Result(*ar)

You can of course skip the creation of Result in sleep and only turn
it into one in the display loop, but it all depends on additional
requirements (and adds some clarity to what is happening, I think).
 
S

Steven D'Aprano

Oscar said:
Terry said:
On 2/18/2013 6:47 AM, John Reid wrote: [snip]
Is this a problem with namedtuples, ipython or just a feature?

With canSequence. If isinstance was available and the above were written
before list and tuple could be subclassed, canSequence was sensible when
written. But as Oscar said, it is now a mistake for canSequence to
assume that all subclasses of list and tuple have the same
initialization api.

No, it is not a mistake. It is a problem with namedtuples that they
violate the expectation that they should have the same constructor
signature as other tuples. After all, namedtuples *are* tuples, they
should be constructed the same way. But they aren't, so that violates a
reasonable expectation.

It is a mistake. A namedtuple class instance provides all of the
methods/operators provided by a tuple. This should be sufficient to
fill the tuplishness contract.

"Should be", but *doesn't*.

If your code expects a tuple, then it should work with a tuple. Namedtuples
are tuples, but they don't work where builtin tuples work, because their
__new__ method has a different signature.

I can understand arguing that this is "acceptable breakage" for various
reasons -- practicality beats purity. I can't understand arguing that the
principle is wrong.

Requiring that obj satisfy a contract
is one thing. When you get to the point of requiring that type(obj)
must do so as well you have gone beyond duck-typing and the normal
bounds of poly-morphism.

Constructor contracts are no less important than other contracts. I'm going
to give what I hope is an example that is *so obvious* that nobody will
disagree.

Consider the dict constructor dict.fromkeys:

py> mydict = {'a':1}
py> mydict.fromkeys(['ham', 'spam', 'eggs'])
{'eggs': None, 'ham': None, 'spam': None}


Now I subclass dict:

py> class MyDict(dict):
.... @classmethod
.... def fromkeys(cls, func):
.... # Expects a callback function that gets called with no arguments
.... # and returns two items, a list of keys and a default value.
.... return super(MyDict, cls).fromkeys(*func())
....

Why would I change the syntax like that? Because reasons. Good or bad,
what's done is done and there is my subclass. Here is an instance:

py> mydict = MyDict({'a': 1})
py> isinstance(mydict, dict)
True


Great! So I pass mydict to a function that expects a dict. This ought to
work, because mydict *is* a dict. It duck-types as a dict, isinstance
agrees it is a dict. What could possibly go wrong?

What goes wrong is that some day I pass it to a function that calls
mydict.fromkeys in the usual fashion, and it blows up.

py> mydict.fromkeys(['spam', 'ham', 'eggs'])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in fromkeys
TypeError: 'list' object is not callable

How is this possible? Is mydict not a dict? It should be usable anywhere I
can use a dict. How is this possibly acceptable behaviour for something
which claims to be a dict?

This is a violation of the Liskov Substitution Principle, and a violation of
normal expectations that if mydict quacks like a dict, it should lay eggs
like a duck.

That namedtuple's constructor is __new__ rather than fromkeys is an
irrelevant distraction. The principle still applies. It is perfectly
reasonable to expect that if instance t is a tuple, then *any* method on t
should have the same signature, regardless of whether that method is
called "index", "__getitem__", or "__new__".

If this fundamental principle is violated, there should be a very good
reason, and not just because "constructor contracts aren't important".

It's still unclear what the purpose of canSequence is, but I doubt
that there isn't a better way that it (and its related functions)
could be implemented that would not have this kind of problem.

Incorrect. The problem is with *namedtuples*, not canSequence, because
namedtuples promise to implement a strict superset of the behaviour of
builtin tuples, while in fact they actually *take behaviour away*. Tuples
promise to allow calls to the constructor like this:

any_tuple.__new__(type(any_typle), iterable))

but that fails if any_tuple is a namedtuple.

I am not arguing for or against the idea that this is an *acceptable*
breakage, give other requirements. But from the point of view of interface
contracts, it is a breakage, and as the Original Poster discovered, it can
and will break code.
 
R

raymond.hettinger

I'm aware how to construct the namedtuple and the tuple. My point was
that they use different syntaxes for the same operation and this seems
to break ipython. I was wondering if this is a necessary design feature
or perhaps just an oversight on the part of the namedtuple author or
ipython developers.

It was not an oversight on the part of the namedtuple author.
It was a deliberate design decision to improve usability for
named tuple's primary use case.

Consider a namedtuple such as:

Person = namedtuple('Person', ['name', 'rank', 'serial_number'])

With the current signature, instances can be created like this:

p = Person('Guido', 'BDFL', 1)

If instead, the __new__ signature matched that of regular tuples,
you would need to write:

p = Person(('Guido', 'BDFL', 1))

The double parens are awkward. You would lose tooltips that prompt you forPerson(name, rank, serial_number). You would lose the ability to use keyword arguments such as Person(rank='BDFL', serial_number=1, name='Guido') or Person(**d). The repr for the namedtuple would lose its eval(repr(t)) roundtrip property.

If your starting point is an existing iterable such as s=['Guido', 'BDFL', 1], you have a couple of choices: p=Person(*s) or p=Person._make(s).. The latter form was put it to help avoid unpacking and repacking the arguments.


Raymond
 
R

raymond.hettinger

I'm aware how to construct the namedtuple and the tuple. My point was
that they use different syntaxes for the same operation and this seems
to break ipython. I was wondering if this is a necessary design feature
or perhaps just an oversight on the part of the namedtuple author or
ipython developers.

It was not an oversight on the part of the namedtuple author.
It was a deliberate design decision to improve usability for
named tuple's primary use case.

Consider a namedtuple such as:

Person = namedtuple('Person', ['name', 'rank', 'serial_number'])

With the current signature, instances can be created like this:

p = Person('Guido', 'BDFL', 1)

If instead, the __new__ signature matched that of regular tuples,
you would need to write:

p = Person(('Guido', 'BDFL', 1))

The double parens are awkward. You would lose tooltips that prompt you forPerson(name, rank, serial_number). You would lose the ability to use keyword arguments such as Person(rank='BDFL', serial_number=1, name='Guido') or Person(**d). The repr for the namedtuple would lose its eval(repr(t)) roundtrip property.

If your starting point is an existing iterable such as s=['Guido', 'BDFL', 1], you have a couple of choices: p=Person(*s) or p=Person._make(s).. The latter form was put it to help avoid unpacking and repacking the arguments.


Raymond
 
G

Gregory Ewing

Steven said:
Terry Reedy wrote:
That might be a reason that people give, but it's a bad reason from the
perspective of interface contracts, duck-typing and the LSP.

Only if you're going to pass the class off to something as
a factory function.

Note that having a different constructor signature is *not*
an LSP violation for *instances* of a class. The constructor
is not part of the interface for instances, only for the
class itself.

In practice, it's very common for a class to have a different
constructor signature from its base class, and this rarely
causes any problem.

IPython is simply making a dodgy assumption. It gets away with
it only because it's very rare to encounter subclasses of
list or tuple at all.
 
S

Steven D'Aprano

On Mon, 18 Feb 2013 23:48:46 -0800, raymond.hettinger wrote:

[...]
If your starting point is an existing iterable such as s=['Guido',
'BDFL', 1], you have a couple of choices: p=Person(*s) or
p=Person._make(s). The latter form was put it to help avoid unpacking
and repacking the arguments.


It might not be obvious to the casual reader, but despite the leading
underscore, _make is part of the public API for namedtuple:

http://docs.python.org/2/library/collections.html#collections.namedtuple
 
S

Steven D'Aprano

On Mon, 18 Feb 2013 23:48:46 -0800, raymond.hettinger wrote:

[...]
If your starting point is an existing iterable such as s=['Guido',
'BDFL', 1], you have a couple of choices: p=Person(*s) or
p=Person._make(s). The latter form was put it to help avoid unpacking
and repacking the arguments.


It might not be obvious to the casual reader, but despite the leading
underscore, _make is part of the public API for namedtuple:

http://docs.python.org/2/library/collections.html#collections.namedtuple
 

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,770
Messages
2,569,583
Members
45,074
Latest member
StanleyFra

Latest Threads

Top