Reputation: 33
I have a python class, let's call it MyClass
, with several bound methods. I need the behaviour of one of these methods, call it .dynamic_method()
, to be drastically different depending on the value of a parameter set during object creation. There will be a handful of variations of dynamic_method
which will share very little code between them, so it makes sense to define them as separate functions/methods rather than as a single large method with many options and conditionals. I do not need to be able to change the behaviour of this method after creating the object, everything can be set during __init__()
.
For reference, MyClass
manages data sets, and .dynamic_method
deals with some IO aspects of this data which change depending on dataset and context.
This is not technically difficult to achieve, but none of the methods I've come up with feel completely correct:
__init__()
using one of the methods described here: Adding a Method to an Existing Object Instance. This seems like the most natural choice to me, but many of the answers in that thread strongly discourages the use of this pattern..dynamic_method_a
, .dynamic_method_b
etc, and set an option/alias so that MyClass.dynamic_method()
calls the correct one. This will result in a very long class definition, and lots of unused methods for each object..dynamic_method()
simply calls the correct function with self
as the first argument. This feels like a wilful misuse of the concept of methods, and will break linting.What would be considered the most "Pythonic" way to achieve this behaviour? One of the above methods, or is there an obvious alternative I've missed?
Upvotes: 3
Views: 87
Reputation: 2155
I would rather go for option 3, since I find it cleaner, but it depends on how many implementation variations you need. Option 4 (use a factory) is good as well, but it will generate more code. On the other side, option 4 is by far more scalable and maintainable.
So, since you want to change the behaviour of the dynamic_method()
within __init__()
, you could do it like this:
class MyClass(object):
def __init__(self, param=None):
self.dynamic_method = {
"a": self.dynamic_a,
"b": self.dynamic_b,
"c": self.dynamic_c}[param]
def dynamic_a(self):
print("a functionality")
def dynamic_b(self):
print("b functionality")
def dynamic_c(self):
print("c functionality")
>> m = MyClass("b")
>> m.dynamic_method()
b functionality
Upvotes: 1
Reputation: 578
I would suggest the factory, Indeed it adds complexity, but let me point out few advantages.
code sample:
class DataStruct:
def __init__(self, input_: str):
self.Field = input_
class MyClass:
def __init__(self, data: DataStruct):
self.Data: DataStruct = data
def dynamic_method(self):
# Abstract Method
pass
def __str__(self):
return self.Data.Field
class MyClassFunctionalityOne(MyClass):
def __init__(self, data: DataStruct):
super().__init__(data)
def dynamic_method(self):
self.Data.Field = self.Data.Field.upper()
class MyClassFunctionalityTwo(MyClass):
def __init__(self, data: DataStruct):
super().__init__(data)
def dynamic_method(self):
self.Data.Field = "and now for something completely different"
class MyClassFactory:
def __init__(self):
pass
@classmethod
def manufacture(cls, input_: DataStruct) -> MyClass:
#replace this if..elif chain to contain the tests on the data you need for determine the right method
if input_.Field.count('one') > 0:
obj: MyClass = MyClassFunctionalityOne(input_)
elif input_.Field.count('two')> 0:
obj: MyClass = MyClassFunctionalityTwo(input_)
else:
obj = None
return obj
# script starts here
received_data = DataStruct('This kind of data should result in functionality one')
object1 = MyClassFactory.manufacture(received_data)
received_data = DataStruct('This kind of data should result in functionality two')
object2 = MyClassFactory.manufacture(received_data)
print (type(object1))
print (type(object2))
print ('*'*5, 'objects before dynamic_method', '*'*5)
print (object1)
print (object2)
object1.dynamic_method()
object2.dynamic_method()
print ('*'*5, 'objects after dynamic_method', '*'*5)
print (object1)
print (object2)
output:
<class '__main__.MyClassFunctionalityOne'>
<class '__main__.MyClassFunctionalityTwo'>
***** objects before dynamic_method *****
This kind of data should result in functionality one
This kind of data should result in functionality two
***** objects after dynamic_method *****
THIS KIND OF DATA SHOULD RESULT IN FUNCTIONALITY ONE
and now for something completely different
Upvotes: 1
Reputation: 811
I think you are over-complicating this issue.
if you don't have any important reason to change the method attribute (first option), I would go with just writing a nice if-else in dynamic_method()
to call the other functions (3rd option) or if I really want to be fancy I would make a makeshift switch-case out of a dict
like this:
def dynamic_method(self):
return {
<value to run foo1>: foo1,
<value to run foo2>: foo2,
<value to run foo3>: foo3
}[self.method_variation](self)
def foo1(self):
pass
def foo2(self):
pass
def foo3(self):
pass
Upvotes: 3