0

I would like to iterate through a selection of class instances and set a member variable equal to a value. I can access the members value with:

for foo in range(1,4): #class members: pv1, pv2, pv3
    bar[foo] ='{0}'.format(locals()['pv' + str(foo)+'.data'])

However when I try to set/mutate the values like so:

for foo in range(1,4): #class members: 
    '{0}'.format(locals()['pv' + str(foo)+'.data']) = bar[foo]

I obviously get the error:

SyntaxError: can't assign to function call

I have tried a few methods to get it done with no success. I am using many more instances than 3 in my actual code(about 250), but my question is hopefully clear. I have looked at several stack overflow questions, such as Automatically setting class member variables in Python -and- dynamically set an instance property / memoized attribute in python? Yet none seem to answer this question. In C++ I would just use a pointer as an intermediary. What's the Pythonic way to do this?

DarkRafe
  • 23
  • 1
  • 5
  • 3
    Stop using numbered variables. Get a list. – user2357112 May 26 '18 at 02:26
  • The class members "pv1,pv2,pv3" are kivy objects. I might be wrong, but I don't think kivy "id"s can be items in a list. I will certainly be testing that out shortly though, it's in interesting idea! – DarkRafe May 26 '18 at 02:32
  • 2
    @DerikWolz You can put a reference to any Python object in a list. – gilch May 26 '18 at 02:37

2 Answers2

0

An attr is a valid assignment target, even if it's an attr of the result of an expression.

for foo in range(1,3):
    locals()['pv' + str(foo)].data = bar[foo]

Another developer wrote a few lines about setattr(), mostly about how it should be avoided.

setattr is unnecessary unless the attribute name is dynamic.

But they didn't say why. Do you mind elaborating why you switched your answer away from setattr()?

In this case, the attr is data, which never changes, so while

for i in range(1, 3):
    setattr(locals()['pv' + str(i)], 'data', bar[i])

does the same thing, setattr isn't required here. The .data = form is both good enough and typically preferred--it's faster and has clearer intent--which is why I changed it. On the other hand, if you needed to change the attr name every loop, you'd need it, e.g.

for i in range(1,3):
    setattr(locals()['pv' + str(i)], 'data' + str(i), bar[i])

The above code sets attrs named data1, data2, data3, unrolled, it's equivalent to

pv1.data1 = bar[1]
pv2.data2 = bar[2]
pv3.data3 = bar[3]

I originally thought your question needed to do something like this, which is why I used setattr in the first place. Once I tested it and got it working I just posted it without noticing that the setattr was no longer required.

If the attr name changes at runtime like that (what the other developer meant by "dynamic") then you can't use the dot syntax, since you have a string object rather than a static identifier. Another reason to use setattr might be if you need a side effect in an expression. Unlike in C, assignments are statements in Python. But function calls like setattr are expressions.

Community
  • 1
  • 1
gilch
  • 10,813
  • 1
  • 23
  • 28
  • first thank you this answer is very succinct and I really like it. However, your previous answer listed setattr() and I see you've now changed it. Another developer wrote a few lines about setattr(), mostly about how it should be avoided. But they didn't say why. Do you mind elaborating why you switched your answer away from setattr()? – DarkRafe May 26 '18 at 22:50
  • Great breakdown Gilch! I really appreciate your clarification! – DarkRafe Jun 01 '18 at 20:27
0

Here is an example of creating a class which explicitly allows access through index or attribute calls to change internal variables. This is not generally promoted as 'good programming' though. It does not explicitly define the rules by which people should be expected to interact with the underlying variables.

the definition of __getattr__() function allows for the assignment of (object).a .

the definition of __getitem__() function allows for the assignment of (object)['b']

class Foo(object):
    def __init__(self, a=None,b=None,c=None):
        self.a=a
        self.b=b
        self.c=c
    def __getattr__(self, x):
        return self.__dict__.get(x, None)
    def __getitem__(self, x):
        return self.__dict__[x]

print
f1 = Foo(3,2,4)
print 'f1=', f1.a, f1['b'], f1['c']

f2 = Foo(4,6,2)
print 'f2=', f2.a, f2['b'], f2['c']

f3 = Foo(3,5,7)
print 'f3=', f3.a, f3['b'], f3['c']

for x in range(1, 4):
    print 'now setting f'+str(x)
    locals()['f'+str(x)].a=1
    locals()['f'+str(x)].b=1
    locals()['f'+str(x)].c=1

print    
print 'f1=', f1.a, f1['b'], f1['c']
print 'f2=', f2.a, f2['b'], f2['c']
print 'f3=', f3.a, f3['b'], f3['c']

The result is

f1= 3 2 4
f2= 4 6 2
f3= 3 5 7
now setting f1
now setting f2
now setting f3

f1= 1 1 1
f2= 1 1 1
f3= 1 1 1
webmite
  • 575
  • 3
  • 6
  • thank you for assisting me as well. At present I don't have any of my code base in Python 2.x but I appreciate what was being said regardless. – DarkRafe May 26 '18 at 22:54