EuroPython 2018: More Than You Ever Wanted To Know About Python Functions
Mark Smith has been a Python developer & trainer for 18 years and is now trying out Developer Relations to see how that feels.
Functions are normally taught early on, because curricula want to go through with the basics fast, so the details get lost at first, and sometimes you never catch up with them.
Simple functions
Let's look at a simple function without parameters and without return values. The first line, defining the function is usually only executed once in the lifetime of the program. Functions are regular variables, and you can assign them regularly to other variables.
Functions with parameters
You can call a function with several parameters in many different ways:
def send(recipient, message): # parameters print(f'{recipient}: {message}') # call with positional arguments send('foo', 'bar') # call with named arguments (commonly called keyword arguments, only they're not keywords) send(message='bar', recipient='foo') # a mix, raising a TypeError! send('foo', recipient='bar')
Let's called them "named arguments" instead of "keyword arguments", as they're just not keywords. They're very practical, as you don't have to memorize order! Be careful mixing named and positional arguments, if you accidentally overload one of the arguments, you'll get a TypeError.
Functions with default values
You can also provide default arguments, which have to be defined after positional arguments.
def send(recipient, message, sender='me'): print(f'{recipient}: {message}') # call with positional arguments, leave or set default send('foo', 'bar') send('foo', 'bar', 'not me') # call with named arguments send(message='bar', recipient='foo', sender='not me')
Beware of mutable default arguments! Don't set def foo(argument1=[])
-
lists are mutable, and if you now go ahead and alter argument1
in the
method, it will persist across method calls (as the definition line is only
executed once). Use instead None
, and check if the value is None
, set the
variable to the default.
Beware of passing in mutable values to functions, and then just using them
in that function, because you'll find yourself in side effect hell. So don't use
mutating functions on mutable objects you receive, but rather create new objects
([] + ['foo']
instead of append
).
Variadic parameters and "keyword" arguments
def printf(fmt, *args): pass def printf(fmt, **args): pass
This soaks up all remaining unnamed arguments. You can't provide this
explicitly as a named tuple. You also can't pass in a dictionary directly. You
can combine the two of them. Make sure that *args
is the last positional
argument.
Calling methods
So generally you want to use keyword arguments to call methods, as you're future proof, and more readable. If you want ot force people to use keyword arguments, use the *
shortcut (which you can also stick in the middle or your parameters):
def send(*, message=None, recipient=None): pass
Sadly, in Python unpacking arguments looks very similar to the parameter syntax, so with longer or optional arguments, you may want to do:
args = ('1', 2, 3) kwargs = {'foo': 34} call_method(*args, **kwargs)
Also, keep in mind that None
is returned if you don't return something explicitly.
Scopes
There were three scopes: functions, modules, and builtins, searched in this order. If you use a non-defined variable, which is present in the module scope, that variable will be used.
This makes assigning to global variables tricky, because instead you'll find yourself with a local variable instead. Premature access to such a variable may also crash your program in horrible wasy (UnboundLocalError). Use the global
declaration to make explicit that you use a global variable (it's not really about global variables, you just go from functions to module scope).
Three scopes are the past though – by now we have inner functions, as you can define functions within functions. Assigning to outer function scoped variables, use nonlocal
. Both nonlocal
and global
should be use with extreme caution, and you should reconsider your structure in most places before using though.
Classes
Methods on classes look very much like a function. Their first parameter will never be explicitly passed. Class.do_foo
is a function, but instance.do_foo
is a bound method. (Fun fact! You can use __class__
, not self.__class__
to figure out what class you're in, starting in Python 3. This is how the fancy short super()
works.)
If a class defines a __get__
, this will used to access the method. (This is why you don't need to pass self
in.)
Callable objects
If you define __call__
on objects, suddenly they're callable.
Inspecting functions
Use inspect.signature
to look how the function signature looks like. Look at enforce which enforces types at runtime using this.
Function attributes
Functions have __code__
, exposing bytecode. So this is where you go to do weird hackery.