Pythonic way to create dynamic class methods - python

I am wondering if the following strategy is a proper/pythonic way to create a dynamic function within a method. The goal is to have a class that can calculate a value based on a complex model defined by FUN(), but I want to be able to change that model within a script without rewriting it, or creating a bunch of different types of classes (since this function is the only thing that I expect to change).
I also read in a response to this question that the way I have it structured may end up being slower? I intend to call setupFunction() 1 to 3 times a simulation (changing the model) and call FUN many thousands of times.
Pseudocode...
class MyClass:
def __init__(self, model = 'A'):
self.setupFunction(model)
# Other Class Stuff...
def setupFunction(self, _model):
if _model == 'A':
def myFunc(x):
# Do something with x, store in result
return result
else:
def myFunc(x):
# Do something different with x
return result
self.FUN = myFunc
# Other Class Methods... some of which may call upon self.FUN
Model1 = MyClass('A')
Model2 = MyClass('B')
print(Model1.FUN(10))
print(Model2.FUN(10))
I have done some minor tests and the above seems to not break upon first glance. I know I could also do something similar by doing the following instead, but then it will have to test for the model on each call to FUN() and I will have many different model cases in the end:
class MyClass():
def __init__(self, model = 'A'):
def FUN(self, x):
if self.model == 'A':
# result = Stuff
else:
# result = Other Stuff
return result
Still new to python, so thanks for any feedback!

Not sure if I understood your question...
What about something like this?
class MyClass():
model_func = {'A' : funca, 'B' : funcb}
def __init__(self, model):
self.func = self.model_func[model]
def funca():
pass
def funcb():
pass
a = MyClass('A')
a.func()
b = MyClass('B')
b.func()
Other option might be something like this (better separation of concerns):
class Base(object):
def __new__(cls, category, *arguments, **keywords):
for subclass in Base.__subclasses__():
if subclass.category == category:
return super(cls, subclass).__new__(subclass, *arguments, **keywords)
raise Exception, 'Category not supported!'
class ChildA(Base):
category = 'A'
def __init__(self, *arguments, **keywords):
print 'Init for Category A', arguments, keywords
def func(self):
print 'func for Category A'
class ChildB(Base):
category = 'B'
def func(self):
print 'func for Category B'
if __name__ == '__main__':
a = Base('A')
a.func()
print type(a)
b = Base('B')
b.func()
print type(b)

You can use __new__, to return different subclasses:
class MyClass():
def __new__(self, model):
cls = {'A': ClassA, 'B': ClassB}[model]
return object.__new__(cls)
class ClassA(MyClass):
def func():
print("This is ClassA.func")
class ClassB(MyClass):
def func():
print("This is ClassB.func")
a = MyClass('A')
a.func()
b = MyClass('B')
b.func()

Related

Recommended way of subclassing a class while maintaining backwards compatibility

Here is the scenario. I have a class Foo that looks like this:
class Foo:
def __init__(self, prop):
self.prop = prop
def method(self):
if self.prop == PROP_VAL_1:
do_something()
elif self.prop == PROP_VAL_2:
do_something_2()
else:
raise ValueError(f"Invalid value {self.prop}")
I want to do a refactor in which I end up with two subclasses of Foo like this:
class FooA(Foo):
def method():
do_something()
class FooB(Foo):
def method():
do_something_2()
However, the issue is that I can't change how Foo is used because there is code currently in use that uses Foo. Is it possible to somehow have the superclass Foo instantiate objects of type FooA and FooB depending on the value of prop? Another thing I want to avoid is having Foo import it's subclasses since this would lead to circular dependencies. Is there some sort of "best practice" for this as well?
Rename the Foo class, then replace it with a factory function.
class NewFoo:
def __init__(self, prop):
self.prop = prop
class FooA(NewFoo):
def method():
do_something()
class FooB(NewFoo):
def method():
do_something_2()
def Foo(prop):
if prop == PROP_VAL_1:
return FooA(prop)
elif prop == PROP_VAL_2:
return FooB(prop)
By definition, you want the class Foo to return an instance that is not Foo, but a subclass of Foo.
I would caution against this, as it may be surprising to the users of your class.
You have a few options:
You can use a factory function, as #Barmar suggests but use a name such as foo_factory and keep the method Foo.method intact with some aggressive logging. This allows you to incrementally change all call sites from Foo to foo_factory and have the logging as peace of mind that no one is directly instantiating Foo anymore.
Another option is to modify Foo.__new__ to return an instance of a subclass:
I modified your code a bit to an MRE.
PROP_VAL_1 = 1
PROP_VAL_2 = 2
class Foo:
def __new__(cls, prop):
# if the class was instantiated directly
if cls is Foo:
if prop == PROP_VAL_1:
return FooA(prop)
elif prop == PROP_VAL_2:
return FooB(prop)
else:
raise ValueError(f"Invalid value {prop}")
# this is only reached if a subclass is instantiated
return super().__new__(cls)
def __init__(self, prop):
self.prop = prop
class FooA(Foo):
def method(self):
return 'do_something'
class FooB(Foo):
def method(self):
return 'do_something_2'
foo = Foo(prop=2)
print(isinstance(foo, Foo)) # True
print(type(foo)) # <class '__main__.FooB'>
print(foo.method()) # do_something_2

