exec and closures

  • Thread starter Alejandro Dubrovsky
  • Start date
A

Alejandro Dubrovsky

About a month ago, there was a thread on auto-assigning decorators for
__init__. One by André Roberge is here:
http://groups.google.com/group/comp.lang.python/browse_frm/
thread/32b421bbe6caaeed/0bcd17b1fa4fb07c?#0bcd17b1fa4fb07c

This works well for simple cases, but doesn't take keyword arguments or
set default values. I wrote a more extensive version implementing python
call semantics, but it seemed awkard to be repeating something the
compiler does already, so I tried execing a function definition on the
fly with the right parameters that would function as the decorator. Like
this (adjust the indentation variable if it throws a syntax error)

def autoassign(_init_):
import inspect
import functools

argnames, _, _, defaults = inspect.getargspec(_init_)
argnames = argnames[1:]

indentation = ' '
settings = ['self.%s = %s' % (arg[1:], arg) for arg in argnames
if arg[0] == '_']

if len(settings) <= 0:
return _init_

if defaults is None:
args = argnames[:]
else:
args = argnames[:-len(defaults)]
for key, value in zip(argnames[-len(defaults):],defaults):
args.append('%s=%s' % (key, repr(value)))

template = """def _autoassign(self, %(args)s):
%(setting)s
_init_(self, %(argnames)s)
""" % {'args' : ", ".join(args), 'setting' : "\n".join(['%s%s' %
(indentation, setting) for setting
in settings]), 'argnames' : ', '.join(argnames)}

try:
exec template
except SyntaxError, e:
raise SyntaxError('%s. line: %s. offset %s:\n%s' %
(e.msg, e.lineno, e.offset, template))
return _autoassign


Which creates what looked like the right template, but when instantiating
a class that uses that (eg
class A(object):
@autoassign
def __init__(self,_a):
pass
a = A(3)

it throws a
NameError: global name '_init_' is not defined

Is there a way to bind the _init_ name at exec time?

Thanks,
ale
 
P

Peter Otten

Alejandro said:
About a month ago, there was a thread on auto-assigning decorators for
__init__. One by André Roberge is here:
http://groups.google.com/group/comp.lang.python/browse_frm/
thread/32b421bbe6caaeed/0bcd17b1fa4fb07c?#0bcd17b1fa4fb07c

This works well for simple cases, but doesn't take keyword arguments or
set default values. I wrote a more extensive version implementing python
call semantics, but it seemed awkard to be repeating something the
compiler does already, so I tried execing a function definition on the
fly with the right parameters that would function as the decorator. Like
this (adjust the indentation variable if it throws a syntax error)

def autoassign(_init_):
import inspect
import functools

argnames, _, _, defaults = inspect.getargspec(_init_)
argnames = argnames[1:]

indentation = ' '
settings = ['self.%s = %s' % (arg[1:], arg) for arg in argnames
if arg[0] == '_']

if len(settings) <= 0:
return _init_

if defaults is None:
args = argnames[:]
else:
args = argnames[:-len(defaults)]
for key, value in zip(argnames[-len(defaults):],defaults):
args.append('%s=%s' % (key, repr(value)))

template = """def _autoassign(self, %(args)s):
%(setting)s
_init_(self, %(argnames)s)
""" % {'args' : ", ".join(args), 'setting' : "\n".join(['%s%s' %
(indentation, setting) for setting
in settings]), 'argnames' : ', '.join(argnames)}

try:
exec template
except SyntaxError, e:
raise SyntaxError('%s. line: %s. offset %s:\n%s' %
(e.msg, e.lineno, e.offset, template))
return _autoassign


Which creates what looked like the right template, but when instantiating
a class that uses that (eg
class A(object):
@autoassign
def __init__(self,_a):
pass
a = A(3)

it throws a
NameError: global name '_init_' is not defined

Is there a way to bind the _init_ name at exec time?

Use a dedicated namespace:

namespace = dict(_init_=_init_)
exec template in namespace
return namespace["_autoassign"]

Peter
 
A

alitosis

Alejandro said:
def autoassign(_init_):
import inspect
import functools
argnames, _, _, defaults = inspect.getargspec(_init_)
argnames = argnames[1:]
indentation = ' '
settings = ['self.%s = %s' % (arg[1:], arg) for arg in argnames
if arg[0] == '_']
if len(settings) <= 0:
return _init_
if defaults is None:
args = argnames[:]
else:
args = argnames[:-len(defaults)]
for key, value in zip(argnames[-len(defaults):],defaults):
args.append('%s=%s' % (key, repr(value)))
template = """def _autoassign(self, %(args)s):
%(setting)s
_init_(self, %(argnames)s)
""" % {'args' : ", ".join(args), 'setting' : "\n".join(['%s%s' %
(indentation, setting) for setting
in settings]), 'argnames' : ', '.join(argnames)}
try:
exec template
except SyntaxError, e:
raise SyntaxError('%s. line: %s. offset %s:\n%s' %
(e.msg, e.lineno, e.offset, template))
return _autoassign
[snip]
Is there a way to bind the _init_ name at exec time?

Use a dedicated namespace:

namespace = dict(_init_=_init_)
exec template in namespace
return namespace["_autoassign"]

Peter

That works! Excellent, thanks.
I still don't understand why the original doesn't work. I thought
exec with no namespace specified used the current context, in which
_init_ would be _init_ already. But understanding can wait.
ale
 

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,769
Messages
2,569,576
Members
45,054
Latest member
LucyCarper

Latest Threads

Top