py3k feature proposal: field auto-assignment in constructors

52 views
Skip to first unread message

coldpizza

unread,
Jan 27, 2008, 12:06:09 PM1/27/08
to
There is a pattern that occurs fairly often in constructors in Python
and other OOP languages.

Let's take an example:

class Server(object):
def __init__(self, host, port, protocol, bufsize, timeout):
self.host = host
self.port = port
self.protocol = protocol
self.bufsize = bufsize
self.maxthreads = maxthreads
self.timeout = timeout

Imho, in the class above the assignment to instance fields does not
contain much programming logic and therefore can be safely 'abstracted
away' by the language itself with a syntax which would look something
like this:

class Server(object):
def __init__(self, @host, @port, @protocol, @bufsize, @timeout):
pass

This would be equivalent to the first example above, yet it does not
obfuscate the code in any way. Or does it? It does look much cleaner
to me.

Of course, the ampersand is just an arbitrary choice and might have
bad connotations for those who read it as 'take address of' but @ has
some allusion to delegates which maybe is ok.

I am not an experienced programmer and I am not sure if this is
necessarily a good idea, so I wanted to get some feedback from more
experienced Pythonistas before submitting it elsewhere.


André

unread,
Jan 27, 2008, 12:46:29 PM1/27/08
to

If you search on this list, you will find that there has been *many*
proposals to remove self (which, I realize is slightly different than
what yo propose) and that the main argument can be summarized as
"Explicit is better than implicit."

Personally, I like the idea you suggest, with the modification that I
would use "." instead of "@", as in

class Server(object):
def __init__(self, .host, .port, .protocol, .bufsize, .timeout):
pass

André

Wildemar Wildenburger

unread,
Jan 27, 2008, 1:16:25 PM1/27/08
to
André wrote:
> Personally, I like the idea you suggest, with the modification that I
> would use "." instead of "@", as in
>
> class Server(object):
> def __init__(self, .host, .port, .protocol, .bufsize, .timeout):
> pass
>
I like :)

However, you can probably cook up a decorator for this (not certain, I'm
not a decorator Guru), which is not that much worse.

Still, I'd support that syntax (and the general idea.).

/W

Diez B. Roggisch

unread,
Jan 27, 2008, 1:32:08 PM1/27/08
to
Wildemar Wildenburger schrieb:

Just for the fun of it, I implemented a decorator:

from functools import *
from inspect import *

def autoassign(_init_):
@wraps(_init_)
def _autoassign(self, *args, **kwargs):
argnames, _, _, _ = getargspec(_init_)
for name, value in zip(argnames[1:], args):
setattr(self, name, value)
_init_(self, *args, **kwargs)

return _autoassign

class Test(object):
@autoassign
def __init__(self, foo, bar):
pass

t = Test(10, 20)

print t.bar


Diez

Torsten Bronger

unread,
Jan 27, 2008, 1:41:07 PM1/27/08
to
Hallöchen!

Wildemar Wildenburger writes:

Well, you save one or two lines per class. Not enough in my
opinion.

Tschö,
Torsten.

--
Torsten Bronger, aquisgrana, europa vetus
Jabber ID: bro...@jabber.org
(See http://ime.webhop.org for further contact info.)

Wildemar Wildenburger

unread,
Jan 27, 2008, 1:48:15 PM1/27/08
to
Diez B. Roggisch wrote:
> Just for the fun of it, I implemented a decorator:
>
> from functools import *
> from inspect import *
>
> def autoassign(_init_):
> @wraps(_init_)
> def _autoassign(self, *args, **kwargs):
> argnames, _, _, _ = getargspec(_init_)
> for name, value in zip(argnames[1:], args):
> setattr(self, name, value)
> _init_(self, *args, **kwargs)
>
> return _autoassign
>

This is neat. :) Could that maybe be extended to only assign selected
args to the instance and let others pass unchanged. So that, for instance:

@autoassign("foo", "bar")
def __init__(self, foo, bar, baz):
super(baz)

?W

André

unread,
Jan 27, 2008, 2:00:12 PM1/27/08
to
On Jan 27, 2:48 pm, Wildemar Wildenburger

If one goes back to the original idea instead, the decision of using
automatic assignment should depend on the signature of the __init__
function. Here's an implementation (using "_" instead of "." as it
would lead to a syntax error):

from functools import *
from inspect import *

def autoassign(_init_):
@wraps(_init_)
def _autoassign(self, *args, **kwargs):
argnames, _, _, _ = getargspec(_init_)
for name, value in zip(argnames[1:], args):

if name.startswith("_"):
setattr(self, name[1:], value)
_init_(self, *args, **kwargs)

return _autoassign

class Test(object):
@autoassign
def __init__(self, _foo, _bar, baz):
print 'baz =', baz

t = Test(1, 2, 3)
print t.foo
print t.bar
print t.baz

#== the output is

baz = 3
1
2
Traceback (most recent call last):
File "/Users/andre/CrunchySVN/branches/andre/src/tools_2k.py", line
24, in exec_code
exec code in local_dict
File "User's code", line 23, in <module>
AttributeError: 'Test' object has no attribute 'baz'

#======
André

Dustan

unread,
Jan 27, 2008, 6:26:19 PM1/27/08
to
On Jan 27, 12:41 pm, Torsten Bronger <bron...@physik.rwth-aachen.de>
wrote:

