Re: Class initialization

Guido van Rossum (Guido.van.Rossum@cwi.nl)
Sat, 28 Dec 91 17:46:11 +0100

In response to my thinking-aloud about a built-in new() function to
replace the current class instantiation operation "classname()", Tim
Peters notices that such a new() function is easily defined in Python,
goes on to show how, discovers his solution doesn't quite work, and
wonders why.

Rest assured, this is not a problem with your version of Python: you
have just discovered that the obvious way to create a tuple of one
element (a singleton) doesn't work. That is:

>>> a = (1, 2, 3)
>>> a
(1, 2, 3)
>>> b = (1, 2)
>>> b
(1, 2)
>>>

but

>>> c = (1)
>>> c
1
>>>

If you think about it, the reason is obvious: parentheses are used for
grouping, and you certainly don't want (3+4)*5 to construct a tuple
out of (3+4) -- then the outcome would be (7, 7, 7, 7, 7) instead or 35!

In early Python there were *no* singleton tuples. I borrowed tuples
from ABC, which also has no singleton tuples, but then added tuple
operations that weren't in ABC, such as subscripting and slicing, and
it became obvious that singletons were a necessity (they could be
created by a slice of length one, for instance). The solution is not
pretty, but effective: you must write a trailing comma. With another
small syntactic hack, an empty pair of parentheses can be used to
indicate an empty tuple.

|def new(args):
| if type(args) = type(('a','dummy','tuple')):
| Class = args[0]
| Instance = Class()
| return Class.init(Instance, args[1:])
| # just 1 arg, presumably a class
| return args().init()
|
|This works pretty well as is (except ... see later):

As you noticed, it doesn't work if the class's init function takes
exactly one argument. The reason is that your call
"Class.init(Instance, args[1:])" always passes a tuple. The proper
fix is not to change the init function but to make one argument an
exception as well. Here's my version:

def new(args):
if type(args) <> type(()):
args = (args,) # Tuple-ize
Class = args[0]
args = args[1:]
instance = Class()
if len(args) = 0:
return instance.init()
elif len(args) = 1:
return instance.init(args[0])
else:
return instance.init(args)

And just to encourage you to go out and fetch the new release, here's
the version for 0.9.4 -- this is the first case where I found that the
new bulti-in apply() function is handy:

def new(args):
if type(args) <> type(()):
args = (args,) # Tuple-ize
Class = args[0]
args = args[1:]
instance = Class()
return apply(instance.init, args)

I think if you compare the two versions you can see why the new
parameter handling is better. The initial "if" is still necessary;
maybe there should be extra syntax to indicate that the (remaining)
arguments should be stored as a tuple in the last formal parameter?

|>>> a,(b) = (1,(2,)) # does not unpack
|>>> a
|1
|>>> b
|(2,)
|>>> a,(b,) = (1,(2,)) # does unpack
|>>> a
|1
|>>> b
|2

One could make the point that since parenthesized left-hand sides of
assignments don't make much sense they should always be treated as
tuples. However, this would lead to inconsistencies:

>>> (a, b, c) = (1, 2, 3) # a=1; b=2; c=3
>>> (a, b) = (1, 2) # a=1; b=2
>>> (a) = (1) # a=1

This currently works as expected, but with that change made the last
statement would be the same error as

>>> (a,) = 1

is today.

|>>> def init(x,(y,)):
|Parsing error: file <stdin>, line 1:
|def init(x,(y,)):
| ^
|Unhandled exception: run-time error: syntax error
|>>>

This was an oversight that will be fixed in version 0.9.5.

|C) When a user can program a solution this easily, the case for building
| it into the language is weak.

I was proposing it as a *replacement* of the current notation
"Classname()" (i.e., calling a class as if it were a parameterless
function). I've noticed that it is quite a common mistake to write

newinstance = Class.init(initialization_arguments)

instead of

newinstance = Class().init(initialization_arguments)
# ^^

and the error message that is issued in this case is rather confusing,
since "Class.init" is a valid function, only it has one more argument
than is provided...

I'm not saying that a change is absolutely necessary, but I feel that
the current solution is overloading function call with something
rather different.

An alternative is to encourage a programming style where each class
defines its own new() function that creates an instance and
initializes it. It will still be necessary to separate the init()
function from the new() function, otherwise derived classes wouldn't
have a way to call the base class's init() function.

Anybody else on the list got an opinion? Should I change the language
or not?

--Guido van Rossum, CWI, Amsterdam <guido@cwi.nl>
"That was never five minutes just now!"
"I'm afraid it was."