Python: detect changes in a subclass - python-3.x

Let's consider the following example:
class SubClass:
def __init__(self):
self._param = None
#property
def param(self):
print('read param')
return self._param
#param.setter
def param(self, value):
print('set param')
self._param = value
class MainClass:
def __init__(self):
self._var = SubClass()
#property
def var(self):
print('read var')
return self._var
#var.setter
def var(self, value):
print('set var')
self._var = value
If I do:
cls = MainClass()
cls.var.param = 3
I obtain:
'read var'
'set param'
How can I make MainClass aware that var.param has changed?
Useful additional info: consider that in my actual code param is not a scalar but an array with hundreds of elements, so I would like to avoid to create a copy and then just compare them. Moreover, param and var are not the only properties.

One approach is to pass a method from MainClass as an argument when instantiating SubClass, and have SubClass call it whenever it changes a variable:
class SubClass:
def __init__(self, changed):
self._param = None
self.changed = changed
#property
def param(self):
print('read param')
return self._param
#param.setter
def param(self, value):
print('set param')
self._param = value
self.changed('param')
class MainClass:
def __init__(self):
self._var = SubClass(self.changed)
def changed(self, name):
print("Subclass changed: ", name, getattr(self._var, name))
#property
def var(self):
print('read var')
return self._var
#var.setter
def var(self, value):
print('set var')
self._var = value
m = MainClass()
m.var.param = 'test'

Related

Question about setter and calling MyClass()

I have a class like this:
class A:
b = ""
#property
some_property(self):
raise Error()
#some_property.setter
def some_property(self, some_property_var)
self.b = some_property_var
and i want to do A(some_property='A'), but i can't because
TypeError: A() takes no arguments
. Is there any way to make it work?
I think this is a better example of how to demonstrate what you are trying to do;
#set a property in a class in python
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
#property
def name(self):
return self._name
#name.setter
def name(self, name):
self._name = name
#property
def age(self):
return self._age
#age.setter
def age(self, age):
self._age = age
p = Person("John", 36)
So in your case;
class A:
def __init__(self, b):
self.b = b
#property
def b(self):
return self._b
#b.setter
def b(self, b):
self._b = b
a = A(1)
print(a.b)
Based on your comments;
#create a class that takes keyword arguments
class A:
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
a = A(a=1, b=2)

subclass for NameError: name 'side_length' is not defined

class Polygon():
def __init__(self, side_lengths):
self.side_lengths = side_lengths
def perimeter(self):
return sum(self.side_lengths)
def __str__(self):
side_lengths = len(self.side_lengths)
return 'Polygon with {} sides'.format(side_lengths)
class Triangle(Polygon):
def __init__(self, side_lengths):
super().__init__(side_lengths)
def area(self):
s = sum(self.side_lengths)/2
a = self.side_lengths[0]
b = self.side_lengths[1]
c = self.side_lengths[2]
return float((s*(s-a)*(s-b)*(s-c)) ** 0.5)
def __str__(self):
return 'Triangle, Area: {:.2f} units^2, Perimeter: {:.2f} units'.format(self.area(), self.perimeter())
class Rectangle(Polygon):
def __init__(self, side_lengths):
super().__init__(side_lengths)
def area(self):
return float(self.side_lengths[0]*self.side_lengths[1])
def __str__(self):
return 'Rectangle, Area: {:.2f} units^2, Perimeter: {:.2f} units'.format(self.area(), self.perimeter())
class Square(Rectangle):
def __init__(self, side_length):
self.side_length = side_length
def area(self):
return float(4*side_length)
def __str__(self):
return 'Square, Area: {:.2f} units^2, Perimeter: {:.2f} units'.format(self.area(), self.perimeter())
For this program, I want to calculate the area of the square when the input of side_length is a float instead of a list, but name error occurs. How to deal with such issue?
class Square(Rectangle):
def __init__(self,side_length):
super().__init__(side_length)
I also tried this method, it does not work as well.

How to Access Outer class variables in Inner Class (Python)?

