Class callback doesn't work after defining variables - python

I am trying to call back a class method within another class. It works fine if I don't define the variable x,y,z (see commented portion) while creating objects. However, if I explicitly define the variable names, it doesn't work. Wondering what's making this happen?
class ClassA():
def __init__(self, a, b):
self.a = a
self.b = b
def method_a(self):
return f'A, method_a, {self.a}, {self.b}'
def callback_method(self, *args):
obj = ClassB(*args)
return obj.method_b()
class ClassB():
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def method_b(self):
return f'B, method_b, {self.x}, {self.y}, {self.z}'
A = ClassA(a=1, b=2)
print(A.method_a())
# A, method_a, 1, 2
B = ClassB(x=3, y=4, z=5)
print(B.method_b())
# B, method_b, 3, 4, 5
print(A.callback_method(10, 11, 12))
# B, method_b, 10, 11, 12
# print(A.callback_method(x=10, y=11, z=12)) # <------ doesn't work

You defined the callback_method to only accept positional arguments with *args but no keyword arguments.
Instead you can make it accept both & pass it on to ClassB in order to make sure you can call it with either positional or keyword arguments:
class ClassA():
...
def callback_method(self, *args, **kwargs):
obj = ClassB(*args, **kwargs)
return obj.method_b()
Result:
print(A.callback_method(x=10, y=11, z=12)) # Output: 'B, method_b, 10, 11, 12'

Related

How do I create a representation when my constructor has added kwargs?

How do I create a representation when my constructor has added kwargs?
class Thing:
def __init__(self, a, b, **kwargs):
self.a = a
self.b = b
self.__dict__.update(kwargs)
# def __repr__(self):
# return ???
thing1 = Thing(6, 5, color="red")
Assuming your exact scenario (i.e. every parameter is assigned to an attribute as-is), this should work:
class Thing:
def __init__(self, a, b, **kwargs):
self.a = a
self.b = b
self.__dict__.update(kwargs)
def __repr__(self):
argnames = self.__init__.__code__.co_varnames[1:self.__init__.__code__.co_argcount]
attrdict = dict(self.__dict__)
args = [repr(attrdict.pop(k)) for k in argnames]
kwargs = [f"{k}={repr(v)}" for k, v in attrdict.items()]
return f"{self.__class__.__name__}({', '.join(args + kwargs)})"
thing1 = Thing(6, 5, color="red")
print(repr(thing1))
Note that it does not detect cycles, and it depends on correct representation of each attribute's value.
Of course, you can also simplify the code by hardcoding the argument names, since you know them:
def __repr__(self):
argnames = ["a", "b"]
# ...

Python method that returns instance of class or subclass while keeping subclass attributes

I'm writing a Python class A with a method square() that returns a new instance of that class with its first attribute squared. For example:
class A:
def __init__(self, x):
self.x = x
def square(self):
return self.__class__(self.x**2)
I would like to use this method in a subclass B so that it returns an instance of B with x squared but all additional attributes of B unchanged (i. e. taken from the instance). I can get it to work by overwriting square() like this:
class B(A):
def __init__(self, x, y):
super(B, self).__init__(x)
self.y = y
def square(self):
return self.__class__(self.x**2, self.y)
If I don't overwrite the square() method, this little code example will fail because I need to pass a value for y in the constructor of B:
#test.py
class A:
def __init__(self, x):
self.x = x
def square(self):
return self.__class__(self.x**2)
class B(A):
def __init__(self, x, y):
super(B, self).__init__(x)
self.y = y
#def square(self):
# return self.__class__(self.x**2, self.y)
a = A(3)
a2 = a.square()
print(a2.x)
b = B(4, 5)
b2 = b.square()
print(b2.x, b2.y)
$ python test.py
9
Traceback (most recent call last):
File "test.py", line 20, in <module>
b2 = b.square()
File "test.py", line 6, in square
return self.__class__(self.x**2)
TypeError: __init__() takes exactly 3 arguments (2 given)
Overwriting the method once isn't a problem. But A potentially has multiple methods similar to square() and there might be more sub(sub)classes. If possible, I would like to avoid overwriting all those methods in all those subclasses.
So my question is this:
Can I somehow implement the method square() in A so that it returns a new instance of the current subclass with x squared and all other attributes it needs for the constructor taken from self (kept constant)? Or do I have to go ahead and overwrite square() for each subclass?
Thanks in advance!
I'd suggest implementing .__copy__() (and possibly .__deepcopy__ as well) methods for both classes.
Then your squared can be simple method:
def squared(self):
newObj = copy(self)
newObj.x = self.x **2
return newObj
It will work with inheritance, assuming all child classes have correctly implemented __copy__ method.
EDIT: fixed typo with call to copy()
Full working example:
#test.py
from copy import copy
class A:
def __init__(self, x):
self.x = x
def square(self):
newObj = copy(self)
newObj.x = self.x **2
return newObj
def __copy__(self):
return A(self.x)
class B(A):
def __init__(self, x, y):
super(B, self).__init__(x)
self.y = y
def __copy__(self):
return B(self.x, self.y)
a = A(3)
a2 = a.square()
print(a2.x)
b = B(4, 5)
b2 = b.square()
print(b2.x, b2.y)
check if the object contains y then return the right class instance:
class A:
x: int
def __init__(self, x):
self.x = x
def square(self):
if hasattr(self, 'y'):
return self.__class__(self.x ** 2, self.y)
return self.__class__(self.x**2)
class B(A):
y: int
def __init__(self, x, y):
super(B, self).__init__(x)
self.y = y
# def square(self):
# return self.__class__(self.x**2, self.y)

