Defining Python class instance attributes - python

Given a class definition that allows 3 possible inputs:
class FooBar(object):
def __init__(self, x=None, y=None, z=None):
if x is not None:
self.x = x
elif if y is not None:
self.y = y
elif if z is not None:
self.z = z
else:
raise ValueError('Please supply either x,y or z')
This 3 inputs are related each other, lets say:
x = .5*y = .25*z
This also implies:
y = .5*z = 2*x
and
z = 2*y = 4*x
When creating a instance of FooBar(), the user need to supply one of those and the __init__ takes care of it.
Now I would like to do the following
If any one of the 3 variables are changed the others change following the relationship.
To try to accomplish that I did:
#property
def x(self):
return self._x
#x.setter
def x(self, value):
self._x = value
self._y = 2*self._x
self._z = 4*self._x
And to the others:
#property
def y(self):
return self._y
#y.setter
def y(self, value):
self._y = value
self._x = .5*self._y
self._z = 2*self._y
#property
def z(self):
return self._z
#z.setter
def z(self, value):
self._z = value
self._x = .25*self._z
self._y = .5*self._z
Is this the correct approach?

I think you make this more complicated than you have to. If the variables are related, and one can fully be determined by the other, there is no need to store three variables. You can store one variable, and dynamically calculate the others. Like:
class Foo(object):
def __init__(self, x=None, y=None, z=None):
if x is not None:
self.x = x
elif x is not None:
self.y = y
elif z is not None:
self.z = z
else:
raise ValueError('Provide an x, y, or z value.')
#property
def x(self):
return self._x
#x.setter
def x(self, value):
self._x = x
#property
def y(self):
return self._x / 2.0
#y.setter
def y(self, value):
self._x = 2 * value
#property
def z(self):
return self._x / 4.0
#z.setter
def z(self, value):
self._x = 4 * value
We thus store only a _x attribute on the class, and all the rest of the getters and setters, use the _x attribute (we can of course use _y or _z instead).
Furthermore something that is not very elegant is that a programmer can instantiate a Foo(x=1, y=425). As you can see, that means that it contains inconsistency. Perhaps it is worth raising an error in that case.
You can ensure that you only have one parameter provided by adding the following check in the init module:
class Foo(object):
def __init__(self, x=None, y=None, z=None):
data = [i for i in [x, y, z] if i is not None]
if len(data) > 1:
raise ValueError('Multiple parameters provided.')
if x is not None:
self.x = x
elif x is not None:
self.y = y
elif z is not None:
self.z = z
else:
raise ValueError('Provide an x, y, or z value.')
# ...
Here we thus construct a list of all non-None values, if there is more than one, the programmer provided two or more values, and then it is better to raise an exception.

Related

Simple python inheritance with singleton class failing with no stack trace

class B:
#property
def x(self):
print('x getter')
return self.x
#x.setter
def x(self, x) -> None:
print('x setter')
self.x = x + 1
class A(B):
__instance = None
def __new__(self):
''' Virtually private constructor '''
if not A.__instance:
A.__instance = object.__new__(self)
A.__instance.__setup()
return A.__instance
def __setup(self):
self.x = 10
def minus(self):
self.x -= 3
a1 = A()
Class A is a singleton class.
I'm not sure what is causing the program to fail as there is no stack trace and it just fails.
You are not using #property and setter correctly. You need a different name for the actual underlying property:
class B:
#property
def x(self):
print('x getter')
return self._x
#x.setter
def x(self, x) -> None:
print('x setter')
self._x = x + 1

Subclassing a numpy ndarray is not working properly

I am just trying to subclass ndarray to create a special 2x1 vector with attributes x and y.
class Vec(np.ndarray):
def __new__(cls, x:int, y:int):
# print('In __new__ with class %s' % cls)
obj = super().__new__(cls, (2,), np.int16,
np.asarray((x,y)), offset=0, strides=None, order=None)
obj.x = x
obj.y = y
return obj
def __array_finalize__(self, obj):
if obj is None: return
self.x = getattr(obj, 'x', None)
self.y = getattr(obj, 'y', None)
a = Vec(1,2)
b = Vec(3,4)
c = a + b
print(c.x, c.y) # results 1 2
what is going wrong?
The problem is that your x and y are just another fields of the Vec object alongside your 2d array. And numpy doesn't know about them, hence doesn't do anything when calling the __add__ a.k.a. +. So x and y should be values in the array, and you can make properties out of them:
class Vec(np.ndarray):
def __new__(cls, x:int, y:int):
obj = super().__new__(cls, (2,), np.int16, np.asarray((x,y)),
offset=0, strides=None, order=None)
obj.x = x
obj.y = y
return obj
#property
def x(self):
return self[0]
#property
def y(self):
return self[1]
#x.setter
def x(self, x):
self[0] = x
#y.setter
def y(self, y):
self[1] = y
a = Vec(1,2)
b = Vec(3,4)
c = a + b
print(c.x, c.y) # results 4 6

Changing variables from one module doesn't affect the variables passed to another class

