How to get object attributes to update dynamically in Python - python

I'd like to create a class that has 2 input attributes and 1 output attribute such that whenever one of the input attributes are modified the output attribute is modified automatically
I've tried defining the attributes as instance variables within and outside the constructor function but in either case, after instantiating the object, the output attribute remains fixed at the value set at the moment of instantiation
class Example():
def __init__(self,n):
self.name=n
inA=1
inB=1
if inA==1 and inB==1:
outA=1
else:
outA=0
when instantiated outA is set to 1 as expected
but if I try to update:
object.inA=0
object.outA remains 1 whereas I need it to be updated to 0
Trying to avoid the use of functions if possible. New to python and OOP so sorry if this question is nonsensical or has an obvious answer

If you want instance attributes that depend on other instance attributes, properties are the way to go.
class Example:
def __init__(self, n):
self.name = n
self.inA = 1
self.inB = 1
#property
def outA(self):
return self.inA and self.inB
You access outA like a regular instance attribute, obj.outA.
>>> my_obj = Example("example")
>>> my_obj.outA
1
Changing the attributes inA and inB affect outA.
>>> my_obj.inA = 0
>>> my_obj.outA
0

You can create a function in the class and some other minor changes:
class Example():
def __init__(self,n):
self.name=n
self.inA=1
self.inB=1
def f(self):
if self.inA==1 and self.inB==1:
self.outA=1
else:
self.outA=0
To call it:
a = Example('foo')
a.inA = 0
a.f()
print(a.outA)
Output:
0
As you can see, taking out:
a.f()
line would make it give an error:
AttributeError: 'Example' object has no attribute 'outA'

Do you want it to return your output?
Expanding on U9-Forward's answer:
class Example():
def __init__(self,n):
self.name = n
self.inA = 1
self.inB = 1
def f(self):
return self.inA and self.inB

Related

Why is super() not behaving like I expected when assigning to a class variable of the base class?

I am attempting to experiment with classes so I can better understand what they do. I wanted to build a counter which records the number of instances of a class (MyClass):
class ObjectCounter: # I want this to count the number of objects in each class
myclass_obj_count = 0
class MyClass(ObjectCounter):
def __init__(self):
super().myclass_obj_count += 1 # AttributeError: 'super' object has no attribute 'myclass_obj_count'
m1 = MyClass()
m2 = MyClass()
m3 = MyClass()
print(ObjectCounter.myclass_obj_count)
Since that didn't work, I looked online for someone trying to do the same thing. Here is some code I found online. This works as expected, and I feel like I have a basic understanding of how this works. This is a better solution to the task I was attempting, but I'm not satisfied because I want to know how super() works.
class geeks:
counter = 0
def __init__(self):
geeks.counter += 1
g1 = geeks()
g2 = geeks()
g3 = geeks()
print(geeks.counter) # this gives an expected result
Therefore, I tried this instead:
class ObjectCounter: # I want this to count the number of objects in each class
myclass_obj_count = 0
def add_myclass(self):
self.myclass_obj_count += 1
class MyClass(ObjectCounter):
def __init__(self):
super().add_myclass()
my_class_1 = MyClass()
my_class_2 = MyClass()
my_class_3 = MyClass()
print(ObjectCounter.myclass_obj_count) # expected output: 3
Instead of getting the expected output of 3, I got an output of 0. Why is this happening?
First, be aware of the += operator; it's implementation is quite subtle:
a += b
becomes
a = a.__iadd__(b)
This perhaps strange definition allows python to support it even for immutable types (like strings).
Note what happens when used for a class variable that is referred to by the alias self
class ObjectCounter: # I want this to count the number of objects in each class
myclass_obj_count = 0
def add_myclass(self):
self.myclass_obj_count += 1
# effectively becomes:
# self.myclass_obj_count = self.myclass_obj_count.__iadd__(1)
This will introduce an instance variable of the same name, shadowing the class variable.
You don't even need the subclass to test this:
>>> x = ObjectCounter()
>>> x.add_myclass()
>>> x.add_myclass()
>>> x.add_myclass()
>>> x.myclass_obj_count
3
>>> ObjectCounter.myclass_obj_count
0
Referring to the class variable directly instead of using self fixes this
def add_myclass(self):
ObjectCounter.myclass_obj_count += 1
I'm hesitant to give definite answers of what happens under the hood when class variables, super() and assignments are used, other than it just doesn't work. Perhaps because it would be quite ambiguous of whether or not we are defining class variables or new instance variables.
super() won't let you assign to either;
class ObjectCounter:
myclass_obj_count = 0
def __init__(self):
self.x = 'test'
class MyClass(ObjectCounter):
def __init__(self):
super().__init__()
print(super().myclass_obj_count) # reading works just fine
print(type(super())) # this isn't actually exactly the same as "ObjectCounter"
super().myclass_obj_count = 123 # no good
super().x = 'foo' # also no good.
All in all, for any assignment to class variables you can use the class name itself.

