A pure function returns the same output for the same input.
A non-pure function can return different outputs for the same input.
A function can have side effects (besides returning a value)
Pure functions without side effects are easier to reason about.
But sometimes side effects are necessary.
Functions that doesn’t modify the input arguments are easier to reason about.
def easier_function(values):
l2 = list(values) # copy🤔
for i in range(len(l2)):
l2[i] = min(0, l2[i])
return l2
>>> x = [1, 2, -1]
>>> easier_function(x)
[0, 0, -1],
>>> x
[1, 2, -1]
Just be aware that copying large datasets can be slow.
Makes it easy to use a function with many arguments.
Python’s default arguments are evaluated once when the function is defined, not each time the function is called.
Since Python is a dynamic language, the type of the returned variable is allowed to vary.
But it usually a bad idea, since you can not tell from reading the code, which type will be returned.
def is_operable(height, period):
if height < 10:
return height < 5.0 and period > 4.0
else:
return "No way!"
>>> if is_operable(height=12.0, period=5.0):
... print("Go ahead!")
...
Go ahead!
Important
Is this the result you expected?
A non-empty string or a non-zero value is considered “truthy” in Python!
Python is a dynamically typed language -> the type of a variable is determined at runtime.
from datetime import date
class Interval:
def __init__(self, start:date, end:date):
self.start = start
self.end = end
@staticmethod
def from_string(date_string):
start_str, end_str = date_string.split("|")
start = date.fromisoformat(start_str)
end = date.fromisoformat(end_str)
return Interval(start, end)
>>> dr = Interval.from_string("2020-01-01|2020-01-31")
>>> dr
<__main__.Interval at 0x7fb99efcfb90>
from dataclasses import dataclass
@dataclass
class Interval:
start: date
end: date
@staticmethod
def from_string(date_string):
start_str, end_str = date_string.split("|")
start = date.fromisoformat(start_str)
end = date.fromisoformat(end_str)
return Interval(start, end)
>>> dr = Interval.from_string("2020-01-01|2020-01-31")
>>> dr
Interval(start=datetime.date(2020, 1, 1), end=datetime.date(2020, 1, 31))
On a regular class, equality is based on the memory address of the object.
class Interval:
def __init__(self, start:date, end:date):
self.start = start
self.end = end
def __eq__(self, other):
return self.start == other.start and self.end == other.end
>>> dr1 = Interval(start=date(2020, 1, 1), end=date(2020, 1, 31))
>>> dr2 = Interval(start=date(2020, 1, 1), end=date(2020, 1, 31))
>>> dr1 == dr2
True
For a dataclass, equality is based on the values of the fields.
from dataclasses import dataclass, field
@dataclass
class Quantity:
unit: str = field(compare=True)
standard_name: field(compare=True)
name: str = field(compare=False, default=None)
>>> t1 = Quantity(name="temp", unit="C", standard_name="air_temperature")
>>> t2 = Quantity(name="temperature", unit="C", standard_name="air_temperature")
>>> t1 == t2
True
>>> d1 = Quantity(unit="m", standard_name="depth")
>>> d1 == t2
False
Modules are files containing Python code (functions, classes, constants) that belong together.
$tree analytics/
analytics/
├── __init__.py
├── date.py
└── tools.py
The analytics package contains two modules:
tools
moduledate
module__init__.py
__init__.py
can be empty, and it indicates that the directory it contains is a Python package__init__.py
can also execute initialization code__init__.py
Example: mikeio/pfs/__init__.py
:
By adhering to the naming conventions, your code will be easier to read for other Python developers.
lowercase_with_underscores
CamelCase
UPPERCASE_WITH_UNDERSCORES
GRAVITY = 9.81
AVOGADRO_CONSTANT = 6.02214076e23
SECONDS_IN_A_DAY = 86400
N_LEGS_PER_ANIMAL = {
"human": 2,
"dog": 4,
"spider": 8,
}
Python will not prevent you from changing the value of a constant, but it is a convention to use all uppercase characters for constants.
Python package development