Reputation: 11
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
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
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
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