> Hallöchen!
>
>
>
> Wildemar Wildenburger writes:
> > André wrote:
>
> >> Personally, I like the idea you suggest, with the modification
> >> that I would use "." instead of "@", as in
>
> >> class Server(object):
> >> def __init__(self, .host, .port, .protocol, .bufsize, .timeout):
> >> pass
>
> > I like :)
>
> > However, you can probably cook up a decorator for this (not
> > certain, I'm not a decorator Guru), which is not that much worse.
>
> > Still, I'd support that syntax (and the general idea.).
>
> Well, you save one or two lines per class. Not enough in my
> opinion.

Are you referring to the alternate syntax or to the decorator? Either
way, you could be saving 4 or 5 or more lines, if you have enough
arguments.

Wildemar Wildenburger

unread,
Jan 27, 2008, 6:39:02 PM1/27/08
to
Dustan wrote:
>> Well, you save one or two lines per class. Not enough in my
>> opinion.
>
> Are you referring to the alternate syntax or to the decorator? Either
> way, you could be saving 4 or 5 or more lines, if you have enough
> arguments.

OK, but then again, every decent IDE should give you the tools to write
an automation for that. Not that I don't like the idea of
auto-assignment, but, you know ...

/W

Terry Reedy

unread,
Jan 27, 2008, 7:13:27 PM1/27/08
to pytho...@python.org

"André" <andre....@gmail.com> wrote in message
news:7dcc86da-6ed7-48ec...@v17g2000hsa.googlegroups.com...

return _autoassign

#== the output is

=================================

I think this version, with this name convention, is nice enough to possibly
go in the stdlib if there were an appropriate place for it. Not sure where
though. If there were a classtools module....

tjr

Ben Finney

unread,
Jan 27, 2008, 7:49:30 PM1/27/08
to
"André" <andre....@gmail.com> writes:

> Personally, I like the idea you suggest, with the modification that I
> would use "." instead of "@", as in
>
> class Server(object):
> def __init__(self, .host, .port, .protocol, .bufsize, .timeout):
> pass

-1.

That leading dot is too easy to miss when looking over the code.

--
\ "Intellectual property is to the 21st century what the slave |
`\ trade was to the 16th." —David Mertz |
_o__) |
Ben Finney

Steven D'Aprano

unread,
Jan 27, 2008, 8:01:31 PM1/27/08
to

You know, not everybody uses a "decent IDE", by choice or necessity, and
even if they did, having what is essentially a macro to type for you
doesn't solve the essential problem that you're writing the same thing
THREE TIMES instead of once. And then you have to keep it all in sync
through who knows how many code revisions and refactorings.

class Parrot(object): # after many revisions...
def __init__(self, genus, species, variety, name, age, colours,
wingspan, beaksize, healthstate, language, vocabulary):
self.wingspan = wingspan
self.beaksize = beaksize
self.name = name
self.age = age
self.binomial_name = (genus, species)
self.breed = variety
self.colour = colour
self.language = language
self.state = get_state(healthstate)
self.words = vocabulary
self.colors = colours


What IDE will spot the error(s) in the above?


Here's another version, assuming syntax support for auto-assignment for
names starting with an ampersand:

class Parrot(object): # after many revisions...
def __init__(self, genus, species, variety, &name, &age, &colours,
&wingspan, &beaksize, healthstate, &language, vocabulary):
self.binomial_name = (genus, species)
self.breed = variety
self.state = get_state(healthstate)
self.words = vocabulary

See how much easier it is to keep the attributes synced with the
arguments? Don't Repeat Yourself in action.


I think the biggest minus on this proposal is that it creates new syntax
that is only meaningful in the __init__ method of a class. "Special cases
aren't special enough to break the rules." I'd vote for it, but
conditionally. What should this do?

def foo(x, y, &z):
pass

Create foo.z perhaps?

--
Steven

Wildemar Wildenburger

unread,
Jan 27, 2008, 8:13:07 PM1/27/08
to
Ben Finney wrote:
> "André" <andre....@gmail.com> writes:
>
>> Personally, I like the idea you suggest, with the modification that I
>> would use "." instead of "@", as in
>>
>> class Server(object):
>> def __init__(self, .host, .port, .protocol, .bufsize, .timeout):
>> pass
>
> -1.
>
> That leading dot is too easy to miss when looking over the code.
>

class Server(object):
def __init__(self, self.host, self.port,
self.protocol, self.bufsize, self.timeout):
pass

?

/W

Paul McGuire

unread,
Jan 27, 2008, 8:17:04 PM1/27/08
to
On Jan 27, 6:13 pm, "Terry Reedy" <tjre...@udel.edu> wrote:
>
> I think this version, with this name convention, is nice enough to possibly
> go in the stdlib if there were an appropriate place for it.  Not sure where
> though.  If there were a classtools module....
>
> tjr

+1

I thought at one time there was to be a "decorators" module in the
stdlib for just this kind of useful item. At minimum, you could post
this to the Python wiki at http://wiki.python.org/moin/PythonDecoratorLibrary.

-- Paul

MRAB

unread,
Jan 27, 2008, 8:28:33 PM1/27/08
to
On Jan 28, 1:01 am, Steven D'Aprano <st...@REMOVE-THIS-
Well, if:

def __init__(self, &foo):
pass

does:

def __init__(self, foo):
self.foo = foo

then:

def foo(x, y, &z):
pass

does:

def foo(x, y, &z):
x.z = z

Don't think that's useful, though...

Christian Heimes

unread,
Jan 27, 2008, 8:37:08 PM1/27/08
to pytho...@python.org
Paul McGuire wrote:
> I thought at one time there was to be a "decorators" module in the
> stdlib for just this kind of useful item. At minimum, you could post
> this to the Python wiki at http://wiki.python.org/moin/PythonDecoratorLibrary.

Please take the idea to the Python developer list. Several decorators
are either already implemented (e.g. the first decorator is
functools.wraps) and others are too special but some of the decorators
including auto assignment seem useful.

Christian

Steven D'Aprano

unread,
Jan 27, 2008, 8:47:18 PM1/27/08
to
On Sun, 27 Jan 2008 19:13:27 -0500, Terry Reedy wrote:

[snip]

> class Test(object):
> @autoassign
> def __init__(self, _foo, _bar, baz):
> print 'baz =', baz

[snip]

> I think this version, with this name convention, is nice enough to
> possibly go in the stdlib if there were an appropriate place for it.
> Not sure where though. If there were a classtools module....

-1/2

I don't like the name convention. _name already has a perfectly good
convention: it's a private name, don't mess with it. That includes in
function/method signatures. With your convention, _foo is public.

I suppose you could write __foo for a private name, and ___foo for a
*really* private name, relying on the decorator to strip one of the
underscores. But counting all those underscores is a PITA, and what
happens if you don't actually want that private name set as an instance
attribute?

As nice as this feature would be, and I vote +2 on the functionality, I
wonder whether the amount of line noise in method definitions now will be
approaching Perlish levels? We've got default values, type annotations
(in Python3), *args and **kwargs, _ private names, and now we want to add
auto-assignment.

If we do get syntax support, I vote +1 on &foo, +1/2 on @foo, -1 on .foo
and -1 on self.foo. (It's explicit, but it's long...).


--
Steven

Arnaud Delobelle

unread,
Jan 27, 2008, 8:51:28 PM1/27/08
to
On Jan 27, 6:32 pm, "Diez B. Roggisch" <de...@nospam.web.de> wrote:
> Wildemar Wildenburger schrieb:
>
> > André wrote:
> >> Personally, I like the idea you suggest, with the modification that I
> >> would use "." instead of "@", as in
>
> >> class Server(object):
> >>     def __init__(self, .host, .port, .protocol, .bufsize, .timeout):
> >>         pass
>
> > I like :)
>
> > However, you can probably cook up a decorator for this (not certain, I'm
> > not a decorator Guru), which is not that much worse.
>
> > Still, I'd support that syntax (and the general idea.).
>
> Just for the fun of it, I implemented a decorator:
>
> from functools import *
> from inspect import *
>
> def autoassign(_init_):
>      @wraps(_init_)
>      def _autoassign(self, *args, **kwargs):
>          argnames, _, _, _ = getargspec(_init_)
>          for name, value in zip(argnames[1:], args):
>              setattr(self, name, value)
>          _init_(self, *args, **kwargs)
>
>      return _autoassign

Nice! I've got a slight variation without magic argument names:

def autoassign(*names):
def decorator(f):
def decorated(self, *args, **kwargs):
for name in names:
setattr(self, name, kwargs.pop(name))
return f(self, *args, **kwargs)
return decorated
return decorator

class Test(object):


@autoassign('foo', 'bar')

def __init__(self, baz):


print 'baz =', baz

t = Test(foo=1, bar=2, baz=6)
# or Test(6, foo=1, bar=2)

print t.foo
print t.bar
print t.baz

--
Arnaud

Arnaud Delobelle

unread,
Jan 27, 2008, 8:56:31 PM1/27/08
to
On Jan 28, 1:47 am, Steven D'Aprano <st...@REMOVE-THIS-
cybersource.com.au> wrote:
[...]

> As nice as this feature would be, and I vote +2 on the functionality, I
> wonder whether the amount of line noise in method definitions now will be
> approaching Perlish levels? We've got default values, type annotations
> (in Python3), *args and **kwargs, _ private names, and now we want to add
> auto-assignment.

Don't forget keyword only arguments! I find signatures too
complicated already, please let's not clutter them even more.

--
Arnaud

André

unread,
Jan 27, 2008, 9:02:51 PM1/27/08
to
On Jan 27, 9:47 pm, Steven D'Aprano <st...@REMOVE-THIS-


Here's a version that
1. does not require new syntax
2. does not *necessarily* override the "_" prefix convention
3. follows the "Explicit is better than implicit" convention when
being called.

(Note: I do not necessarily recommend the "self_" choice)
========
from functools import wraps
from inspect import getargspec

def autoassign(prefix):
def _autoassign(_init_):
@wraps(_init_)
def __autoassign(self, *args, **kwargs):


argnames, _, _, _ = getargspec(_init_)
for name, value in zip(argnames[1:], args):

if name.startswith(prefix):
setattr(self, name[len(prefix):], value)
_init_(self, *args, **kwargs)

return __autoassign
return _autoassign

class Test(object):
@autoassign('self_')
def __init__(self, self_foo, self_bar, baz):


print 'baz =', baz

t = Test(1, 2, 3)
print t.foo
print t.bar
print t.baz # raises an exception

=============
André

Russ P.

unread,
Jan 27, 2008, 9:50:56 PM1/27/08
to
On Jan 27, 5:13 pm, Wildemar Wildenburger
<lasses_w...@klapptsowieso.net> wrote:
> Ben Finney wrote:

That makes sense to me.

Come to think of it, why restrict this convention to the constructor?
Or am I just opening a can of worms?


Paul Rubin

unread,
Jan 27, 2008, 9:57:53 PM1/27/08
to
Wildemar Wildenburger <lasse...@klapptsowieso.net> writes:
> class Server(object):
> def __init__(self, self.host, self.port,
> self.protocol, self.bufsize, self.timeout):
> pass
> ?

That could temporarily bind those attributes but it shouldn't
persistently mutate the object.

How about:

class Server(object):
def __init__(self, host, port, protocol, bufsize, timeout):

self.(host, port, protocol, bufsize, timeout) = \
host, port, protocol, bufsize, timeout

That's fairly explicit yet cuts down the total amount of boilerplate.

André

unread,
Jan 27, 2008, 10:18:29 PM1/27/08
to
On Jan 27, 10:57 pm, Paul Rubin <http://phr...@NOSPAM.invalid> wrote:

Not much; you're still repeating each variable name 3 times. I prefer
the standard way of one definition per line over this notation
myself.

André

Ben Finney

unread,
Jan 27, 2008, 10:33:42 PM1/27/08
to
"Russ P." <Russ.P...@gmail.com> writes:

> On Jan 27, 5:13 pm, Wildemar Wildenburger
> <lasses_w...@klapptsowieso.net> wrote:
> > class Server(object):
> > def __init__(self, self.host, self.port,
> > self.protocol, self.bufsize, self.timeout):
> > pass
> >
> > ?
>

> That makes sense to me.

Not to me. 'self' is a name that doesn't exist until *after* that
'def' statement is completed; in any other statement, that would mean
'self.foo' in the same statement would raise a NameError.

Special-casing it for a function declaration complicates the language
for little gain: the rules of what is valid when become more
complicated. Special cases aren't special enough to break the rules.

--
\ "I took a course in speed waiting. Now I can wait an hour in |
`\ only ten minutes." -- Steven Wright |
_o__) |
Ben Finney

