user1624579
user1624579

Reputation: 33

Setting properties programmatically

Question

I want to accomplish the following:

tmp = do_stuff(tmp2)
if tmp != None:
    num = tmp

However the variables to be assigned in my problem are variables (better: properties) of an object instance:

class mycls(object):
    def __init__(self):
        self.__a, self._b, self.c = 2, 3, 5
    def set_a(self, val): self.__a = val
    def set_b(self, val): self._b = val
    def set_c(self, val): self.c = val
    A = property(fset = set_a)
    B = property(fset = set_b)
    C = property(fset = set_c)
tmp2 = [7, 11, 13]
inst = mycls()
tmp = do_stuff(tmp2[0])
if tmp != None: inst.A = tmp
tmp = do_stuff(tmp2[1])
if tmp != None: inst.B = tmp
tmp = do_stuff(tmp2[2])
if tmp != None: inst.C = tmp

Obviously the last part looks highly repetitive and in my case needs to be applied to over 10 variables. My question now is: how can those last 6 lines be compressed?

Own attempts at solving the problem

Ideally I would prefer a solution like:

for i in [[inst.A, 0], [inst.B, 1], [inst.C, 2]]:
    tmp = do_stuff(tmp2[i[1]])
    if tmp != None: i[0] = tmp

However this doesn't work (because the inst.X are evaluated and you can't store primitive datatypes by reference/pointer). Another approach I came up with was to use the variable names as strings and fiddle with inst.__dict__, e.g. like this:

for i in [["A", 0], ["B", 1], ["C", 2]]:
    tmp = do_stuff(tmp2[i[1]])
    if tmp != None: inst.__dict__[i[0]] = tmp

However this plan was foiled by the fact that A, B and C are properties.

LISP-like macros would be an enormous help here, unfortunately Python doesn't seem to support macros and I don't want to add dependencies to one of the macro libraries for Python I found on-line (which I didn't test since I won't be able to use them anyway).

I also want to avoid using anonymous functions in a list since this will likely result in the same amount of code as in the second code listing.

My last resort to solve this problem would currently be evals, which I wanted to avoid at all cost.

Upvotes: 3

Views: 1187

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1121814

You are looking for setattr():

for i in [["A", 0], ["B", 1], ["C", 2]]:
    tmp = do_stuff(tmp2[i[1]])
    if tmp != None: setattr(inst, i[0], tmp)

To quote the documentation:

This is the counterpart of getattr(). The arguments are an object, a string and an arbitrary value. The string may name an existing attribute or a new attribute. The function assigns the value to the attribute, provided the object allows it. For example, setattr(x, 'foobar', 123) is equivalent to x.foobar = 123.

Note that since None is a singleton in python, usually one tests for it using the is or is not operators:

for i in [["A", 0], ["B", 1], ["C", 2]]:
    tmp = do_stuff(tmp2[i[1]])
    if tmp is not None:
        setattr(inst, i[0], tmp)

and the PEP 8 python styleguide discourages compound statements; keep the if suite on it's own line, even if it is just one line.

Upvotes: 5

Related Questions