Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement python method with signature like ([start ,] stop [, step]), i.e. default keyword argument on the left

Since in python 3.X the build-id range() function returns no longer a list but an iterable, some old code fails as I use range()to conveniently generate lists I need.

So I try to implement my own lrange function like this:

def lrange(start = 0, stop, step = 1):
    ret = []
    while start < stop:
        ret.append(start)
        start += step
    return ret

giving me a "non-default argument follows default argument" interpreter error.

If I look at Python's range() it seems to be possible.

I posted this question mainly because I was wondering if/how one can implement a function with such a signature on his own

like image 291
Rafael T Avatar asked Dec 26 '11 15:12

Rafael T


People also ask

How do you create a function signature in Python?

We can get function Signature with the help of signature() Function. It takes callable as a parameter and returns the annotation. It raises a value Error if no signature is provided. If the Invalid type object is given then it throws a Type Error.

What is the signature of a function in Python?

In general the signature of a function is defined by the number and type of input arguments the function takes and the type of the result the function returns. The signature of that function, described in an abstract way would be the set {int, int, int}.

What are the 3 types of arguments in Python?

Hence, we conclude that Python Function Arguments and its three types of arguments to functions. These are- default, keyword, and arbitrary arguments.

How do you use keyword arguments in Python?

Embrace keyword arguments in PythonConsider using the * operator to require those arguments be specified as keyword arguments. And remember that you can accept arbitrary keyword arguments to the functions you define and pass arbitrary keyword arguments to the functions you call by using the ** operator.


2 Answers

Quick Answer

This question popped up when I first started learning Python, and I think it worthwhile to document the method here. Only one check is used to simulate original behavior.

def list_range(start, stop=None, step=1):
    if stop is None:
         start, stop = 0, start
    return list(range(start, stop, step))

I think this solution is a bit more elegant than using all keyword arguments or *args.

Explanation

Use a Sentinel

The key to getting this right is to use a sentinel object to determine if you get a second argument, and if not, to provide the default to the first argument while moving the first argument to the second.

None, being Python's null value, is a good best-practice sentinel, and the idiomatic way to check for it is with the keyword is, since it is a singleton.

Example with a proper docstring, declaring the signature/API

def list_range(start, stop=None, step=1):
    '''
    list_range(stop) 
    list_range(start, stop, step)

    return list of integers from start (default 0) to stop,
    incrementing by step (default 1).

    '''
    if stop is None:
         start, stop = 0, start
    return list(range(start, stop, step))

Demonstration

>>> list_range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list_range(5, 10)
[5, 6, 7, 8, 9]
>>> list_range(2, 10, 2)
[2, 4, 6, 8]

And it raises an error if no arguments are given, unlike the all-keyword solution here.

Caveat

By the way, I hope this is only considered from a theoretical perspective by the reader, I don't believe this function is worth the maintenance, unless used in a central canonical location to make code cross-compatible between Python 2 and 3. In Python, it's quite simple to materialize a range into a list with the built-in functions:

Python 3.3.1 (default, Sep 25 2013, 19:29:01) 
[GCC 4.7.3] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
like image 164
Russia Must Remove Putin Avatar answered Sep 21 '22 03:09

Russia Must Remove Putin


I've never actually given this much thought, but first of all, to solve your problem you should just be able to wrap range(...) with list(range(...)) in you're code.

Using keyword arguments, you could implement a signature like that since you are not required to specify the actual key when calling

def f(x=None, y=None, z=None):
    print x, y, z

f(1, 2, 3)
#output: 1 2 3

Then, you could inspect the values to determine how you should handle them. So to emulate range

def f(x=None, y=None, z=None):
    if z is not None: # then all values were assigned
        return range(x, y, z)
    elif y is not None: # then a start stop was set
        return range(x, y):
    else: # only one value was given
        return range(x)

The point here isn't to be a wrapper for range (as above, just use list) but rather to give some insight on if one was actually trying to emulate the builtin range signature for something custom.

Also keep in mind this isn't a complete solution, f(z=1) could cause problems with the above, so you want to provide sane defaults for each [kwarg] while checking for required kwarg

def f(x=0, y=None, z=1):
    if y is None:
        raise Exception()
    return range(x, y, z)

would be a little more insightful to a python method with a signature like ([start], stop, [step])

like image 39
dskinner Avatar answered Sep 22 '22 03:09

dskinner