Tim Chase

unread,
Jan 27, 2008, 11:30:45 PM1/27/08
to Wildemar Wildenburger, pytho...@python.org
> This is neat. :) Could that maybe be extended to only assign selected
> args to the instance and let others pass unchanged. So that, for instance:
>
> @autoassign("foo", "bar")
> def __init__(self, foo, bar, baz):
> super(baz)

I've seen some folks import inspect/functools, but from my
testing, the __init__ method in question has a .func_code object
that already has the varnames in it. However, as you suggest, a
version below allows for named defaults, and letting others go
un-defaulted. I'm not sure about naming conventions for
class-style decorators--start with a cap, like a class should
("AutoAssign") or because it behaves like a function, use
"auto_assign", or any of a number of other variants. Anyways...

class auto_assign(object):
def __init__(self, *varnames):
self.args = set(varnames)
def __call__(self, fn):
autoassign = self
def wrapper(self, *args, **kwargs):
for argname, argvalue in zip(
fn.func_code.co_varnames[1:],
args):
if argname in autoassign.args:
setattr(self, argname, argvalue)
for argname in autoassign.args:
if argname in kwargs:
setattr(self, argname, kwargs[argname])
fn(self, *args, **kwargs)
return wrapper

class Foo(object):
@auto_assign('foo', 'baz', 'fred')
def __init__(self, foo, bar, baz, *args, **kwargs):
pass