Is there a way to pass a function call to an inner object?

Is there a way in python to pass a function call to an inner object, maybe through a decorator or wrapper? In the example below, class A holds a list of class B objects, and one of the class B objects is selected as the active object. I want class A to function as a passthrough, just identifying which of the class B objects that the call goes to. However, class A doesn't know what type of class it is going to hold beforehand, so I can't just add a set_var function to class A. It has to work for any generic function that class B has. It will only have one type of class in its objects list, so it could take class B as an input when it is instantiated and dynamically create functions, if that's a possibility. The client wouldn't know whether it's dealing with class A or class B. The code below is as far as I got.
class A:
def __init__(self):
self.objects = []
self.current_object = 0
def add_object(self, object):
self.objects.append(object)
class B:
def __init__(self):
self.var = 10
def set_var(self, new_var):
self.var = new_var
a_obj = A()
b_obj1 = B()
b_obj2 = B()
a_obj.add_object(b_obj1)
a_obj.add_object(b_obj2)
a_obj.set_var(100)
You could use the generic __getattr__ to delegate to the wrapped object.
class A:
def __init__(self):
self.objects = []
self.current_object = 0
def add_object(self, obj):
self.objects.append(obj)
self.current_object = obj
def __getattr__(self, name):
return getattr(self.current_object, name)
class B:
def __init__(self):
self.var = 10
def set_var(self, new_var):
self.var = new_var
a_obj = A()
b_obj1 = B()
b_obj2 = B()
a_obj.add_object(b_obj1)
a_obj.add_object(b_obj2)
a_obj.set_var(100)
print(b_obj2.var)
That will print "100".
You will still get an AttributeError if the wrapped object doesn't have the expected method.
It was interesting to look at this, it is intentionally rough but it does indeed allow you to call one the B instance's set_var methods.
The code below uses sets as a quick and dirty way to see the difference in callable methods, and if there is; it sets the attribute based on that name. Binding the method to the A instance.
This would only bind set_var once from the first object given.
def add_object(self, object):
self.objects.append(object)
B_methods = set([m for m in dir(object) if callable(getattr(object, m))])
A_methods = set([m for m in dir(self) if callable(getattr(self, m))])
to_set = B_methods.difference(A_methods)
for method in to_set:
setattr(self, method, getattr(object, method))

Shared class int across subclasses

