[Python-Dev] Default constructor values (Re: [Python-checkins] python/dist/src/Doc/lib libfuncs.tex,1.134,1.135)

Alex Martelli aleaxit@yahoo.com
Fri, 13 Jun 2003 10:14:08 +0200


On Friday 13 June 2003 09:40 am, M.-A. Lemburg wrote:
   ...
> >>If you really happen to have a need for this, why can't you
> >>introduce factory functions which take care of your particular
> >>use case ? I don't think it's common enough to risk accidental
> >>progamming errors in other user's code.
> >
> > Marc, you haven't shown why it's so bad either, so please shut up.
>
> I beg your pardon: Just look at the last sentence in my reply. It is
> you that hasn't shown a single qualified use case for this "feature".
> You also haven't shown why the defaults you have chosen were picked
> and what the reasoning was.

It appears to me that both the pluses and minuses for this design choice
are so small as not to justify the intensity of the debate (but I may be
missing some subtext).  As long as the behavior of such built-in types
as str, int, long, bool, float &c is consistent -- either all of them can be
called without arguments (each returning a "canonical 'zero' instance" of
the respective type), or none of them can -- I'd be (abstractly) happy.  As
it happens some of them had this minor feature at least as far back as
2.2.2 (earlier I think) so it seems obvious to me that it shouldn't be taken
away in 2.3 (what a gratuitous incompatibility that would be!), and that the
debate is purely theoretical.

As for use cases -- I have not had one in real life in Python (so far), and
indeed if any of the built-in types didn't respect the rule I believe no use
case could present itself.  I did have uses for a very similar feature of C++
types (in C++ templates) and I did note Andrew Koenig speaking up on
this thread about the same thing.  The parallels are not exact, I think.  One
example is making a "canonical type-matching representation" of e.g. a
tuple:
    canonical_tuple = tuple( [ type(x)() for x in original_tuple ] )
to be used e.g. as a dictionary key for multiple dispatch based on types.
In Python, you have some alternatives to this approach, e.g. relying on
the fact that types are first-class objects:
    types_tuple = tuple( [ type(x) for x in original_tuple ] )

There may still be advantages in using the "tuple of typical instances"
rather than the tuple of types -- minor advantages, to be sure, but then
the "error risk" that seems to be the only disadvantage to offset it is
also quite minor in itself.  For example, consider:

>>> original_tuple = (1, 2.0, 3L, '4', u'5', False)
>>> canonical_tuple = tuple([type(x)() for x in original_tuple])
>>> types_tuple = tuple([type(x) for x in original_tuple])
>>> pick_can = pickle.dumps(canonical_tuple, 2)
>>> pick_typ = pickle.dumps(types_tuple, 2)
>>> len(pick_can)
32
>>> len(pick_typ)
129
>>>

Pickling the canonical-tuple rather than the types-tuple saves about 3/4
the space.  Roughly similar ratios can be seen if what one wants is a
readable string representation of the canonical or types tuple:

>>> len(str(canonical_tuple))
28
>>> len(str(types_tuple))
92
>>>


So, I think some use cases do exist for the general-ish rule "whenever
it makes some sense, a type is callable without arguments and when so
called returns the one instance of that type which evaluates to false".

That 'false instance' then becomes the "default, or rather, canonical"
instance of the type, and can be used to 'stand for the type' in cases such
as the above tuples.  Given any instance of the unknown type the canonical
instance can be obtained as type(x)().

Other uses involve e.g. simplifying a generator (or other callable) that takes 
a factory function and may call it without arguments: in some cases this
will let you pass the type object directly, e.g. int, rather than making up a
simple factory such as lambda: 0.  Tiny and marginal advantages, to be
sure.  But the claimed disadvantage also looks tiny and marginal to me.


Alex