f = Foo('hello', 42, 3.14, fred='wilma', barney='betty')
try:
print f.foo
except AttributeError:
print "Could not print f.foo"

try:
print f.bar
except AttributeError:
print "Could not print f.bar"

try:
print f.baz
except AttributeError:
print "Could not print f.baz"

try:
print f.fred
except AttributeError:
print "Could not print f.fred"

try:
print f.barney
except AttributeError:
print "Could not print f.barney"

-tkc


Gabriel Genellina

unread,
Jan 27, 2008, 11:47:07 PM1/27/08
to pytho...@python.org
En Sun, 27 Jan 2008 23:51:28 -0200, Arnaud Delobelle
<arn...@googlemail.com> escribió:

> Nice! I've got a slight variation without magic argument names:
>

> class Test(object):


> @autoassign('foo', 'bar')

> def __init__(self, baz):


> print 'baz =', baz
>

En Mon, 28 Jan 2008 00:02:51 -0200, André <andre....@gmail.com>
escribió:

> Here's a version that
> 1. does not require new syntax
> 2. does not *necessarily* override the "_" prefix convention
> 3. follows the "Explicit is better than implicit" convention when
> being called.
>

> class Test(object):
> @autoassign('self_')
> def __init__(self, self_foo, self_bar, baz):
> print 'baz =', baz

I would like a signature-preserving version. The help system, pydoc, the
inspect module, and likely any other introspection tool see those
decorated methods with a different signature than the original one.
Even if one writes a signature-preserving decorator (maybe along the lines
of this article by M. Simionato [1]), names like "self_foo", "self_bar"
are ugly. Furthermore, argument names are part of the public interfase,
but here they're tied to the implementation: what if later I want to keep
a reference to baz? I must change the argument name to self_baz, breaking
all users of the code.

