简体   繁体   中英

How to implement a function to cover both single and multiple values

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)

List comprehension :

[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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM