Comparison · Perl 5 vs Python 3
Perl or Python for a log-shaping pipeline?
One task, two implementations, the same input file, and a decision matrix that names its own limits. Nobody wins this outright, and the page is structured so you can see why.
- Reviewed
- Reading time
- 14 minutes
- Benchmark
- Not run
- Examples
- Not CI-tested
01
The task
Read a mixed-format access log from standard input, extract five fields, normalise timestamps to UTC, drop bot traffic by user-agent pattern, and emit newline-delimited JSON — as a single file that a colleague can run without a build step.
Constraints
- Runs on a stock Linux host with no package installation permitted.
- Input is 2–20 GB per run, streamed, never fully in memory.
- The person maintaining it in two years may not be the person writing it.
Scope If your job differs on any of those three constraints, the conclusion below does not transfer. The third constraint in particular is the one that flips the answer most often.
02
Decision summary
For this task the two are close enough that the deciding factor is your environment, not the language. Perl wins when you cannot install anything and the transformation is regex-shaped. Python wins when the output schema is going to grow, or when the maintainer pool is larger. Anyone who tells you one is categorically faster here has not defined the task.
03
Choosing between them
Choose Perl when
- You cannot install packages and need more than shell provides — Perl 5 is present on effectively every Unix host.
- The core of the work is pattern extraction and rewriting, where the regex engine is the program.
- The script must stay a single file with no virtualenv and no lockfile.
- You are already maintaining Perl in this codebase and consistency is worth more than novelty.
Choose Python when
- The output schema will grow into structured records with validation.
- You need the wider library surface for downstream analysis in the same process.
- The maintainer pool matters more than startup footprint.
- You want type annotations as a maintenance aid on a long-lived pipeline.
04
Environment and versions
- Operating system
- Not recorded
- CPU
- Not recorded
- Perl
- Not recorded
- Python
- Not recorded
05
The two implementations
Both satisfy the task statement exactly. Both are written for a competent maintainer rather than for a benchmark. A line-by-line diff between two languages would be noise — the lines were never going to match — so the files are segmented by what each passage is doing instead. Pick a stage and both sides mark the passages that answer it.
What differs here
Nothing is selected, so both files are shown in full. Pick a stage on the left to mark the passages that implement it on both sides — and to read what actually changes there.
A genuine tie. Perl reaches for two core modules; Python reaches for two standard-library
modules. Neither needs an install step. The constraint that decides this stage is not in
the code at all — it is whether perl or python3 is on the host,
which is the availability dimension below.
Perl binds the precompiled pattern in list context and receives the captures directly,
falling through with or next — two lines. Python matches into an object,
guards on it, then unpacks — four. The Perl is shorter; the Python names the failure
case explicitly, which is the whole argument in miniature.
Identical logic, different grammar. Perl tests the raw line against a
qr// with a binding operator, inside the loop condition if you want it
there. Python calls .search() and branches. This is the clearest case on
the page of regex being syntax versus regex being a library.
Close to a wash, and worth saying so. Time::Piece is core and its
strptime handles the %z offset; Python's datetime
does the same and converts with a named method. Python reads marginally better here;
Perl needs one fewer concept to get there.
Perl builds a hashref and encodes it, with 0 + forcing numeric context so
status and bytes do not serialise as strings — a real trap, and invisible until
something downstream compares them. Python's int() does the same job more
visibly. This stage is where the structured-output dimension lives: it is the passage
that grows when the schema does.
#!/usr/bin/perl
use strict;
use warnings;
use JSON::PP ();
use Time::Piece;
my $json = JSON::PP->new->canonical;my $bot = qr/bot|crawler|spider|slurp/i;my $line_re = qr/^(\S+) \S+ \S+ \[([^\]]+)\] "([^"]*)" (\d{3}) (\S+)/;
sub to_utc {
my $t = Time::Piece->strptime($_[0], '%d/%b/%Y:%H:%M:%S %z');
return gmtime($t->epoch)->datetime . 'Z';
}
while (my $line = <STDIN>) { next if $line =~ $bot; my ($ip, $ts, $req, $status, $bytes) = $line =~ $line_re or next;
print $json->encode({
ip => $ip,
ts => to_utc($ts),
req => $req,
status => 0 + $status,
bytes => $bytes eq '-' ? 0 : 0 + $bytes,
}), "\n";}
#!/usr/bin/env python3
import json, re, sys
from datetime import datetime, timezone
LINE = re.compile(r'^(\S+) \S+ \S+ \[([^\]]+)\] "([^"]*)" (\d{3}) (\S+)')BOT = re.compile(r'bot|crawler|spider|slurp', re.I)
def to_utc(ts: str) -> str:
dt = datetime.strptime(ts, "%d/%b/%Y:%H:%M:%S %z")
return dt.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
for line in sys.stdin: if BOT.search(line):
continue m = LINE.match(line)
if not m:
continue
ip, ts, req, status, size = m.groups() print(json.dumps({
"ip": ip,
"ts": to_utc(ts),
"req": req,
"status": int(status),
"bytes": 0 if size == "-" else int(size),
}, sort_keys=True))
Both files are 27 lines. That symmetry is not a trick of formatting — it is what happens when two competent implementations answer the same five questions, and it is the reason a raw line-count comparison would have told you nothing.
06
Results
No benchmark has been executed for this comparison. When one is, it will publish the harness, the input corpus checksum, the hardware, the number of runs and the variance — or it will not publish.
This block is deliberately empty rather than filled with a chart of numbers we did not measure. An outline chart with no data behind it would be worse than nothing.
07
Tradeoff matrix
Five dimensions. Each carries which way it leans, how confident we are, and the basis for
that confidence. A dimension with no basis is published as none rather than
being quietly dropped.
| Dimension | Perl | Python | Leans | Confidence | Basis |
|---|---|---|---|---|---|
| Availability without installation | Present on essentially every Unix-like host, including minimal container base images that ship a full perl. | Usually present on Linux distributions, but the version varies and minimal images increasingly ship without it. | Perl | High | Widely observable property of common base images; not a benchmark. |
| Regex ergonomics | Regex is language syntax. Match, substitute and bind read as one operation. | Regex is a library. Correct, capable, and consistently more verbose for substitution-heavy work. | Perl | High | Language design, directly inspectable in the code above. |
| Structured output and validation | JSON::PP is in core and adequate. Schema validation means reaching for CPAN. |
json is in the standard library; dataclasses and validation libraries are a well-trodden path. |
Python | Medium | Standard-library surface, verifiable from each language’s documentation. |
| Throughput on the stated task | Not measured | Not measured | Unknown | None | No benchmark has been run. We are not going to repeat someone else’s numbers on hardware we did not measure. |
| Maintainability by the next person | Depends heavily on discipline: strict, warnings, named subs and no clever punctuation variables. |
More conventional defaults, and a larger pool of people who will recognise the idioms. | Python | Low | Judgement, not measurement. Weight it against your own team. |
The lean column carries a glyph as well as a colour, and the confidence column is plain text — nothing in this table depends on you being able to distinguish two hues.
08
Weigh it against your situation
The table above is our evidence. What it is worth depends entirely on which constraints you are actually under, and we cannot see those from here. So set them yourself: mark each dimension irrelevant, relevant or decisive, and the page reports where your weighting lands against our dimensions.
Leans Perl
Leans Perl
Leans Python
No basis
Leans Python
09
Methodology
- The task statement is the contract. Both implementations must satisfy it exactly; anything either one does extra is out of scope.
- Code samples are written for readability by a competent maintainer, not for benchmark scores.
- Dimensions carry an explicit confidence level.
nonemeans we have no basis and say so instead of guessing.
10
Limitations
- No throughput measurement. Any performance claim you take from this page is one we did not make.
- Single task shape. Results do not generalise to CPU-bound numeric work, where neither language is the right answer.
- Version-sensitive. Both languages move; re-read the reviewed date before relying on this.
11
Corrections and discussion
If a dimension is wrong, say which one and why. Corrections that name a dimension and cite a source are applied and credited; the reviewed date moves when they are.