How to access the variables of the outer class in the Inner Class?
class Student:
def __init__(self,Name,rollNumber):
self.Name=Name
self.rollNumber=rollNumber
self.lap=self.Laptop()
def Show(self):
print(self.Name)
print(self.lap.show())
class Laptop:
def __init__(self):
self.brand = "Mac"
self.cpu = "i9"
self.ram = 16
def show(self):
return self.brand
#staticmethod
def Show():
return s1.Name
s1=Student("Teja",2)
print(s1.Name,s1.rollNumber)
s1.Show()
print(s1.lap.brand)
system=s1.lap
print(system.brand)
print(system.cpu)
print(system.show())
print(system.Show())

Why does one property work and the other not?

It happens that I have two property implementations. but one works and the other does not. That is, that in one it does not even enter the setters.
I gave myself the task of wandering online, and this happens to me is very rare, both are supposed to work.
Do you know why this happens?
Thank you
class QuickTasks():
def __init__(self, name=None, value=None):
self.name = name
self.value = value
#property
def name(self):
return self._name
#name.setter
def name(self, value):
if isinstance(value, str):
self._name = value
else:
raise TypeError("name must be str")
#property
def value(self):
return self._value
#value.setter
def value(self, value):
if isinstance(value, int):
self._value = value
else:
raise TypeError("value must be int")
obj = QuickTasks(name=4, value='j')
print(obj.name)
obj.name = 5
print(obj.name)
################################################################
class TreeNode(object):
def __init__(self, value = None):
self.value = value
self._left_node = None
self._right_node = None
#property
def value(self):
return self._value
#value.setter
def value(self, value):
if isinstance(value, int):
self._value = 8
else:
raise TypeError("value must be int")
def main():
tree_node = TreeNode(3)
#tree_node.value = 3
print (tree_node.value)
if __name__ == '__main__':
print("")
main()
When you hit the line:
obj = QuickTasks(name=4, value='j')
and it assigns to self.name in the QuickTasks initializer, it raises a TypeError, which you don't catch. That bypasses the rest of your code entirely, skipping all uses of the value property (and of TreeNode entirely).

Override abstract setter of property in Python 3

What is the simplest / most pythonic way to override only the setter of an abstract property in Python 3? Variant 3 seems to mean the least effort for the derived class implementor. Is it correct? Does it have disadvantages?
import abc
class A1(metaclass=abc.ABCMeta):
def __init__(self, x, **kwargs):
super().__init__(**kwargs)
self._x = x
#property
def x(self):
return self._x
#x.setter
#abc.abstractmethod
def x(self, value):
self._x = value
class B1(A1):
#property
def x(self):
return super().x
#x.setter
def x(self, value):
print("B1 setter")
super(B1, self.__class__).x.fset(self, value)
b1 = B1(x=1)
b1.x = 3
print(b1.x)
class A2(metaclass=abc.ABCMeta):
def __init__(self, x, **kwargs):
super().__init__(**kwargs)
self._x = x
#abc.abstractmethod
def _get_x(self):
return self._x
#abc.abstractmethod
def _set_x(self, value):
self._x = value
x = property(_get_x, _set_x)
class B2(A2):
def _get_x(self):
return super()._get_x()
def _set_x(self, value):
print("B2 setter")
super()._set_x(value)
x = property(_get_x, _set_x)
b2 = B2(x=1)
b2.x = 3
print(b2.x)
class A3(metaclass=abc.ABCMeta):
def __init__(self, x, **kwargs):
super().__init__(**kwargs)
self._x = x
def _get_x(self):
return self._x
#abc.abstractmethod
def _set_x(self, value):
self._x = value
x = property(
lambda self: self._get_x(),
lambda self, value: self._set_x(value))
class B3(A3):
def _set_x(self, value):
print("B3 setter")
super()._set_x(value)
b3 = B3(x=1)
b3.x = 3
print(b3.x)
So, yes, you listed a lot of ways in there - and although the one that requires more code is your variant 3, the most straighforard, least surprising way to do it is your variant 1 -
It just works, and is perfectly readable, no surprises - and there seems to be no simpler way than calling fget explicitly there.

Resources