[1] http://www.phyast.pitt.edu/~micheles/python/documentation.html

--
Gabriel Genellina

Russ P.

unread,
Jan 28, 2008, 1:31:08 AM1/28/08
to
On Jan 27, 7:33 pm, Ben Finney <bignose+hates-s...@benfinney.id.au>
wrote:

OK, then how about a special function that could be called from inside
the constructor (or anywhere else for that matter) to initialize a
list of data members. For example,

self.__set__(host, port, protocol, bufsize,
timeout)

This would be equivalent to

self.host = host
self.port = port
# etc.

I'm not sure if that is technically feasible, but it would cut down on
repetition of names.

Torsten Bronger

unread,
Jan 28, 2008, 2:04:05 AM1/28/08
to
Hallöchen!

Dustan writes:

> On Jan 27, 12:41 pm, Torsten Bronger <bron...@physik.rwth-aachen.de>
> wrote:
>

>> [...]


>>
>> Well, you save one or two lines per class. Not enough in my
>> opinion.
>
> Are you referring to the alternate syntax or to the decorator?
> Either way, you could be saving 4 or 5 or more lines, if you have
> enough arguments.

Mostly, I write them in one or two lines, e.g.

def __init__(self, id, kind, person, feedname):
self.id, self.kind, self.person = id, kind, person

(Sometimes I break after the "=".) Still, I don't think that this
at-most-once-per-class use case justifies a special syntax (whether
within the current language or not) that everyone else has to learn,
too.

Paddy

unread,
Jan 28, 2008, 2:13:14 AM1/28/08
to
On Jan 27, 5:06 pm, coldpizza <vri...@gmail.com> wrote:
> There is a pattern that occurs fairly often in constructors in Python
> and other OOP languages.
>
> Let's take an example:

>
> class Server(object):
> def __init__(self, host, port, protocol, bufsize, timeout):
> self.host = host
> self.port = port
> self.protocol = protocol
> self.bufsize = bufsize
> self.maxthreads = maxthreads
> self.timeout = timeout
>
> Imho, in the class above the assignment to instance fields does not
> contain much programming logic and therefore can be safely 'abstracted
> away' by the language itself with a syntax which would look something
> like this:
>
> class Server(object):
> def __init__(self, @host, @port, @protocol, @bufsize, @timeout):
> pass
>
> This would be equivalent to the first example above, yet it does not
> obfuscate the code in any way. Or does it? It does look much cleaner
> to me.
>
> Of course, the ampersand is just an arbitrary choice and might have
> bad connotations for those who read it as 'take address of' but @ has
> some allusion to delegates which maybe is ok.
>
> I am not an experienced programmer and I am not sure if this is
> necessarily a good idea, so I wanted to get some feedback from more
> experienced Pythonistas before submitting it elsewhere.

Is it not possible to write a function that queries its call stack
when run to find the name of all arguments and locals() of the level
above so you could write:

class test(object):
def __init__(self, x, y):
arg2inst()

and automatically assign self.x=x; self.y=y ?

It could be extended so that...

class test(object):
def __init__(self, x, y):
arg2inst("x")

... then only assigns x.


Has anyone seen something like this in the cookbook?

- Paddy.

Steven D'Aprano

unread,
Jan 28, 2008, 2:47:24 AM1/28/08
to
On Mon, 28 Jan 2008 08:04:05 +0100, Torsten Bronger wrote:

>> Are you referring to the alternate syntax or to the decorator? Either
>> way, you could be saving 4 or 5 or more lines, if you have enough
>> arguments.
>
> Mostly, I write them in one or two lines, e.g.
>
> def __init__(self, id, kind, person, feedname):
> self.id, self.kind, self.person = id, kind, person


It's not the number of lines that is important, but the amount of
redundant code, and the amount of redundant code is identical whether you
write it in one line or three.

The problem is that instance initialization frequently and regularly
breaks the principle "Don't Repeat Yourself". Whether you initialize your
code like this:

self.id = id
self.kind = kind
self.person person

or like this:

self.id = id; self.kind = kind; self.person = person

or like this:

self.id, self.kind, self.person = id, kind, person

you are repeating yourself.

Unfortunately, without syntactical support, I don't think there is any
easy way to tell the compiler which arguments to auto-initialize and
which to skip. And Guido rightly is reluctant to create special syntax
for special cases, and something which happens only in __init__ (and
maybe __new__?) is certainly a special case.

That leaves a decorator solution, married with a convention for names.

Here's a thought... why assume that the convention is a prefix? What
about a suffix?

@autoassign
def __init__(self, spam_, ham_, eggs):
pass

A trailing underscore doesn't conflict with the conventions for leading
underscores. The only conflict is with the convention that if you want a
name that looks like a reserved word you put an underscore after it.
Since Python has very few reserved words, and they rarely make good
argument names, there should be far fewer conflicts with an underscore
suffix rather than a prefix.


I'd still prefer compiler support, preferably with a leading & as syntax.
Since all the work would happen at compile time, it wouldn't effect the
runtime speed, and it wouldn't lead to any confusion with function
signatures. The class you get would be exactly the same as if you had
done the attribute initialization by hand, except the compiler did it.

That's the ideal solution, but failing that, a decorator solution with a
trailing _ gets my vote.

--
Steven

