Say you have a value like this:
n = 5
and a function that returns the factorial of it, like so:
factorial(5)
How do you handle multiple values:
nums = [1,2,3,4,5]
factorial (nums)
so it returns the factorials of all these values as a list?
What's the cleanest way to handle this, without writing 2 methods? Does Python have a good way to handle these kinds of situations?
def Factorial(arg):
try:
it = iter(arg)
except TypeError:
pass
else:
return [Factorial(x) for x in it]
return math.factorial(arg)
If it's iterable, apply recursivly. Otherwise, proceed normally.
Alternatively, you could move the last return
into the except
block.
If you are sure the body of Factorial
will never raise TypeError
, it could be simplified to:
def Factorial(arg):
try:
return [Factorial(x) for x in arg]
except TypeError:
return math.factorial(arg)
[fac(n) for n in nums]
EDIT:
Sorry, I misunderstood, you want a method that handles both sequences and single values? I can't imagine why you wouldn't do this with two methods.
def factorial(n):
# implement factorial here
return answer
def factorial_list(nums):
return [factorial(n) for n in nums]
The alternative would be to do some sort of type-checking, which is better avoided unless you have some terribly compelling reason to do so.
EDIT 2:
MizardX's answer is better, vote for that one. Cheers.
This is done sometimes.
def factorial( *args ):
def fact( n ):
if n == 0: return 1
return n*fact(n-1)
return [ fact(a) for a in args ]
It gives an almost magical function that works with simple values as well as sequences.
>>> factorial(5)
[120]
>>> factorial( 5, 6, 7 )
[120, 720, 5040]
>>> factorial( *[5, 6, 7] )
[120, 720, 5040]
If you're asking if Python can do method overloading: no. Hence, doing multi-methods like that is a rather un-Pythonic way of defining a method. Also, naming convention usually upper-cases class names, and lower-cases functions/methods.
If you want to go ahead anyway, simplest way would be to just make a branch:
def Factorial(arg):
if getattr(arg, '__iter__', False): # checks if arg is iterable
return [Factorial(x) for x in arg]
else:
# ...
Or, if you're feeling fancy, you could make a decorator that does this to any function:
def autoMap(f):
def mapped(arg):
if getattr(arg, '__iter__', False):
return [mapped(x) for x in arg]
else:
return f(arg)
return mapped
@autoMap
def fact(x):
if x == 1 or x == 0:
return 1
else:
return fact(x-1) + fact(x-2)
>>> fact(3)
3
>>> fact(4)
5
>>> fact(5)
8
>>> fact(6)
13
>>> fact([3,4,5,6])
[3, 5, 8, 13]
Although a more Pythonic way is to use variable argument lengths:
def autoMap2(f):
def mapped(*arg):
if len(arg) != 1:
return [f(x) for x in arg]
else:
return f(arg[0])
return mapped
@autoMap2
def fact(n):
# ...
>>> fact(3,4,5,6)
[3, 5, 8, 13]
Putting the two together into a deep mapping decorator:
def autoDeepMap(f):
def mapped(*args):
if len(args) != 1:
return [mapped(x) for x in args]
elif getattr(args[0], '__iter__', False):
return [mapped(x) for x in args[0]]
else:
return f(args[0])
return mapped
@autoDeepMap
def fact(n):
# ...
>>> fact(0)
1
>>> fact(0,1,2,3,4,5,6)
[1, 1, 2, 3, 5, 8, 13]
>>> fact([0,1,2,3,4,5,6])
[1, 1, 2, 3, 5, 8, 13]
>>> fact([0,1,2],[3,4,5,6])
[[1, 1, 2], [3, 5, 8, 13]]
>>> fact([0,1,2],[3,(4,5),6])
[[1, 1, 2], [3, [5, 8], 13]]
Or if you don't like the list comprehension syntax, and wish to skip having a new method:
def factorial(num):
if num == 0:
return 1
elif num > 0:
return num * factorial(num - 1)
else:
raise Exception("Negative num has no factorial.")
nums = [1, 2, 3, 4, 5]
# [1, 2, 3, 4, 5]
map(factorial, nums)
# [1, 2, 6, 24, 120, 720]
You might want to take a look at NumPy/SciPy's vectorize .
In the numpy world, given your single-int-arg Factorial function, you'd do things like
vFactorial=np.vectorize(Factorial)
vFactorial([1,2,3,4,5])
vFactorial(6)
although note that the last case returns a single-element numpy array rather than a raw int.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.