Is it possible to have a class attribute targeting another attribute from the same object and have a function to update the value of the target?
class MyObject(object):
def __init__(self):
self.var_1 = 1
self.var_2 = 2
self.var_3 = 3
self.current_var = self.var_1
def update_var(self, value):
self.current_var = ...
Expected outcome:
>>> x = MyObject()
>>> x.update_var(10)
>>> x.var_1
10
>>> x.current_var = x.var_2
>>> x.update_var(5)
>>> x.var_2
5
You can use the __dict__ of the object or as said by #bla setattr,
And Enum so you don't use string to specify the attribute:
from enum import Enum
class MyObject(object):
def __init__(self):
self.var_1 = 1
self.var_2 = 2
self.var_3 = 3
self.current_var = None
def update_var(self, value):
if self.current_var is None:
raise Exception('Current var is not set')
self.__dict__[self.current_var.name] = value
setattr(self, self.current_var.name, value) # Same result
m = MyObject()
attrs = vars(m)
attrs_enum = Enum("attrs_enum", attrs)
m.var_1 # 1
m.current_var = attrs_enum.var_1
m.update_var(10)
m.var_1 # 10
m.current_var = attrs_enum.var_2
m.var_2 # 2
m.update_var(20)
m.var_2 # 20
I don't like using a string to specify the attribute, but this is solution
I suggest making current_var a property that acts as a proxy for a given instance attribute. You can use set_current_var to update the proxy target.
Code
class MyObject(object):
current_var = 1
def __init__(self):
self.var_1 = 1
self.var_2 = 2
self.var_3 = 3
def set_current_var(self, name):
self._current_var = name
#property
def current_var(self):
return getattr(self, self._current_var)
#current_var.setter
def current_var(self, value):
setattr(self, self._current_var, value)
Example
x = MyObject()
print(x.var_1) # 1
x.set_current_var('var_1')
print(x.current_var) # 1
x.current_var = 4
print(x.var_1) # 4
You can create a wrapper class for your MyObject attribute values. That way, a reference will exist from the contents of current_var to the attribute bound in __dict__:
class _int:
def __init__(self, _val):
self.value = _val
def __repr__(self):
return str(self.value)
class MyObject(object):
def __init__(self):
self.var_1 = _int(1)
self.var_2 = _int(2)
self.var_3 = _int(3)
self.current_var = self.var_1
def update_var(self, value):
self.current_var.value = value
x = MyObject()
x.update_var(10)
print(x.var_1)
x.current_var = x.var_2
x.update_var(5)
print(x.var_2)
Output:
10
5
Related
I wrote a simple class code like below.
class Fridge:
def __init__(self):
self.isOpened = False
self.foods = []
def open(self):
self.isOpened = True
def put(self, thing):
if self.isOpened:
self.foods.append(thing)
def close(self):
self.isOpened = False
class Food:
pass
And then import the module...
import class_practice as fridge
f = fridge.Fridge()
apple = fridge.Food()
elephant = fridge.Food()
f.open()
f.put(apple)
f.put(elephant)
print(f.foods)
The print output is
[<class_practice.Food object at 0x7fe761fce5f8>, <class_practice.Food object at 0x7fe761fce710>]
In this case, if I want to print out f.foods as the object name of [apple, elephant],
how could I do?
##Revised##
I want to extract data from
[<class_practice.Food object at 0x7fe761fce5f8>, <class_practice.Food object at 0x7fe761fce710>]
as the form of ['apple', 'elephant'].
It is like,
a = 'hello'
b = id(a)
print(ctypes.cast(b, ctypes.py_object).value)
And then, the result is 'hello'
Store the value in a instance attribute, and return it from the __repr__ dunder method:
class Food:
def __init__(self, value):
self.value = value
def __repr__(self):
return self.value
You will need to pass the value when constructing Food:
apple = fridge.Food("apple")
elephant = fridge.Food("elephant")
Or try the following:
class Fridge:
def __init__(self):
self.isOpened = False
self.foods = []
def open(self):
self.isOpened = True
def put(self, thing):
if self.isOpened:
self.foods.append(thing)
def close(self):
self.isOpened = False
class Food:
def __init__(self, value):
self.value = value
def __repr__(self):
return self.value
import class_practice as fridge
f = fridge.Fridge()
f.open()
f.put(fridge.Food('apple'))
f.put(fridge.Food('elephant'))
print(f.foods)
Output:
[apple, elephant]
How can I use the get descriptor to output values of list elements?
class X(object):
def __init__(self,value):
self.value = value
def __get__(self,obj,objtype):
return self.value
class Y(object):
a = X(1)
b = X(2)
c = [X(3),X(4)]
y = Y()
print(y.a)
print(y.b)
print(y.c[0])
Output:
1
2
<__main__.X object at ...>
Desired Output:
1
2
3
This snippet could bring you closer, but it's not the same. Z subclasses a list and defines __get__ for acting as a descriptor.
class X(object):
def __init__(self, value):
self.value = value
def __get__(self, obj, objtype):
return self.value
def __repr__(self):
return "X(%r)" % self.value
class Z(list):
def __get__(self, obj, objtype):
return self
def __getitem__(self, index):
"""override brackets operator, suggested by Azat Ibrakov"""
list_item = super(Z, self).__getitem__(index)
try:
return list_item.value
except AttributeError:
return list_item
class _LiteralForContainerDescriptorZ(object):
def __getitem__(self, keys):
"""override brackets operator, basing on https://stackoverflow.com/a/37259917/2823074"""
if not isinstance(keys, tuple):
keys = (keys,)
assert not any(isinstance(key, slice) for key in keys) # avoid e.g. ZL[11:value, key:23, key2:value2]
return Z(keys)
ZL = _LiteralForContainerDescriptorZ()
Using _LiteralForContainerDescriptorZ is optional, it gives a bit nicer syntax.
class Y(object):
a = X(1)
b = X(2)
c = Z([X(3.14), X(4)]) # define 'c' using constructor of Z class inherited from list
d = ZL[X(3.14), X(4)] # define 'd' using custom literal
y = Y()
for statement_to_print in [
"y.a", "y.b", "y.c","y.d", "y.c[0]", "y.c[1]", "y.d[0]",
]:
value = eval(statement_to_print)
print("{st:9} = {ev:<16} # type: {tp}".format(
st=statement_to_print, ev=value, tp=type(value).__name__))
Calling it, the prints are:
y.a = 1 # type: int
y.b = 2 # type: int
y.c = [X(3.14), X(4)] # type: Z
y.d = [X(3.14), X(4)] # type: Z
y.c[0] = 3.14 # type: float
y.c[1] = 4 # type: int
y.d[0] = 3.14 # type: float
I have a simple class (Node) that has an ID and 3 coordinates (X,Y,Z). Its ID must be an integer and its coordinates floats, therefore I have used the following class definition.
I'm new to OO programming, but it seems "heavy" for such a simple class. Is there any way to compact that and make it less repetitive? For instance if I had 10 coordinates this would be a bit heavy.
Anyway it works I'm just wondering if there's a better way to do that.
class Node():
def __init__(self):
self.ID = 0
self.X = 0
self.Y = 0
self.Z = 0
#property
def ID(self):
return self._ID
#ID.setter
def ID(self,value):
self._ID = int(value)
#property
def X(self):
return self._X
#X.setter
def X(self,value):
self._X = float(value)
#property
def Y(self):
return self._Y
#Y.setter
def Y(self,value):
self._Y = float(value)
#property
def Z(self):
return self._Z
#Z.setter
def Z(self,value):
self._Z = float(value)
In Python, if you want to provide read and write access to attributes, you simply make them "public".
Like so:
class Node():
def __init__(self):
self.ID = 0 # No underscores
self.X = 0 # means
self.Y = 0 # public
self.Z = 0 # (by convention)
Now you can use your class like this:
n = Node()
n.Z = 9
This is perfectly fine, because you can still decide later on to adjust the behavior of the read and write operations (using the #property decorator), without braking the interface of your class.
You might also want to look into dataclasses (introducted in Python 3.7):
from dataclasses import dataclass
#dataclass
class Node:
ID = 0
X = 0
Y = 0
Z: float = 0 # type hints are optional
A final note: class attributes are lowercase by convention. Only constants should be written with full uppercase letters.
What you want is a custom descriptor, not property itself.
class Force(object):
def __init__(self, type_, var):
self.type = type_
self.var = "_" + var
def __get__(self, obj, type):
# obj is None when the descriptor is accessed via
# the class, rather than an instance.
# type is the class through which the descriptor is accessed;
# not used here.
if obj is None:
return self
return getattr(obj, self.var)
def __set__(self, obj, value):
setattr(obj, self.var, self.type(value))
class Node:
ID = Force(int, 'ID')
X = Force(float, 'X')
Y = Force(float, 'Y')
Z = Force(float, 'Z')
def __init__(self):
self.ID = 0
self.X = 0
self.Y = 0
self.Z = 0
Python 3.6 added support for a __set_name__ method which is called automatically when the descriptor is instantiated, receiving the name the descriptor is assigned to as an argument.
class Force:
def __init__(self, type_):
self.type = type_
def __set_name__(self, owner, name):
# Owner is the class which contains the descriptor;
# not used here
self.var = "_" + name
def __get__(self, obj, type):
if obj is None:
return self
return getattr(obj, self.var)
def __set__(self, obj, value):
setattr(obj, self.var, self.type(value))
class Node:
ID = Force(int)
X = Force(float)
Y = Force(float)
Z = Force(float)
def __init__(self):
self.ID = 0
self.X = 0
self.Y = 0
self.Z = 0
(I'm certain this can be improved. Force.__init__ could take an initial value for each instance of the descriptor, instead of requiring Node.__init__ to initialize each.)
I have a class that has instances of other classes as its properties:
from ctypes import *
class C():
A = propA()
B = propB()
def __init__(self):
class c_struct(Structure):
_fields_ = [('_A', c_int), ('_B', c_int)]
self._c_struct = c_struct()
I need to somehow "connect" the propA and propB classes to the internal structure _c_struct created in C to eventually get the behavior like so:
c = C()
c.A.val = 12
c.B.val = 13
c._c_struct._A == c.A.val == 12
Where C.A is able to manipulate C._c_struct._A. I've attempted to do something like this:
from ctypes import *
class propParent():
def set_ref(self, ext_ref):
self._val = ext_ref
#property
def val(self):
return self._val
#val.setter
def val(self, val):
self._val = val
class propA(propParent):
# Further definitions here
pass
class propB(propParent):
# Further definitions here
pass
class C():
A = propA()
B = propB()
def __init__(self):
class c_struct(Structure):
_fields_ = [('_A', c_int), ('_B', c_int)]
self._c_struct = c_struct()
self.A.set_ref(self._c_struct._A)
self.B.set_ref(self._c_struct._B)
But it appears that self._c_struct._A returns a Python integer object and not the reference to the _A internal c_int object of the structure. How do I connect a property to a sub property of another property in the same class?
This'd seem like a fit for a descriptor, which the field c_struct._A is also:
In [3]: c_struct._A
Out[3]: <Field type=c_int, ofs=0, size=4>
In [4]: c_struct._A.__get__
Out[4]: <method-wrapper '__get__' of _ctypes.CField object at 0x7f967303cf48>
which is why it returns an int when accessed through a c_struct instance instead of the field itself. Start by defining one suitable for your use case:
class prop:
def __init__(self, name):
self.name = name
def __repr__(self):
return 'prop({!r})'.format(self.name)
def __get__(self, instance, owner):
if not instance:
return self
return getattr(instance._c_struct, '_{}'.format(self.name))
def __set__(self, instance, value):
setattr(instance._c_struct, '_{}'.format(self.name), value)
or just
def prop(name):
name = '_{}'.format(name)
return property(
lambda self: getattr(self._c_struct, name),
lambda self, value: setattr(self._c_struct, name, value))
Then define your original class:
class C:
A = prop('A')
B = prop('B')
def __init__(self):
class c_struct(Structure):
_fields_ = [('_A', c_int), ('_B', c_int)]
self._c_struct = c_struct()
Test:
In [36]: c = C()
In [37]: c.A
Out[37]: 0
In [38]: c._c_struct._A
Out[38]: 0
In [39]: c.A = 12
In [40]: c._c_struct._A
Out[40]: 12
For extra credit and if you're using a recent enough version of Python you can use object.__set_name__() to remove the need to duplicate the name, without having to resort to a meta class:
class prop:
def __init__(self, name=None):
self.name = name
def __set_name__(self, owner, name):
if self.name is None:
self.name = name
...
and then define C as:
class C:
A = prop()
B = prop()
...
class DockerEngine(Device):
def __init__(self):
super(DockerInfo, self).__init__()
self.docker_id = None
self.host_ip_address = None
self.total_containers = 0
self.running_containers = 0
self.paused_containers = 0
self.stopped_containers = 0
#property
def host_ip_address(self):
return self._host_ip_address
#host_ip_address.setter
def host_it_address(self, ip):
self._host_ip_address = ip
#property
def docker_id(self):
return self._docker_id
#docker_id.setter
def docker_id(self, id):
self._docker_id = id
When I initialize a DockerEngine object, it complains that in __init__ self.host_ip_address, can't set attribute.
Your code has a typo
Change host_it_address to host_ip_address.
#host_ip_address.setter
def host_it_address(self, ip): <--- WRONG FUNCTION NAME
self._host_ip_address = ip