Shared class int across subclasses - python

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.

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))

How to get object attributes to update dynamically in 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

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

using variables in class functions in another class (python)

I want to use the variables i have declared inside a function in one class, in another class.
For example i want to use the variable "j" in another class. Is it possible? (I read somewhere that it might have something to do with instance variables but fully couldn't understand the concept).
class check1:
def helloworld(self):
j = 5
class check1:
def helloworld(self):
self.j = 5
check_instance=check1()
print (hasattr(check_instance,'j')) #False -- j hasn't been set on check_instance yet
check_instance.helloworld() #add j attribute to check_instance
print(check_instance.j) #prints 5
but you don't need a method to assign a new attribute to a class instance...
check_instance.k=6 #this works just fine.
Now you can use check_instance.j (or check_instance.k) just like you would use any other variable.
This may seems a little bit like magic until you learn that:
check_instance.helloworld()
is completely equivalent to:
check1.helloworld(check_instance)
(If you think about it a little bit, that explains what the self parameter is).
I'm not completely sure what you're trying to achieve here -- There are also class variables which are shared by all instances of the class...
class Foo(object):
#define foolist at the class level
#(not at the instance level as self.foolist would be defined in a method)
foolist=[]
A=Foo()
B=Foo()
A.foolist.append("bar")
print (B.foolist) # ["bar"]
print (A.foolist is B.foolist) #True -- A and B are sharing the same foolist variable.
j cannot be seen by another class; however, I think you meant self.j, which can.
class A(object):
def __init__(self, x):
self.x = x
class B(object):
def __init__(self):
self.sum = 0
def addA(self, a):
self.sum += a.x
a = A(4)
b = B()
b.addA(a) # b.sum = 4
Using class inheritane it is very easy to "share" instance variables
example:
class A:
def __init__(self):
self.a = 10
def retb(self):
return self.b
class B(A):
def __init__(self):
A.__init__(self)
self.b = self.a
o = B()
print o.a
print o.b
print o.retb()

Categories