Reputation: 275
I want to generate a class
in which the number of input elements is determined by a conditional that is either True
or False
. I tried something like this
class Test:
def __init__(self, cond):
self.cond = cond
if cond == True:
def __call__(self, a, b, c):
d1 = a + b + c
return d1
elif cond == False:
def __call__(self, a, b):
d2 = a + b
return d2
result1 = Test(cond=True)(a, b, c)
result2 = Test(cond=False)(a, b)
but it does obviously not work, and it provides the following error:
TypeError: 'Test' object is not callable
I suspect that I am using the wrong kind of def
, as __init__
is probably not suitable in this case.
class
in such a way?class
?I know it would be fairly easy using a def
function and not a class
.
Upvotes: 0
Views: 1185
Reputation: 123531
Your code doesn't work because __call__()
is a function local to the __init__()
method, so defining it there doesn't affect the class
' definition. I don't know how "pythonic" it is, but the following seems like the simplest way to do what you want—essentially making __init__()
act like a class decorator.
class Test:
def __init__(self, cond):
self.cond = cond
if cond:
def __call__(self, a, b, c):
d1 = a + b + c
return d1
else:
def __call__(self, a, b):
d2 = a + b
return d2
setattr(self.__class__, '__call__', __call__) # Assign to class.
if __name__ == '__main__':
a, b, c = 10, 30, 2
result1 = Test(cond=True)(a, b, c)
result2 = Test(cond=False)(a, b)
print(f'{result1}, {result2}') # -> 42, 40
Upvotes: 1
Reputation: 4305
While I would agree that perhaps other ways are better, if you remember that functions are first class objects in Python, then the most direct way of doing as close to what you have already done, is to redefine __call__ this way:
class Test:
def method1(self, a, b, c):
d1 = a + b + c
return d1
def method2(self, a, b):
d2 = a + b
return d2
def __init__(self, cond):
self.cond = cond
if cond:
self.__call__=self.method1
else:
self.__call__=self.method2
Test(cond=True)(1, 2, 3)
6
Test(cond=False)(1, 2)
3
Upvotes: 1
Reputation: 3931
Without going into metaclass stuff, the simplest way to implement that is to define __call__
as accepting a variable quantity of arguments instead.
class Test:
def __init__(self, cond):
self.cond = cond
def __call__(self, *args):
if self.cond:
if len(args) != 3:
raise ValueError('When cond=True, requires 3 arguments')
else:
if len(args) != 2:
raise ValueError('When cond=False, requires 2 arguments')
return sum(args)
Upvotes: 2
Reputation: 2695
In this case you can achieve the same result more pythonically by processing the arguments as a collection of arguments (thus skipping the initial configuration with your cond
, since Python doesn't force you to have a fixed number of them), and either:
class Test:
def __call__(self, args):
return sum(args)
my_test = Test()
print(my_test([1, 2, 3]))
# output: 6
class Test:
def __call__(self, *args):
return sum(args)
my_test = Test()
print(my_test(1, 2, 3))
# output: 6
Note that *args
is effectively a list itself, the main difference is in how the callable is called.
Both of these work with any number of arguments. If you have specific behaviour corresponding to a specific amount of arguments, you can check the length of args
as you would any list, and access its elements in the same way, for example with args[0]
.
If the initial configuration is required, you can enforce a certain behaviour while still processing the arguments flexibly. For example, the following version of the class rejects input lengths different than 2 if cond is True
, and inputs different than 3 if it's False
, all with the same method:
class Test:
def __init__(self, cond):
self.cond = cond
def __call__(self, *args):
if (self.cond is True and len(args) != 3) \
or (self.cond is False and len(args) != 2):
raise ValueError
return sum(args)
You can of course add a default value to cond
in the __init__
if you want to make it possible to omit it when instancing the class.
Upvotes: 2
Reputation: 189936
A common way to avoid this in the first place is to define a subclass instead.
class Test:
def __call__(self, a, b, c):
return a + b + c
class Test2 (Test):
def __call__(self, a, b):
return a + b
result1 = Test()(a, b, c)
result2 = Test2()(a, b)
Superficially, this is hardly an improvement; but in situations where you might use a conditional in more than one place, this restructuring can pay itself back handsomely rather quickly.
Upvotes: 3
Reputation: 26057
I would restructure your code. It's not a good idea to add conditionals or logics into __init__()
which serves only for initializing of variables.
Instead you should make the dunder __call__()
separate so it can be called on the class instantiation.
class Test:
def __init__(self, cond):
self.cond = cond
def __call__(self, a, b, c=0):
if self.cond:
return a + b + c
else:
return a + b
a, b, c = 1, 2, 3
result1 = Test(cond=True)(a, b, c)
result2 = Test(cond=False)(a, b)
print(result1) # 6
print(result2) # 3
Upvotes: 5
Reputation: 338406
Don't do conditional function definitions.
class Test:
def __init__(self, cond):
self.cond = cond
def __call__(self, a, b, c=0):
if self.cond:
return a + b + c
else:
return a + b
a, b, c = 1, 2, 3
print(Test(cond=True)(a, b, c))
# => 6
print(Test(cond=False)(a, b))
# => 3
Also, don't do comparisons like if cond == True:
and elif cond == False:
, that's also an antipattern. If cond
is supposed to be a Boolean value, if cond:
and else:
are perfectly fine.
Upvotes: 3