29
loading...
This website collects cookies to deliver better user experience
tuple
is framed as the less potent brother of list
. In long-living code, however, limiting functionality is an asset. This article shows where list
-ridden code would be better of using a tuple.dict
objects passed around to convey data that has a fixed structure, list
objects that happen to not be appended to, inserted to, sliced from, sorted, int
objects that can be multiplied, added to, ... etc....tuple
over other data structures.tuple
, and most of the time this is seen as a liability (not always). This is a frame rooted in the comfort of the code author to choose a data type that can do everything they would ever need.tuple
from the frame of code reviewing and maintenance, and starts from the comfort of the reader.def main():
inputs = [1, 2, -3, 4, -5]
results = process(inputs)
tabulate(inputs, results)
tabulate
receives [1, 2, 3, 4, 5]
as its inputs argument?process
function does. It may be implemented asdef process(inputs):
# all my inputs are mine!
originals = tuple(inputs)
inputs.sort(reverse=True, key=abs)
total = sum(
multiprocess_pool.map(
sum,
more_itertools.chunked(inputs, 3)))
return tuple(i/total for i in originals)
def main():
inputs = (1, 2, 3, 4, 5)
results = process(inputs)
tabulate(inputs, results)
tabulate
function could receive anything but (1, 2, 3, 4, 5)
. It becomes trivial to see that the inputs
object cannot be mutated. How neat is that!?def generate_data():
return tuple({
"temperature °C": float(tc),
"time (ms)": float(tms),
"location": loc
} for tc, tms, loc in csv_rows("datapoints.csv"))
# In a totally different module, we find the consumer:
def print_location_temperature(data):
get_loc = lambda s: s["location"]
for loc, samples in group_by(data, key=get_loc):
sum_t = sum(s["temperature"] for s in samples)
avg_t = sum_t / len(data)
print(f"average temperature in {loc}: {avg_t}")
s["temperature"]
will surely raise KeyError
!dicts
, it won't, but by giving it dataclass
es! This totally makes sense, because we know the structure of the data upfront, and we want to have two pieces of code using the same structure - exactly what dataclass
is for!@dataclass(frozen=True) # reap benefits of immutable data
class Sample:
temperature_C: float
time_ms: float
location_id: str
def generate_data() -> Iterable[Sample]:
return Sample(
temperature_C=float(tc),
time_ms=float(tms),
location_id=loc)
for tc, tms, loc in csv_rows("datapoints.csv"))
# In a totally different module, we find the consumer:
def print_location_temperature(data: Iterable[Sample]):
get_loc = lambda s: s.location_id
for loc, samples in group_by(data, key=get_loc):
sum_t = sum(s.temperature_C for s in samples)
avg_t = sum_t / len(data)
print(f"average temperature in {loc}: {avg_t}")
mypy
can check for attribute errors. (I bet you're smarter than me and long spotted that the len(data)
should have been len(samples)
).