Using Python 2.7 (as many people in DCC Packages like Maya do) can be a bit tricky when combining all the argument types that Python offers.

The simple case of using positional and keyword arguments is well documented:

def positional_and_default(
    positional1, positional2, 
    default1='default1_val', default2='default2_val'
    ) 

But Python supports four types of arguments:

  1. Positional (def func(positional))
  2. Keyword (def func(default='default_val'))
  3. Variable positional (def func(*args))
  4. Variable keyword (def func(**kwargs))

And when when regular and variable arguments are combined, things can get a bit confusing.
First there are the

Unambiguous Combinations

def args_and_kwargs(*args, **kwargs):
    '''
    All combinations work.
    args and kwargs can both be assigned or just one of them.

    This is the ONLY way to always clearly separate positional and keyword arguments because 
    kwargs will NEVER be assigned by position. If any named arguments with default values are 
    used in a function's signature (name=default_val) there's always a chance that they will 
    be assigned by position in some parameter combinations.
    '''
    print 'args = {args}, kwargs = {kwargs}'.format(**locals())

args_and_kwargs('positional1', 'positional2', kw1='kw1_val')
# args = ('positional1', 'positional2'), kwargs = {'kw1': 'kw1_val'}


def positional_args_and_kwargs(positional1, *args, **kwargs):
    '''
    All combinations work.
    All three can be assigned.
    positional1 can be assigned with either args or kwargs because kwargs only grabs kwargs and
    cannot be assigned by position.
    '''
    print 'positional1 = "{positional1}", args = {args}, kwargs = {kwargs}'.format(**locals())

positional_args_and_kwargs('positional1', 'positional2', kw1='kw1_val')
# positional1 = "positional1", args = ('positional2',), kwargs = {'kw1': 'kw1_val'}
positional_args_and_kwargs('positional1', 'positional2')
# positional1 = "positional1", args = ('positional2',), kwargs = {}
positional_args_and_kwargs('positional1', kw1='kw1_val')
# positional1 = "positional1", args = (), kwargs = {'kw1': 'kw1_val'}

But if regular and variable arguments are combined more freely there are the

Combinations With Caveats

These still work, but the results might be unituitive at first or not all ways of assigning the arguments are possible.

def args_and_default_val(default1='default_val1', *args):
    '''
    On call positional args need to come first, keyword args need to follow. So:

    args can only be assigned if default1 is assigned because default1 will 'grab' the first
    positional argument and leave the rest to args. Like this:

    args_and_default_val('positional1', 'positional2')
    # works BUT: default1=positional1, args=('positional2',) !!!

    args_and_default_val(default1='val', 'positional1', 'positional2')
    # Fails because on call keyword args need to come last

    args_and_default_val('positional1', 'positional2', default1='val')
    # doesn't work because default1 will be assigned using the positional method and then again
    # using the keyword method
    '''
    print 'args = {args}, default1 = "{default1}"'.format(**locals())

args_and_default_val(default1='default1_val') # works
# args = (), default1 = "default1_val"
args_and_default_val('positional1', 'positional2')
# args = ('positional2',), default1 = "positional1"


def positional_default_args_and_kwargs(positional1, default1='default_val', *args, **kwargs):
    '''
    All can be assigned like this:
    positional_default_args_and_kwargs(
        'positional1', 'positional2', 'positional3', 'positional4',
        kw1='val1', kw2='val2')
    # The key is that default1 is NOT assigned by name but rather by position.
    # Result: positional1=positional1 default1=positional2
    #         args=('positional3', 'positional4')
    #         kwargs={'kw1': 'val1', 'kw2': 'val2'}

    # These two calls don't work even though the first should give the same result as above
    positional_default_args_and_kwargs(
        'positional1',
        default1='positional2',
        'positional3', 'positional4',
        kw1='val1', kw2='val2')
    # Fails because kwarg before non-kwarg is not allowed

    positional_default_args_and_kwargs(
        'positional1', 'positional2', 'positional3',
        default1='positional4',
        kw1='val1', kw2='val2')
    # Fails because default1 is first assigned positionally and then again by name

    In conclusion: Like in args_and_default_val, args can only be assigned if default1 was 
    assigned positionally.
    '''
    print 'positional1 = "{positional1}", default1 = "{default1}", args = {args}, kwargs = {kwargs}'.format(**locals())

positional_default_args_and_kwargs('positional1')
# positional1 = "positional1", default1 = "default_val", args = (), kwargs = {}
positional_default_args_and_kwargs('positional1', default1='default1_val')
# positional1 = "positional1", default1 = "default1_val", args = (), kwargs = {}
positional_default_args_and_kwargs('positional1', 'positional2', 'positional3', 'positional4')
# positional1 = "positional1", default1 = "positional2", 
# args = ('positional3', 'positional4'), kwargs = {}
positional_default_args_and_kwargs('positional1', default1='default1_val', kw1='kw1_val')
# positional1 = "positional1", default1 = "default1_val", args = (), kwargs = {'kw1': 'kw1_val'}
positional_default_args_and_kwargs(
	'positional1', 'positional2', 'positional3', 'positional4', 
	kw1='kw1_val', kw2='kw2_val')
# positional1 = "positional1", default1 = "positional2", 
# args = ('positional3', 'positional4'), kwargs = {'kw1': 'kw1_val', 'kw2': 'kw2_val'}

If anyone wants to grab the whole example code to modify and run it, here it is:

