问题
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