Russ P.

unread,
Jan 28, 2008, 2:56:16 AM1/28/08
to
On Jan 27, 11:47 pm, Steven D'Aprano <st...@REMOVE-THIS-

The problem with a trailing underscore is that it creates another
valid name, so if someone used the name foo_, it would conflict with
your convention. You need a character that is not part of a valid
Python identifier or operator, such as &, $, %, @, !, ~, or ^.

Ben Finney

unread,
Jan 28, 2008, 3:21:48 AM1/28/08
to
"Russ P." <Russ.P...@gmail.com> writes:

> OK, then how about a special function that could be called from
> inside the constructor (or anywhere else for that matter) to
> initialize a list of data members. For example,
>
> self.__set__(host, port, protocol, bufsize,
> timeout)
>
> This would be equivalent to
>
> self.host = host
> self.port = port
> # etc.
>
> I'm not sure if that is technically feasible, but it would cut down
> on repetition of names.

It's much more attractive, because it doesn't change the function
signature. In fact, here's a variation that doesn't even need a
language change::

>>> class Foo(object):
... def __init__(self, spam, eggs, beans):
... self.__dict__.update(dict(
... (name, value) for (name, value) in vars().items()
... if name in ['spam', 'beans']))
...
>>> foo = Foo("some spam", "more eggs", "other beans")
>>> foo.spam
'some spam'
>>> foo.eggs


Traceback (most recent call last):

File "<stdin>", line 1, in ?
AttributeError: 'Foo' object has no attribute 'eggs'
>>> foo.beans
'other beans'

--
\ "If consumers even know there's a DRM, what it is, and how it |
`\ works, we've already failed." —Peter Lee, Disney corporation, |
_o__) 2005 |
Ben Finney

Torsten Bronger

unread,
Jan 28, 2008, 3:40:35 AM1/28/08
to
Hallöchen!

Steven D'Aprano writes:

> On Mon, 28 Jan 2008 08:04:05 +0100, Torsten Bronger wrote:
>
>>> Are you referring to the alternate syntax or to the decorator? Either
>>> way, you could be saving 4 or 5 or more lines, if you have enough
>>> arguments.
>>
>> Mostly, I write them in one or two lines, e.g.
>>
>> def __init__(self, id, kind, person, feedname):
>> self.id, self.kind, self.person = id, kind, person
>
> It's not the number of lines that is important, but the amount of
> redundant code, and the amount of redundant code is identical
> whether you write it in one line or three.

I doubt that there is redunancy. Don't be misled by the fact that
the string "id" appears twice. The expession is minimal in both
cases. The only difference is that in one case you have the string
"id" twice, and in the other case you have a special syntax or even
a new identifier. The information contents is the same.

> The problem is that instance initialization frequently and

> regularly breaks the principle "Don't Repeat Yourself". [...]

I don't see why. It say "I want *this* parameter be turned into an
instance variable of the same name". Why is this repeating myself?

In my opinon, it would be repeating yourself if in *all* __init__s
you want to have *all* parameters turned into instance variables of
the same name. However, we all know that sometimes the names should
be different, or you want to do some trivial transformation before
the assignment.

Granted that it's a frequent use case which may justify syntactic
sugar, but the straightforward solution is so simple that I think a
new syntax would make the language just slightly more complicated
without simplifying anything really.

> [...]


>
> Here's a thought... why assume that the convention is a prefix? What
> about a suffix?
>
> @autoassign
> def __init__(self, spam_, ham_, eggs):
> pass
>

> [...] Since Python has very few reserved words, and they rarely


> make good argument names, there should be far fewer conflicts with
> an underscore suffix rather than a prefix.

I use them rather frequently, and I see them regularly in the
stdlib, so I think this would cause confusion.

> I'd still prefer compiler support, preferably with a leading & as
> syntax.

Please, no! ;-) I like that Python tries to avoid hacker
characters in the code in favour of english words.

Please bear in mind that it is a frequent use case, so you will have
it in virtually every __init__ in fresh Python code. I prefer to
see instance variables be defined in an brain-friendly explicit way
rather than single characters that hide it.

Russ P.

unread,
Jan 28, 2008, 4:06:14 AM1/28/08
to
On Jan 28, 12:21 am, Ben Finney <bignose+hates-s...@benfinney.id.au>
wrote:
> `\ works, we've already failed." --Peter Lee, Disney corporation, |
> _o__) 2005 |
> Ben Finney

If you can wrap that in a clean function that works for every class
you might have something.

Steven D'Aprano

unread,
Jan 28, 2008, 4:06:17 AM1/28/08
to
On Mon, 28 Jan 2008 19:21:48 +1100, Ben Finney wrote:

> In fact, here's a variation that doesn't even need a language
> change::
>
> >>> class Foo(object):
> ... def __init__(self, spam, eggs, beans):
> ... self.__dict__.update(dict(

> ... (name, value) for (name, value) in \
> ... vars().items() if name in ['spam', 'beans']))
> ...


You still need to repeat yourself twice. That's 33% better than repeating
yourself three times, but 100% worse than repeating yourself once.

Other problems:

(1) Readability suffers greatly.

(2) Performance takes a big hit.