I want to have a counter which increments every time a subclass is instantiated. How would I achieve this such that the last statement below evaluates to True:
class Abstract(ABC):
counter = 0
class A(Abstract):
pass
class B(Abstract):
pass
a = A()
b = B()
a.counter += 1
b.counter == 1
Currently each subclass gets its own counter, rather than sharing the one outlined in the superclass.
Would this work for you?
global_counter = 0
class Abstract:
def __init__(self):
global global_counter
global_counter += 1
class A(Abstract):
def __init__(self):
super().__init__()
class B(Abstract):
def __init__(self):
super().__init__()
a = A()
b = B()
print(global_counter) # (output: 2)
So I've implemented several different ways to achieve what I wanted:
Using a global keyword as suggested by #nihilok
Creating a custom Counter class to handle the integer value (essentially a fancy int).
using an int type in the super class and having its methods directly reference it using the super class's name instead of simply using the cls passed in during a class method.
My favourite (least amount of extra parts and most canonical to how I tend to write my objects) was the last method. Where the above translates to something like:
lass Abstract(ABC):
counter = 0
#staticmethod
def increment():
Abstract.counter += 1. # instead of cls.counter += 1
class A(Abstract):
pass
class B(Abstract):
pass
a = A()
b = B()
a.increment()
b.increment()
a.counter == b.counter # now true.

Modifying a class attribute from instance, different behaviour depending on type?

If I create a class in Python and I give it a class attribute (this is taken directly from the docs, here), as
class Dog:
tricks = []
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
I see that, as the docs suggest, when doing
d1 = Dog('d1')
d2 = Dog('d2')
d1.add_trick('trick')
print d2.tricks
I get that the trick is added to d2 as well:
['trick']
This is because tricks is a class attribute rather than an instance attribute so gets shared across all instances (correct me if this is not orthodox!).
Now, suppose I do this instead
class Dog:
a = 1
def __init__(self, name):
self.name = name
def improve_a(self):
self.a += 1
and I run
d1 = Dog('d1')
d2 = Dog('d2')
d1.improve_a()
print d1.a, d2.a
this gives me 2 and 1 respectively, namely the count for the second instance has not changed. Why is this, why the behaviour difference?
The int class does not define the += operator (__iadd__ method). That wouldn't make sense because it is immutable.
That's why += defaults to + and then =. reference
self.a += 1 becomes self.a = self.a + 1
Now the first time you call improve_a the following happens:
read class attribute a and put it on the stack
add 1 to the item on the stack
create a new instance attribute a and assign it the value on the stack
That means the class attribute is not changed at all and you add a new instance attribute.
On every subsequent call of improve on the same object the instance attribute is incremented, because attribute lookup starts on the instance dict and will only go to the class dict if that attribute does not exist.
If you do the same with a mutable class which overloads the __iadd__ method you can get different behaviour:
class HasList:
some_list = []
def add_something(self, value):
some_list += [value]
fst = HasList()
sec = HasList()
fst.add_something(1)
fst.add_something(2)
sec.add_something(3)
print(HasList.some_list, fst.some_list, sec.some_list)
You will see that all instances and the class itself still hold the same list. The print shows the same list [1, 2, 3] each time. You can also check that all three lists are identical: fst.some_list is sec.some_list and fst.some_list is HasList.some_list # -> True.
That is because list.__iadd__ just calls list.extend and returns itself inside the method (at least if it was written in python).

Apply a function to all instances of a class

I am looking for a way to apply a function to all instances of a class. An example:
class my_class:
def __init__(self, number):
self.my_value = number
self.double = number * 2
#staticmethod
def crunch_all():
# pseudocode starts here
for instances in my_class:
instance.new_value = instance.my_value + 1
So the command my_class.crunch_all() should add a new attribute new_value to all existing instances. I am guessing I will have to use #staticmethod to make it a "global" function.
I know I could keep track of the instances that are being defined by adding something like my_class.instances.append(number) in __init__ and then loop through my_class.instances, but I had no luck so far with that either. Also I am wondering if something more generic exists. Is this even possible?
Register objects with the class at initialisation (i.e. __init__) and define a class method (i.e. #classmethod) for the class:
class Foo(object):
objs = [] # registrar
def __init__(self, num):
# register the new object with the class
Foo.objs.append(self)
self.my_value = num
#classmethod
def crunch_all(cls):
for obj in cls.objs:
obj.new_value = obj.my_value + 1
example:
>>> a, b = Foo(5), Foo(7)
>>> Foo.crunch_all()
>>> a.new_value
6
>>> b.new_value
8

Categories