I have a class in one file1, which uses variables (that are constantly changing) from file2 but the changes are not being applied in the original class:
class element():
def __init__(self, x, y)
animations = [pic1, pic2, pic3]
self.x = x
self.y = y
self.pos = pos
self.atts = atts
def update()
...
file2:
x = 100
y = 100
pos = 10
atts = ['red', '#122']
el1 = element(x,y)
...
Value types such as the parameters used in x and y are copied when passed as an attribute so if you change them in your second module, the change will not be applied in your object, so you will need to either update them again:
x = 100
y = 100
pos = 10
atts = ['red', '#122']
el1 = element(x, y, pos, atts)
x = 50
el1.x = x
Or you can use a reference type (another object) that you can share between the two files to wrap the value types:
class ElementConfig():
def __init__(self, x, y, pos, atts):
self._x = x
self._y = y
self._pos = pos
self._atts = atts
#property
def x(self):
return self._x
#x.setter
def x(self, value):
self._x = value
#property
def y(self):
return self._y
#y.setter
def y(self, value):
self._y = value
#property
def pos(self):
return self._pos
#pos.setter
def pos(self, value):
self._pos = value
#property
def atts(self):
return self._atts
#atts.setter
def atts(self, value):
self._atts = value
class Element():
def __init__(self, config: ElementConfig):
self._config = config
#property
def config(self):
return self._config
And in the other module:
from file1 import ElementConfig, Element
elementConfig = ElementConfig(100, 100, 10, ['red', '#122'])
el1 = Element(elementConfig)
print(el1.config.x)
The result is 100
elementConfig.x = 200
print(el1.config.x)
Now the result is 200

Why does PyCharm raise a warning when using #property here?

In tutorials I have seen two types of instance attribute naming for the purpose of using #property. Here is code showing examples of both. They also seem to work differently.
class A:
def __init__(self, x):
self.x = x
#property
def x(self):
return self.__x
#x.setter
def x(self, x):
if x > 1000:
self.__x = 1000
else:
self.__x = x # Instance attribute __x defined outside __init__
class B:
def __init__(self, x):
self._x = x
#property
def x(self):
return self._x
#x.setter
def x(self, x):
if x > 1000:
self._x = 1000
else:
self._x = x
a = A(9999)
print(a.x) # -> 1000
b = B(9999) # -> 9999
print(b.x)
b.x = 9999
print(b.x) # -> 1000
I like the behaviour of class A better as it seems that the #x.setter is used immediately in __init__, however that piece of code gives me a warning in PyCharm (I have it as a comment). Why would there be a warning if that is the proper use of a Python's property setter? There are no warnings in class B. Could I somehow call #x.setter in __init__ the same way as in class A without a warning?
It seems to be a bug in PyCharm: https://youtrack.jetbrains.com/issue/PY-25263.
A temporary solution I found was to add self._x = None in the __init__. So the code would be:
class A:
def __init__(self, x):
self._x = None
self.x = x
#property
def x(self):
return self._x
#x.setter
def x(self, x):
if x > 1000:
self._x = 1000
else:
self._x = x
a = A(9999)
print(a.x) # -> 1000

Is it possible to instantiate an object of one class in two different ways?

Here is an example which creates a point as p=Point(x, y). Assume that I have some array ppp=(x, y) where x and y are numbers and I want to make it of class Point but in the way: p=Point(ppp). I can do either one or another way but not both simultaneously. Is it possible to have both ways?
There are two different ways to acquire the result, the first is to analyse arguments that you pass to __init__ and in dependence of their quantity and type - choose a decision what are you using to instantiate class.
class Point(object):
x = 0
y = 0
def __init__(self, x, y=None):
if y is None:
self.x, self.y = x, x
else:
self.x, self.y = x, y
The other decision is to use classmethods as instantiators:
class Point(object):
x = 0
y = 0
#classmethod
def from_coords(cls, x, y):
inst = cls()
inst.x = x
inst.y = y
return inst
#classmethod
def from_string(cls, x):
inst = cls()
inst.x, inst.y = x, x
return inst
p1 = Point.from_string('1.2 4.6')
p2 = Point.from_coords(1.2, 4.6)
If you know that you have a tuple/list while creating the instance, you can do: p = Point(*ppp), where ppp is the tuple.
class Point:
def __init__(self, x, y=None):
if isinstance(x, tuple):
self.x, self.y = x
else:
self.x = x
self.y = y
Yes:
class Point(object):
def __init__(self, x, y=None):
if y is not None:
self.x, self.y = x, y
else:
self.x, self.y = x
def __str__(self):
return "{}, {}".format(self.x, self.y)
print Point(1,2)
# 1, 2
print Point((1,2))
# 1, 2
I would guess that your looking for a way to overload your constructor, as is common in statically typed languages such as C++ and Java.
This is not possible in Python. What you can do is provide different keyword argument combinations, something like:
class Point(object):
def __init__(self, x=None, y=None, r=None, t=None):
if x is not None and y is not None:
self.x = x
self.y = y
elif r is not None and t is not None:
# set cartesian coordinates from polar ones
Which you would then use as:
p1 = Point(x=1, y=2)
p2 = Point(r=1, t=3.14)

Categories