>>> class Parrot(object):
... def __init__(self, name, colour, breed):
... self.name = name
... self.colour = colour
... self.breed = breed
...
>>> class Parrot2(object):
... def __init__(self, name, colour, breed):
... self.__dict__.update(dict((name, value) for
... (name, value) in vars().items()
... if name in ['name', 'colour', 'breed']))
...
>>>
>>>
>>> import timeit
>>> timeit.Timer("Parrot(1, 2, 3)",
... "from __main__ import Parrot").repeat()
[3.3467490673065186, 2.2820541858673096, 2.2934978008270264]
>>> timeit.Timer("Parrot2(1, 2, 3)",
... "from __main__ import Parrot2").repeat()
[13.148159027099609, 13.015455961227417, 11.936856985092163]

--
Steven

Arnaud Delobelle

unread,
Jan 28, 2008, 7:09:48 AM1/28/08
to
On Jan 28, 4:47 am, "Gabriel Genellina" <gagsl-...@yahoo.com.ar>
wrote:

> En Sun, 27 Jan 2008 23:51:28 -0200, Arnaud Delobelle
> <arno...@googlemail.com> escribió:

>
> > Nice! I've got a slight variation without magic argument names:
>
> > class Test(object):
> > @autoassign('foo', 'bar')
> > def __init__(self, baz):
> > print 'baz =', baz
[...]

> I would like a signature-preserving version. The help system, pydoc, the
> inspect module, and likely any other introspection tool see those
> decorated methods with a different signature than the original one.
> Even if one writes a signature-preserving decorator (maybe along the lines
> of this article by M. Simionato [1]), names like "self_foo", "self_bar"
> are ugly. Furthermore, argument names are part of the public interfase,
> but here they're tied to the implementation: what if later I want to keep
> a reference to baz? I must change the argument name to self_baz, breaking
> all users of the code.

Sligthly improved (not for performance! but signature-preserving and
looks for default values)

from functools import wraps
from inspect import getargspec

from itertools import izip, chain

def autoassign(*names):
def decorator(f):
fargnames, _, _, fdefaults = getargspec(f)
defaults = [(n,v) for (n,v)
in izip(reversed(fargnames), reversed(fdefaults))
if n in names]
@wraps(f)
def decorated(self, *args, **kwargs):
self.__dict__.update(defaults)
for name, arg in chain(izip(fargnames, args),
kwargs.iteritems()):
if name in names:
setattr(self, name, arg)


return f(self, *args, **kwargs)
return decorated
return decorator

class Test(object):


@autoassign('foo', 'bar')

def __init__(self, foo, bar=3, baz=6):


print 'baz =', baz

t = Test(1, 2, 6)
u = Test(foo=8)

print t.foo # 1
print t.bar # 2

print u.foo # 8
print u.bar # 3 (default)

--
Arnaud


print t.baz # AttributeError

Steven Bethard

unread,
Jan 28, 2008, 10:45:54 AM1/28/08
to

You should definitely post this to the cookbook:

http://aspn.activestate.com/ASPN/Cookbook/Python

STeVe

André

unread,
Jan 28, 2008, 11:08:18 AM1/28/08
to

If I may suggest, I would extend this so that autoassign's signature
would be as follows:

autoassign(all=True, include_only=None, exclude=None)

Either one of include_only or exclude could be a list of function to
which the automatic assignment would apply (or not). I was planning
to write this up and submit it to the cookbook later this evening, but
since the suggestion has been made, someone else can jump on it. ;-)

André

Bruno Desthuilliers

unread,
Jan 28, 2008, 12:19:06 PM1/28/08
to
Paddy a écrit :
(snip)

> Is it not possible to write a function that queries its call stack
> when run to find the name of all arguments and locals() of the level
> above

> so you could write:
>
> class test(object):
> def __init__(self, x, y):
> arg2inst()
>
> and automatically assign self.x=x; self.y=y ?

Might be possible using the inspect module. But as far as I'm concerned,
I'm not sure I really like the idea that much - no rationale here, just
a feeling...

Arnaud Delobelle

unread,
Jan 28, 2008, 1:44:59 PM1/28/08
to
On Jan 28, 4:30 am, Tim Chase <python.l...@tim.thechases.com> wrote:
> I've seen some folks import inspect/functools, but from my
> testing, the __init__ method in question has a .func_code object
> that already has the varnames in it.

in py3k f.func_code gives way to f.__code__, this is why inspect may
be preferable

--
Arnaud

Arnaud Delobelle

unread,
Jan 28, 2008, 2:19:33 PM1/28/08
to
On Jan 28, 4:08 pm, "André" <andre.robe...@gmail.com> wrote:
[...]

> If I may suggest, I would extend this so that autoassign's signature
> would be as follows:
>
> autoassign(all=True, include_only=None, exclude=None)
>
> Either one of include_only or exclude could be a list of function to
> which the automatic assignment would apply (or not).   I was planning
> to write this up and submit it to the cookbook later this evening, but
> since the suggestion has been made, someone else can jump on it. ;-)
>
> André

I've modified my little decorator (see Test1, Test2, Test3 for
usage). I'll post it later on the cookbook if there seems to be no
bugs and noone raises valid point against it:)

from functools import wraps
from inspect import getargspec, isfunction
from itertools import izip, ifilter, starmap

def autoassign(*names, **kwargs):
if kwargs:
exclude, f = set(kwargs['exclude']), None
sieve = lambda l:ifilter(lambda nv: nv[0] not in exclude, l)
elif len(names) == 1 and isfunction(names[0]):
f = names[0]
sieve = lambda l:l
else:
names, f = set(names), None
sieve = lambda l: ifilter(lambda nv: nv[0] in names, l)


