tags:

views:

53

answers:

6

I'm trying to get rid of exec in a code similar to this:

class A(object):
    for field in ['one', 'two', 'three']:
        exec '%s = "%s value"' % (field, field)

...so that:

>>> A.one
'one value'
>>> A.two
'two value'
>>> A.three
'three value'

EDIT: and also the requirement mentioned in the subject is met i.e. A.one is 'one value', before A is instantiated (not to be mistaken for A() instantiated).

Is there a way?

+2  A: 

Use the setattr function.

class A(object):
  pass

for field in ['one', 'two', 'three']:
  setattr(A, field, field + ' value')
KennyTM
I should have repeated the requirement from the subject... although now I'm not sure if it holds for the version with `exec`.
dhill
I tested it and the version with `exec` is executed before `__metaclass__.__new__()`... I also tried putting setattr() inside `A` before posting, but it didn't work as `A` is not defined yet.
dhill
+1  A: 

You say you want to generate something dynamically that the metaclass can process. There's a very simple way to achieve that without resorting to hackery like exec. All you have to do is think of this in a different way: modify the metaclass so that the names are generated there.

class AutoFieldMeta(type):
    def __new__(mcs, name, bases, d):
        for field in d.get('AUTOFIELDS', ()):
            d[field] = field + ' value'
        return type.__new__(mcs, name, bases, d)

class A(object, metaclass=AutoFieldMeta):
    AUTOFIELDS = ('one', 'two', 'three')

>>> A.one
'one value'
>>> 

and if you don't want to modify the existing metaclass you can subclass it.

Duncan
beat me by 33 seconds but I'll spend the extra time for quality;)
aaronasterling
This does the job, it deserves +1, but I'm still here waiting in hope that you can do it in three clear lines like the original code and without `exec`. If only there was a way to call `=` (statements...).
dhill
If you ignore the metaclass definition then it's only two lines and they're clearer than the original. You *can* do it trivially in the same number of lines as your original without using exec, but you really don't want to go down that route [in a class body you can assign to `vars()/locals()` and unlike assigning to `locals()` in a function it will actually work, probably, this week]. `vars()[field] = "%s value" % field`
Duncan
Metaclass is good for declarative programming from provider's perspective. Then you are _interpreting_ class content (it still looks cumbersome to me, but that's not something you have to care about as user).What I'm trying to say is that maybe class body _should_ (_and has not yet_) become the one obvious place for modifications at creation time. You should be able to use language power for this.It's like a difference between syntax and semantics. Using metaclasses, you act on semantics, I need to act on syntax. The semantics are provided by Django already and I don't want to interfere.
dhill
You are of course right that this is not standardized and is not guaranteed to work next week.
dhill
+2  A: 

I'd just inherit from the metaclass and do it there

class MyMetaClass(MetaClass):
    def __new__(meta, classname, baseclasses, classdict):
        fields = classdict['__myfields__']
        for field in fields:
            classdict[field] = field + ' value'
        del classDict['__myfields__']
        MetaClass.__new__(meta, classname, baseclasses, classdict)

Then you can just do:

class A(object):
    __metaclass__ = MyMetaClass
    __myfields__ = ['one', 'two', 'three']
aaronasterling
+2  A: 
>>> values = 'one', 'two', 'three'
>>> A = type('A', (object,), {i: i + ' value' for i in values})
>>> A.one
'one value'
>>> A.three
'three value'
SilentGhost
A: 

Let me preface this by saying that this is incredibly hackish but eliminates the exec. We just stick the attributes on the current stack frame as the code in the class statement is being executed. It is only guaranteed to work on CPython and there are warnings against doing it in the manual. I would highly recommend that you go with duncans metaclass solution.

import sys

class A(object):
    for field in ['one', 'two', 'three']:
        sys._getframe().f_locals[field] = field + ' value'
aaronasterling
+1  A: 

This works too and I think it's a happy end...

class A(object):
    for a in ['one', 'two', 'three']:
        locals().update({a: a + ' value'})

And for anyone searching for assignment expression in Python, this is in the same mood as aaronasterling answer :):

http://code.activestate.com/recipes/202234-assignment-in-expression/

dhill
+1 for reminding me about `locals`
aaronasterling
Or if you wanted a one liner: `vars().update((field, field + " value") for field in ('one', 'two', 'three'))` ick
Duncan