RicketyTensor
RicketyTensor

Reputation: 11

How to set values of class attributes through a list

I have a class with many attributes of type double. The goal is to define a subset of these attributes in a list that will be iterated over and the corresponding values should be changed.

I would like to avoid referencing to the attributes of the class by their name, since I don't want to use any typed in str in the code.

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

foo = Foo()

variables = [foo.a, foo.b]

for i in range(len(variables)):
    variables[i] = 1.0

print(foo.a)

This code returns 0.0 which is correct for the code above. The question is, how to write the code, so that the result would be the updated value of 1.0?

Upvotes: 1

Views: 527

Answers (3)

Gribouillis
Gribouillis

Reputation: 2220

A simple solution using a vector of symbolic references to the attributes

class VarVect:
    def __init__(self, foo, names):
        self.foo = foo
        self.names = names

    def __len__(self):
        return len(self.names)

    def __getitem__(self, i):
        return getattr(self.foo, self.names[i])

    def __setitem__(self, i, val):
        setattr(self.foo, self.names[i], val)

class Foo(object):
    def __init__(self):
        self.a = 0.0
        self.b = 0.0
        self.c = 0.0
        self.var = VarVect(self, ('a', 'b', 'c'))

foo = Foo()

for i in range(len(foo.var)):
    foo.var[i] = 1.0

print(foo.a)

A drawback of such designs is that foo.var[i] = 1.0 will be substantially slower than foo.a = 1.0 if you have many assigments to make.

Upvotes: 0

SyntaxVoid
SyntaxVoid

Reputation: 2623

This is just for fun, please do not ever ever ever do this.

Python interns small numbers and only ever creates one copy. Internally, python trusts that these pointers aren't changed. It will create the small integers, and then store the pointers, telling itself "this pointer will always point to the correct number. But, if you're sneaky enough, you can change what the pointer is pointing to, then when you tell python to give you an integer, say 1, it will look up the value at that memory location and spit it back to you, regardless of what it actually is.

In comes ctypes!

import ctypes
class Foo:
    def __init__(self):
        self.a = 3
        self.b = 2
        self.c = 1
foo = Foo()
variables = [foo.a, foo.b, foo.c]
print(f"foo.a is {foo.a}. foo.b is {foo.b}. foo.c is {foo.c}.")
for v in variables:
    ctypes.cast(id(v), ctypes.POINTER(ctypes.c_int))[6] = v-1
print("*~* ctypes magic *~*")
print(f"foo.a is {foo.a}. foo.b is {foo.b}. foo.c is {foo.c}.")
print(f".... But now 1 is {1}, 2 is {2}, and 3 is {3}... Oh no!")

Output:

foo.a is 3. foo.b is 2. foo.c is 1.
*~* ctypes magic *~*
foo.a is 2. foo.b is 1. foo.c is 0.
.... But now 1 is 0, 2 is 1, and 3 is 2... Oh no!

This may crash your interpretter. It may also make python segfault if you chose the right numbers~

Upvotes: 0

chepner
chepner

Reputation: 531075

variables should be a list of attribute names, not the values of those attributes.

variables = ['a', 'b']
for v in variables:
    setattr(foo, v, 1.0)

Python doesn't have a notion of "bound attributes" which would let you store a reference to foo.a directly in the list, though you could store object/name pairs in the list.

variables = [(foo, 'a'), (foo, 'b')]
for obj, attr in variables:
    setattr(obj, attr, 1.0)

You could make the attributes properties instead, which would allow you, given

from functool import partial


class Foo(object):
    @property
    def a(self):
        return self._a

    @a.setter(self, v):
    def a(self):
        self._a = v

    # Likewise for b and c
    # I leave it as an exercise to define a custom
    # descriptor that would let you write
    # a = my_property(0.0)
    # b = my_property(0.0)
    # c = my_property(0.0)

    def __init__(self):
        self.a = 0.0
        self.b = 0.0
        self.c = 0.0

foo = Foo()

to write

foo = Foo()

variables = [partial(x.fset, foo) for x in [Foo.a, Foo.b]]

for v in variables:
    v(1.0)

It's similar to the tuple solution above, but shifts the boilerplate into the definition of variables rather than the loop that sets the values.

Upvotes: 1

Related Questions