There are three levels of availability for functions in Python.
Some functions are so ubiquitous that they are just built in
and generally available by defualt. These include things like print(), input(), and type().
Many functions must be imported from libraries. For example, after importing the math library, you will
have access to things like math.log10() or math.sin()
It is often desireable to write ones own functions for specific applications. this is especially useful to make ones program modular.
So far, we have used functions that were built into Python or imported from
libraries such as math. As programs become larger, however, it is
often useful to write your own functions. A function groups together a sequence
of statements that perform a specific task. Once a function has been defined, it
can be called as many times as needed throughout a program.
A function definition begins with the def keyword, followed by the
function's name and a pair of parentheses. If the function requires information
to perform its task, one or more parameters are listed inside the
parentheses. Parameters are variables that receive values when the function is
called. The first line of the function definition ends with a colon
(:), and every statement that belongs to the function must be
indented.
def function_name(parameter1, parameter2):
...
...
return value
The statements represented by the ellipses (...) are the body of
the function. They perform whatever calculations or operations are required.
Many functions end with a return statement, which sends a value
back to the part of the program that called the function. The returned value can
be stored in a variable, used in an expression, or passed directly to another
function.
Big idea: Functions allow you to divide a large programming problem into smaller, self-contained pieces. Well-designed functions make programs easier to read, easier to test, and easier to reuse.
One advantage of writing your own functions is that they can be reused in other parts of a program. In this example, we will define one function that calculates a chemical rate constant using the Arrhenius equation, and a second function that estimates the derivative of any function using a simple numerical approximation.
Suppose the rate constant follows the Arrhenius equation
\[ k=Ae^{-E_a/(RT)} \]
where \(A=1.00\times10^{13}\ {\rm s^{-1}}\), \(E_a=75.0\ {\rm kJ/mol}\), and \(R=8.314\ {\rm J\,mol^{-1}\,K^{-1}}\).
from math import exp
def rate_constant(T):
A = 1.0e13
Ea = 75000.0
R = 8.314
return A * exp(-Ea / (R * T))
def derivative(F, x):
return (F(1.01 * x) - F(x)) / (0.01 * x)
T = 298.15
k = rate_constant(T)
dkdT = derivative(rate_constant, T)
print(f"T = {T:.2f} K")
print(f"k = {k:.3e} s^-1")
print(f"dk/dT = {dkdT:.3e} s^-1 K^-1")
Output
T = 298.15 K
k = 7.238e-01 s^-1
dk/dT = 7.360e-02 s^-1 K^-1
The function rate_constant() calculates the Arrhenius rate
constant for any temperature. Instead of writing the Arrhenius equation every
time we need it, we simply call
rate_constant(T).
The second function,
derivative(F, x),
is much more general.
Instead of accepting a number as its first parameter, it accepts
another function.
The parameter F can represent any function that accepts one
argument.
The derivative is estimated by evaluating the function at two nearby values of the independent variable:
(F(1.01 * x) - F(x)) / (0.01 * x)
This expression approximates the slope of the function using a small forward difference. Because the two points are very close together (only 1% apart), the estimated slope is usually very close to the true derivative.
Since derivative() accepts a function as an argument, it is not
limited to the Arrhenius equation. It can be used to estimate the derivative of
any single-variable function.
dkdT = derivative(rate_constant, 298.15)
Here, the function rate_constant (notice there are
no parentheses) is passed to derivative().
The derivative function then evaluates
rate_constant()
internally at two nearby temperatures to estimate the slope.
This is called numerical differentiation. It provides an approximation to the true derivative, and its accuracy depends on how small the step size is. Choosing a smaller step generally improves the approximation, although making the step too small can introduce roundoff errors due to finite numerical precision.
One subtle but important detail is that the function name
rate_constant is passed to derivative()
without parentheses. Writing rate_constant passes the
function itself, allowing derivative() to decide when and how many
times to evaluate it. If we instead wrote
rate_constant(298.15), Python would immediately calculate the rate
constant at 298.15 K and pass the resulting number to
derivative(). Since the derivative function needs to evaluate the
rate constant at two nearby temperatures, it must receive the function itself,
not the value returned by the function.
def keyword, followed by the
function name, its parameters, and a colon.
return statement sends a value back to the code that called
the function.
Big picture: Functions make programs modular. By writing small, well-defined functions that perform individual tasks, you can build larger scientific programs that are easier to understand, test, reuse, and maintain. Passing functions as arguments allows you to write general-purpose numerical algorithms that work with many different mathematical models.