Is there a clean Pythonic way to override default method args in a subclass in Python?

I am inheriting from a 3rd party class and want to change the default value on one of the class's methods. I can think of a few ways of doing this, but none feel clean.
So, if the base class is
class Base:
def foo(self, a=1, b=2, c=3):
return a, b, c
I want a class MyClass(Base) where b defaults to 7 instead. So,
my_obj = MyClass()
my_obj.foo() # (1, 7, 3)
my_obj.foo(b=5) # (1, 5, 3)
my_obj.foo(2, 4) # (2, 4, 3)
my_obj.foo(8) # (8, 7, 3)
I've been able to achieve the desired behavior by manually modifying args and kwargs, but it doesn't feel clean:
class OptionOne(Base):
def foo(self, *args, **kwargs):
# b is the second argument, so it is an arg if and only if there are 2 or more args
if len(args) < 2:
# If b wasn't passed as an arg, update kwargs to default b to 7.
kwargs['b'] = kwargs.get('b', 7)
return super().foo(*args, **kwargs)
Try/excepting a type error seems a little neater, but it isn't that readable:
class OptionTwo(Base):
def foo(self, *args, **kwargs):
try:
return super().foo(b=7, *args, **kwargs)
except TypeError:
# If b was already specified (as an arg or kwarg)
# "TypeError: foo() got multiple values for keyword argument 'b'"
# will be raised, so we don't override b in this case.
return super().foo(*args, **kwargs)
Is there a cleaner way of doing this that I am missing?
Maybe try something like the below. This is basically overwriting the default value of b in the constructor of the subclass.
class Base:
def __init__(self, a=1, b=2, c=3):
self.a = a
self.b = b
self.c = c
def print_me(self):
print(self.a, self.b, self.c)
class MyClass(Base):
def __init__(self, a=1, b=7, c=3):
super(MyClass, self).__init__(a, b, c)
test1 = Base()
test1.print_me()
test2 = MyClass()
test2.print_me()
test3 = MyClass(0, 1)
test3.print_me()
test4 = MyClass(b=5)
test4.print_me()
Output:
1 2 3
1 7 3
0 1 3
1 5 3
You can manually change the defaults after defining a new method. It's not great, since defaults are stored only as a tuple without any explicit reference to the parameters they apply to.
class OptionTwo(Base):
def foo(self, a, b, c):
return super().foo(a, b, c)
foo.__defaults__ = (Base.foo.__defaults__[:1]
+ (7,)
+ Base.foo.__defaults__[2:])
On the plus side, this is equivalent to setting the default values in the definition of foo itself, but doesn't require you to actually know the old defaults.
You could also abstract this to a decorator if you like.
def update_defaults(src, i, v):
def _decorator(f):
f.__defaults__ = src.__defaults__[:i] + (v,) + src.__defaults[i+1:]
return f
return _decorator
class OptionTwo(Base):
#update_defaults(Base.__defaults__, 1, 7)
def foo(self, a, b, c):
return super().foo(a, b, c)

Pass class as function argument and reset class repeatedly within in function

In my python script I have defined a class similar to the following (admittedly bogus) class:
import copy
class Example:
def __init__(self, a, b):
self.a = a
self.b = b
self.__default__ = copy.deepcopy(self.__dict__)
self.t = 0
self.d = False
def do(self):
self.a += self.b - self.t
self.t += 1
if self.t == self.b:
self.d = True
return self.a
def reset(self):
self.__init__(**self.__default__)
Now, I would like to pass an instance of this class to my main function and repeatedly reset the instance to its default state. Despite having a look here, here, here and here, I couldn't get it going. The working example below gives the desired result, yet resets the instance in the main function explicitly. The dysfunctional example is one of my many tries to make it work using a reset method.
# working example:
def main(x):
agg = []
for i in range(x):
klass = Example(1, 3)
while not klass.d:
a = klass.do()
agg.append(a)
return agg
# dysfunctional example:
def main2(klass, x):
agg = []
for i in range(x):
klass.reset()
while not klass.d:
a = klass.do()
agg.append(a)
return agg
Then main(5) gives
res = main(5)
print(res)
>>> [4, 6, 7, 4, 6, 7, 4, 6, 7, 4, 6, 7, 4, 6, 7]
whereas
ex = Example(1, 3) # default state
res = main2(ex, 5)
print(res)
throws the error: TypeError: __init__() got an unexpected keyword argument '__default__'
Since I would like to avoid having to re-instantiate the class in the main script anew for different reasons, I would be grateful if someone could help me out with the reset method.
How about something like that:
class Example:
def __init__(self, *args, **kwargs):
"""This stores the default state then init the instance using this default state"""
self.__default_args__ = args
self.__default_kwargs__ = kwargs
self.init(*args, **kwargs)
def do(self):
"""Do whatever you want """
self.a += self.b - self.t
self.t += 1
if self.t == self.b:
self.d = True
return self.a
def init(self, a, b):
"""Inits the instance to a given state"""
self.a = a
self.b = b
self.t = 0
self.d = False
return self
def reset(self):
"""Resets the instance to the default (stored) state"""
return self.init(*self.__default_args__, **self.__default_kwargs__)
Here is an implementation using context manager:
class Example:
def __init__(self, a, b):
self.a = a
self.b = b
self.t = 0
self.d = False
def do(self):
self.a += self.b - self.t
self.t += 1
if self.t == self.b:
self.d = True
return self.a
def __enter__(self):
self._a = self.a
self._b = self.b
def __exit__(self, *args):
self.t = 0
self.d = False
self.a = self._a
self.b = self._b
def main2(klass, x):
agg = []
for i in range(x):
with klass:
while not klass.d:
a = klass.do()
agg.append(a)
return agg
ex = Example(1, 3)
res = main2(ex, 5)
print(res)
A reusable way to do this would be to implement a Resettable class to be inherited.
Resettable
class Resettable:
def __init__(self, *args, **kwargs):
self.__args__ = args
self.__kwargs__ = kwargs
def reset(self):
self.__init__(*self.__args__, **self.__kwargs__)
Usage
Using a property to define an attribute that entirely depends on other attributes will also smoothen the process of resetting. This idea of having a single source of truth is generally a helpful paradigm for states that need to go back and forth in time.
class Example(Resettable):
def __init__(self, a=0):
self.a = a
super().__init__(a)
def do(self):
self.a += 1
return self.a
#property
def d(self):
return self.a > 3 # or any other condition
def main(instance, x):
agg = []
for _ in range(x):
instance.reset()
while not instance.d:
a = instance.do()
agg.append(a)
return agg
print(main(Example(), 3)) # [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
The underlying assumption of the Resettable class is that the arguments passed to the constructor contain all the information needed to reset, using properties make that assumption easier to satisfy.

Python: Creating a new instance of itself from a base class (possibly via reflection?)

I have code that looks like the following and I'm trying to refactor it.
# Abstract, do not instantiate Base
class Base:
...
class A(Base):
...
def func():
x = A(1, 2, 3)
x.x()
y = A(4, 5, 6)
y.y()
z = A(7, 8, 9)
z.z()
class B(Base):
...
def func():
x = B(1, 2, 3)
x.x()
y = B(4, 5, 6)
y.y()
z = B(7, 8, 9)
z.z()
class C(Base):
...
def func():
x = C(1, 2, 3)
x.x()
y = C(4, 5, 6)
y.y()
z = C(7, 8, 9)
z.z()
Due to the problem needing recursion to solve, the classes will recursively call themselves through func() (so A will make some A's that will make some more A's until the recursive function finishes), and the best way for me to refactor this is to dynamically create the class. If there was a way to get the final runtime class, I could refactor it to look like:
# Abstract, do not instantiate Base
class Base:
...
def func():
constructor = # Magic here to get the actual class type's constructor
# I'm guessing reflection would do something like this...? I don't know.
x = constructor.__init__(1, 2, 3) # If this is an A, make an A... if it's a B, make a B... etc
x.x()
y = constructor.__init__(4, 5, 6)
y.y()
z = constructor.__init__(7, 8, 9)
z.z()
class A(Base):
...
class B(Base):
...
class C(Base):
...
I don't know much about Python and it's reflection capability, or if there's some other better way of doing this.
Can this be done?
Is there a better way than reflection?
Note: the constructor args are just arbitrary placeholders I made up.
Note 2: performance is irrelevant for this, I am just interested in if this is possible, and if so -- how?
Edit: Based on the answered solution, this code works:
class Test:
def __init__(self):
print("Made BaseClass")
def func(self):
rofl = self.__class__()
print(rofl.__class__)
class SubClass(Test):
def __init__(self):
super().__init__()
print("Made SubClass")
a = SubClass()
a.func()
You can use the __class__ attribute of the value:
class A(object):
pass
class B(A):
pass
b = B()
c = b.__class__()
I think what you actually want here is to use class methods rather than regular methods.
You don't appear to be using self anywhere. In fact, you don't even really need an instance to exist for these functions to work. So:
class Test:
def __init__(self):
print("Made BaseClass")
#classmethod
def func(cls):
rofl = cls()
print(rofl.__class__)
class SubClass(Test):
def __init__(self):
super().__init__()
print("Made SubClass")
Or, for your real case:
class Base:
def __init__(self, *args): pass
def x(self): pass
def y(self): pass
def z(self): pass
#classmethod
def func(cls):
x = cls(1, 2, 3)
x.x()
y = cls(4, 5, 6)
y.y()
z = cls(7, 8, 9)
z.z()
class A(Base): pass
class B(Base): pass
class C(Base): pass
a = A()
a.func() # calls Base.func, constructs and uses A instances
A.func() # also calls Base.func, constructs and uses A instances

Categories

Resources