Short answer: Use a star to collect the arguments in a tuple and then add a special case for a tuple of length one to handle a single iterable argument.
Source material: The C code that handles the logic can be found at: https://github.com/python/cpython/blob/da20d7401de97b425897d3069f71f77b039eb16f/Python/bltinmodule.c#L1708
Simplified pure python code: If you ignore the default and key keyword arguments, what's left simplifies to:
def mymax(*args):
if len(args) == 0:
raise TypeError('max expected at least 1 argument, got 0')
if len(args) == 1:
args = tuple(args[0])
largest = args[0]
for x in args[1:]:
if x > largest:
largest = x
return largest
There are other nuances, but this should get you started.
Documentation: The special handling for the length one case versus other cases is documented here:
Return the largest item in an iterable or the largest of two or more arguments.
If one positional argument is provided, it should be an iterable. The largest item in the iterable is returned. If two or more positional arguments are provided, the largest of the positional arguments is returned.
More complete version: This includes some of aforementioned nuances like the key and default keyword arguments and the use of iterators instead of slices:
sentinel = object()
def mymax(*args, default=sentinel, key=None):
"""max(iterable, *[, default=obj, key=func]) -> value
max(arg1, arg2, *args, *[, key=func]) -> value
With a single iterable argument, return its biggest item. The
default keyword-only argument specifies an object to return if
the provided iterable is empty.
With two or more arguments, return the largest argument.
"""
if not args:
raise TypeError('max expected at least 1 argument, got 0')
if len(args) == 1:
it = iter(args[0])
else:
if default is not sentinel:
raise TypeError('Cannot specify a default for max() with multiple positional arguments')
it = iter(args)
largest = next(it, sentinel)
if largest is sentinel:
if default is not sentinel:
return default
raise ValueError('max() arg is an empty sequence')
if key is None:
for x in it:
if x > largest:
largest = x
return largest
largest_key = key(largest)
for x in it:
kx = key(x)
if kx > largest_key:
largest = x
largest_key = kx
return largest
# This makes the tooltips nicer
# but isn't how the C code actually works
# and it is only half correct.
mymax.__text_signature__ = '($iterable, /, *, default=obj, key=func)'
Answer from Raymond Hettinger on Stack OverflowQuestion about max function in numpy source code, saving O(100) nanoseconds by assigning a function
Yes, big O is used here in its loose sense, meaning "bounded by".
And yes, assigning to a variable takes (a small amount of) time. But the other thing that takes time is looking up names. If you're calling um.maximum.reduce, Python needs to find um in the local namespace, then find maximum in the namespace of um, and then find reduce in the namespace of maximum. Each of those steps takes (again, a small amount of) time. Whereas once you've assigned it to a local variable, you'd only need one lookup to find it.
So if you're calling a function repeatedly in a very time-constrained environment, it will certainly be cheaper to assign it once to a local variable and call that rather than incurring the repeated cost of the two extra lookups each time.
More on reddit.comWriting my own min function?
How long should a .py file be?
How long is too long for a single method/function
A good goal is to make a function do exactly one thing. In realistic programming, this usually doesn't happen strictly, but it's still a good way to judge a function's length. If it's doing too much, you should definitely split it up. I try not to let my functions get past 20-30 lines unless absolutely necessary before I start looking for ways to break them up. A function that is 350 lines of code is almost definitely too large: even if there isn't much repeated code, you could benefit from splitting code into different functions just for simplicity in reading and understanding as well as for maintenance on your program.
More on reddit.comAt this line: https://github.com/numpy/numpy/blob/v1.16.1/numpy/core/_methods.py#L16-L28
It says "saave those O(100) nanoseconds" and then assigns functions to variables, e.g. umr_maximum = um.maximum.reduce
I have a few questions,
-
What does O(100) mean, is it "up to but no more than 100 nanoseconds"?
-
Why does assigning to a variable save time? Doesn't it only cost time (and memory) to do the variable assignment?
I'm not trying to be pedantic I am genuinlely just wondering if there's anything I can learn from this small lighthearted comment! I am very new to both python as well as big O notation.
Yes, big O is used here in its loose sense, meaning "bounded by".
And yes, assigning to a variable takes (a small amount of) time. But the other thing that takes time is looking up names. If you're calling um.maximum.reduce, Python needs to find um in the local namespace, then find maximum in the namespace of um, and then find reduce in the namespace of maximum. Each of those steps takes (again, a small amount of) time. Whereas once you've assigned it to a local variable, you'd only need one lookup to find it.
So if you're calling a function repeatedly in a very time-constrained environment, it will certainly be cheaper to assign it once to a local variable and call that rather than incurring the repeated cost of the two extra lookups each time.
-
Saving O(1) time means that the time savings don't grow with the size of the input (or they only do up to a certain input size). Technically O(100) ms means the exact same thing, but the author may be abusing notation here to indicate that the time savings will be around 100ms. Or it may be a joke and all it's trying to say is that the amount of time saved is very small.
-
Yes, assigning the variable will take some time and space, but after that you save time each time you access the variable because reading a single variable takes less time than reading a variable and two attributes. (Not that this minuscule time saving is something you should worry about in your own code).
Does anyone know what the code is behind the python prewritten "min" function is?