def decorator(f):
fargnames, _, _, fdefaults = getargspec(f)

# Remove self for fargnames and make sure fdefaults is a tuple
fargnames, fdefaults = fargnames[1:], fdefaults or ()
defaults = list(sieve(izip(reversed(fargnames),
reversed(fdefaults))))


@wraps(f)
def decorated(self, *args, **kwargs):

assigned = dict(sieve(izip(fargnames, args)))
assigned.update(sieve(kwargs.iteritems()))
# It would be nice to have a builtin to exhaust iterators:
for _ in starmap(assigned.setdefault, defaults): pass
self.__dict__.update(assigned)


return f(self, *args, **kwargs)
return decorated

return f and decorator(f) or decorator

class Test(object):
@autoassign('foo', 'bar')
def __init__(self, foo, bar=3, baz=6):
print 'baz =', baz

class Test2(object):
@autoassign
def __init__(self, foo, bar): pass

class Test3(object):
@autoassign(exclude=('foo', 'bar'))
def __init__(self, foo, bar, baz=5, **kwargs): pass

t = Test(1, 2, 5)
u = Test(foo=8)
v = Test2(10, 11)
w = Test3(100, 101, foobar=102)

print t.foo # 1
print t.bar # 2

print u.foo # 8
print u.bar # 3 (default)

print v.foo, v.bar # 10 11
print w.baz, w.foobar # 5 102

for obj, attr in ('w', 'foo'), ('w', 'bar'), ('t', 'baz'):
try:
getattr(globals()[obj], attr)
except AttributeError:
print '%s.%s raises AttributeError' % (obj, attr)

==== output ====
baz = 5
baz = 6
1
2
8
3
10 11
5 102
w.foo raises AttributeError
w.bar raises AttributeError
t.baz raises AttributeError

--
Arnaud

Tim Chase

unread,
Jan 28, 2008, 5:27:06 PM1/28/08
to Arnaud Delobelle, pytho...@python.org
> I've modified my little decorator (see Test1, Test2, Test3 for
> usage). I'll post it later on the cookbook if there seems to be no
> bugs and noone raises valid point against it:)

One other area that was mentioned obliquely: preservation of
docstrings (and other function attributes)

I couldn't tell from your code if it attempted to publish
function attributes:

class Foo(object):
@autoassign
def __init__(self, alpha, beta, gamma):
"Do something"
pass

f = Foo(3,1,4)
print f.__init__.__doc__

I can't say I use doc-strings in __init__ methods often, and I
suspect the autoassign is only helpful most of the time for
__init__ methods, but if it's slated for the cookbook,
completeness would be worth aspiring to.

-tkc

Arnaud Delobelle

unread,
Jan 28, 2008, 5:44:27 PM1/28/08
to
On Jan 28, 10:27 pm, Tim Chase <python.l...@tim.thechases.com> wrote:
> > I've modified my little decorator (see Test1, Test2, Test3 for
> > usage). I'll post it later on the cookbook if there seems to be no
> > bugs and noone raises valid point against it:)
>
> One other area that was mentioned obliquely: preservation of
> docstrings (and other function attributes)

I think @wraps(...) does this (definitely copies __doc__).

--
Arnaud

Terry Reedy

unread,
Jan 28, 2008, 10:58:52 PM1/28/08
to pytho...@python.org

"Steven D'Aprano" <st...@REMOVE-THIS-cybersource.com.au> wrote in message
news:13pqd16...@corp.supernews.com...

| I don't like the name convention. _name already has a perfectly good
| convention: it's a private name, don't mess with it. That includes in
| function/method signatures. With your convention, _foo is public.

Since local names, including params are inaccesible outside a function, I
don't see how the convention applies. However, the underscore could just
as well go at the end of the name. There no current convention I know of
with that.

tjr

Terry Reedy

unread,
Jan 28, 2008, 11:01:20 PM1/28/08
to pytho...@python.org

"André" <andre....@gmail.com> wrote in message
news:e82b9c41-935d-45c9...@i3g2000hsf.googlegroups.com...

|Here's a version that
|1. does not require new syntax
|2. does not *necessarily* override the "_" prefix convention

'self_' is way too bulky and intrusive. Putting '_' at the end of the word
is nearly as easy to detect and conflicts with no convention I know of.

tjr


coldpizza

unread,
Jan 29, 2008, 6:55:35 AM1/29/08
to
Hi,

I appreciate everyone's feedback on the topic.

Having reflected on what has been said here, I now realize that
creating more complexity is not the way to go. I would rather favor
something that relies on existing language features, something like
the default keyword argument assignment in functions.

This is probably stupid but as a noob I would have liked something
like:
def __init__( self. = host, self. = port, self. = timeout, message =
"Connected."):
pass

This is probably even more preposterous than @host, @port, but to me
it would make more sense.

I suppose the subject has exhausted itself and I am not going to
follow it up. If anyone is interested in taking it on, then please do.

Best,
coldpizza


On Jan 29, 6:01 am, "Terry Reedy" <tjre...@udel.edu> wrote:
> "André" <andre.robe...@gmail.com> wrote in message

Reply all
Reply to author
Forward
0 new messages