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

不想你离开。 提交于 2019-12-22 08:43:44

问题


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


回答1:


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]



回答2:


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])




回答3:


What about:

>>> def my_range(*args):
...     return list(range(*args))
... 
>>> my_range(0, 10, 2)
[0, 2, 4, 6, 8]



回答4:


You can try something like this:

def lrange(*args):
    # Default values
    start = 0
    step = 1

    # Assign variables based on args length
    if len(args) == 1:
        stop, = args
    elif len(args) == 2:
        start, stop = args
    elif len(args) == 3:
        start, stop, step = args
    else:
        raise TypeError('lrange expected at most 3 arguments, got {0}'
                        .format(len(args)))
     ...

If you try to use range in the interpreter, you'll see that it doesn't accept keyword arguments, so it's certainly playing some trick around variable number of arguments as in the example above.




回答5:


You can use the *args and **kwargs features:

def myrange(*args, **kwargs):
  start = 0
  stop = None
  if (len(args) == 1):
    stop = args[0]
  if (len(args) >= 2 ):
    start = args[0]
    stop = args[1]
  start = kwargs.get("start", start)
  stop = kwargs.get("stop", stop)
  return list(range(start, stop))

You'd need to put in some more error checking, and support the step operator.

In this case, you're probably better off implementing it like this:

def myrange(*args):
  return list(range(*args))

Note that the builitn range funcion doesn't support kwargs.



来源:https://stackoverflow.com/questions/8637130/how-to-implement-python-method-with-signature-like-start-stop-step-i

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!