简体   繁体   中英

Function that accepts both expanded arguments and tuple

Is there a Pythonic way to create a function that accepts both separate arguments and a tuple? ie to achieve something like this:

def f(*args):
    """prints 2 values 
            f(1,2)
                1 2
            f( (1,2) )
                1 2"""
    if len(args) == 1:
        if len(args[0]) != 2:
            raise Exception("wrong number of arguments")
        else:
            print args[0][0],args[0][1]
    elif len(args) == 2:
        print args[0],args[1]
    else:
            raise Exception("wrong number of arguments")

First of all I don't know if it is very wise to do so. Say a person calls a function like:

f(*((1,4),(2,5)))

As you can see the tuple contains two elements. But now for some reason, the person calls it with only one element (because for instance the generator did not generated two elements):

f(*((1,4),))

Then the user would likely want your function to report this, but now it will simply accept it (which can lead to complicated and unexpected behavior). Okay printing the elements of course will not do much harm. But in a general case the consequences might be more severe.

Nevertheless an elegant way to do this is making a simple decorator that first checks if one element is fed it checks if one tuple element is feeded and if so expands it:

def one_tuple(f):
    def g(*args):
        if len(args) == 1 and isinstance(args[0],tuple):
            return f(*args[0])
        else:
            return f(*args)
    return g

And apply it to your f :

@one_tuple
def f(*args):
    if len(args) == 2:
        print args[0],args[1]
    else:
        raise Exception("wrong number of arguments")

The decorator one_tuple thus checks if one tuple is fed, and if so unpacks it for you before passing it to your f function.

As a result f does not have to take the tuple case into account : it will always be fed expanded arguments and handle these (of course the opposite could be done as well).

The advantage of defining a decorator is its reuse : you can apply this decorator to all kinds of functions (and thus make it easier to implement these).

I do not agree to the idea myself (even though I like the fact that python does not require the definition of variable types and thus allows such a thing) but there might be cases where such a thing is needed. So here you go:

def my_f(a, *b):

    def whatever(my_tuple):
        # check tuple for conformity
        # do stuff with the tuple
        print(my_tuple)
        return

    if hasattr(a, '__iter__'):
        whatever(a)
    elif b:
        whatever((a,) + b)
    else:
        raise TypeError('malformed input')
    return

Restructured it a bit but the logic stays the same. if "a" is an iterable consider it to be your tuple, if not take "b" into account as well. if "a" is not an iterable and "b" is not defined, raise TypeError

my_f((1, 2, 3)) # (1, 2, 3)
my_f(1, 2, 3)   # (1, 2, 3)

The Pythonic way would be to use duck typing. This will only work if you are sure that none of the expanded arguments are going to be iterable.

def f(*args):
    def g(*args):
        # play with guaranteed expanded arguments

    if len(args) == 1:
        try:
            iter(args[0])
        except TypeError:
            pass
        else:
            return g(*args[0])
    return g(*args)

This implementation offers a slight improvement on @Ev.Kounis's answer for cases where a single non-tuple argument is passed in. It can also easily be turned into the equivalent decorator described by @WillemVanOnsem. Use the decorator version if you have more than one function like this.

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