Can we pass the class object inside its own method?

I have a class A object method which uses another class B object's method, which the argument is class A object.
class A():
def calculate(self):
B = B.calculator(A)
class B():
def calculator(self, A):
...do something with A.attributes
It is possible to just pass attributes into the object, but I would see this possibility as the last priority. I am definitely a bit oversimplify my case, but I am wondering if there is a way to pass the entire class
Edit:
Sorry for the confusion. At the end I am trying to call class A object and A.calculate(), which will call class B obj and calculator function.
class A():
def __init__(self, value):
self.value = value
def calculate(self):
Bobj = B()
Bobj.calculator(A)
class B():
def calculator(self, A):
...do something with A.value
def main():
Aobj = A(value)
Aobj.calculate()
Your scenario does not currently indicate that you want to use any information from B when calculating A. There are a few ways of getting the functionality that you want.
Scenario: B stores no information and performs calculation. B should be a function
def B(value):
```do something with value```
return
class A():
def __init__(self, value):
self.value = value
def calculate(self):
return B(self.value)
def main():
Aobj = A(value)
Aobj.calculate()
Scenario: B stores some other information, but internal B information is not needed for the calculation. B should have a static method
class B():
#staticmethod
def calculate(value):
```do something with value```
return
class A():
def __init__(self, value):
self.value = value
def calculate(self):
return B.calculate(self.value)
def main():
Aobj = A(value)
Aobj.calculate()

How to get the class from which a method was called?

The get_calling_class function must pass the following tests by returning the class of the method that called the A.f method:
class A:
def f(self): return get_calling_class()
class B(A):
def g(self): return self.f()
class C(B):
def h(self): return self.f()
c = C()
assert c.g() == B
assert c.h() == C
Walking the stack should give the answer.
The answer should ideally be, in the caller's stack frame.
The problem is, the stack frames only record the function
names (like so: 'f', 'g', 'h', etc.) Any information about
classes is lost. Trying to reverse-engineer the lost info,
by navigating the class hierarchy (in parallel with the
stack frame), did not get me very far, and got complicated.
So, here is a different approach:
Inject the class info into the stack frame
(e.g. with local variables),
and read that, from the called function.
import inspect
class A:
def f(self):
frame = inspect.currentframe()
callerFrame = frame.f_back
callerLocals = callerFrame.f_locals
return callerLocals['cls']
class B(A):
def g(self):
cls = B
return self.f()
def f(self):
cls = B
return super().f()
class C(B):
def h(self):
cls = C
return super(B, self).f()
def f(self):
cls = C
return super().f()
c = C()
assert c.h() == C
assert c.g() == B
assert c.f() == B
Related:
get-fully-qualified-method-name-from-inspect-stack
Without modifying the definition of subclasses:
Added an "external" decorator, to wrap class methods.
(At least as a temporary solution.)
import inspect
class Injector:
def __init__(self, nameStr, valueStr):
self.nameStr = nameStr
self.valueStr = valueStr
# Should inject directly in f's local scope / stack frame.
# As is, it just adds another stack frame on top of f.
def injectInLocals(self, f):
def decorate(*args, **kwargs):
exec(f'{self.nameStr} = {self.valueStr}')
return f(*args, **kwargs)
return decorate
class A:
def f(self):
frame = inspect.currentframe()
callerDecoratorFrame = frame.f_back.f_back # Note:twice
callerDecoratorLocals = callerDecoratorFrame.f_locals
return callerDecoratorLocals['cls']
class B(A):
def g(self): return self.f()
def f(self): return super().f()
class C(B):
def h(self): return super(B, self).f()
def f(self): return super().f()
bInjector = Injector('cls', B.__name__)
B.g = bInjector.injectInLocals(B.g)
B.f = bInjector.injectInLocals(B.f)
cInjector = Injector('cls', C.__name__)
C.h = cInjector.injectInLocals(C.h)
C.f = cInjector.injectInLocals(C.f)
c = C()
assert c.h() == C
assert c.g() == B
assert c.f() == B
I found this link very interesting
(but didn't take advantage of metaclasses here):
what-are-metaclasses-in-python
Maybe someone could even replace the function definitions*,
with functions whose code is a duplicate of the original;
but with added locals/information, directly in their scope.
*
Maybe after the class definitions have completed;
maybe during class creation (using a metaclass).

Python: pass whole self of class to init of new class

I have a class and a sub-class, I'd like to pass the whole of the self of the class to the sub-class. I can pass self over to the new class explicitly easily enough, e.g.
class foo:
def __init__(self, a, b):
self.a = a
self.b = b
self.c = 'foo'
def foo_method(self):
print('a foo method')
class bar(foo):
def __init__(self, foo_object):
self.a = foo_object.a
self.b = foo_object.b
self.c = foo_object.c
def bar_method(self):
print('a bar method')
foo_object = foo(a = 'a', b = 'b')
bar_object = bar(foo_object)
bar_object.a
Is there a more succinct way to pass these over? Something like:
class bar(foo):
def __init__(self, foo_object):
self = self.foo_object
Update:
Thanks https://stackoverflow.com/users/10104112/bastien-antoine, the following solution worked:
class bar(foo):
def __init__(self, foo_object):
self.__dict__ = foo_object.__dict__.copy()
def bar_method(self):
print('a bar method with ' + str(self.c))
Have you tried the copy builtins library?
Otherwise I think you can easily implement your own .copy() method that would copy the values from the old object __dict__ to the new one. Something like this:
class MyObject:
a = None
def set_default_values(self):
self.a = 1
def copy(self, old):
if type(self) == type(old):
self.__dict__ = old.__dict__.copy()
else:
raise TypeError('Wrong type')
if __name__ == "__main__":
obj_1 = MyObject()
print(obj_1.a)
obj_1.set_default_values()
print(obj_1.a)
obj_2 = MyObject()
print(obj_2.a)
obj_2.copy(obj_1)
print(obj_2.a)
Note that I've added a type checking to be sure that you copy attributes that would exist otherwise, but I think simply self.__dict__ = old.__dict__.copy() would work fine, thought you might end up with attributes you might not suppose to have in the new object.
Hope this helps!
I think that you can do that with
class bar(foo):
def __init__(self):
super(bar, self).__init__()
with this code, you ran the init function for the subclass

Change Python Class attribute dynamically

I have a Class B inheriting Class A with a class attribute cls_attr.
And I would like to set dynamically cls_attr in class B.
Something like that:
class A():
cls_attr= 'value'
class B(A):
def get_cls_val(self):
if xxx:
return cls_attr = 'this_value'
return cls_attr = 'that_value'
cls_attr = get_cls_val()
I tried several things. I know i might not be looking in the right place but i am out of solutions.
EDIT: Classes are django admin classes
Thanks.
class attributes can be read on the class or an instance, but you can only set them on the class (trying to set them on an instance will only create an instance attribute that will shadow the class attribute).
If the condition is known at import time, you can just test it in the class body:
xxx = True
class A(object):
cls_attr = 'value'
class B(A):
if xxx:
cls_attr = 'this_value'
else
cls_attr = 'that_value'
Now if you want to change it during the program's execution, you either have to use a classmethod:
class B(A):
#classmethod
def set_cls_attr(cls, xxx):
if xxx:
cls.cls_attr = 'this_value'
else:
cls.cls_attr = 'that_value'
or if you need to access your instance during the test:
class B(A):
def set_cls_attr(self, xxx):
cls = type(self)
if xxx:
cls.cls_attr = 'this_value'
else:
cls.cls_attr = 'that_value'
What about using classmethod and polymorphically overriding it in subclass?
class A:
#classmethod
def cls_attr(cls):
return 'value'
class B(A):
#classmethod
def cls_attr(cls):
if cond():
return 'this'
else:
return 'that'
assert A.cls_attr() == 'value'
cond = lambda: True
assert B.cls_attr() == 'this'
cond = lambda: False
assert B.cls_attr() == 'that'
The easiest solution for me is with property decorator:
class B:
#property
def attr_name(self):
""" do your stuff to define attr_name dynamically """
return attr_name
This seems to do what you want:
>>> class B(A):
#classmethod
def set_cls_val(cls, x):
if x == 1:
cls.cls_attr = "new"
>>> c = B()
>>> c.cls_attr
'value'
>>> c.set_cls_val(B, 1)
>>> c.cls_attr
'new'
>>> B.cls_attr
'new'
Just set it within the function.
EDIT: Updated to set the class attribute and not the instance attribute, thanks #bruno-desthuilliers.
EDIT: Updates once again, thanks #bruno-desthuilliers. I should think my answers through more clearly. But what you want is answered below.

Categories