py3k feature proposal: field auto-assignment in constructors

R

Russ P.

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

?

/W

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?
 
P

Paul Rubin

Wildemar Wildenburger said:
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.
 
A

André

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.

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é
 
B

Ben Finney

Russ P. said:
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.
 
T

Tim Chase

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
 
G

Gabriel Genellina

En Sun, 27 Jan 2008 23:51:28 -0200, Arnaud Delobelle
Nice! I've got a slight variation without magic argument names:

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


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
 
R

Russ P.

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

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.
 
T

Torsten Bronger

Hallöchen!
[...]

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.

Tschö,
Torsten.
 
P

Paddy

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.
 
S

Steven D'Aprano

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.
 
R

Russ P.

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.

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 ^.
 
B

Ben Finney

Russ P. said:
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::
... def __init__(self, spam, eggs, beans):
... self.__dict__.update(dict(
... (name, value) for (name, value) in vars().items()
... if name in ['spam', 'beans']))
... Traceback (most recent call last):
'other beans'
 
T

Torsten Bronger

Hallöchen!
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.

Tschö,
Torsten.
 
R

Russ P.

Russ P. said:
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::
... def __init__(self, spam, eggs, beans):
... self.__dict__.update(dict(
... (name, value) for (name, value) in vars().items()
... if name in ['spam', 'beans']))
...Traceback (most recent call last):
'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

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

Steven D'Aprano

In fact, here's a variation that doesn't even need a language
change::
... 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.

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

Arnaud Delobelle

En Sun, 27 Jan 2008 23:51:28 -0200, Arnaud Delobelle
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)
 
S

Steven Bethard

Arnaud said:
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)

You should definitely post this to the cookbook:

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

STeVe
 
A

André

Arnaud said:
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)

You should definitely post this to the cookbook:

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

STeVe

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é
 
B

Bruno Desthuilliers

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...
 
A

Arnaud Delobelle

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
 

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,774
Messages
2,569,598
Members
45,161
Latest member
GertrudeMa
Top