# Unambiguous combinations
##########################
def args_and_kwargs(*args, **kwargs):
    '''
    All combinations work.
    args and kwargs can both be assigned or just one of them.

    This is the ONLY way to always clearly separate positional and keyword arguments because 
    kwargs will NEVER be assigned by position. If any named arguments with default values are 
    used in a function's signature (name=default_val) there's always a chance that they will 
    be assigned by position in some parameter combinations.
    '''
    print 'args = {args}, kwargs = {kwargs}'.format(**locals())


def positional_args_and_kwargs(positional1, *args, **kwargs):
    '''
    All combinations work.
    All three can be assigned.
    positional1 can be assigned with either args or kwargs because kwargs only grabs kwargs and
    cannot be assigned by position.
    '''
    print 'positional1 = "{positional1}", args = {args}, kwargs = {kwargs}'.format(**locals())


# Combinations with caveats
###########################
def args_and_default_val(default1='default_val1', *args):
    '''
    On call positional args need to come first, keyword args need to follow. So:

    args can only be assigned if default1 is assigned because default1 will 'grab' the first
    positional argument and leave the rest to args. Like this:

    args_and_default_val('positional1', 'positional2')
    # works BUT: default1=positional1, args=('positional2',) !!!

    args_and_default_val(default1='val', 'positional1', 'positional2')
    # Fails because on call keyword args need to come last

    args_and_default_val('positional1', 'positional2', default1='val')
    # doesn't work because default1 will be assigned using the positional method and then again
    # using the keyword method
    '''
    print 'args = {args}, default1 = "{default1}"'.format(**locals())


def positional_default_args_and_kwargs(positional1, default1='default_val', *args, **kwargs):
    '''
    All can be assigned like this:
    positional_default_args_and_kwargs(
        'positional1', 'positional2', 'positional3', 'positional4',
        kw1='val1', kw2='val2')
    # The key is that default1 is NOT assigned by name but rather by position.
    # Result: positional1=positional1 default1=positional2
    #         args=('positional3', 'positional4')
    #         kwargs={'kw1': 'val1', 'kw2': 'val2'}

    # These two calls don't work even though the first should give the same result as above
    positional_default_args_and_kwargs(
        'positional1',
        default1='positional2',
        'positional3', 'positional4',
        kw1='val1', kw2='val2')
    # Fails because kwarg before non-kwarg is not allowed

    positional_default_args_and_kwargs(
        'positional1', 'positional2', 'positional3',
        default1='positional4',
        kw1='val1', kw2='val2')
    # Fails because default1 is first assigned positionally and then again by name

    In conclusion: Like in args_and_default_val, args can only be assigned if default1 was 
    assigned positionally.
    '''
    print 'positional1 = "{positional1}", default1 = "{default1}", args = {args}, kwargs = {kwargs}'.format(**locals())


def foo(a, b=3, *args, **kwargs):
    '''
    Quick example function:

    Examples:

    foo(x) # a=x, b=3, args=(), kwargs={}
    foo(x, y) # a=x, b=y, args=(), kwargs={}
    foo(x, b=y) # a=x, b=y, args=(), kwargs={}
    foo(x, y, z, k) # a=x, b=y, args=(z, k), kwargs={}
    foo(x, c=y, d=k) # a=x, b=3, args=(), kwargs={'c': y, 'd': k}
    foo(x, c=y, b=z, d=k) # a=x, b=z, args=(), kwargs={'c': y, 'd': k}
    '''

if __name__ == '__main__':
    print '\nUnambiguous combinations'
    print '========================'
    print '\nargs_and_kwargs'
    args_and_kwargs('positional1', 'positional2', kw1='kw1_val')
    # args = ('positional1', 'positional2'), kwargs = {'kw1': 'kw1_val'}

    print '\npositional_args_and_kwargs'
    positional_args_and_kwargs('positional1', 'positional2', kw1='kw1_val')
    # positional1 = "positional1", args = ('positional2',), kwargs = {'kw1': 'kw1_val'}
    positional_args_and_kwargs('positional1', 'positional2')
    # positional1 = "positional1", args = ('positional2',), kwargs = {}
    positional_args_and_kwargs('positional1', kw1='kw1_val')
    # positional1 = "positional1", args = (), kwargs = {'kw1': 'kw1_val'}

    print '\nCombinations with caveats'
    print '=========================='
    print '\nargs_and_default_val'
    args_and_default_val(default1='default1_val') # works
    # args = (), default1 = "default1_val"
    args_and_default_val('positional1', 'positional2')
    # args = ('positional2',), default1 = "positional1"

    print '\npositional_default_args_and_kwargs'
	positional_default_args_and_kwargs('positional1')
	# positional1 = "positional1", default1 = "default_val", args = (), kwargs = {}
	positional_default_args_and_kwargs('positional1', default1='default1_val')
	# positional1 = "positional1", default1 = "default1_val", args = (), kwargs = {}
	positional_default_args_and_kwargs('positional1', 'positional2', 'positional3', 'positional4')
	# positional1 = "positional1", default1 = "positional2", 
	# args = ('positional3', 'positional4'), kwargs = {}
	positional_default_args_and_kwargs('positional1', default1='default1_val', kw1='kw1_val')
	# positional1 = "positional1", default1 = "default1_val", args = (), kwargs = {'kw1': 'kw1_val'}
	positional_default_args_and_kwargs(
		'positional1', 'positional2', 'positional3', 'positional4', 
		kw1='kw1_val', kw2='kw2_val')
	# positional1 = "positional1", default1 = "positional2", 
	# args = ('positional3', 'positional4'), kwargs = {'kw1': 'kw1_val', 'kw2': 'kw2_val'}




If you have any questions or comments please do get in touch on Twitter

You might be interested in
Other Tutorials