2

I'm having a problem with not being able to reference a member variable indirectly.

I have a class with a function which does one thing. This function uses a member variable during its execution, and which member variable it uses is dependent on the input parameter cmd. Here's some code:

class Foo(object):
    def __init__(self):
        self.a = 0
        self.b = 0

    def bar(self, cmd):
        if cmd == "do_this":
            index = self.a
        elif cmd == "do_that":
            index = self.b

        filename = "file-%d.txt" % index
        fp = open("filename", "w")

        # do some more things which depend on and *should* change
        # the value of self.a or self.b

        return

Now I understand why this code doesn't do what I want. Because we are using the immutable int type, our variable index refers to the value of the member variable self.a or self.b, not self.a or self.b themselves. By the end of the function, index refers to a different valued integer, but self.a and self.b are unchanged.

I'm obviously thinking too C++-ish. I want something equivalent to

index = &(self.a)
filename = "file-%d.txt" % *index

How do I accomplish this in a Pythonic way?

2
  • Note that this sort if if/elif chain is very often best replaced with a dict ({'do_this': self.a, 'do_that': self.b}). Commented Aug 31, 2011 at 16:06
  • You approach not working doesn't have anything to do with immutability of the value. Commented Aug 31, 2011 at 17:11

4 Answers 4

3

There are any number of ways to do this--setattr and the attribute name as a string, writing functions or methods which do the setting for each attribute, etc. One that is fairly clear and may be convenient is

class Foo(object):

    def __init__(self):
        self.values = {'a': 0, 'b': 0}

    def bar(self, cmd):
        if cmd == "do_this":
            index = 'a'
        elif cmd == "do_that":
            index = 'b'

        filename = "file-%d.txt" % index
        f = open("filename", "w")

        ...

        self.values[index] = something

Your intuition to avoid using attribute names as strings is usually right (although that is easily as good a solution in this particular case), and the stock solution to avoiding looking up attributes and variables by strings is "use a dict".

Sign up to request clarification or add additional context in comments.

Comments

3

In this particular case, I would simply keep track of the string name of the attribute you're interested in. something like:

class Foo(object):
    def __init__(self):
        self.a = 0
        self.b = 0

    def bar(self, cmd):
        if cmd == "do_this":
            index_name = 'a'
        elif cmd == "do_that":
            index_name = 'b'

        index = getattr(self, index_name)

        filename = "file-%d.txt" % index
        fp = open("filename", "w")

        # do some more things which depend on and *should* change
        # the value of index
        #              ^^^^^

        setattr(self, index_name, index)

        return

6 Comments

I was hoping to avoid using the string id of the member... It just seems a little sloppy to me. Is this a pythonic style of coding?
using getattr() and setattr() will almost always be preferred over other approaches, which are necessarily less explicit, for the specific case of indirectly operating on object attributes.
@Harry, Most of the time it's prudent to avoid using getattr and setattr, but in this case it is fairly benine. It does not make the applicable code very hard to read or understand, and it does not accept arbitrary values for attribute names.
You can write a proxy class that will at least make the code that manipulates the attributes a little nicer; see my answer.
@TokenMacGuy, getattr and setattr are preferred over using a dict?
|
0

Obviously if you're having a problem because the value is immutable, make it mutable! This of course is a complete hack and probably doesn't meet any definition of "Pythonic".

class Foo(object):
    def __init__(self):
        self.a = [0]
        self.b = [0]

    def bar(self, cmd):
        if cmd == "do_this":
            index = self.a
        elif cmd == "do_that":
            index = self.b

        filename = "file-%d.txt" % index[0]
        fp = open("filename", "w")

        # do some more things which depend on and *should* change
        # the value of self.a or self.b
        index[0] = 42

        return      

2 Comments

Hehe I thought of this trick too but yeah... not the prettiest code! Thanks :)
"Put it in a list" is the stock example of how to add a level of indirection, but I'm not sure I've ever seen the case where I thought it seemed like the best solution.
0

If you don't want to keep track of strings, you can abstract that away using an attribute proxy class like the following:

class AttributeProxy(object):
    def __init__(self, obj_inst, attr_name):
        self.obj_inst, self.attr_name = obj_inst, attr_name

    @property
    def value(self):
        return getattr(self.obj_inst, self.attr_name)

    @value.setter
    def value(self, value):
        setattr(self.obj_inst, self.attr_name, value)

Usage:

class Test(object):
    x = 0
    y = 1

test = Test()

x = AttributeProxy(test, "x")
y = AttributeProxy(test, "y")

print x.value   # 0
x.value += 1
print x.value == y.value

Of course, it's still using getattr() and setattr() behind the scenes, but the client code looks a little nicer. Compare x.value += 1 to setattr(obj, "x", getattr(obj, "x") + 1) frex.

2 Comments

This strikes me as overengineering. This is more code to understand, debug, and test with no real benefit.
Entirely possible. :-) It would be somewhat nicer if properties didn't have to be bound to object instances.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.