1.080.1 QR Code Generation Libraries#

Python QR generation compared: qrcode, segno, zxing-cpp, pyStrich, qrcodegen. Styling vs standards, the Pillow tax, Micro QR/rMQR and measured speed.

Evidence: measured in your browser. A Workshop model runs this survey’s own code in the tab, so its numbers can be re-run rather than trusted.

Try it: qrkadelic — a working demo of this survey’s findings. In your browser: six Python QR libraries all choose the same code and read back the same, so the choice is everything else, and how far you can push the styling before your phone stops reading it.

At a glance#

encode one url matrix only lower is faster
zxing-cpp: 0.189 mszxing-cpp0.189 mssegno: 3.249 mssegno3.249 msqrcode: 4.048 msqrcode4.048 msqrcodegen: 7.209 msqrcodegen7.209 msPyQRCode: 10.264 msPyQRCode10.264 ms
LibraryHow it worksBest forLatest release
qrcode (python-qrcode)Pure Python encoder + pluggable image-factory rendererBranded and styled codes, and being the library already installed8.2 · 2025-05-01
segnoPure Python encoder, 13 native serialisers, effectively zero dependenciesStandards depth, print-ready vector output and the smallest real footprint1.6.6 · 2025-03-12
zxing-cppPython bindings over the C++ ZXing port (compiled extension)Throughput, the widest symbology set, and verifying your own output3.1.1 · 2026-07-29
pyStrichPure Python multi-symbology encoder/renderer core (QR, Data Matrix, PDF417, Aztec, 1-D)QR alongside other symbologies without a compiled extension; DXF output0.20 · 2026-08-27
qrcodegen (Nayuki)Matrix-only reference encoder, one source across Java/TS/Python/Rust/C++/CSeveral runtimes sharing one audited encoder, or the smallest possible install1.8.0 · 2022-04-17
PyQRCodePure Python, minimal API, SVG native and PNG via pypngNothing new — migrate to segno, which occupies the same niche and does more1.2.1 · 2016-06-20

Latest release observed from PyPI in 2026-09.

What the research found

  • The encoder is a commodity — every library selects the same symbol version and decodes identically — Given the same URL at error correction M, every library tested selected a 33-module version-4 symbol and every output decoded back to the same string. Module-for-module the patterns are NOT always identical: re-probed 2026-08-17, qrcode and qrcodegen agree on a URL while segno differs, all three differ on HELLO WORLD, and all three converge to bit-identical output on a 200-digit numeric payload — numeric mode leaves no segmentation or mask latitude to disagree over. The divergence is not actionable; what it settles is that ‘which library encodes better’ has no answer, and the choice is made on renderer, footprint and symbology breadth instead.
  • segno’s default constructor returns a Micro QR for short payloads at error levels L, M and Q — make() picks the smallest symbol that fits, and Micro QR is in the candidate set. Micro QR is standardized but read far less reliably by consumer phone scanners, so a symbol that passes a library round-trip can fail on a real phone. Every axis is controllable — micro=False, version=, make_qr(), make_micro() — and error=‘H’ yields a standard symbol on its own, because level H does not exist in Micro QR (segno raises). That narrows the trap rather than removing it: L, M and Q are just as ordinary a choice and still return Micro.
  • ‘ISO-compliant’ in this category means compliant with a superseded edition — ISO/IEC 18004:2024 (published 2024-08) cancels and replaces 18004:2015. segno’s own README still claims the 2015 edition; qrcodegen claims Model 2. No library surveyed claims the current edition. Codes still scan — but a real conformance obligation needs the edition number, not the adjective.
  • “You can cover 30% of a QR code” is off by roughly three times — 30% is level H’s codeword RECOVERY capacity, not area you may paint over — a solid centred blob concentrates damage into contiguous codewords, the worst case for Reed-Solomon. Measured by sweeping a centred square to failure: L 2.5%, M 4.5%, Q 11%, H 11% of symbol area. As a logo sized by width at a 10% pad: L 13.5%, M 18.5%, Q 28.0%, H 29.5%. M to Q is the jump and Q to H is nearly flat, because both select the same symbol version and the extra codewords go to the larger symbol. Identical across all four zxing-cpp binarizers and confirmed against a phone at every level — unlike low-contrast failure, which is decoder-dependent.
  • The dependency that decides your install size is Pillow, not the QR library — Pillow measured 22.95 MB against 0.04-1.89 MB for the libraries themselves. The real question is never ‘how big is this library’ but ‘does it need Pillow’ — which is why segno reaching PNG, PDF and EPS natively at 0.30 MB is a category-defining property rather than a detail.

Explainer

Domain Explainer: QR Code Generation#

For readers deciding whether this category is relevant to them, and what they need to know before reading the passes.


The one-paragraph version#

A QR code is a black-and-white grid that encodes a short string of bytes, wrapped in enough redundancy that a camera can read it back even when part of it is dirty, creased or hidden. The rules for building that grid are frozen in an international standard, they have not meaningfully changed in years, and half a dozen Python libraries implement them. Because the rules are frozen and the libraries agree, choosing a QR library is almost never a decision about QR codes. It is a decision about what file formats you need out the other end, how much weight you are willing to add to your install, and whether QR is the only symbology you need.

If you only ever need “a PNG of this URL, once, in a script”, any library here will do and you should stop reading. The rest of this page is for people who need to know why the others exist.


What a QR code actually is#

A symbology, not a format#

The word to hold onto is symbology: a specification for turning data into a printable pattern that a scanner can turn back into data. QR is one of many — Data Matrix, PDF417, Aztec and the familiar striped retail barcodes are others. A symbology is not a file format. A QR code is not “a PNG”; a PNG is one of many ways to draw one.

That distinction explains an otherwise confusing feature of this category. The hard part of a QR library — the encoder — produces a grid of dark and light squares, usually called a module matrix. Turning that grid into something you can use is a completely separate job, and libraries differ enormously in how much of that second job they take on. One of the libraries surveyed here does none of it at all: it hands you the grid and wishes you well.

The anatomy of a symbol#

Look at a QR code and you can pick out its parts:

  • Finder patterns — the three large concentric squares in the corners. These are how a scanner locates the code and works out its orientation and perspective. A QR code photographed at an angle is still readable because these three fixed points let the decoder undo the distortion.
  • Timing patterns — the alternating line of modules running between the finder patterns, which tells the decoder the grid spacing.
  • Alignment patterns — smaller squares scattered through larger symbols, correcting for the paper not being flat.
  • Format and version information — small reserved regions encoding how big the symbol is and which error-correction level and mask were used.
  • Data and error-correction modules — everything else. Your payload, plus the redundancy that lets it survive damage.
  • The quiet zone — the blank margin, four modules wide, required all the way around the symbol. It is not decoration. A code printed hard against a dark background or another graphic often will not scan, and a missing quiet zone is one of the most common causes of a “the code doesn’t work” complaint.

Versions, or why codes get bigger#

QR has forty versions, which means forty sizes. Version 1 is a 21-by-21 grid. Each step up adds four modules per side, ending at 177-by-177 for version 40. More data means a higher version means a physically larger code or smaller modules.

This is the mechanism behind a rule that surprises people: shorter payloads make better codes. Not because short strings are aesthetically preferable, but because a version-3 symbol has fat, forgiving modules that a cheap camera reads instantly from a distance, and a version-25 symbol crammed into the same physical square has modules a quarter the size that need better optics, better print quality and a closer scan. This is why link shorteners and QR codes go together so often, and it is the single most useful piece of practical knowledge in this domain.

Error correction, and the trade you are actually making#

QR uses Reed-Solomon error correction, at four selectable levels named L, M, Q and H in increasing order of redundancy. Higher levels survive more damage.

The trade is not free, and it is not the one people assume. Redundancy occupies data capacity, so raising the error-correction level on a fixed payload pushes you toward a higher version — a bigger, denser symbol. Choosing H “to be safe” can produce a code that is harder to scan than the M version it replaced, because you have traded module size for damage tolerance and module size is what your camera actually cares about at distance.

The one place high error correction earns its cost is when something is deliberately covering part of the symbol — most commonly a logo dropped into the middle. That works because the redundancy is there to reconstruct the obscured modules. It is a real technique, and it is also the reason branded codes fail more often than plain ones.

Encoding modes#

QR encodes different content at different densities. Numeric mode packs digits tightest, alphanumeric handles a restricted uppercase-and-symbols set, byte mode handles arbitrary data including UTF-8 text, and kanji mode encodes Japanese characters compactly. A good encoder segments a payload across modes to minimise the symbol; a simple one uses byte mode for everything and produces a slightly larger code.

Two related concepts appear in library feature lists. ECI is a mechanism for declaring which character set the bytes are in, rather than leaving the scanner to guess. Structured Append splits one logical payload across several physical symbols that a decoder can reassemble — useful when the content will not fit in one readable code.

The QR family#

“QR code” usually means the standard symbol, but there are relatives, and library support for them is one of the sharpest lines in this category:

  • Micro QR (sizes M1 to M4) — much smaller symbols with a single finder pattern, for marking things that have very little room. Standardized and legitimate; noticeably less reliably supported by consumer phone scanners than standard QR. That last point matters and is easy to miss.
  • rMQR — a rectangular Micro QR variant, for long thin spaces where a square will not fit.
  • Model 1 — the obsolete original, which you will see referenced in library documentation and should never choose to generate.

Support for these is uneven, and one library in this survey will hand you a Micro QR symbol by default if your payload is short enough — a behavior worth understanding before you discover it in production.


What these libraries do and do not decide#

They do not decide what your payload means#

A QR code containing https://example.com is not “a URL QR code”. It is a QR code containing text that happens to look like a URL, which scanners recognize by convention and offer to open. The same is true of Wi-Fi credentials, contact cards, calendar events and payment strings: those are string formats agreed between applications and scanner vendors, layered on top of a symbology that knows nothing about them.

The practical consequence is that a library advertising “vCard support” is advertising string formatting, not QR capability, and you can get exactly the same result by formatting the string yourself. Do not let convenience helpers drive a library choice.

They do not decide whether your code scans#

Scannability is a physical property, produced by module size, print quality, contrast, the quiet zone, the surface, the lighting and the camera. A library can give you a correct symbol. Everything after that is a printing and design problem, and it is where most real-world QR failures actually live.

The single best investment anyone deploying QR codes can make is testing the actual printed artifact with actual phones — several models, at the distance and lighting the code will really be seen in. No amount of library selection substitutes for that.


Why this category looks the way it does#

A frozen standard changes the economics#

QR Code is specified by ISO/IEC 18004. The current edition is the fourth, published in August 2024, replacing the 2015 edition. But the symbol — the geometry, the mask patterns, the version ladder, the error-correction scheme — has been stable far longer than any library implementing it.

Three consequences run through the whole survey:

Encoders have converged. When six independent implementations are all implementing the same finished specification, they produce the same output. This survey checked rather than assumed, and on ordinary payloads every library tested selected the identical symbol version. “Which one encodes better” is a question with no useful answer.

Maintenance signals are hard to read. In a fast-moving category, eighteen months without a release means trouble. Here it might mean the library is finished. That ambiguity is real and the survey’s strategic pass spends its time on it — distinguishing carefully between “has not released” and “is not maintained”, which are different diagnoses.

Differentiation moved elsewhere. Since nobody can win on encoding, the projects compete on rendering, on dependency weight, on how much of the wider barcode world they cover, and on stated standards conformance. Those are the axes the survey uses.

The Pillow question#

One structural fact dominates the practical side of this category. Pillow, Python’s imaging library, measured about 23 MB installed — roughly a hundred times the size of most of the QR libraries that depend on it, and in some cases several hundred times.

So “how big is this library” is almost always the wrong question. The right one is does it need Pillow to do what I need. A library that produces raster output natively, or that you only need vector output from, avoids the cost entirely. A library that routes PNG through Pillow does not, no matter how small its own code is. Once you see that, several apparently puzzling positions in this category become obvious.

Two published comparisons, both vendor-authored#

If you research this category yourself you will find two documents that dominate the results: a feature-matrix comparison written by one library’s maintainer, and a 2026 correctness benchmark written by another library’s maintainer that ranks that library first.

Both are useful and neither is neutral. The survey cites both, labels both, and independently verified what it could. For a symbology this old and this widely deployed, there is remarkably little independent comparative evidence available, and a reader should treat confident published rankings in this space with the scepticism their authorship earns.


What you need to decide before reading the passes#

Four questions, in roughly this order of importance:

  1. What has to come out the other end? A PNG on a screen, a vector file that goes to a press, CAD geometry for something that gets etched, or just the raw matrix for you to draw yourself. This single question eliminates more options than everything else combined.
  2. Does the install size or the dependency count matter? In a container, a serverless function, or anything facing a security review, this is decisive — and it is very largely the Pillow question.
  3. Is QR the only symbology you need? If Data Matrix, PDF417, Aztec or the retail barcodes are also on the list, that changes the answer completely and narrows the field to two candidates.
  4. Does the code have to look like something? Color, rounded modules, a logo. Only one library in the category treats this as a core capability rather than a plugin.

Notice that “which one is most correct” is not on the list. That is the finding, not an omission.


Where to go next#

  • S1 — Rapid Discovery maps the field: what exists, who uses it, and what each one is for. Start there.
  • S2 — Comprehensive Discovery opens each library up: how the encoder and renderer are separated, where the performance differences come from, and a feature matrix built from measurements rather than documentation.
  • S3 — Need-Driven Discovery works through six readers with different constraints — volume, print, branding, footprint, industrial marking, and cross-language consistency — and shows how the same category produces six different answers.
  • S4 — Strategic Discovery asks which of these will still be there in five years, and confronts the awkward fact that in a frozen-standard category the usual signals do not mean what they normally mean.
  • 1.080 Image Processing — what you reach for once you have a raster QR code and need to composite, resize or convert it.
  • 1.101 PDF Generation — the usual downstream destination for a print workflow, and frequently confused with this category because both end in a document.

Trademark note, as recorded in segno’s own documentation: “QR Code” and “Micro QR Code” are registered trademarks of DENSO WAVE INCORPORATED.

S1: Rapid Discovery

S1 Approach — Rapid Discovery#

What this pass is for#

S1 maps the category. It answers “what exists, who uses it, and what is each one actually for” — fast enough to be worth reading before you have committed to anything, and shallow enough that it does not pretend to be a manual. No code appears in this pass by design. If you want to know how a library is built, that is S2; if you want to know who should pick it, that is S3; if you want to know whether it will still be there in five years, that is S4.

The category, and its edges#

In scope: Python libraries that generate QR codes — turning a payload into a symbol you can print, display, or embed. That covers encoding (choosing modes, versions and error correction), and rendering (turning the resulting matrix into pixels or vectors).

Out of scope: reading and decoding QR codes, which is a different problem with a different field, and which this survey touches only where a library happens to do both. Also out of scope: general image processing (1.080) and PDF generation (1.101), both of which frequently sit downstream of a QR generator without being part of this category.

One boundary catches people. QR is a symbology, not a file format and not a protocol. A QR generator’s job ends the moment it has produced a correct module matrix and a serialization of it. Anything about what the payload means — a URL, a payment string, a vCard, a Wi-Fi credential — is convention layered on top by scanners and applications, not something the symbology or these libraries decide. A library that offers “vCard helpers” is offering string formatting, not QR capability.

Why this category behaves unusually#

QR Code is standardized as ISO/IEC 18004. The symbology is old, stable, and frozen: the geometry, the error-correction scheme, the mask patterns and the version ladder have not meaningfully moved in the working lifetime of most of the libraries here. That has two consequences that shape everything below.

First, the encoder is a commodity. Getting from a payload to a correct matrix is a solved problem with a published reference algorithm, and the libraries here largely agree with each other. This survey tested that directly rather than assuming it — see the symbol-version comparison in S2. When six independent implementations produce byte-identical results on ordinary payloads, “which one encodes better” stops being the interesting question.

Second, staleness reads differently here than in a fast-moving category. A QR library that has not shipped a release in a year is not obviously dying; the thing it implements did not change either. That makes maintenance signals harder to read, not easier, and it is why S4 spends its time separating “no release” from “no activity”.

What actually differentiates these libraries#

If the encoder is commoditised, the differences have to live somewhere else. They live in four places, and these are the axes used throughout this pass:

  1. Renderer breadth and quality. What can it write out — raster, vector, print formats, terminal, CAD — and how good is the output it produces? This is where most of the engineering investment in this category has gone.
  2. Dependency footprint. Whether the library drags in an imaging stack, a compiled extension, or nothing at all. In a category where the core algorithm is small, the dependencies frequently outweigh the library by two orders of magnitude.
  3. Symbology breadth. Whether you get QR only, or QR plus its small-format relatives (Micro QR, rMQR), or QR as one member of a much larger barcode family (Data Matrix, PDF417, Aztec, and the 1-D symbologies).
  4. Standards depth. Whether the project treats conformance as a stated commitment with a test corpus behind it, or as an implementation detail that works well enough in practice.

Evaluation criteria applied to every library below#

Each library file covers the same ground, in the same order, so they can be read side by side:

  • Snapshot — what it is in two sentences, and the niche it actually occupies.
  • Capabilities — encoding features and output formats, stated as what was verified rather than what is advertised.
  • Adoption — download volume and repository signal, with the caveats those numbers deserve.
  • Maintenance — release cadence and repository activity, kept separate from each other.
  • Dependencies and footprint — what it costs to install.
  • License.
  • Trade-offs — what you gain and what you give up relative to the peers.

On the numbers in this pass#

Every figure in these files was checked live on 2026-08-17 against the PyPI JSON API, the GitHub API, and pypistats.org, and every capability claim was verified by running the library rather than by reading its marketing. Where a claim could not be verified, it is absent rather than repeated. Where a figure is a download count, treat it as a measure of diffusion and not of deliberate choice: PyPI counts include continuous integration, container rebuilds and transitive installs, and the largest number in this category belongs partly to people who never picked anything.

Two published comparisons are cited in this survey and both are vendor-authored — segno’s own comparison documentation, and a 2026 benchmark published by the maintainer of one of the libraries it ranks first. Both are useful. Both are labeled as such every time they appear, and neither is laundered into an independent finding.


Other Contenders#

The six libraries with their own files are the ones this survey carries through S2, S3 and S4. This file covers everything else that turned up in the category scan, with the reason each one did not make the primary set. Several are worth knowing about even though they are not candidates — including one that is frequently mistaken for a member of this category and is not.

All figures checked 2026-08-17.

treepoem — actively maintained, but it shells out#

Version 3.28.0, released 2025-09-09. Repository pushed 2026-08-13. 164 GitHub stars. 925,922 downloads in the last 30 days.

By adoption and activity this belongs in the primary set — it draws more traffic than zxing-cpp’s near neighbours and is under current development. It is excluded for an architectural reason: treepoem renders barcodes by driving BWIPP through Ghostscript, an external binary that must be installed separately and invoked as a subprocess.

That buys extraordinary symbology coverage, because BWIPP implements essentially every barcode ever standardized. It costs everything a subprocess costs: an external system dependency outside your package manager, a much larger installed footprint, and per-code latency in a different class entirely. The vendor-authored benchmark discussed below measured it in the low thousands of milliseconds per PNG — three orders of magnitude slower than the compiled option — and at 101.5 MB installed.

Treat it as the answer to “I need a symbology nothing else supports and I control the deployment image”, not as a general QR generator. It is the strongest candidate for promotion if a future edition of this survey widens its scope to subprocess-backed tools.

PyQRCodeNG — a fork that went stale too#

Version 1.3.6, released 2020-01-01. Repository pyqrcode/pyqrcodeNG, 12 stars, last pushed 2021-12-21.

A fork of PyQRCode created to carry it forward after the original stopped releasing. It has now been quiet longer than the original had been when the fork started. Listed here only so that anyone who finds it while researching a PyQRCode migration knows it is not a live target.

qrcode-artistic — the plugin, not a library#

Version 3.0.2, released 2023-11-27.

A segno plugin providing artistic and image-backed output. It belongs in this file rather than in the primary set because it is not an alternative to anything — it is the answer to “segno cannot do branded codes”, and that answer is architecturally different from qrcode’s built-in styling layer.

The distinction matters when you are choosing. With qrcode, styling is a core capability maintained alongside the encoder. With segno, styling is a separate package on a separate release cadence, currently about three years past its last release. Whether that is acceptable depends entirely on how central branded output is to what you are doing, and it is a real point of difference between the two leading pure-Python options rather than a detail.

qrcode-styled — stale port of a JavaScript styling library#

Version 0.2.2, released 2023-08-02.

A Python port of the qr-code-styling JavaScript project. Nothing since 2023. Named here because searches for “styled QR codes in Python” surface it, and it is not a maintained option.

MyQR and amzqr — artistic QR, both long stale#

MyQR 2.3.1, released 2016-09-24. amzqr 0.0.1, released 2021-04-06.

Both target artistic and animated QR output — codes blended with an image or a GIF. Both are effectively dormant. Their niche is real and periodically requested, and nothing currently maintained fills it well; that is a genuine gap in the Python category rather than an oversight in this survey.

opencv-python-headless — has an encoder, not a candidate#

OpenCV ships a QR encoder alongside its far better-known detector. It is not evaluated here: nobody adds a computer-vision library to a project in order to generate a QR code, and this survey did not probe it. The one public multi-library benchmark that included it rated it “not recommended” for generation. Mentioned only because “OpenCV can do QR codes” is true and misleading in roughly equal measure — the part it is good at is reading them, which is outside this survey’s scope.

python-barcode — not in this category at all#

Version 0.16.1, released 2025-08-27.

Frequently proposed as an alternative when someone asks about QR codes in Python. It generates 1-D symbologies only — EAN, UPC, Code 39, Code 128 and similar — and produces no QR codes whatsoever. It is a good library for a different job. Listed here purely to close the question, because it comes up every time.

A note on the published comparisons in this space#

Two comparison documents dominate search results for this category, and both are written by people with a stake in the outcome.

segno’s comparison documentation compares qrcode, qrcodegen and segno on a feature matrix. It is accurate on the points this survey independently verified — Micro QR support, dependency counts, output breadth — and it is written by segno’s maintainer. This survey also found one row of it to be wrong: it credits qrcodegen with Kanji support, which holds for that project’s Java and C++ members but not for the Python package.

A July 2026 blog benchmark of seven libraries against a 26-case correctness corpus reports pyStrich passing 26 of 26 and rates it “best general purpose”, with zxing-cpp fastest. It is the most methodologically explicit public comparison this survey found — the corpus is characterised, and outputs were verified by decoding. It is also published by pyStrich’s own maintainer, a fact that must travel with every citation of it. Two of its claims were independently corroborated here (zxing-cpp’s speed advantage, and qrcodegen’s lack of file output); its correctness ranking was not reproduced by this survey and is reported as an attributed claim rather than a finding.

The absence of an independent, reproducible correctness benchmark is itself a finding about this category. For a symbology this old and this widely deployed, there is remarkably little neutral comparative evidence available.

Excluded by scope#

Decoding and scanning librariespyzbar, qreader, and the read side of zxing-cpp — are outside this survey, which covers generation. The exception is where read and write coexist in one package, which is a genuine architectural advantage and is treated as such in the zxing-cpp file.

Image processing is 1.080. PDF generation is 1.101. Both sit downstream of a QR generator often enough to be confused with it, and neither is part of this category.


PyQRCode#

Snapshot#

Retired. This file exists so that a reader who arrives holding PyQRCode — and given its download volume, a great many people are — gets an explicit verdict rather than an omission.

PyQRCode is a simple pure-Python QR generator with SVG, PNG, EPS and terminal output and an unusually small API surface. It works. Its symbols were verified round-tripping correctly through an independent decoder in this survey; nothing about it is broken. The problem is not quality, it is time: its last release was 2016-06-20, and everything that has happened in this category since has happened without it.

The recommendation is to migrate. What makes it worth a page is why it is still so widely installed, which is a more interesting fact than its retirement.

Capabilities#

Basic QR generation with error-correction selection. SVG output is native; PNG output requires the optional pypng extra. Terminal output is available. The API is minimal — closer to a function call than a library — which is what earned it its following.

No Micro QR, no rMQR, no structured append, no ECI or Kanji handling worth the name, no styling layer. It predates the problems those features solve.

Adoption — the interesting part#

628,262 downloads in the 30 days to 2026-08-17 (pypistats), against 427 GitHub stars.

Read that number again next to the release date. A package that has shipped nothing in a decade is being installed more than six hundred thousand times a month — more than half a million installs of software whose author last cut a release when Python 3.5 was current. It draws roughly fifty times the traffic of the actively developed pyStrich and roughly forty-nine times that of qrcodegen.

That is the single most instructive adoption fact in this category, and it says less about PyQRCode than about how dependency choices actually propagate: they get made once, written into a requirements file or a tutorial, and then copied forward by people who are not choosing at all. Download volume in this category measures inertia at least as much as it measures preference — which is a caution that applies just as much to the number at the top of the table as to this one.

Maintenance — and a correction#

Version 1.2.1, released 2016-06-20. Repository last pushed 2024-04-15.

Those two dates need to be kept apart. A flat “last update: June 2016” and a “nine-year maintenance gap” is the usual reading, and the repository was in fact touched in 2024. The correct statement is narrower and more useful: there has been no released artifact for about ten years, despite occasional repository activity. The practical consequence is the same — what you install is the 2016 code — but the reasoning matters, because “abandoned” and “not releasing” are different diagnoses and this survey applies that distinction to qrcode, segno and qrcodegen too.

GitHub stars: 427 (api.github.com, 2026-08-17) — not a large number in absolute terms, and a maturity signal worth sourcing rather than copying forward.

A fork exists — PyQRCodeNG, version 1.3.6 from 2020-01-01, twelve stars, last pushed 2021-12-21. It is not a live migration target; it is stale in its own right, and it is covered in the other-contenders file.

Dependencies and footprint#

The package itself measured 0.12 MB with pypng as an optional extra at 0.17 MB. Small, and free of any imaging-library dependency. On footprint alone it remains competitive — which is worth acknowledging, because that is presumably part of why it stuck.

No declared Python version constraint at all, which in 2026 is a red flag rather than a feature: it means nothing has been asserted about what it runs on since before the current release cadence existed.

License#

BSD three-clause, cleanly declared.

Performance#

Roughly 10.264 ms per code in this survey’s measurement — the slowest of the five encoders tested, about three times segno’s 3.249 ms and about fifty-four times zxing-cpp’s 0.189 ms, under identical conditions.

Trade-offs#

There is no case in which this is the best available choice in 2026. Its virtues — small, simple, dependency-light, SVG-native — are all matched or exceeded by segno, which is smaller in effective footprint, three times faster, supports the entire QR family, emits thirteen verified output formats, and has been touched this year.

Migration guidance, stated as a category observation: the natural target is segno, which occupies the same “small and dependency-free” niche PyQRCode occupied and does more within it. Note that a compatibility shim named segno-mimos is sometimes suggested for this migration and is not available on PyPI — it exists only as a GitHub repository last touched in 2020. Anyone planning a migration should plan to change call sites rather than to drop in a shim.

Where it sits: a legacy dependency to schedule out, not an option to weigh. If it is in a requirements file and the code works, nothing is on fire — the symbols it produces are valid. But it should not be chosen again, and it should not appear in new work.


pyStrich#

Snapshot#

The newest arrival in this category, and the only pure-Python option that treats QR as one member of a barcode family rather than as the whole job. pyStrich generates Data Matrix, PDF417, Aztec, Code 128 including GS1-128, Code 39, EAN-13 and ITF-14 alongside QR, sharing one matrix-encoding and rendering core across all of them.

It is also, strictly speaking, not new. It descends from huBarcode, with contributions running from around 2007, and the repository itself dates from 2015. What is new is the activity: version 0.19 shipped 2026-07-23 and the repository was pushed 2026-08-17 — the same day this survey was written. After years of quiet, this is a project under active redevelopment, and that is the most important and most uncertain thing about it.

Capabilities#

QR generation with explicit error-correction-level selection at construction, and output through the widest renderer surface of any pure-Python option after segno: PNG, SVG, EPS, terminal art, plain ASCII, PIL image objects, raw image data, and data URLs for both PNG and SVG.

The distinctive one is DXF. No other library in this survey emits CAD geometry, and that is not a niche curiosity — it is the format that goes into laser engravers, CNC toolpaths and mechanical drawings. If a QR code has to end up cut, etched or machined into a physical part rather than printed onto one, this is currently the only option in this category that hands you the geometry directly.

Its QR output was verified round-tripping correctly through an independent decoder, from both its file-saving path and its PIL image path — the symbols it produces are valid, not merely plausible.

The QR family beyond standard QR is absent: no Micro QR, no rMQR. Its breadth runs sideways across symbologies rather than downward into the QR specification.

Adoption#

76,806 downloads in the 30 days to 2026-08-17 (pypistats), against 106 GitHub stars. That is the second-lowest volume in this survey — roughly a fifty-fourth of segno’s and a three-hundred-and-eighty-fourth of qrcode’s.

For a package that shipped its current release three weeks before this survey, a low number is expected rather than damning. It does mean there is no meaningful production track record to point at yet, and no community of people who have already hit its edges.

Maintenance#

Version 0.19, released 2026-07-23. Repository pushed 2026-08-17. Along with zxing-cpp, this is one of only two projects in the category with both a recent release and current activity — and it is the only pure-Python one.

That is good news, with a genuine caveat attached. A revival that is weeks old has not yet demonstrated that it is a revival rather than a burst. The version number is still below 1.0. The project is substantially one maintainer’s work, operating under a company name (Method B Ltd) rather than an individual’s, which is a mild governance improvement over a personal project but not a foundation. S4 treats “will this still be maintained” as an open question, because it is one.

Dependencies and footprint#

This needs care, because the project describes itself as pure Python and that is true of its encoders but misleading about its installation. pyStrich declares an unconditional dependency on Pillow, plus a typing backport. There is no Pillow-free path — unlike qrcode, where Pillow sits behind an optional extra, and unlike segno, which reaches raster output without it at all.

The package’s own code measured 0.72 MB. Pillow measured 22.95 MB. The footprint of a pyStrich install is therefore dominated by a dependency the library’s own description does not lead with. If a small install is a requirement, this is disqualifying, and it is the one place where reading the project’s self-description instead of its metadata would mislead you.

Declared Python support is 3.10 or later — the narrowest in this survey.

License#

Apache-2.0 per the repository LICENSE text. GitHub classifies it as “Other” because the file also carries an SIL Open Font License notice covering the Courier Prime glyph outlines bundled for SVG and EPS text labels. That is a bundled-asset notice, not a restriction on the library, but anyone running automated license scanning should expect it to be flagged for manual review rather than passing silently.

The benchmark question#

pyStrich currently sits at the top of the only public multi-library correctness benchmark this survey found: a 26-case corpus spanning numeric, alphanumeric, Latin-1, UTF-8, kanji and mixed-mode payloads, published July 2026, on which pyStrich is reported as passing 26 of 26 and rated “best general purpose”.

That benchmark is published by pyStrich’s own maintainer. The blog, the repository and the library’s documentation domain all trace to the same person and company. The methodology is described and the corpus is characterised, which is more than most vendor comparisons offer, and the result may well be correct. But it is a vendor-authored ranking of its own product, and this survey did not reproduce the corpus. It is reported here as a claim with attribution, not as a finding.

Trade-offs#

You gain: the broadest symbology coverage available without a compiled extension; DXF output, which nothing else in this category produces; a renderer surface well beyond what its download numbers suggest; active current development; and a permissive license.

You give up: maturity, on every axis. The lowest adoption of any live option here, a sub-1.0 version, no Micro QR or rMQR, the narrowest Python support, a revival too young to have proven itself, and a hard Pillow dependency that contradicts the “pure Python” framing most readers will take from its description. You also give up the ability to cite an independent quality signal, because the only benchmark that ranks it first is its own.

Where it sits: the multi-symbology pure-Python bet. Take it when you need QR plus Data Matrix or PDF417 or Aztec and a compiled extension is off the table, or when DXF output is what you actually need. Watch it rather than standardize on it if what you need is QR alone — the established options do that job with more history behind them.


qrcode (python-qrcode)#

Snapshot#

The default by adoption, and by a wide margin. qrcode — distributed under that name on PyPI, developed as lincolnloop/python-qrcode on GitHub — is what most Python code that generates a QR code is using, usually because it was the first result and it worked. Its distinguishing engineering choice is a pluggable renderer layer: the encoder produces a matrix, and a swappable image factory turns that matrix into output. That one decision is why it owns the styling niche in this category, and why it is the only library here where “make the code look like our brand” is a first-class capability rather than an afterthought or a plugin.

It is not the most standards-complete option, it is not the fastest, and it is not the smallest. It is the one with the most reach, the most examples written about it, and the widest surface for making a QR code that does not look like every other QR code.

Capabilities#

Encoding covers the standard QR ladder: all four error-correction levels are exposed, and version selection is automatic with a fitting pass. What it does not cover is the rest of the QR family — there is no Micro QR support in the package’s namespace, and no structured-append facility for splitting one payload across several symbols. Both were checked directly rather than inferred from documentation.

Output is where it earns its position. PNG comes through Pillow, SVG through a dedicated image factory, and a styled raster path supports module drawers and color masks — rounded modules with a radial gradient color mask was confirmed working in this survey. Logo embedding is widely described in third-party tutorials; this survey confirmed that styled output works but was unable to confirm the exact parameter that controls image embedding through introspection, so no parameter name is asserted here.

There is one measurable cost to the renderer design worth flagging early. Its SVG emitter writes one rectangle per module, and for the same payload it produced a file roughly sixteen times the byte size of segno’s path-based output. For a code on screen that is irrelevant. For thousands of codes in a print workflow or an asset bundle, it is not.

Adoption#

29,511,144 downloads in the 30 days to 2026-08-17 (pypistats), against 4,928 GitHub stars. That download figure is roughly seven times the next library in the category. A mirror check on the same package showed mirrors account for around 0.2% of the total, so this is not a mirroring artifact.

It is, however, a diffusion number rather than a preference number. A package this widely transitively depended upon accumulates installs from projects that never evaluated anything. Read it as “this is what you will find already in the environment”, which is itself a real and legitimate reason to choose it, rather than as a quality signal.

For calibration: in October 2025 the package drew 6,087,537 downloads a month, “10x more than the next competitor”. Both figures have moved — the volume is now nearly five times higher and the lead has narrowed to about seven to one.

Maintenance#

Version 8.2, released 2025-05-01. The repository was last pushed 2026-03-25. Those two dates say different things and both matter: the project is being worked on, but nothing has shipped to PyPI in roughly fifteen months. In a frozen-standard category that is much less alarming than it would be elsewhere — there is little pressure to ship — but it does mean anyone waiting on a fix in main is waiting on a release that has no announced date.

Governance is the most conventional in this set: an organization-associated project (Lincoln Loop) rather than a single individual’s work, with a contributor base and issue traffic proportionate to its reach.

Dependencies and footprint#

The base install is light and, outside Windows, dependency-free at the declared level: colorama is pulled in only on win32. Everything interesting is behind extras — Pillow for the PIL image path, pypng for the PNG path. The package’s own code is about 0.14 MB installed.

The extras, though, are where the cost is. Pillow measured 22.95 MB installed in the test environment, roughly 160 times the size of qrcode itself. If you need raster output through Pillow, that is the footprint, and it is the single largest line item in this entire category.

Declared Python support is 3.9 or later, below 4.

License#

BSD three-clause (repository LICENSE, “Copyright (c) 2011, Lincoln Loop”). GitHub classifies the repository as “Other”, and the distribution metadata carries both a BSD classifier and an “Other/Proprietary” classifier, which is a metadata artifact rather than a licensing complication — the LICENSE text itself is a standard BSD three-clause grant.

Trade-offs#

You gain: the largest install base in the category, so the highest chance the library is already present and the highest volume of existing examples; the best styling and branding support of any option here, by a clear margin, without reaching for a plugin; a conventional governance story; and a permissive license.

You give up: Micro QR and structured append, neither of which it implements; byte-efficient vector output, where its per-module rectangle emitter is substantially heavier than the alternatives; and dependency lightness the moment you want raster output, because Pillow dominates the footprint. It is also not the project to reach for if stated standards conformance with a published test corpus is something you have to demonstrate to somebody — it does not make that claim, and does not try to.

Where it sits: the pragmatic default. Choose it because it is already there, because you need the code to look like something, or because the ecosystem around it matters more to you than the capabilities you are giving up. Choose against it when footprint, vector efficiency, or the wider QR family are what decide the question.


qrcodegen (Nayuki)#

Snapshot#

The reference implementation, and the one library here that is not really trying to be a Python library. qrcodegen is the Python member of Project Nayuki’s multi-language QR generator — one repository publishing the same carefully specified encoder in Java, TypeScript/JavaScript, Python, Rust, C++ and C, written for clarity and correctness rather than convenience.

Its defining characteristic is what it refuses to do. It produces a module matrix and stops. There is no PNG writer, no SVG writer, no file output of any kind. You get the symbol’s size, its version, its error-correction level, its mask, and a function to ask whether a given module is dark. Everything downstream is yours.

That is a deliberate and defensible design. It is also the reason it has the smallest adoption in this category by two orders of magnitude.

Capabilities#

Encoding is thorough within its scope: all forty standard versions, all four error-correction levels, automatic selection of the smallest version in a caller-specified range, automatic evaluation of all eight mask patterns with optional manual override, and optional boosting of the error-correction level when it costs no extra version. Segments can be constructed manually — numeric, alphanumeric, byte and ECI — and combined.

Two absences matter and both were verified directly rather than assumed.

There is no file output whatsoever. This is not a gap to work around casually; rendering a QR matrix correctly means handling quiet zones, scaling and, for print, vector geometry. Choosing this library means committing to write and maintain that yourself, or to hand the matrix to something that already does it.

The Python port has no Kanji segment constructor. Its segment API exposes numeric, alphanumeric, byte, ECI and automatic segmentation, and nothing else. This matters because a widely-read published comparison — segno’s own documentation — credits qrcodegen with Kanji support. That claim is true of the Java and C++ members of the project. It is not true of what pip install qrcodegen gives you. The library’s own README is consistent with the probe: it claims Model 2 conformance and lists ECI segments among its features, and makes no Kanji claim. If a survey reader takes one correction away from this file, it should be that one.

There is no Micro QR support and no rMQR.

Adoption#

12,853 downloads in the 30 days to 2026-08-17 (pypistats) — the lowest in this survey, roughly one two-thousand-three-hundredth of qrcode’s volume. The repository carries 6,717 GitHub stars, the highest count in this category by a comfortable margin.

Those two numbers point in opposite directions and both are true. The stars belong to a well-regarded multi-language reference project that people admire and cite. The downloads belong to the Python package specifically, and almost nobody installs it. The star count is not a Python adoption signal, and reading it as one is an easy mistake to make.

For calibration: in October 2025 the package drew 9,950 downloads a month and the repository had 6,200 stars. Both figures have held roughly steady — this is the one library in the category whose adoption numbers did not move.

Maintenance#

Python version 1.8.0, released 2022-04-17. The repository was last pushed 2025-01-23.

This library is often described as “actively maintained”. It is not: the released Python artifact is more than four years old, and the repository has been quiet for about nineteen months. The distinction matters because “actively maintained” does real work in any recommendation that leans on it.

The mitigating argument is genuine and belongs here. This is a reference implementation of a frozen standard, written to be finished. A finished library does not need releases, and the project’s stated design goal was clarity and correctness rather than an evolving feature set. Whether “finished” or “abandoned” is the right reading is exactly the judgement S4 has to make, and the two are hard to distinguish from the outside.

Dependencies and footprint#

None, and 0.04 MB installed — the smallest package in this survey by an order of magnitude, and the smallest by a very large margin once you account for the fact that it also declines to pull in an imaging stack.

If the entire requirement is “get me the matrix, in a container that should be as close to empty as possible”, nothing else here comes near it.

Declared Python support is 3 or later.

License#

MIT, stated in the project README (“Copyright © 2025 Project Nayuki”). There is no LICENSE file at the repository root, so the GitHub API reports no license for the repository — a metadata gap rather than a legal ambiguity, but one that will trip automated license scanning and is worth knowing before it does.

Performance#

Roughly 7.209 ms per code in this survey’s measurement — second slowest of the five encoders tested, and about twice segno’s 3.249 ms, under identical conditions (CPython 3.12.3, one machine, one payload, 1,000 iterations, matrix construction only). Correctness-and-clarity implementations are not usually fast, and this one is not an exception.

Trade-offs#

You gain: a cross-language contract — the same encoder, from one source of truth, in six languages, which is the only reason in this category to reach for it and a real one; the smallest possible footprint; a design explicitly optimized for being read and audited; fine-grained control over segments, masks and version ranges; and a permissive license.

You give up: rendering, entirely. Kanji, in the Python port. Micro QR and rMQR. Speed, relative to segno. And release currency — this is the stalest live artifact in the survey.

There is one more thing worth weighing against its core argument. This survey checked whether the encoders in this category actually disagree, and on ordinary numeric and byte payloads all of the pure-Python implementations selected the identical symbol version. If the reason you were considering this library is “we need one implementation everywhere so the outputs match”, that requirement may already be satisfied by libraries that also render.

Where it sits: the reference. Take it when several runtimes must share one audited encoder, when the matrix is all you want, or when the install has to be as close to nothing as possible. Take something else the moment you need a file out of it.


S1 Recommendation — The Map#

There is no category default, and the reason is specific#

Most surveys that decline to name a winner do so because the options are close. That is not what happened here. This category has no default because the thing everyone assumes they are choosing is not actually in contention.

QR encoding is a solved, frozen problem. This survey tested that rather than asserting it: given the same URL at the same error-correction level, all six libraries — three of them not written by anyone who has spoken to the others — selected the identical 33-module version-4 symbol at the same error-correction level, and every output decoded back to the same string.

A correction, because the looser version of this claim is easy to make and wrong. “The same symbol” holds for version, size, error correction and decoded content — the things that determine whether your code works. It does not always hold module-for-module. Re-probed 2026-08-17:

payloadversionqrcode = qrcodegen= segno
a URL3yesno
HELLO WORLD1nono
200 digits5yesyes

The numeric payload is the case where all three converge to bit-identical output, and it is the one the earlier draft of this section generalised from. Numeric mode is fully determined — there is no segmentation choice and little mask latitude — so the implementations have nothing to disagree about. Byte and alphanumeric content leave room in segment optimization and mask evaluation, and the libraries use it differently.

None of that divergence is actionable: every symbol is conformant, every one decodes to the same string, and no reader can prefer one pattern over another. It is stated here because a survey that claims byte-identical output and can be shown otherwise in ten seconds has spent its credibility on a stronger claim than it needed. On ordinary content, the encoders do not meaningfully disagree.

So “which library generates better QR codes” has no answer worth the question. What you are actually choosing between is four things the encoder has nothing to do with:

  1. What it can write out — raster, vector, print formats, CAD geometry.
  2. What it costs to install — and in this category that means: does it drag in Pillow.
  3. How much of the barcode world it covers — QR only, the QR family, or a symbology set.
  4. How it fails you — a surprising default, a missing renderer, a compiled dependency, a decade without a release.

The map#

The category sorts cleanly on two axes: implementation substrate (pure Python versus compiled) and scope (QR only versus the wider barcode family).

Pure Python, QR-focusedqrcode, segno, qrcodegen, PyQRCode. This is where almost all the traffic is and where the historical argument in this category has been fought. Within it, the split is renderer versus encoder: qrcode invested in output and styling, segno invested in the symbology and in output breadth, qrcodegen invested in the encoder alone and declined to render at all, and PyQRCode invested in 2016.

Pure Python, multi-symbology — pyStrich, alone. Newly revived, lowest adoption of the live options, and the only place in this quadrant you can get Data Matrix, PDF417 or DXF output.

Compiled, multi-symbology — zxing-cpp, alone. An order of magnitude faster than everything else, the only Micro QR and rMQR creation available, and the only library here that also reads.

Compiled, QR-focused — empty. Nobody has built a fast Python-native QR-only encoder, presumably because if you are paying for a compiled extension you may as well have the whole barcode family with it.

What each library is actually for#

  • qrcode — it is already installed, and you need the code to look like your brand. The largest ecosystem in the category and the only first-class styling layer. Pay for it in Pillow and in heavy SVG.
  • segno — the symbol matters more than its decoration. The most complete pure-Python QR-family encoder, thirteen verified output formats, effectively no dependencies, compact print-ready vector. Pay for it in styling, and in learning one surprising default.
  • zxing-cpp — throughput, or symbologies pure Python cannot reach, or the ability to verify what you produced. Pay for it in a binary dependency.
  • pyStrich — QR alongside Data Matrix, PDF417 or Aztec without a compiled extension, or DXF output for something that gets cut rather than printed. Pay for it in maturity and in a Pillow dependency its own description understates.
  • qrcodegen — several runtimes must share one audited encoder, or the matrix is all you want. Pay for it in rendering, which you will write yourself.
  • PyQRCode — nothing. Migrate.

The three things most likely to bite a new adopter#

These are the findings this pass would want a reader to carry forward even if they read nothing else.

Popularity and completeness point at different libraries, and the gap is large. qrcode took 29,511,144 downloads in the 30 days to 2026-08-17 against segno’s 4,177,918 — a seven-to-one lead — while segno is the one with Micro QR, structured append, Kanji, thirteen output formats and a stated conformance claim. Neither number is wrong; they are measuring different things, and download volume in this category measures inertia at least as much as preference. The clearest evidence for that: PyQRCode, which has not shipped a release since 2016, still draws 628,262 installs a month.

segno’s default constructor can hand you a Micro QR code. Short payloads — including short URLs — came back as M1 to M4 Micro symbols in testing. Micro QR is standardized and legitimate and considerably less reliably read by consumer phone scanners than standard QR. The behavior is documented and there are explicit constructors that force one family or the other. It is still the most consequential thing a segno adopter can get wrong.

“ISO-compliant” currently means “compliant with a superseded edition”. ISO/IEC 18004:2024 replaced the 2015 edition in August 2024. segno’s own README still claims the 2015 edition; qrcodegen claims Model 2 conformance. No library in this survey claims the current edition. Codes produced to the old edition still scan — the symbology is stable — but anyone with a genuine conformance obligation must check the edition number rather than trust the adjective.

Carried into S2#

All six. S2 opens each one up to ask why it behaves the way it does: how the encoder and the renderer are separated, where the performance differences come from, and what the feature matrix looks like when it is built from measurements rather than from documentation.


segno#

Snapshot#

The standards-complete pure-Python option, and the most capable encoder in this category. Where qrcode invested in rendering, segno invested in the symbology itself: it implements the QR family rather than just QR, it states a conformance claim and backs it with a published test corpus, and it does all of that with effectively no third-party dependencies. It also emits more output formats than anything else here — thirteen were verified working from a single object in this survey, with no failures and nothing extra to install.

It is the option chosen on correctness and footprint rather than on popularity, and it carries one surprising default that new adopters get wrong.

Capabilities#

The QR family, not just QR. Micro QR sizes M1 through M4 are supported and were verified working. Kanji mode works. ECI is available. Structured Append — splitting one payload across several symbols — was verified producing a four-symbol sequence. None of the other pure-Python libraries in this survey offers that combination, and qrcode offers none of it.

Output breadth is the other headline. From one encoded object, this survey wrote PNG, SVG, compressed SVG, EPS, PDF, plain text, ANSI terminal output, PAM, PPM, XBM, XPM, LaTeX and PBM — thirteen formats, all succeeding, none requiring a third-party imaging library. Its SVG in particular is compact: for the same payload it produced a file roughly one-sixteenth the byte size of qrcode’s per-module rectangle output.

Two behaviors are worth knowing before you adopt it, and both are documented rather than hidden:

Error correction is silently boosted by default. The library will raise the error-correction level above what you asked for when the chosen symbol has spare capacity. This is almost always what you want and occasionally not what you expected.

The default constructor can hand you a Micro QR code. Asked for a short payload, segno returns the smallest symbol that fits — and Micro QR symbols are smaller than the smallest standard QR. Short strings, and even a short URL, came back as M1 to M4 Micro symbols in testing. Micro QR is legitimate, standardized, and considerably less reliably supported by consumer phone scanners than standard QR. The library documents the behavior and provides explicit constructors that force one family or the other, so this is a default worth knowing rather than a defect — but it is the single most consequential thing an adopter of this library can get wrong, and it is discussed further in S2 and S3.

What it does not have: rMQR, the rectangular Micro QR variant, is absent.

Adoption#

4,177,918 downloads in the 30 days to 2026-08-17 (pypistats), against 795 GitHub stars. That places it second in the category by volume, at roughly a seventh of qrcode’s traffic.

The gap between its download rank and its capability rank is the most quotable fact about this category: the most complete implementation is not the most used one, and the difference is not close. That said, the gap has narrowed. In October 2025 segno drew 561,296 downloads a month against qrcode’s 6,087,537 — an eleven-to-one lead. It is now closer to seven to one.

Maintenance#

Version 1.6.6, released 2025-03-12. The repository was last pushed 2026-07-23. As with qrcode, those are two different signals: the project is active, but the released artifact is roughly seventeen months old. For a library implementing a frozen standard with a passing test corpus, that is a defensible steady state rather than decay — but it does mean the version you install is not the version being worked on.

Governance is the main structural risk: this is substantially one maintainer’s project. The test corpus and documentation are unusually thorough for a single-maintainer library, which mitigates but does not remove the bus-factor concern.

Dependencies and footprint#

Effectively zero. The only declared dependency is a backport that applies solely to Python versions below 3.10; on any modern interpreter, installing segno installs segno. The package measured 0.30 MB on disk.

That matters more than it sounds. Because its renderers are native — including the raster and print formats — segno reaches PNG, PDF and EPS without Pillow. In a category where Pillow is a 22.95 MB line item, a library that produces print-ready vector and raster output for under a third of a megabyte is a materially different proposition from its peers.

Declared Python support is 3.5 or later, the widest in this set.

License#

BSD three-clause, cleanly declared in both the repository and the distribution metadata.

Note for anyone writing user-facing copy: segno’s own documentation records that “QR Code” and “Micro QR Code” are registered trademarks of DENSO WAVE INCORPORATED.

Standards posture — read the edition, not the word#

segno is the library in this category that makes an explicit conformance claim, and its exact wording matters. Its own README states that it implements ISO/IEC 18004:2015(E) and that the project provides more than 1,500 test cases at 98% or better coverage to verify standard-conformant generation against that edition.

The current edition of the standard is ISO/IEC 18004:2024, published August 2024, which cancels and replaces the 2015 edition. No library in this survey claims conformance to it. This is not a reason to distrust segno — symbol geometry is stable and codes produced to the 2015 edition still scan — but it means “ISO-compliant” in this category currently means “compliant with a superseded edition”. Anyone with a real conformance obligation needs to check the edition number rather than the adjective. S4 develops this.

Trade-offs#

You gain: the most complete QR-family encoder available in pure Python; the widest verified output-format range in the category at the smallest dependency cost; the only structured-append and Micro QR support among the pure-Python options; compact, print-suitable vector output; a stated conformance claim with a real test corpus behind it; and the widest Python version support here.

You give up: styling. There is no built-in equivalent to qrcode’s module drawers and color masks; the answer to branded output is a separate plugin, which is a different adoption decision from a core feature. You also give up the comfort of the crowd — roughly one seventh the install base — and you take on single-maintainer risk. And you inherit a default that will hand you a Micro QR symbol unless you ask it not to.

Where it sits: the correctness-and-footprint choice. Choose it when the symbol matters more than its decoration, when dependencies are counted, when you need vector output that goes to a press, or when Micro QR or structured append are actually on your requirements list. Choose against it when branding is the requirement, or when you need rMQR or a symbology beyond the QR family.


zxing-cpp#

Snapshot#

The outlier, and the one addition to this category that changes its shape. Every other library in this survey is a Python program that implements QR encoding. zxing-cpp is a set of Python bindings over the C++ port of ZXing — a long-established, multi-contributor barcode library — shipped as a compiled extension with no Python dependencies at all.

Two things follow from that, and they are the reasons to consider it. It is roughly an order of magnitude faster than anything else here. And it is the only library in this survey that both writes and reads barcodes, which makes it the only one that can verify its own output — a property this survey relied on directly, using it as the decoding oracle to confirm that every other library produces symbols a conformant reader accepts.

Capabilities#

Its creatable-format list is the broadest in this category by a wide margin, and it includes two QR-family members nothing else here can produce: Micro QR and rMQR, the rectangular Micro QR variant. Both were verified creating successfully. Beyond the QR family it creates Data Matrix, Aztec, PDF417 and MicroPDF417, MaxiCode, and a long list of 1-D symbologies including Code 128, Code 39, Code 93, ITF, Codabar and the EAN/UPC family.

Output is deliberately narrow relative to that breadth: a barcode object yields SVG and an image buffer, and you take it from there. There is no styled-output layer, no color masks, no module drawers. This is a barcode engine with a thin Python face, not a graphics library.

One behavior will confuse anyone comparing symbol sizes across libraries, because it looks like a defect and is not. Its image output adds the standard four-module quiet zone on every side by default. For a payload that every library in this survey encoded as a 33-module version-4 symbol, its image buffer reports 41 by 41 — that is 33 plus four modules of quiet zone on each side, not a larger symbol. The encoders agree; the framing differs.

The Python API is in visible motion: the older single-call write function emits a deprecation warning in the current release in favor of a create-then-serialize pair. That is a small, well-signposted migration, but it is the only library here where the current release is actively deprecating its own surface.

Adoption#

1,056,907 downloads in the 30 days to 2026-08-17 (pypistats), against 1,968 GitHub stars on the upstream project. That places it third in this category by volume, ahead of every option except qrcode and segno — which is notable for a library most Python QR tutorials have never mentioned.

Part of that traffic belongs to its decoding side rather than its generation side. Read the number as evidence the project is well established rather than as evidence of QR-generation adoption specifically.

Maintenance#

Version 3.1.1, released 2026-07-29. The repository was last pushed 2026-08-13. This is the freshest release in the entire category by a margin of more than a year, and the only project here with both a recent release and recent repository activity.

It is also the only project in this survey with a multi-contributor structure and a named open-source license at the repository root. Where every pure-Python option in this category carries single-maintainer risk, this one does not.

Dependencies and footprint#

No declared Python dependencies. The installed distribution measured 1.89 MB — the largest single package in this category, and still an order of magnitude below the 22.95 MB Pillow tax that the raster-capable pure-Python options incur.

The cost is categorical rather than numeric: this is a compiled extension. That means wheels for your platform and Python version, or a C++ toolchain. On mainstream platforms this is invisible. On unusual architectures, in restricted build environments, or anywhere a security process treats native code differently from pure Python, it is the deciding factor — and it is the reason a slower pure-Python library is sometimes the correct answer.

Declared Python support is 3.9 or later.

License#

Apache-2.0, declared at the repository root — the cleanest licensing story in this survey, and the only one carrying an explicit patent grant.

Performance#

Roughly 0.189 ms per code in this survey’s measurement, against 3.249 ms for segno, the fastest pure-Python option. Conditions matter and are stated in full: CPython 3.12.3, a single machine, one payload, 1,000 iterations, matrix construction only with no file writing. Under those conditions it is about seventeen times faster than the best pure-Python alternative and about fifty-four times faster than the slowest.

For a handful of codes this is meaningless. It becomes the whole argument only when codes are produced per request or in large batches.

Trade-offs#

You gain: by far the highest throughput in the category; the only Micro QR and rMQR creation available in Python here; by far the widest symbology coverage; decoding in the same package, which means you can verify what you produced; the freshest release and the only multi-contributor governance in the survey; and a clean Apache-2.0 grant.

You give up: pure Python, with everything that implies for exotic platforms, build-from-source environments and security review. You give up styling entirely — there is no branded-output story. You give up the extended print-format range that segno offers natively. And you accept an API currently deprecating part of itself, plus a general-purpose barcode surface where a QR-specific library would give you a narrower, more obvious one.

Where it sits: the engine choice. Take it when throughput is the constraint, when you need symbologies or QR variants outside what pure Python offers, or when generating and verifying in one place is worth more than avoiding a binary dependency. Take something else when a compiled extension is itself the problem, or when what you actually needed was a nice-looking code.

S2: Comprehensive

S2 Comprehensive Discovery — Approach#

Survey of Software 1.080.1 — QR Code Generation Libraries. All figures verified 2026-08-17 unless otherwise dated.

Purpose#

S1 established who is in the category and how much of the world uses each option. S2 asks the engineering question: what is each of these libraries actually built out of, and what does that construction buy or cost? The unit of analysis is architecture and measured behavior, not usage instructions. Nothing in this pass is written toward a particular application; every library is judged on category merits, and concrete scenarios are deferred to the S3 personas.

The frame that makes this category legible#

A QR code generator is not one machine. It is two separable machines bolted together, and almost every difference between the libraries here is a difference in which half the project invested in.

Machine one: the encoder. Payload in, module matrix out. It performs mode segmentation (deciding which stretches of the input are numeric, alphanumeric, byte or kanji, and how to split them so the total bit cost is lowest), appends the mode and character-count headers, pads to the capacity of the chosen symbol version, computes Reed–Solomon error-correction codewords at the requested level, interleaves data and EC blocks, lays the result into the matrix around the fixed function patterns, evaluates the standard’s candidate mask patterns against its penalty rules, and writes the format and version information. This is a large amount of specified arithmetic, and it is completely determined by the standard. Two correct encoders given the same payload, the same error-correction level and the same freedom of choice should produce the same symbol.

Machine two: the renderer. Module matrix in, bytes on disk out — PNG, SVG, EPS, PDF, DXF, a terminal escape sequence, a data URI. Nothing here is specified by ISO/IEC 18004 beyond the quiet zone and the requirement that light and dark modules be distinguishable. The renderer is where a library gets to have taste: vector emitters that fuse modules into paths versus emitters that stamp one rectangle per module, styling hooks, color masks, embedded artwork, CAD output.

Holding those two halves apart explains the category’s central oddity — that “the popular one” and “the correct one” are different libraries. The adoption leader invested in machine two and shipped a pluggable renderer layer that won the styling niche. The standards-complete option invested in machine one and in the breadth of serialisers hanging off a single encoder. The reference implementation shipped only machine one and declined to write a renderer at all. A native binding brought both halves over from a C++ barcode library and gets an order of magnitude of speed and two symbol families nothing else here can create, at the cost of a compiled extension. Rank these on one axis and you will get the wrong answer for most readers.

Our own probe (facts §3.6) makes the point empirically: for the same URL at error-correction level M, all six libraries measured produced the same 33-module, version-4 symbol, and on a 200-digit numeric payload all four pure-Python encoders again agreed on version 5. On ordinary payloads the encoders do not differ. That is the single most useful thing S2 has to say to a reader who arrived expecting encoder quality to be the deciding factor.

Evaluation dimensions applied to every library#

Each library file works through the same eight dimensions, in roughly this order:

  1. Encoder completeness — versions and EC levels supported; mode coverage (numeric, alphanumeric, byte, kanji); ECI; Structured Append; whether the Micro QR (M1–M4) and rMQR families can be created, not merely read.
  2. Encoder defaults and their surprises — what the library does when you do not tell it what you want. Automatic error-correction boosting and automatic symbol-family selection are both live behaviors in this set, and one of them is the most consequential trap in the category.
  3. Renderer architecture — how output formats are reached. A pluggable factory layer, a fan of serialisers on one object, a per-symbology renderer module, or nothing at all are four different designs with different extension stories.
  4. Output surface and output quality — which formats exist, and where we measured the emitted bytes, how large they were for an identical payload. Byte size is a proxy for how the vector emitter is written.
  5. Dependency footprint — declared requirements, Requires-Python, and installed size on disk. In this category the footprint question is almost entirely a Pillow question.
  6. API design patterns — the shape of the interface, not its documentation: what the library makes easy, what it makes possible, and what it does not express at all.
  7. Performance — encode cost under stated conditions, plus a note on what that measurement excludes.
  8. Maintenance reality — released-artifact age versus repository activity, which diverge sharply here and must be reported as two separate facts.

Measurement conditions#

Every timing figure quoted in this pass comes from one probe run by the survey author on 2026-08-17: 1,000 encodes of a single fixed URL payload (https://example.com/a-moderately-long-url?id=12345&ref=survey) at error-correction level M, on CPython 3.12.3 under Linux (WSL2), on one machine, reported as a simple mean. It measures matrix construction only — no file I/O, no PNG compression, no vector serialization. That isolates machine one. It is not an end-to-end benchmark, because end-to-end cost in this category is dominated by whichever renderer and image library you attach, which is a different question with a different answer.

Installed-size figures are the summed file sizes of each distribution in one uv virtual environment on the same date. Download counts are pypistats /recent last_month values fetched 2026-08-17; a mirror check on the largest package showed mirror traffic at roughly 0.2% of the total, so the headline figures are not mirror-inflated — but PyPI counts include CI runs, container rebuilds and transitive installs, so they measure diffusion, not deliberate adoption, and are quoted here with that caveat attached.

A standing rule for this pass#

Two of the most quotable documents in this category are written by parties with an interest in the outcome: a 26-case correctness benchmark published by one library’s own maintainer, and a comparison matrix published by another library’s maintainer. Both are useful and both are cited here — always labeled as vendor-authored in the same sentence as the claim, and never laundered into an independent finding. Where our own probes corroborate a vendor claim, we say so and take ownership of it; where they contradict one, we say that too. One such contradiction is recorded in this pass.


S2 Feature Comparison#

A technical matrix across the libraries carried into S2. Every cell is drawn from facts verified 2026-08-17 — either from registry metadata, from installed distribution metadata, or from probes run by the survey author on CPython 3.12.3. Where a capability was not probed, the cell says so rather than guessing; “not observed” means we did not find it and did not test for it specifically, which is weaker than “absent”.

PyQRCode appears in these tables because a reader may already be holding it, not because it is a live candidate. Its verdict is in the recommendation file.

Release and adoption#

qrcodesegnozxing-cpppyStrichqrcodegenPyQRCode
Latest version8.21.6.63.1.10.191.8.01.2.1
Released2025-05-012025-03-122026-07-292026-07-232022-04-172016-06-20
Repo last pushed2026-03-252026-07-232026-08-132026-08-172025-01-232024-04-15
Stars4,9287951,9681066,717 (polyglot repo)427
Downloads, 30 days to 2026-08-1729,511,1444,177,9181,056,90776,80612,853628,262
LicenseBSD 3-clause (GitHub: “Other”)BSD-3-ClauseApache-2.0Apache-2.0 (GitHub: “Other”)MIT per Readme; no LICENSE fileBSD-3-Clause

Download figures are pypistats last_month values. A mirror check on the largest package put mirror traffic at ~0.2% of the total, so these are not mirror-inflated — but PyPI counts include CI, container rebuilds and transitive installs, so they measure diffusion, not deliberate adoption.

Encoder capability#

qrcodesegnozxing-cpppyStrichqrcodegenPyQRCode
Standard QR (v1–40)yesyesyesyesyesyes
Micro QR (M1–M4) creationno API (probed)yes (M1 at 15×15 probed)yes (MicroQRCode created)not observedno API (probed)not probed
rMQR creationnot observedno (absent from signature and namespace)yes (RMQRCode created)not observednot observednot probed
Kanji modenot verified hereyes (mode='kanji' probed)not probednot probedno — no make_kanji in Python 1.8.0not probed
ECInot verified hereyesnot probednot probedyes (make_eci)not probed
Structured Appendno API (probed)yes — 4 symbols, versions [7,7,7,7]not probednot probednot probednot probed
Explicit version / mask controlnot probedyes — both in make() signaturenot observed in the create/render surfacenot on the constructoryes (Readme: manual or automatic mask)not probed
Automatic EC boostingnot probedyes, on by default (boost_error=True)not probednot probedyes (Readme claim)not probed

Micro QR is a two-library capability in this survey — segno in pure Python, zxing-cpp compiled. rMQR is a one-library capability. Those are the only two hard functional walls in the category; everything else is a matter of degree.

Symbol agreement — the headline finding#

Payload https://example.com/a-moderately-long-url?id=12345&ref=survey at error correction M:

LibraryResult
segno (make_qr)33 modules, version 4
qrcode33 modules, version 4
qrcodegen33 modules, version 4
pyqrcode33 modules, version 4
pyStrich33 modules, version 4
zxing-cpp33 modules, version 4

A second probe on 200 numeric digits at EC M returned version 5 from segno, qrcode, qrcodegen and pyqrcode alike.

All six libraries produce the same symbol for the same ordinary payload. This is itself the finding, and it reframes the whole category: encoder selection is not a correctness decision on ordinary input. Whatever separates these libraries, it is not what shape of code comes out.

Two footnotes that must travel with this table. First, zxing-cpp’s to_image() reports 41 × 41 because add_quiet_zones=True is its default and the standard requires a 4-module quiet zone per side: 33 + 8 = 41. That is a margin, not a larger symbol, and reporting it as a size difference is simply an error. Second, a third row of the probe — Japanese text — is not reported as a comparison, because segno’s make() returned a Micro QR there (see the segno file) and pyqrcode’s result was at a different effective encoding. Only the numeric and byte rows are stated as findings.

Round-trip verification#

Every library’s PNG output was read back with a conformant decoder (zxing-cpp). segno OK, qrcode OK, pyqrcode OK, pyStrich OK (both its save() path and its Pillow image path), zxing-cpp OK. No library in this set is broken in the basic case. qrcodegen is absent from this table because it produces no file to read back.

Note the limit of this test: a conformant decoder reads symbols that consumer phone hardware may not — Micro QR in particular. Round-trip success proves conformance, not field scannability.

Output surface#

Formats produced (verified)
segno13, from one object, no third-party dependency: png, svg, svgz, eps, pdf, txt, ans, pam, ppm, xbm, xpm, tex, pbm
pyStrichASCII, DXF, EPS, raw image data, Pillow image, SVG, terminal art, PNG and SVG data URIs, save/save_eps/save_svg
qrcodePNG (via Pillow), SVG (SvgImage), styled PNG (StyledPilImage + module drawer + color mask), plus an open factory seam for third-party renderers
zxing-cppSVG (to_svg), image object (to_image)
PyQRCodeSVG, PNG (needs pypng)
qrcodegennone — matrix only (get_size, get_version, get_module, get_mask, get_error_correction_level)

DXF is unique to pyStrich in this set — the CAD/engraving path. An extension seam for new renderers is unique to qrcode. segno’s answer to formats it does not have is a plugin (qrcode-artistic, last released 2023-11-27) or post-processing.

SVG bytes for the same payload#

LibrarySVG size
segno1,934 B
segno (svgz)527 B
zxing-cpp3,486 B
PyQRCode3,421 B
qrcode30,816 B

Roughly a 16× gap between qrcode and segno for identical content — one <rect> element per module against a path-based emitter. Invisible in any feature matrix, since both correctly claim “SVG”.

Dependencies and footprint#

Declared runtime dependenciesInstalled MBRequires-Python
segnoimportlib-metadata only below Python 3.10 — effectively none0.30>=3.5
qrcodegennone0.04>=3
PyQRCodepypng>=0.0.13 under extra png0.12none declared
qrcodecolorama (win32); pillow>=9.1.0 under extra pil/all; pypng under extra png/all0.14>=3.9,<4.0
pyStrichpillow unconditionally (>=10.3 below 3.11, >=12.3.0 from 3.11) + typing-extensions>=4.15.00.72>=3.10
zxing-cppnone — ships a compiled extension1.89>=3.9
(reference) Pillow22.95
(reference) pypng0.17

Read that last block first: the footprint question in this category is a Pillow question. Pillow is ~76× segno’s entire install and ~574× qrcodegen’s. qrcode is dependency-free off Windows until you want a raster image; pyStrich requires Pillow always, including on its vector and terminal paths. qrcode carries the only capped Requires-Python upper bound in the set.

Encode performance#

1,000 encodes of one fixed URL at EC level M, matrix construction only, no file I/O, CPython 3.12.3, Linux (WSL2), one machine, 2026-08-17, simple mean:

Libraryms per code
zxing-cpp0.189
segno3.249
qrcode4.048
qrcodegen7.209
pyqrcode10.264

pyStrich was not timed and this survey states no figure for it.

The compiled option is ~17× faster than the fastest pure-Python encoder and ~54× faster than the slowest. Within pure Python the entire field spans ~3.2×. This measurement excludes serialization entirely — and serialization is where segno does hand-written Python work that qrcode delegates to Pillow’s C code, so the ordering need not survive an end-to-end benchmark. We did not run one.

The third-party correctness benchmark, and its conflict of interest#

A 26-case corpus — “numeric, alphanumeric, Latin-1, UTF-8, kanji and mixed-mode payloads from 9 to 2,510 characters” at specified EC levels, outputs verified by decoding — was published 2026-07-16 and updated 2026-07-31. Reported results:

LibraryCases passed (of 26)Verdict given
pyStrich26“best general purpose”
zxing-cpp24“fastest overall”
segno24“strong pure Python alternative”
qrcode22“most popular but has limitations”
treepoem“avoid for this use”
qrcodegen“too limited (no file output)”

The benchmark is published by pyStrich’s own maintainer. Its author is Michael Mulqueen; pyStrich’s repository is mmulqueen/pyStrich and its PyPI documentation URL points at Method B Ltd’s domain. The study that ranks pyStrich first is a vendor document, and this survey states that in the same breath as any figure from it.

That is not a claim the numbers are false — the methodology is described, the corpus is characterised, and decode verification is the right technique. It is a statement about what the result can be used for. This survey did not reproduce the 26-case corpus, and the correctness ranking is not verified here.

Two claims from it we did independently corroborate, and state as our own findings on our own probes: zxing-cpp is by far the fastest (our measurement: 0.189 ms against 3.249 ms for the next), and qrcodegen has no file output (confirmed by probing its entire public surface). Nothing else from that source is adopted.

The same discipline applies to segno’s published comparison matrix, written by segno’s maintainer. Its Micro QR and dependency rows match our probes and we state those independently. Its Kanji row is wrong for the Python qrcodegen portpip install qrcodegen gives you no make_kanji, a capability that exists only in the Java and C++ siblings.

What the matrix shows#

  1. The encoder is commoditised. All six libraries produced an identical 33-module version-4 symbol for the same URL, and all four pure-Python encoders agreed again on a numeric payload. Choosing on “which one encodes correctly” is choosing on a dimension where they do not differ.
  2. The renderer is where the category actually splits. Thirteen serialisers with no dependency, an open factory seam, a DXF emitter, two output methods, or nothing at all — four incompatible philosophies, and a measured 16× byte gap between two libraries that both say “SVG”.
  3. Footprint is a Pillow question, not a library question. Pillow’s 22.95 MB dwarfs every library’s own code. The distinction is whether Pillow is optional (qrcode), mandatory (pyStrich) or absent (segno, qrcodegen, zxing-cpp).
  4. Only two functional walls exist. Micro QR creation (segno, zxing-cpp) and rMQR creation (zxing-cpp alone). If either is a requirement, the category collapses to one or two options and nothing else in this document matters.
  5. Released artifacts are old and repositories are not. Four of six shipped their current release before 2026 while their repositories moved on; only zxing-cpp and pyStrich have a 2026 release. For a frozen symbology that is tolerable, but “actively maintained” and “recently released” are two different claims here and must be checked separately.
  6. The best-evidenced quality claims in this category are vendor-authored. Both the 26-case benchmark and the comparison matrix are written by maintainers of libraries they rank. Both are worth reading; neither is independent; and probing found one of them wrong in a row nobody would have thought to doubt.

pyStrich — Comprehensive Analysis (S2)#

Survey of Software 1.080.1. Facts verified 2026-08-17; version 0.19, released 2026-07-23; repository mmulqueen/pyStrich, 106 stars, last pushed 2026-08-17; Apache-2.0 per the repository LICENSE (GitHub’s classifier reports “Other” because of a bundled SIL OFL notice for Courier Prime glyphs). 76,806 downloads in the 30 days to 2026-08-17 per pypistats.

pyStrich is the newest artifact and the least-adopted library in this survey, and those two facts should be stated together and up front. Its 0.19 release is the second-freshest in the set; its 76,806 monthly downloads are roughly 1/384th of qrcode’s 29,511,144 and about 1/54th of segno’s. It is not a package the ecosystem has converged on. It is a package with a distinctive architecture, an unusual output format nothing else here offers, and an active maintainer — and a correspondingly small base of production exposure.

It is also the library whose most-cited third-party evidence is written by its own maintainer, which this file flags every time it appears.


1. Shape of the library: multi-symbology by construction#

pyStrich is not a QR library. It is a barcode library that includes QR, and that shapes everything about it.

Its repository describes it (verbatim) as “a fast pure-Python module for generating 1D and 2D barcodes”, supporting “Code 39, Code 128 (including GS1-128), EAN-13, ITF-14 (and Interleaved 2 of 5), Data Matrix (including GS1 Data Matrix), QR Code, PDF417 and Aztec Code symbologies”, with “PNG, SVG, EPS, DXF and terminal outputs”.

The architecture that follows is a shared matrix-encoder and renderer chassis with a per-symbology encoder plugged into it. Each symbology — QR among them — contributes the logic that turns a payload into a module matrix; everything downstream of that matrix is common machinery. That is the same encoder/renderer split every library in this category has, but here the split is load-bearing in a way it is not elsewhere: it is what lets eight symbologies share one output surface.

The design consequences are real and mostly favourable:

  • Uniformity across symbologies. The renderer surface a caller learns for QR is the renderer surface for Data Matrix, PDF417 and Aztec. A system that must emit several 2-D symbologies gets one API, one output vocabulary and one dependency rather than three libraries with three idioms.
  • Renderer work amortises. A new output format written once serves all eight symbologies. This is the structural reason a small project can afford to ship something as niche as DXF (§3) — the cost is paid once, not per symbology.
  • QR is not the center of attention. The corollary. QR-specific depth — the conformance test corpus, the mask and version knobs, the Micro QR family — is not where a multi-symbology project’s effort naturally concentrates. Compare segno, where QR and Micro QR are the entire subject.

2. Lineage#

pyStrich descends from huBarcode, with contributions from HuDoRa in roughly 2007–2013 and maintenance by Method B Ltd from 2015; the current repository was created 2015-06-23.

That lineage is worth knowing for two opposite reasons. It means the encoder machinery is not new code written last year — the multi-symbology chassis has a long tail behind it, which is reassuring for a library at 106 stars. It also means the package’s identity has changed hands and names, so the version number (0.19) understates the code’s age while the download count understates neither its age nor its adoption. A reader evaluating “how proven is this” gets different answers depending on whether they look at the artifact or the lineage; both are true.

The 2026 activity is genuine: release 0.19 on 2026-07-23, repository pushed 2026-08-17 — the same day this survey verified it. This is the most recently touched repository in the set.

3. Renderer surface, and the one format nobody else has#

The public method surface of QRCodeEncoder, as probed:

get_ascii, get_dxf, get_eps, get_imagedata, get_pilimage, get_rect_marks, get_svg, get_terminal_art, png_dataurl, save, save_eps, save_svg, svg_dataurl, init_renderer, matrix, width, height.

Several things are legible in that list.

DXF output is unique to pyStrich in this survey. No other library here emits it. DXF is a CAD interchange format, and its presence points at a class of use the rest of the category does not serve: laser engraving, CNC marking, plotting, etching — anything where the symbol is not printed as an image but cut or burned as geometry, and the receiving software is a CAD or machine-control application rather than an image viewer. For that class of work the alternative is not another QR library; it is writing a matrix-to-DXF converter yourself, which is exactly the kind of undifferentiated work a library ought to remove. This is the single strongest reason to choose pyStrich on category merits.

The matrix is public. matrix, width and height are exposed alongside the renderers, so the library does not force you through its own output path. A caller who needs a format that is not on the list can take the matrix and render it, without the reverse-engineering that a library hiding its internals would require. Combined with get_rect_marks — a geometric description of the symbol’s dark regions rather than pixels — this is a more open renderer boundary than segno’s fixed serialiser set, though less formalised than qrcode’s factory classes.

Data URIs are first-class. png_dataurl and svg_dataurl are separate methods rather than something you assemble from bytes. That is a small convenience, and a telling one about where the library expects its output to go: inline into HTML and templates.

Terminal output has two forms. get_ascii and get_terminal_art are distinct, which suggests plain-text and escape-sequence renderings are treated as different targets rather than one approximate thing.

4. The dependency claim needs correcting#

The repository calls pyStrich “pure-Python”, and the encoders are. The distribution is not dependency-free. From the installed metadata, pyStrich declares:

  • pillow>=10.3 below Python 3.11, >=12.3.0 from 3.11 — unconditionally, not behind an extra.
  • typing-extensions>=4.15.0.

Requires-Python is >=3.10, the highest floor in this survey’s set.

The distinction matters and gets muddled in this category constantly. “Pure Python” describes how the encoder is implemented; “no dependencies” describes what the install drags in. segno is both. qrcode is pure Python and reaches PNG only through an optional extra, so it is dependency-free until you want a raster image. pyStrich is pure Python and requires Pillow always, including on code paths — SVG, EPS, DXF, terminal output — that have nothing to do with raster images.

The footprint arithmetic, from our environment:

ComponentMB
pyStrich itself0.72
Pillow (mandatory)22.95

So a pyStrich install is roughly 24 MB, of which about 3% is pyStrich. That 23 MB belongs to Pillow and must be attributed to Pillow, not reported as pyStrich’s weight — the library’s own code is small, and a claim like “pyStrich costs 23 MB” is a misattribution that would also indict qrcode[pil] and exonerate nothing. What is fair to say is that pyStrich, unlike qrcode, gives you no configuration in which you avoid it. There is no vector-only install.

For anyone whose selection criterion is the dependency review — hardened containers, size-capped deployment artifacts, environments where a native image library is a supply-chain question — that is disqualifying regardless of how small pyStrich’s own code is. For anyone who already has Pillow in the tree, which is a great many Python environments, the marginal cost is 0.72 MB and the point is moot.

5. API design patterns#

The constructor, as probed:

QRCodeEncoder(text: str | QRCodeData, ecl: QRErrorCorrectionLevel | None = None)

Two design choices are visible in that one line.

Error correction is a constructor parameter with a typed enum, defaulting to None — the library picks. Making EC level a construction-time property rather than a render-time one is correct: EC level changes the matrix, so it belongs where the matrix is decided. A typed enum rather than a string or an integer constant is also the better choice for the usual reasons — it is discoverable, and a wrong value fails at the call rather than deep inside the encoder. Compare qrcode’s module-level integer constants (ERROR_CORRECT_L/M/Q/H), which are the older Python idiom, and segno’s string parameter, which is the loosest.

The payload accepts either a string or a structured QRCodeData object. That union is the seam where anything more complex than “encode this text” would live — mode segmentation, multi-segment payloads, encoding control. This survey did not probe what QRCodeData exposes, and does not characterise its capabilities here. What the signature establishes is only that the door exists.

Absent from the constructor surface: no version parameter, no mask parameter, no micro switch. Set that beside segno’s make(content, error, version, mode, mask, encoding, eci, micro, boost_error) and the difference in posture is plain. segno expects to be asked conformance questions and exposes every lever the standard defines. pyStrich exposes the lever that changes the symbol in the way most callers care about, and keeps the rest internal. That is a reasonable trade for a multi-symbology library — eight symbologies’ worth of format-specific knobs would be an unmanageable surface — but it means pyStrich cannot be driven to produce a specific symbol the way segno can.

Micro QR and rMQR were not observed in pyStrich’s surface. This survey did not probe for them specifically and states only that: not observed, not tested. The two libraries here where Micro QR creation is confirmed are segno and zxing-cpp.

6. Verified behavior#

  • Symbol version agrees with the field. For the payload https://example.com/a-moderately-long-url?id=12345&ref=survey at EC level M, pyStrich produced a 33-module version-4 symbol — identical to segno, qrcode, qrcodegen, pyqrcode and zxing-cpp. Its encoder is not doing anything eccentric on ordinary input.
  • Round-trip decode confirmed, twice over. A conformant decoder read back pyStrich’s output from both its save() PNG path and its get_pilimage() path. Two output paths, both readable. Nothing here is broken.

7. Performance — what we did not measure#

pyStrich was not included in this survey’s timing probe. The 1,000-encode measurement covers zxing-cpp (0.189 ms), segno (3.249 ms), qrcode (4.048 ms), qrcodegen (7.209 ms) and pyqrcode (10.264 ms). There is no pyStrich figure, and this file does not supply one.

The only speed evidence available is from the third-party benchmark discussed in §8, which reported pyStrich in a median range of 1.1–61 ms per PNG against zxing-cpp’s 0.55–14 ms in the same run. That is an end-to-end PNG measurement, not comparable with our encoder-only figures, and it comes from a source authored by pyStrich’s own maintainer. It is recorded here as an attributed claim and nothing more. Any reader for whom encode throughput is a real constraint should measure pyStrich themselves; this survey cannot place it in the ordering.

8. The third-party benchmark and its conflict of interest#

The most-cited external evidence about this library is a blog post titled “What’s the best Python QR code generator library?”, published 2026-07-16 and updated 2026-07-31. It runs a corpus of 26 cases — described as numeric, alphanumeric, Latin-1, UTF-8, kanji and mixed-mode payloads from 9 to 2,510 characters, at specified error-correction levels, with outputs verified by decoding. It reports pyStrich passing 26 of 26, ahead of zxing-cpp and segno at 24 and qrcode at 22, and names pyStrich “best general purpose”.

That benchmark is published by pyStrich’s maintainer. The blog’s author is Michael Mulqueen; pyStrich’s repository is mmulqueen/pyStrich and its PyPI documentation URL points at method-b.uk, Method B Ltd being the same party. The benchmark that ranks pyStrich first is a vendor document.

This does not make the numbers false. The methodology is described, the corpus is characterised, and verification by decoding is the right technique. It does mean the result cannot be used as an independent finding, and this survey will not launder “26 of 26, best general purpose” into one. We did not reproduce the corpus. The correctness ranking is unverified by this survey.

Two adjacent claims from the same source we did corroborate independently, and those we state as our own: that zxing-cpp is by far the fastest, and that qrcodegen has no file output. Both were confirmed by our own probes. The correctness ordering was not.

The right way to hold all of this: a 26-case decode-verified corpus is a more serious quality artifact than most libraries in this category can point to at all, and it is evidence a prospective adopter should read. It is simply not evidence that can be cited about pyStrich without naming who ran it.

8a. Comparison within the category#

Versus segno. The pure-Python comparison, and the one where the “pure Python” label misleads. Both describe themselves that way and only segno is dependency-free; pyStrich requires Pillow unconditionally, making the installed footprints roughly 0.30 MB and 24 MB. On QR specifically segno is deeper — Micro QR, Kanji, Structured Append, a named ISO edition, a quantified test corpus, mask and version control. pyStrich is broader — eight symbologies and DXF. The choice turns on whether the requirement is more QR or more symbologies.

Versus qrcode. Both are Pillow-dependent renderer-forward libraries, so footprint does not separate them. qrcode has the styling layer and the plugin seam; pyStrich has the other seven symbologies and DXF. Adoption separates them by nearly three orders of magnitude, which is the headline of this pairing.

Versus zxing-cpp. The other multi-symbology option, and the direct rival. zxing-cpp is compiled, has no Python dependency at 1.89 MB, measured at 0.189 ms per code, decodes as well as encodes, and creates Micro QR and rMQR. pyStrich is source you can read and patch, and emits DXF. Where a compiled artifact is acceptable, zxing-cpp is the stronger engine; where it is not, or where the output must be CAD geometry, pyStrich is the answer.

Versus qrcodegen. Almost inverse libraries. qrcodegen is 0.04 MB, one symbology, no renderer, six languages; pyStrich is eight symbologies, a wide renderer surface, and one language with a 23 MB image dependency behind it.

9. Where this library is the wrong answer#

  • Wrong when Pillow is unacceptable. Unconditional, no extras-based escape, no vector-only install. This is the clearest disqualifier.
  • Wrong when a specific symbol must be reproduced — no version, mask or family control on the constructor surface we probed.
  • Wrong when Micro QR or rMQR is required — not observed here; segno and zxing-cpp are the confirmed answers.
  • Wrong when installed base is itself the criterion. 76,806 downloads a month and 106 stars is real usage, and it is two to three orders of magnitude below the leaders. For an organization whose dependency policy weights production exposure — how many other people would have found the bug first — that gap is the finding, and no architectural merit offsets it.
  • Wrong when encode throughput is a hard constraint, simply because this survey cannot tell you what it is (§7).

And where it is straightforwardly right: DXF output, which is unique here and opens CAD, engraving and machine-marking work that nothing else in the category addresses; multi-symbology systems, where one library covering Data Matrix, PDF417, Aztec and the linear formats alongside QR replaces several; and environments that already carry Pillow, where the footprint objection evaporates.

10. Summary#

pyStrich is the multi-symbology option: a shared encoder/renderer chassis carrying eight symbologies, descended from huBarcode, actively maintained, with the freshest repository activity in the survey and a renderer surface that reaches DXF — unique here, and the one capability in this file that has no substitute. Its encoder agrees with the whole field on symbol version for an ordinary URL and its output round-trips through a conformant decoder from two separate paths. Set against that: an unconditional Pillow requirement with no way out, a constructor that exposes error-correction level but not version, mask or symbol family, no timing figure from this survey at all, the most-cited evidence in its favor written by its own maintainer, and the smallest installed base in the set at 76,806 downloads a month against the leader’s 29.5 million. It is a well-shaped library that very few people are using.


qrcode (python-qrcode) — Comprehensive Analysis (S2)#

Survey of Software 1.080.1. Facts verified 2026-08-17; version 8.2, released 2025-05-01; repository lincolnloop/python-qrcode, 4,928 stars, last pushed 2026-03-25; BSD 3-clause per the repository LICENSE file (GitHub’s classifier reports “Other”).

qrcode is the package the ecosystem installs. pypistats recorded 29,511,144 downloads in the 30 days to 2026-08-17, against 4,177,918 for the next library in this survey — roughly a 7:1 ratio. That figure is diffusion rather than deliberate choice (PyPI counts include CI, container rebuilds and transitive installs, and a mirror check on this package put mirror traffic at about 0.2% of the total), but it is diffusion at a scale nothing else in the category approaches. When a random Python project needs a QR code, this is what is already in its lockfile.

The interesting engineering question is why it won, because it did not win on encoder completeness — it is missing several capabilities its smaller competitors have. It won on the renderer.


1. Shape of the library#

qrcode is a pure-Python encoder with a pluggable image-factory renderer layer bolted on top. The two halves are cleanly separated in a way most of this category is not: the encoder builds a module matrix and knows nothing about pixels; the renderer is a class you select at output time, and the library ships several of them plus a documented path for writing your own.

That separation is the whole story of this package. The encoder is competent and unremarkable — our probe found it selecting exactly the same symbol version as every other library in the set on ordinary payloads (§4 below). The renderer layer is where the design investment went, and it is the reason qrcode owns the “make it look like something” niche that the standards-focused libraries do not compete in.

2. The image-factory pattern#

Most libraries in this category expose output as a fan of methods on the code object: one method per format. qrcode instead treats the renderer as a substitutable collaborator. The caller names a factory class — SvgImage for vector output, StyledPilImage for the decorated raster path — and the library drives that class over the matrix it built. Plain Pillow-backed raster output has its own factory in the same family; our probe produced an 815-byte PNG through it.

This is a small architectural decision with large consequences:

  • Output formats are open-ended rather than enumerated. Anyone can supply a factory. The library does not need to grow a new method, a new dependency or a new release to reach a new format; a third party can ship one. Contrast this with a library whose serialisers are a fixed internal list — that list is excellent if your format is on it and a dead end if it is not.
  • Styling composes rather than accumulating parameters. The decorated raster path is assembled from two orthogonal collaborators: a module drawer, which decides the shape drawn for each dark module, and a color mask, which decides the color at each position. Our probe confirmed a combination of RoundedModuleDrawer and RadialGradiantColorMask producing a working styled PNG of 58,289 bytes. The alternative design — one flag per visual effect — would have produced a combinatorial mess of keyword arguments; the drawer-plus-mask decomposition means shape and color vary independently.
  • The styling niche was defensible. A competitor could match the encoder in a weekend. Matching an extension point that third parties have already written against is a different proposition. This is the ordinary dynamics of a plugin boundary, and it is why the adoption gap has not closed even as more standards-complete alternatives matured.

One limit on what we can say here. StyledPilImage.__init__ accepts only *args, **kwargs, so signature introspection could not confirm the parameter name by which an image is embedded in the center of a code. This survey therefore does not state a parameter name for logo embedding. Styled output itself — rounded modules, gradient color mask — was confirmed working; the embedding interface was not, and guessing at it would be exactly the kind of plausible-looking fabrication this category’s documentation is already full of.

3. What the extras buy, and what they cost#

qrcode’s dependency story is more subtle than “no dependencies”, which is how it is sometimes summarized. From the installed distribution metadata:

  • Core install declares colorama, and only on win32. On any other platform the core package has no runtime dependency at all.
  • The pil extra (also pulled in by all) declares pillow>=9.1.0.
  • The png extra (also pulled in by all) declares pypng.

So the bare package is dependency-free off Windows — and produces no raster image. PNG output requires one of the two extras. The library’s core is small (0.14 MB installed in our environment) and its SVG path needs nothing extra; the moment you want a PNG you are choosing between pypng (0.17 MB) and Pillow (22.95 MB).

That Pillow figure is the dominant term in every footprint comparison in this survey, and it is the cost of Pillow, not of qrcode. A qrcode[pil] install is roughly 23 MB of which about 0.6% is qrcode. The styled-output layer is Pillow-only, so the branded-QR use that qrcode is chosen for is exactly the use that forces the large dependency. There is no styling-without-Pillow configuration.

Requires-Python is declared as >=3.9,<4.0 — the only capped upper bound in this survey’s set (segno >=3.5, qrcodegen >=3, zxing-cpp >=3.9, pyStrich >=3.10, PyQRCode none). An upper bound of this kind is a resolver-visible constraint rather than a real compatibility statement, and it matters if you maintain a strict dependency-resolution policy.

4. Encoder behavior and what it does not do#

The encoder itself is unremarkable in the best sense. Our symbol-size probe used the payload https://example.com/a-moderately-long-url?id=12345&ref=survey at error correction M and got a 33-module version-4 symbol from qrcode — the same result as segno, qrcodegen, pyqrcode, pyStrich and zxing-cpp. On a 200-digit numeric payload it selected version 5, again matching every other pure-Python encoder tested. Round-trip decoding of its PNG output with a conformant decoder succeeded. Nothing here is broken or eccentric.

Error-correction levels are exposed as the four module-level constants ERROR_CORRECT_L, ERROR_CORRECT_M, ERROR_CORRECT_Q and ERROR_CORRECT_H, confirmed present. That is the full standard set.

What is absent is more interesting:

  • No Micro QR. There is no Micro API anywhere in the qrcode namespace. Micro QR (M1–M4) is the small-symbol family for cases where a standard version-1 symbol at 21 modules plus a 4-module quiet zone on each side is physically too large. If you need it, this library cannot produce it and no configuration changes that.
  • No Structured Append. There is no structured-append API in the namespace. Structured Append is the standard’s mechanism for splitting one payload across several symbols that a decoder reassembles. Its absence rules out the “payload too large for one symbol” case entirely.
  • Kanji and ECI are claimed absent by a competitor, and we did not verify independently. segno’s own published comparison matrix attributes Kanji and ECI support to segno and qrcodegen but not to qrcode. That document is written by segno’s maintainer and must be read as vendor-authored. Our probes neither confirmed nor refuted the qrcode row, and this survey does not assert it. (We did independently find one error elsewhere in that same matrix — see the qrcodegen file — which is reason enough not to repeat its rows unchecked.)

5. The SVG emitter, and a measured gap#

The clearest measured difference between qrcode and the standards-focused alternatives is not in the matrix. It is in the vector bytes.

For an identical payload, qrcode’s SvgImage produced an SVG of 30,816 bytes. segno’s SVG serialiser for the same payload produced 1,934 bytes — about a 16-fold difference. Both files describe the same symbol.

The cause is a straightforward difference in emitter strategy. qrcode writes one <rect> element per dark module; a version-4 symbol has hundreds of dark modules, each costing a full element with its own coordinate and size attributes. segno’s emitter is path-based — it walks the matrix and expresses runs of modules as segments of a much smaller number of path elements, so adjacent dark modules cost a few path commands rather than an element apiece.

Whether that matters depends entirely on what happens to the SVG next. If it is rasterised once and thrown away, the byte count is irrelevant. If it is inlined into an HTML document, embedded in a data URI, held in a database column, or multiplied across a large batch, a 16× difference in a per-item asset is a real number. The per-<rect> form is also more work for a browser’s renderer, being hundreds of elements in the DOM rather than a handful of paths.

But 16× describes the DEFAULT factory, not the best one this library has, and the difference is large enough that quoting the headline alone misleads. qrcode ships SvgPathImage, which emits a handful of paths instead of a rectangle per module. Measured 2026-08-17, same payload:

qrcode factorybytesvs segno
SvgImage (default)22,86714.7×
SvgFragmentImage22,80114.6×
SvgPathImage6,4654.1×

So a reader who reaches for SvgPathImage closes roughly three-quarters of the gap without leaving the library. segno is still smaller — and still needs no Pillow, which is the larger argument — but “16×” is a fact about an unoptimised call, and anyone comparing the two seriously should be told which factory it refers to. This matters because it is invisible in any feature matrix: both libraries say “SVG”, and both are telling the truth.

6. API design pattern: the object is the code#

The interface treats a QR code as an object you construct and then ask for representations of, rather than a function call that returns bytes. The factory-class argument at render time is the extension seam; the encoder object is stable across renderers. That shape has two practical consequences worth noting at the architecture level.

First, one matrix, many outputs is natural — you build once and render several ways without re-encoding. Second, the renderer is where third-party packages attach, which is why the styling ecosystem around this library grew where it did.

The counter-pattern, seen in the reference implementation profiled elsewhere in this pass, is to expose only the matrix and let the caller write every renderer. The counter-counter-pattern, seen in segno, is to enumerate a large fixed set of serialisers on the code object and forgo the extension seam. qrcode sits between them, and the middle position is a defensible design rather than a compromise.

The contrast is sharp enough that a sketch of the two shapes makes it plainer than prose can — the names below are illustrative of the pattern, not quoted from either library’s documentation:

qrcode:  render(code, factory=SvgImage)   # renderer is an argument;
                                          # unknown formats are someone else's class
segno:   code.save("out.svg")             # renderer is a method;
                                          # unknown formats do not exist

Neither is better in the abstract. The first is open at the edges and requires you to know which class you want; the second is closed and requires you to know nothing. Which one is right depends on whether the format you need is on the second library’s list — and that list, as the segno file shows, is unusually long.

6a. Console and non-image output#

Beyond the image factories, the library can render a code as terminal text. This is worth a paragraph not because ASCII output is technically interesting but because it is the one output path that costs nothing: no Pillow, no pypng, no extra at all. In practice it is what makes qrcode usable as a diagnostic — you can see the symbol without adding 23 MB to the environment to look at it. Several libraries in this set offer some form of terminal rendering; it is a de-facto category convention rather than a differentiator.

6b. Comparison within the category#

Versus segno. These two are the category’s central pairing and they differ on almost every axis except the encoder. segno has Micro QR, Structured Append, Kanji, ECI, thirteen serialisers, zero dependencies and a smaller, faster core. qrcode has the extension seam, the styling ecosystem, and 7× the installed base. Our probes found their encoders agreeing on symbol version for ordinary payloads, so the choice between them is a choice about renderers, footprint and feature coverage — not about correctness. The most common real reason to be on qrcode rather than segno is that something else in the dependency tree already put it there.

Versus the reference implementation (qrcodegen). This is a category boundary rather than a comparison. qrcodegen ships no file output whatsoever; qrcode is mostly file output. A reader choosing between them has not yet decided what kind of problem they have.

Versus the native binding (zxing-cpp). zxing-cpp is roughly 21× faster on our encoder-only measurement, creates Micro QR and rMQR, and also decodes — but it is a compiled extension with an API shaped as a general barcode library, and it has no styling layer at all. The two libraries barely overlap in intent: one is for making codes that look like something, the other for making a great many codes of any symbology very quickly.

Versus pyStrich. pyStrich covers eight symbologies where qrcode covers one, and reaches formats (DXF, EPS) that qrcode reaches only via a factory somebody would have to write. Both depend on Pillow, so the footprint argument does not separate them. Adoption does: 76,806 downloads a month against 29.5 million.

6c. How much logo a styled code will actually carry#

The styling layer is why this library is chosen, and the first question anyone styling a code asks is how much of it they may cover. The commonly repeated answer — “up to 30%” — is wrong in a specific and expensive way.

30% is the codeword recovery capacity of error-correction level H, not an area you may paint over. Reed-Solomon recovers up to that share of codewords, and a solid centred blob is the worst case for it: the damage is contiguous, so it concentrates into whole codewords rather than spreading across blocks. Measured on this survey’s payload, sweeping a centred white square until decoding failed:

ECrecovery capacitymeasured area occlusion
L7%2.5%
M15%4.5%
Q25%11%
H30%11%

Roughly a third of the folklore figure. Restated for a logo — sized as a fraction of symbol width, at a 400:395 aspect with a 10% white pad, which is how a logo is actually placed:

EClargest logo that still decodes
L13.5% of width
M18.5%
Q28.0%
H29.5%

M to Q is the jump; Q to H is nearly flat. Both Q and H select version 4 for this payload, so the additional correction codewords are largely spent on the larger symbol rather than on tolerance. With a centred square the two are identical; with a logo’s shape and pad, H buys about 1.5 points of width over Q. A team paying the symbol-size cost of H expecting a materially bigger logo is not getting one.

These ceilings are not an artifact of one decoder. Swept across all four zxing-cpp binarizers — LocalAverage, GlobalHistogram, FixedThreshold, BoolCast — the limits are identical, and a consumer phone scanner gave up at the same cutoff at every error-correction level.

That last point is worth separating from a neighbouring claim it resembles. Occlusion failure is a property of the code; low-contrast failure is a property of the scanner. A brightly colored mask that this survey’s default binarizer rejected decoded under GlobalHistogram and on a phone without difficulty. Covered modules are destroyed data and no decoder recovers them; a low-contrast module is merely hard to read, and decoders differ in how hard they try. Advice built on the first is durable. Advice built on the second must name the scanner.

Method: one payload, one symbol per level, centred occluder, decoding checked with zxing-cpp across four binarizers and confirmed against one Android phone. Real logo placements that avoid finder and timing patterns may do better.

7. Performance#

Under this survey’s stated conditions — 1,000 encodes of one fixed URL at EC level M, matrix construction only, no file I/O, CPython 3.12.3 on one machine, 2026-08-17 — qrcode averaged 4.048 ms per code.

Read that against the field: segno 3.249 ms, qrcodegen 7.209 ms, pyqrcode 10.264 ms, and the native binding zxing-cpp 0.189 ms. qrcode is comfortably in the middle of the pure-Python pack, about 25% slower than segno and roughly 21× slower than the compiled option. The spread among the pure-Python encoders is about 3.2× top to bottom, which is small enough that encode speed is rarely the reason to move between them; the gap to the native binding is a different order of thing.

Note what this measurement excludes. It does not include Pillow’s PNG encoding, which for a styled image with a gradient mask is not free, and it does not include SVG serialization, where §5 shows qrcode doing substantially more work than its neighbours. An end-to-end figure would be less flattering than the encoder-only one, and we did not measure it.

8. Maintenance reality#

Two facts that are usually conflated and must not be:

  • Latest release: 8.2, 2025-05-01. That is roughly fifteen months without a shipped artifact as of 2026-08-17.
  • Repository last pushed 2026-03-25. The project is not abandoned; work continues in the repository.

The project is alive and the released artifact is old. For a category built on a stable ISO symbology this matters much less than it would elsewhere — QR geometry does not drift, and a 2025 encoder produces symbols that scan identically in 2026 — but it does mean that anything fixed in the repository since May 2025 is not in anything you can pip install. A reader tracking a specific issue should check whether the fix has actually shipped. (This pattern is not unique to qrcode; segno shows the same divergence, with a longer release gap and more recent repository activity.)

The repository carries 4,928 stars. The LICENSE file is BSD 3-clause with a 2011 Lincoln Loop copyright, though GitHub’s automated classifier reports the license as “Other” — a classifier artifact, not a licensing ambiguity, but one that will show up in any automated license audit and is worth pre-empting.

9. The third-party correctness benchmark#

A 26-case correctness corpus published in July 2026 (updated 2026-07-31) scored qrcode at 22 of 26 cases passed, the lowest of the four libraries it measured, and characterised it as “most popular but has limitations”. It reported qrcode’s install size at 30.3 MB — a figure that is mostly Pillow.

This benchmark is published by the maintainer of one of the libraries it ranks first, and this survey did not reproduce its corpus. The correctness ranking is reported here as an attributed claim, not as a finding. What the number does usefully flag is a direction to investigate: the four failures were in a corpus described as spanning numeric, alphanumeric, Latin-1, UTF-8, kanji and mixed-mode payloads from 9 to 2,510 characters, and qrcode’s known gaps in mode coverage (§4) are a plausible place for them to live. Plausible is not verified, and this survey stops there.

10. Where this library is the wrong answer#

Stated as category positions, not as advice to anyone in particular:

  • Wrong when the symbol must be a Micro QR or rMQR. Not a configuration problem; the capability does not exist.
  • Wrong when the payload exceeds one symbol and Structured Append is the correct answer. No API.
  • Wrong when SVG bytes are multiplied or inlined at scale, given the 16× measured gap against a path-based emitter for identical content.
  • Wrong when Pillow is unacceptable — in a hardened container, a security-reviewed dependency set, a serverless bundle with a size ceiling, or anywhere a native image library is a supply-chain question. The styled output that motivates choosing this library is Pillow-only.
  • Wrong as a conformance instrument. It makes no ISO edition claim of the kind segno and qrcodegen make.

And where it is straightforwardly right: when the visual treatment of the code is part of the requirement, when a third-party renderer already exists for the target format, or when it is already a transitive dependency of something else in the environment and the encoder’s ordinary behavior — which our probe found indistinguishable from every competitor’s — is all that is needed.

11. Summary#

qrcode is the adoption default, and the architecture explains the position better than the download count does. Its encoder is ordinary in the precise sense that it agrees with every other encoder tested on ordinary payloads; its distinguishing asset is a renderer layer designed as an extension point, with composable module drawers and color masks that made styled output a solved problem here first. The costs are legible: a Pillow-shaped footprint for the raster path that motivates choosing it, an SVG emitter measured at roughly 16× the byte size of a path-based competitor, no Micro QR, no Structured Append, and a released artifact that has been still since May 2025 while the repository moved on. None of those costs touch the common case, which is exactly why the common case keeps choosing it.


qrcodegen (Nayuki) — Comprehensive Analysis (S2)#

Survey of Software 1.080.1. Facts verified 2026-08-17; Python package version 1.8.0, released 2022-04-17; repository nayuki/QR-Code-generator, 6,717 stars, last pushed 2025-01-23. MIT per the repository Readme (“Copyright © 2025 Project Nayuki (MIT License)”); there is no LICENSE file at the repository root, so GitHub reports no license. 12,853 downloads in the 30 days to 2026-08-17 per pypistats.

qrcodegen is the reference implementation of this category, and reference implementation is meant literally rather than as praise. It is a minimal, matrix-only encoder: it turns a payload into a module matrix, exposes that matrix, and stops. It writes no files. It draws nothing. It has no renderer at all, and this is not an omission the project intends to correct.

Everything interesting about it follows from two facts: the extreme narrowness of scope, and the fact that the Python package is one of six language ports maintained in a single repository.


1. Shape of the library#

The distribution declares no dependencies, and installs at 0.04 MB — the smallest artifact in this survey, roughly 3.5× smaller than the next smallest (qrcode at 0.14 MB) and about 574× smaller than Pillow, which several of its competitors require. Requires-Python is >=3, the loosest declaration in the set.

The entire public surface of the code object, as probed:

get_size, get_version, get_module, get_mask, get_error_correction_level

That is it. Five accessors. get_module(x, y) returns whether a module is dark; get_size returns the module count per side; the rest report what the encoder decided. There is no save, no to_png, no to_svg, no image object, no data URI, no terminal output. Our probe confirms it directly: qrcodegen produced no file output at all, alone in this survey.

The architectural claim implied by that surface is straightforward and, in the framing this pass uses, precise: qrcodegen ships machine one and declines to ship machine two. The encoder is the specified, arithmetic, get-it-exactly-right half. The renderer is the unspecified half where every project makes different choices. Nayuki’s position is that the first half is a library’s job and the second half is yours.

Whether that is austerity or clarity depends on what you were going to do with the output. If you need a PNG on disk, this library does not do the job and no configuration makes it. If you were going to draw the modules into something the library has never heard of — a plotter path, a game world, a fabric pattern, an HTML table, a PDF you are already generating with something else — then every other library in this survey is asking you to pay for and route around a renderer you do not want.

1a. What the consumption pattern actually looks like#

The difference between this library and every other one here is stark enough that a few lines make it clearer than a paragraph — one of the few places in this pass where the code is the comparison:

# Every other library in this survey:
code.save("out.png")

# qrcodegen — this is the whole output surface:
for y in range(qr.get_size()):
    for x in range(qr.get_size()):
        if qr.get_module(x, y):
            emit_dark_square(x, y)   # your renderer, whatever it draws into

The second form is more work when you wanted a PNG, and it is the form when what you wanted was not a PNG. emit_dark_square might append to an SVG path, set a pixel, write a plotter command, fill a cell in a spreadsheet, or place a cube in a scene graph. The library takes no position, which is the point.

2. The segment API: the one place it is generous#

The narrowness is scope, not capability. Where qrcodegen does invest, it exposes more of the standard’s internals than most of this category.

QrSegment exposes exactly these constructors, as probed:

make_alphanumeric, make_bytes, make_eci, make_numeric, make_segments

plus the inspection surface Mode, get_data, get_mode, get_num_chars, get_total_bits, is_alphanumeric, is_numeric.

That is an unusually direct exposure of mode segmentation, the part of QR encoding where real capacity is won and lost. A payload like an uppercase reference code followed by a lowercase suffix is cheaper encoded as an alphanumeric segment plus a byte segment than as one byte segment throughout, because alphanumeric mode packs its restricted character set more densely than byte mode can. make_segments gives you the automatic split; the individual constructors let you override it, and get_total_bits lets you measure the result of your choice before committing to a symbol version.

Almost nothing else in this category lets you inspect segment bit cost. segno gives you a mode parameter — one decision for the whole payload. qrcode exposes error-correction constants and little else at this level. If you are optimising a symbol down a version, or building tooling that reasons about QR capacity rather than merely producing codes, this is the surface for it, and the absence of a renderer is beside the point.

The library’s Readme also claims automatic and manual mask selection and error-level boosting (the same spare-capacity behavior segno defaults on), so the levers the standard defines are present even though the file output is not.

3. A correction to a widely quoted comparison table#

segno’s published comparison page lists qrcodegen as supporting Kanji mode. For the Python package that pip install qrcodegen gives you, that is wrong.

The probe is unambiguous. QrSegment’s constructors are make_alphanumeric, make_bytes, make_eci, make_numeric and make_segments. There is no make_kanji. ECI is present; Kanji is not.

The explanation is not carelessness so much as the hazard of comparing a polyglot project. qrcodegen’s Java and C++ ports do offer Kanji-mode segment construction; the Python port at 1.8.0 does not. A comparison written from the project’s capabilities rather than from the installed Python distribution picks up the wrong answer. Consistent with this, qrcodegen’s own Readme makes no Kanji claim — the upstream project is not overstating anything; the downstream comparison is.

Two things follow, one narrow and one general. Narrowly: if you need Kanji mode in Python, qrcodegen is not the answer, and segno is the confirmed one (our probe saw it report mode='kanji' for Japanese text under Shift-JIS). Generally: this is a demonstration of why vendor comparison matrices in this category must be probed rather than quoted. The segno matrix is a good document, written by a maintainer who knows the standard, and it is wrong in a row that a reader would have no reason to doubt. This survey found it by running the code.

4. The cross-language argument, the reason to choose it#

The Nayuki repository describes itself as a “High-quality QR Code generator library in Java, TypeScript/JavaScript, Python, Rust, C++, C.” The Python package is one port of six, developed in one repository, from one design.

This is the argument for qrcodegen that has nothing to do with Python, and it is the strongest one:

When several runtimes in one system must produce QR codes, using ports of the same implementation removes an entire class of discrepancy. A back end in Python, a browser client in TypeScript and an embedded component in C, each on a different library, is three encoders with three sets of defaults for symbol version selection, error-level boosting, mask choice and mode segmentation. They will usually agree — our probe found the whole Python field agreeing on ordinary payloads — and where they diverge, the divergence is a cross-language debugging problem, which is the expensive kind. Ports of one implementation share the decisions.

This makes reaching for qrcodegen a system-architecture decision rather than a Python-library decision. Evaluated purely as a Python package it is the smallest and one of the least capable options in the survey, and its 12,853 monthly downloads reflect that. Evaluated as one corner of a polyglot system it is the only option in the category that offers anything at all, because no other library here has ports in six languages.

The 6,717 stars belong to the whole polyglot repository, not to the Python port, and should not be read as Python adoption. On that dimension the download count is the relevant figure, and at 12,853 a month it is the smallest in this survey.

5. Conformance claim#

qrcodegen’s Readme claims conformance to the QR Code Model 2 standard, with all 40 versions, all four error-correction levels, manual and automatic mask selection, error-level boosting, and ECI segments.

Two observations.

“Model 2” is the modern QR specification lineage — the versions-1-to-40 family everything in this survey produces — so the claim is a substantive one, not hedging. But it is a different form of claim from segno’s, which names an edition of ISO/IEC 18004 and quantifies its test corpus at more than 1,500 cases at 98%+ coverage. qrcodegen states what it implements; segno states which document it implements and how thoroughly it verified that.

And the same category-wide finding applies here as everywhere: no library in this survey claims conformance to ISO/IEC 18004:2024, the fourth edition published in 2024-08 which cancels and replaces the 2015 edition. That is not an accusation — QR geometry is stable, and codes generated to earlier editions scan — but a reader with a real conformance obligation must read the edition, not the adjective.

6. Performance#

Under this survey’s stated conditions — 1,000 encodes of one fixed URL at EC level M, matrix construction only, CPython 3.12.3, one machine, 2026-08-17 — qrcodegen averaged 7.209 ms per code.

That places it second-slowest of the five libraries timed: zxing-cpp 0.189 ms, segno 3.249 ms, qrcode 4.048 ms, qrcodegen 7.209 ms, pyqrcode 10.264 ms. It is about 2.2× slower than segno and roughly 38× slower than the compiled option.

This deserves a word, because “reference implementation” and “slowest but one” sit oddly together. They should not. The Nayuki implementations are written for clarity and cross-language fidelity — the same algorithm, structured the same way, in six languages, readable as an explanation of the standard. That is a different objective from being fast in CPython, and the two objectives pull in opposite directions: the Python idioms that make an encoder quick are not the ones that keep it recognisably the same code as the C port. The timing is a consequence of the design goal, not a defect against it.

The practical reading: at ordinary volumes 7 ms is invisible next to whatever I/O surrounds it. At volume, this is not the library, and the difference between it and the fastest option here is a factor of roughly 38.

For the same payload qrcodegen produced a 33-module version-4 symbol, matching every other library tested, and version 5 on a 200-digit numeric payload, again matching. The encoder is correct and unremarkable on ordinary input, exactly like all the others.

7. Maintenance reality, stated plainly#

qrcodegen is often described as under “recent (active maintenance)”, unsourced. The verified facts as of 2026-08-17:

  • Python package 1.8.0 was released 2022-04-17 — over four years ago.
  • The repository was last pushed 2025-01-23 — over eighteen months ago.

Both clocks are the oldest of any actively-considered library in this survey. Every other primary option here has repository activity in 2026; this one does not. The characterisation “active maintenance” is now flatly false, and this survey retracts it.

How much that matters is a genuine question rather than a rhetorical one. QR is a frozen symbology; an encoder that was correct in 2022 is correct now, and its correctness does not decay the way a library talking to a moving API does. A dependency-free package with a five-accessor public surface has an unusually small attack surface for bit-rot: no dependencies to go stale, no format libraries to break, essentially no API to churn. Requires-Python >=3 means no version floor has been raised out from under anyone.

Against that: no releases means no fixes, no Python-version compatibility statements as CPython moves, and no response if something is found. And there is a paperwork wrinkle — the Readme states MIT but there is no LICENSE file at the repository root, so GitHub reports no license and an automated license audit will flag it. The intent is unambiguous; the artifact an audit tool reads is absent. For an organization with a formal open-source review, that is a real, if clerical, obstacle.

7a. Comparison within the category#

Versus segno. The instructive pairing, because both are pure-Python correctness-first projects with no dependencies and both are commonly described as “the standards-compliant one” — and they are not the same product. segno is a complete solution: encoder, thirteen serialisers, Micro QR, Kanji, Structured Append, an ISO edition named and a test corpus quantified. qrcodegen is half a solution by design, with a deeper segment API and six language ports. If you are choosing between them purely inside Python and you want files out, segno wins on every axis measured in this pass — it is more capable, more than twice as fast, and more recently maintained. qrcodegen’s case is made outside Python.

Versus qrcode. Not a real comparison. One is a renderer ecosystem; the other has no renderer. A reader weighing these two has not yet settled whether they need an image.

Versus zxing-cpp. Both are chosen for reasons unrelated to Python ergonomics. zxing-cpp offers a mature compiled engine, decoding, and symbol families nobody else creates; qrcodegen offers identical behavior across six runtimes and a 0.04 MB footprint. Where a polyglot system needs speed in each runtime rather than identity across them, the argument shifts.

Versus pyStrich. Opposite philosophies of breadth. pyStrich covers eight symbologies in one Python package; qrcodegen covers one symbology in six languages. Both are answers to “I need more than one of something” — different axes of more.

8. Where this library is the wrong answer#

  • Wrong whenever a file is the required output. No PNG, no SVG, no anything. This is not a limitation to work around; it is the design. The third-party benchmark discussed elsewhere in this pass reached the same conclusion and called it “too limited (no file output)” — a vendor-authored source, but on this specific point one our own probe independently confirms, and we state it as our own finding.
  • Wrong when Kanji mode is needed in Python (§3), despite what the most commonly cited comparison table says.
  • Wrong when Micro QR or rMQR is needed — no Micro API exists in the package.
  • Wrong at volume, at roughly 38× the encode cost of the compiled option.
  • Wrong where license paperwork must be machine-verifiable, until a LICENSE file exists at the repository root.
  • Wrong where release recency is a procurement criterion. Four years without a release is a fact that some review processes simply stop at, and no argument about frozen symbologies changes that outcome.

And where it is right: polyglot systems needing identical symbols from several runtimes; custom rendering targets where a built-in renderer would be dead weight; capacity-optimization and QR tooling work that needs the segment API and get_total_bits; and footprint-critical environments, where 0.04 MB with zero dependencies is a number nothing else in the category approaches.

9. Summary#

qrcodegen is the category’s reference implementation and, by design, its most incomplete library: a matrix-only encoder with five accessors, no file output of any kind, no dependencies, and an installed size of 0.04 MB. Its segment API — make_alphanumeric, make_bytes, make_eci, make_numeric, make_segments, with get_total_bits for measuring the result — is the most direct exposure of mode segmentation in this survey, and it has no make_kanji, correcting a widely quoted comparison table that credits the Python port with a capability only its Java and C++ siblings have. Its real argument is cross-language: six ports of one implementation, which is the only answer this category offers to a system that must generate identical codes from several runtimes. Its costs are a 7.209 ms encode, second-slowest measured; a released artifact from 2022-04-17 and a repository quiet since 2025-01-23, both the oldest here; and a missing LICENSE file that will trip an automated audit despite an unambiguous MIT statement in the Readme.


S2 Recommendation — Engineering Stances#

What the comprehensive pass concluded. These are category positions, not a ranking and not advice to any particular reader; concrete situations belong to the S3 personas. All figures verified 2026-08-17 under the conditions stated in approach.md.

The through-line#

The encoder is a solved, commoditised problem. The differences that remain are in the renderer, the dependency footprint, and the symbology breadth.

That is not an editorial position; it is what the probes returned. Given the same URL at the same error-correction level, qrcode, segno, qrcodegen, pyqrcode, pyStrich and zxing-cpp all produced the identical 33-module version-4 symbol, and the four pure-Python encoders agreed again on a 200-digit numeric payload. Every library’s output round-tripped through a conformant decoder. Nothing in this category is broken in the basic case.

A reader who arrives asking “which library encodes QR correctly” is asking a question the category has already answered six times over. The useful questions are the three that remain, and they have different answers.

Stance 1 — Choose on the renderer, because that is what differs#

Every library here builds the same matrix. What they do with it varies more than anything else in this survey:

  • Thirteen serialisers from one object, no third-party dependency — segno. PNG, PDF, EPS, SVG, the netpbm family, LaTeX, all in pure Python. A closed set, but an unusually long one.
  • An open extension seamqrcode. The renderer is a factory class you supply, which is why the styling ecosystem grew here: composable module drawers and color masks, and a documented place for third parties to attach.
  • A CAD emitter — pyStrich, which reaches DXF and is alone in this survey in doing so. That is not a nicer version of somebody else’s capability; it is the only path in the category from a payload to cut or engraved geometry.
  • Two methods and no styling — zxing-cpp. SVG and an image object, correct and fast, decorated not at all.
  • Nothing — qrcodegen, by design. Matrix in, matrix out, write your own.

The measured consequence worth carrying forward: qrcode’s SVG for an identical payload was 30,816 bytes against segno’s 1,934 — about 16×, one <rect> per module against a path-based emitter. Both correctly advertise “SVG”. Feature matrices cannot see this and byte size is the only way it surfaces.

Stance 2 — Footprint is a Pillow decision, not a library decision#

Pillow is 22.95 MB installed. Every library’s own code in this survey is between 0.04 MB and 1.89 MB. Any footprint comparison that does not separate those two terms is measuring Pillow and reporting it as something else.

The three postures:

  • Pillow optionalqrcode. Dependency-free off Windows, SVG without extras, but the raster and styled paths (the reason most people choose it) require it.
  • Pillow mandatory — pyStrich. Unconditional, including on vector, EPS, DXF and terminal paths that have nothing to do with rasters. No escape configuration exists. Note carefully: the ~23 MB belongs to Pillow; pyStrich’s own code is 0.72 MB.
  • Pillow absent — segno (0.30 MB, no dependencies, thirteen formats), qrcodegen (0.04 MB, no dependencies, no formats), zxing-cpp (1.89 MB, no Python dependencies, compiled).

The striking case is segno: PNG, PDF and EPS output with no image library at all, at roughly 1/76th of Pillow’s size. Where dependency review, container size or supply-chain policy is a real constraint, that combination has no competitor here.

Stance 3 — Two functional walls, and only two#

Almost every capability in this category is present almost everywhere. Two are not:

  • Micro QR creation: segno (pure Python) and zxing-cpp (compiled). Nothing else. qrcode and qrcodegen have no Micro API at all.
  • rMQR creation: zxing-cpp alone. segno, the deepest pure-Python standards implementation, does not have it.

If either family is a requirement, the category collapses to one or two options and every other consideration in this pass is subordinate. If neither is, these rows are noise in the comparison and should be treated as such.

Stance 4 — The most consequential risk is a default, not a missing feature#

segno.make() with its default arguments returns a Micro QR symbol for short payloads — M1 for 1234, M2-M for HELLO, M4-M for a short URL, all confirmed by probe. This is documented behavior, stated in segno’s own README, and it is still the single most consequential thing an adopter in this category can get wrong, because:

  • It fires on short payloads — the ones used in examples, tests and first experiments — and not on long ones, so it looks intermittent.
  • Micro QR is fully conformant, so a round-trip test with a proper decoder passes. Automated verification does not catch it.
  • Consumer phone scanners support Micro QR far less reliably than standard QR, so the failure appears in the field, on the devices you do not control.

The escapes are explicit: make_qr() never produces Micro, make_micro() always does. The generalisable lesson is a category one: in this domain, convenience constructors make family and error-correction decisions on your behalf (segno also defaults boost_error=True), and anything that must be exactly reproducible should be using the specific constructor and stating every parameter.

Stance 5 — “Standards-compliant” means 2015 here, and you should read the edition#

segno makes the strongest conformance claim in the category — ISO/IEC 18004:2015(E) with more than 1,500 test cases at 98%+ coverage. qrcodegen claims QR Code Model 2 conformance. Both are substantive.

ISO/IEC 18004:2015 has been withdrawn. The fourth edition, published 2024-08, cancels and replaces it, with stated changes to encoding efficiency, error correction and Structured Append. No library in this survey claims conformance to the current edition.

This is not an accusation of brokenness. QR geometry is stable and 2015-edition symbols scan today. It is a warning about a word: a reader with a genuine conformance obligation must check which edition a library names, and as of 2026-08-17 the answer everywhere in this category is “the superseded one”.

Stance 6 — Read every quality claim’s authorship before its number#

The two most-quoted evidence artifacts in this category are both vendor documents: a 26-case correctness benchmark ranking pyStrich first, published by pyStrich’s own maintainer, and a comparison matrix ranking segno first, published by segno’s maintainer. Both are methodologically serious and both are worth reading. Neither is independent.

This survey adopts only what it corroborated: that zxing-cpp is by far the fastest (our own probe: 0.189 ms against 3.249 ms for the next), that qrcodegen has no file output, and segno’s Micro QR and dependency rows. The 26-case correctness ranking is reported with attribution and is not verified here. And probing found the segno matrix wrong in one row — it credits the Python qrcodegen port with Kanji support it does not have. That correction is the strongest possible argument for this stance.

Stance 7 — Separate “maintained” from “released”#

Four of the six libraries here shipped their current release before 2026 while their repositories kept moving: qrcode released 2025-05-01 with a 2026-03-25 push; segno released 2025-03-12 with a 2026-07-23 push. Both projects are alive and both released artifacts are old. qrcodegen is old on both clocks — released 2022-04-17, repository last pushed 2025-01-23 — and it is often described, without a source, as being under “active maintenance”. It is not.

Only zxing-cpp (2026-07-29) and pyStrich (2026-07-23) have 2026 releases, and zxing-cpp is renaming an entry point in its current version, which is the other side of the same coin. For a frozen symbology, release age matters less than it would elsewhere — but the two claims must be checked separately, and the previous edition of this survey got it wrong by conflating them.

What carries forward#

All six go to S3 and S4 — five as live candidates and one as a verdict:

LibraryCarried as
qrcodeThe adoption default and the styling answer; an open renderer seam, Pillow-shaped, no Micro QR, no Structured Append
segnoThe standards-complete pure-Python option; smallest capable install, most output formats, one sharp default
zxing-cppThe native outlier; fastest by an order of magnitude, only rMQR, also decodes, compiled
pyStrichThe multi-symbology option; DXF unique here, Pillow unconditional, smallest installed base
qrcodegenThe reference implementation; matrix only, cross-language, smallest footprint, oldest release
PyQRCodeA retirement verdict, not a candidate. No release since 2016-06-20 (repository pushed 2024-04-15), slowest encoder measured at 10.264 ms, and still 628,262 downloads a month — which is the interesting fact and belongs in S3 for the reader who arrives already holding it

No library is eliminated on encoder quality, because on encoder quality there is nothing to eliminate them for. S3 takes the three real axes — renderer, footprint, symbology breadth — and asks who is on which side of them.


segno — Comprehensive Analysis (S2)#

Survey of Software 1.080.1. Facts verified 2026-08-17; version 1.6.6, released 2025-03-12; repository heuer/segno, 795 stars, last pushed 2026-07-23; BSD-3-Clause. 4,177,918 downloads in the 30 days to 2026-08-17 per pypistats.

segno is the category’s standards-completeness answer: a pure-Python encoder that implements more of ISO/IEC 18004 than anything else here that is written in Python, feeds an unusually wide fan of serialisers, and does it with effectively zero third-party dependencies. It is roughly one seventh the installed base of the adoption leader and, on the dimensions S2 measures, the more complete piece of engineering.

It also contains the single most consequential default in this survey — a documented, reproducible behavior that will silently produce a symbol family consumer scanners handle less reliably, unless you know to opt out. That gets its own section, and it is the most important thing in this file.


1. Shape of the library#

segno’s architecture is the inverse of the pluggable-factory approach. There is one encoder and one code object, and the output formats hang off that object as a fixed, enumerated set of serialisers. There is no renderer-plugin seam to speak of; instead the project’s investment went into making the enumerated set large enough that the seam is not missed, and into keeping every serialiser dependency-free.

Our probe wrote all of the following from a single code object, with zero failures and no third-party package installed, for one payload:

FormatBytes
png285
svg1,934
svgz527
eps3,743
pdf1,919
txt1,722
ans8,559
pam1,803
ppm5,109
xbm1,721
xpm1,923
tex23,807
pbm308

Thirteen verified formats, all from one object, all from pure Python. That count is the architectural fact about this library. Writing a PNG without Pillow or pypng means implementing the PNG container and its zlib framing directly; writing a PDF means emitting the document structure by hand; writing LaTeX means emitting drawing commands a TeX toolchain understands. Each of those is a small, self-contained piece of work, and the project has done thirteen of them rather than take a dependency on anything.

The design is closed where qrcode’s is open. You cannot slot in a renderer for a format segno does not know. The bet is that thirteen formats plus vector output plus the netpbm family covers nearly everything, and that the cases it does not cover are better served by taking segno’s matrix or its SVG and post-processing. For artistic output specifically, the project’s answer is an out-of-tree plugin (qrcode-artistic, 3.0.2 as of 2023-11-27) rather than a core feature — which is a real answer, but one carrying its own maintenance clock.

2. Dependency footprint: the zero is real#

From the installed distribution metadata, segno declares exactly one requirement: importlib-metadata>=3.6.0, conditioned on python_version < "3.10". On any modern Python it installs nothing else. Requires-Python is >=3.5, the loosest floor in this survey’s set.

Installed size in our environment: 0.30 MB.

Set that against the category’s dominant footprint term. Pillow, which qrcode’s raster and styled paths need and which pyStrich requires unconditionally, is 22.95 MB — roughly 76 times segno’s entire installed size. A segno install is not “small for a library with this feature set”; it is small full stop, and it is the only library in this survey that reaches PNG, PDF, EPS and SVG without a single third-party package.

That is not an aesthetic point. It is the difference between a dependency review that reads one BSD-licensed pure-Python package and one that reads a native image library with a C dependency chain — and, in size-capped deployment targets, between fitting and not.

3. Encoder completeness#

This is where segno separates from the rest of the pure-Python field. Verified by probe:

  • Micro QR, M1 through M4, created not merely read. segno.make("12345", micro=True) produced designator M1 at a symbol size of 15 × 15. No other pure-Python library in this survey creates Micro QR at all; qrcode and qrcodegen have no Micro API in their namespaces.
  • Kanji mode. segno.make("こんにちは世界", encoding="shift_jis", error="m") reported mode='kanji' — the encoder is selecting the standard’s kanji mode, not falling back to byte mode with a Shift-JIS payload. On CJK text that is a material capacity difference — kanji mode is a dedicated, denser encoding for the double-byte range than the byte-mode fallback.
  • ECI. Supported, per the project’s own documentation and consistent with the encoding-parameter surface.
  • Structured Append, working. segno.make_sequence("x"*600, symbol_count=4) produced 4 symbols at versions [7, 7, 7, 7]. This is the standard’s mechanism for spreading one payload across several symbols that a conformant decoder reassembles in order. qrcode has no structured-append API; this is a capability that exists in this category essentially only here and in the native binding’s underlying library.
  • No rMQR. Rectangular Micro QR is absent — there is no rMQR or rectangular symbol option in segno.make’s signature or the module namespace. In this survey only zxing-cpp creates rMQR.

The full signature of the main entry point, as probed, is:

make(content, error=None, version=None, mode=None, mask=None,
     encoding=None, eci=False, micro=None, boost_error=True)

Read it as a capability inventory. Explicit control over mask index, symbol version, mode and ECI in one call is the surface of a library that expects to be asked conformance questions.

4. The trap: segno.make() can silently hand you a Micro QR#

This is the most consequential thing an adopter of this library can get wrong, and it is a documented behavior, not a bug. It is in this file because documented and expected are different words.

With micro=None — the default — make() selects the smallest symbol that fits the payload, and Micro QR symbols are in the candidate set. Probed side by side:

payloadmake() returnsis_micromake_qr() returns
1234M1True1-H
HELLOM2-MTrue1-H
https://a.coM4-MTrue1-M
こんにちは世界M4-MTrue1-Q

Every one of those short payloads comes back as a Micro QR. The library’s own README states it plainly: “If the content to encode is small enough, a Micro QR code is generated.”

Why it matters: Micro QR is a legitimate part of ISO/IEC 18004 and a conformant decoder reads it, but consumer phone scanners support Micro QR far less reliably than standard QR. A symbol that decodes perfectly in your test harness — including in a round-trip test using a conformant library decoder — can fail on the devices that will actually be pointed at it. The failure mode is therefore the worst kind: silent, correct-by-the-standard, and invisible to automated verification that uses a proper decoder.

Compounding it: the trap fires on short payloads, which are exactly the ones used in examples, unit tests and first experiments. A developer who tries the library with a short string, sees it work, and ships is in the failure path. A developer whose payloads are long URLs never sees it at all, because those exceed Micro capacity and get a standard symbol — so the behavior is also intermittent-looking across a codebase.

The escapes are explicit and documented:

  • segno.make_qr() — never produces a Micro QR. The library’s documentation says exactly that.
  • segno.make_micro() — always produces one, when you actually want it.
  • micro=False on make() itself — the same guarantee without changing constructor, for code that is already calling make() with other arguments.
  • version= — names the symbol outright, in either direction: version=4 forces a standard version-4 symbol, version='M3' forces a Micro one.

And one interaction that decides this for a large share of real callers. Error-correction level H is not available in Micro QR at all — segno rejects it outright, for every Micro version:

segno.make('HI', version='M1', error='H')
ValueError: Error correction level "H" is not available for Micro QR Codes

So segno.make(payload, error='H') returns a standard symbol without anyone having thought about symbol families. Probed across all four levels on a short payload:

callreturnsmicro
make(p, error='L')M2True
make(p, error='M')M2True
make(p, error='Q')M4True
make(p, error='H')1False

This cuts both ways. A team that specifies H — common where codes are printed, scuffed or read at an angle — never meets this behavior. A team that specifies M or Q, which is just as ordinary, still gets a Micro symbol and is squarely in the failure path. The trap is real, but it is narrower than “any short payload”: it is short payloads at levels L, M or Q.

The engineering stance that follows is simple, and it is a category finding rather than a tip: in this library, the general-purpose constructor is the one that makes a family decision on your behalf, and the specific constructors are the ones that do not. Any code path whose output will be scanned by arbitrary consumer hardware should be using make_qr(). Our own symbol-comparison probe used make_qr() for exactly this reason, and the one row of that probe where segno appeared to disagree with every other library — the kanji payload, where make() returned M4 while others returned version 2 — was this behavior, not an encoder difference.

Note also the interaction with the neighbouring default, boost_error=True (§5): between the two, an unqualified segno.make() may hand back a symbol of a different family and a different error-correction level than the arguments literally suggest. Both behaviors are individually defensible and documented. Together they make the default constructor a poor choice for anything that has to be exactly reproducible.

5. boost_error=True: the other silent default#

make() defaults boost_error to True. When the chosen symbol version has spare capacity after encoding the payload, segno raises the error-correction level to use it rather than leaving the capacity idle.

This is straightforwardly good behavior in most cases — free redundancy at identical symbol size, and the resulting symbol is still exactly conformant. The version-4 symbol our probe produced at nominal EC level M is the same 33 modules either way; boosting just spends the slack on more Reed–Solomon codewords.

It matters anyway, for two reasons. First, the EC level you asked for is not necessarily the EC level you got, which matters if you are asserting on it, logging it for compliance, or trying to reproduce a symbol byte-for-byte from another implementation. Second, it means segno and another library given identical arguments can emit different module patterns while both being correct — same version, same size, different data. Cross-implementation symbol comparison in this category has to compare version and decoded payload, not pixels, and this default is one of the reasons why.

6. Standards conformance: read the edition, not the word#

segno makes the strongest conformance claim in the category, and its README states the claim verbatim:

“This package implements ISO/IEC 18004:2015(E) ‘QR Code bar code symbology specification’”

and

“The project provides more than 1500 test cases (coverage >= 98%) to verify a standard conform QR Code and Micro QR Code generation acc. to ISO/IEC 18004:2015(E).”

Fifteen hundred test cases against the specification is a serious quality artifact and by some distance the most substantial conformance evidence any library in this survey offers. Take it at face value.

Then note the edition. ISO/IEC 18004:2015 has been withdrawn. The fourth edition, ISO/IEC 18004:2024, was published in 2024-08 and “cancels and replaces” the 2015 edition — verified 2026-08-17 against the ISO catalog entry (standard 83389), the IEC webstore and the ANSI webstore. The stated changes in the new edition include optimized encoding efficiency, improved error correction and refined Structured Append.

The finding is not that segno is broken. QR symbol geometry is stable, and codes generated to the 2015 edition scan today exactly as they did in 2015. The finding is narrower and applies to the whole category: no library in this survey claims conformance to the current edition of ISO/IEC 18004. The category’s standards-compliant option is compliant with a superseded edition, and a reader with a genuine conformance obligation — a regulated marking requirement, a customer contract that names the standard — must check the edition rather than the word “ISO-compliant”. As of 2026-08-17 that word means “2015” throughout this category.

(“QR Code” and “Micro QR Code” are registered trademarks of DENSO WAVE INCORPORATED, as segno’s own README notes.)

7. The vendor comparison matrix, and where we corrected it#

segno publishes a comparison page against qrcode 8.0.0 and qrcodegen 1.8.0. Its matrix asserts: all three support versions 1–40; Micro QR M1–M4 segno only; Kanji and ECI for “qrcodegen and Segno”; Structured Append and Hanzi segno only; extended outputs (PAM, LaTeX, XPM, data URIs) segno only; and on dependencies, that qrcode needs Pillow/pymaging/colorama while qrcodegen and segno need none. Its benchmarks are stated as run on an Intel i7-8559U with CPython 3.11. It excludes PyQRCode on the grounds that “six years have passed without any updates”.

This is a vendor document — written by segno’s maintainer, ranking segno — and must be cited as one. Our probes independently confirm its Micro QR row and its dependency row, and those two we are willing to state as our own findings.

We also found one row wrong: the matrix credits qrcodegen with Kanji support. The Python port that pip install qrcodegen gives you (1.8.0) has no make_kanji — its segment constructors are exactly make_alphanumeric, make_bytes, make_eci, make_numeric and make_segments. The Kanji claim is true of qrcodegen’s Java and C++ ports, not of its Python one. That is a reasonable mistake for a maintainer comparing a multi-language project, and it is also a demonstration of why vendor matrices in this category should be probed rather than quoted.

8. Performance#

Under this survey’s stated conditions — 1,000 encodes of one fixed URL at EC level M, matrix construction only, CPython 3.12.3, one machine, 2026-08-17 — segno averaged 3.249 ms per code. That is the fastest pure-Python encoder measured, ahead of qrcode at 4.048 ms, qrcodegen at 7.209 ms and pyqrcode at 10.264 ms — a spread of about 3.2× across the pure-Python field.

The compiled option, zxing-cpp, ran at 0.189 ms, roughly 17× faster than segno. That gap is not closable by a pure-Python implementation and should be read as a property of the runtime rather than of segno’s code, which is the fastest thing available under that constraint.

Two caveats. The measurement excludes serialization entirely — and segno’s serialisers are, per §1, hand-written pure Python doing work that qrcode delegates to Pillow’s C code, so the encoder-only ranking need not survive an end-to-end PNG benchmark. We did not measure that. And segno’s own published benchmark numbers are on different hardware and a different Python; do not mix the two sets.

9. Maintenance reality#

  • Latest release: 1.6.6, 2025-03-12 — roughly seventeen months without a shipped artifact as of 2026-08-17, the longest release gap among the actively developed libraries in this set.
  • Repository last pushed 2026-07-23 — under a month before the verification date. The project is clearly alive.

The same alive-project/old-artifact split as qrcode, slightly more pronounced. For a frozen symbology this is a minor concern; the practical consequence is that the 2024 edition of the standard postdates the released artifact, and any alignment work that may exist in the repository is not in what you install.

795 stars, BSD-3-Clause, no licensing ambiguity.

9a. Comparison within the category#

Versus qrcode. The category’s defining pair, and the reason its research question exists at all: the popular library and the complete one are different libraries, by roughly 7:1 on downloads. What S2 adds is that they are not different on the thing most readers assume — their encoders agreed on symbol version for every ordinary payload we tested. The differences are the renderer (an open factory seam and a styling ecosystem there; thirteen closed serialisers and a 16× smaller SVG here), the footprint (Pillow-shaped there for raster output; zero here), and feature coverage (Micro QR, Structured Append and Kanji here, none of them there).

Versus zxing-cpp. The Micro QR capability is split between exactly these two, and rMQR sits only on the other side. zxing-cpp is roughly 17× faster on our encoder-only measurement and also decodes; segno is source, dependency-free, and carries the conformance evidence. Anyone whose constraint is “no compiled artifacts” has already chosen segno; anyone whose constraint is rMQR has already chosen the other.

Versus qrcodegen. Both are dependency-free, correctness-first and pure Python, which makes them look like the same choice and they are not. segno is complete; qrcodegen ships a matrix and no renderer. Within Python, segno is more capable, about 2.2× faster on our measurement, and far more recently maintained. qrcodegen’s argument is cross-language identity, which segno cannot make at all.

Versus pyStrich. Both call themselves pure Python and only segno is dependency-free — pyStrich requires Pillow unconditionally, a roughly 80× difference in installed footprint. pyStrich answers “I need eight symbologies and DXF”; segno answers “I need all of QR and nothing else in my dependency tree”.

10. Where this library is the wrong answer#

  • Wrong when rMQR is required. Only zxing-cpp creates it here.
  • Wrong when the output format is not on the list of thirteen and post-processing the matrix or the SVG is not acceptable — there is no renderer plugin seam.
  • Wrong when artistic or logo-embedded output is a core requirement, unless you accept an out-of-tree plugin last released in 2023 as part of your dependency surface. The styling ecosystem is not here.
  • Wrong when per-code encode cost dominates at a scale where 3.2 ms matters — that is a native-binding problem, and 17× is a large multiple.
  • Wrong when the default constructor will be used by people who have not read this file. That is not a joke: §4 is a real operational hazard, and a team adopting segno without a convention forbidding bare make() on consumer-scanned output has installed a latent defect.

And where it is straightforwardly right: when the dependency footprint is under review, when Micro QR or Structured Append or Kanji mode is actually needed, when output must reach several formats without dragging in an image library, and when the encoder’s conformance evidence is something you may have to point at.

11. Summary#

segno is the most complete encoder in the pure-Python half of this category and the smallest install in the whole of it: 0.30 MB, no third-party dependency, thirteen verified serialisers from a single object, Micro QR M1–M4, Kanji, ECI and working Structured Append, and the fastest pure-Python encode we measured at 3.249 ms. It is also the library with the sharpest edge — a default constructor that will hand short payloads back as Micro QR symbols, documented and reproducible and easy to ship without noticing, with make_qr() as the explicit escape. Its conformance claim, the strongest in the category and backed by more than 1,500 test cases, names the 2015 edition of ISO/IEC 18004, which the 2024 edition has since replaced — a finding that applies to every library here, not just this one.


zxing-cpp — Comprehensive Analysis (S2)#

Survey of Software 1.080.1. Facts verified 2026-08-17; Python package version 3.1.1, released 2026-07-29; repository zxing-cpp/zxing-cpp, 1,968 stars, last pushed 2026-08-13; Apache-2.0. 1,056,907 downloads in the 30 days to 2026-08-17 per pypistats.

zxing-cpp is the outlier in this survey on three separate axes at once. It is the only compiled option — Python bindings over a C++ barcode library rather than a Python implementation. It is the only one that decodes as well as encodes. And it is the only one that can create Micro QR and rMQR symbols, a capability no pure-Python library here has.

Those three properties are not independent. They all follow from the same fact: this is not a Python QR library that grew a fast path. It is a mature C++ barcode engine — a port of the ZXing lineage — with a Python surface attached, and everything distinctive about it in this category is inherited from the engine rather than designed for Python.


1. Shape of the library#

The Python distribution ships a compiled extension and no Python dependencies. Installed size in our environment: 1.89 MB — larger than any pure-Python option here (segno 0.30 MB, pyStrich 0.72 MB, qrcode 0.14 MB) and small next to Pillow’s 22.95 MB, which several of those pure-Python options end up pulling in anyway. Requires-Python is >=3.9.

That size profile inverts a common assumption. “Native binding” is often shorthand for “heavy”; here the native binding is the lighter install than qrcode[pil] or pyStrich, both of which require Pillow. What it costs instead is a compiled artifact: you are relying on a binary wheel existing for your platform, Python version and libc, and where one does not, you are compiling C++. That is a different class of risk from a large pure-Python tree — usually invisible, occasionally total.

The architecture inside the wheel is a C++ barcode engine with a thin binding layer. Practically every design characteristic below is a consequence of that.

2. The read/write duality#

Every other library in this survey is an encoder. zxing-cpp is a barcode library: it encodes and it decodes, and the decode side is the older and larger half of the lineage.

Decoding is out of scope for this survey — scanning belongs to a different category — but the coexistence is itself a category fact worth recording, for two reasons.

First, it makes verification possible without leaving the process. This survey used zxing-cpp as its round-trip oracle: every other library’s PNG output was read back with it. All of them passed — segno, qrcode, pyqrcode, pyStrich (both its save() PNG and its Pillow image path), and zxing-cpp’s own output. The resulting finding is one of the most useful in the survey: no library in this set is broken in the basic case. Producing that finding required exactly one library that reads, and there is exactly one.

Second, it changes what a QR library is for. A system that generates codes and later verifies them — checking a print run, validating a scan pipeline’s inputs, testing that a symbol survived a rendering step — needs both halves. Every other option in this category forces a second dependency for that. This one does not, and if both halves are needed the dependency arithmetic swings hard in its favor.

One caution when using a conformant decoder as an oracle: it reads things consumer hardware may not. A Micro QR symbol round-trips perfectly here and may still fail on a phone. Round-trip success proves conformance, not field scannability.

3. Symbology breadth, including what nothing else can create#

Formats are values of a BarcodeFormat enumeration, and the library exposes a set of formats it can create (AllCreatable) distinct from what it can read. The creatable set is broad — this is a general barcode library and QR is one member of the enum, not the subject.

The two members that matter for this survey, both verified by probe:

  • BarcodeFormat.MicroQRCode is present and in AllCreatable; create_barcode("12345", BarcodeFormat.MicroQRCode) succeeded, reporting ec_level='L'.
  • BarcodeFormat.RMQRCode is present and in AllCreatable; creation succeeded, reporting ec_level='H'.

rMQR — rectangular Micro QR — is creatable here and nowhere else in this survey. segno, the pure-Python library with the deepest standards coverage, has Micro QR but no rMQR: there is no rectangular symbol option in segno.make’s signature or its module namespace. qrcode and qrcodegen have no Micro API at all. If the requirement names rMQR, this survey has exactly one answer, and the choice is therefore not really a choice.

Micro QR is a two-library capability: segno in pure Python, zxing-cpp compiled. Everything else in the set is standard-QR-only.

4. API design: a barcode library that happens to do QR#

The interface shape is the clearest signal of the library’s origins. Rather than a QR-shaped constructor with QR-shaped arguments, you get a format-parameterised factory: create_barcode(content, BarcodeFormat.X) returns a Barcode object, and rendering is to_svg() or to_image() on that object.

The consequences cut both ways.

In its favor, this is the right shape for a system that handles more than one symbology. Adding Data Matrix or a linear format alongside QR is a change of one enum value, not a change of library. Code written against the object is symbology-agnostic by construction. For anyone whose problem is “barcodes” rather than “QR codes”, this is a better abstraction than five single-symbology libraries with five different constructors.

Against it, the QR-specific vocabulary is thinner than in a dedicated QR library. The knobs a QR specialist expects to reach for — explicit mask index, explicit symbol version, mode selection, ECI, an error-boost switch — are not the natural surface of a generic barcode factory. Compare segno’s make(), whose signature enumerates error, version, mode, mask, encoding, eci, micro and boost_error in one line. That is a library that expects conformance questions; this is a library that expects format questions. Neither is wrong; they answer different ones.

Our probe also observed that write_barcode is deprecated in 3.1.1 in favor of create_barcode. That is a small thing with a large implication: this API surface is still moving. The pure-Python libraries in this survey have shipped nothing in over a year; this one is renaming its primary entry point in a release dated 2026-07-29. Active development is a virtue, and a deprecation in the current release is also a maintenance cost that the frozen alternatives do not impose.

5. The quiet-zone gotcha in to_image()#

One probe result must be reported carefully, because misreading it produces a false comparison that is easy to publish and hard to catch.

For the payload https://example.com/a-moderately-long-url?id=12345&ref=survey at EC level M, every library in this survey produced the same 33-module version-4 symbol — segno (via make_qr), qrcode, qrcodegen, pyqrcode, pyStrich and zxing-cpp.

But zxing-cpp’s to_image() at scale 1 returns an image of shape 41 × 41.

That is not a larger symbol. add_quiet_zones=True is the default, and the standard requires a quiet zone of 4 modules on each side of a standard QR symbol: 33 + 4 + 4 = 41. The library is including the mandatory margin in the image it hands you; the others, by default, hand you the symbol and leave the margin to whoever places it. Both are legitimate; one produces a number 24% larger than the other for the same code.

Report this as a default difference, never as an encoder difference. A comparison table that lists zxing-cpp at 41 modules next to segno at 33 is simply wrong. It is also, in practice, the more helpful default: the quiet zone is the most commonly omitted requirement in real QR deployments, and a symbol pasted flush against dark page furniture frequently will not scan. A library that gives you the margin by default is protecting against a failure mode that libraries which do not give it to you routinely suffer.

to_svg() output for the same payload was 3,486 bytes — between segno’s path-based 1,934 and qrcode’s per-rectangle 30,816, so the vector emitter is reasonably economical without being the tightest here.

6. Performance#

Under this survey’s stated conditions — 1,000 encodes of one fixed URL at EC level M, matrix construction only, no file I/O, CPython 3.12.3, one machine, 2026-08-17 — zxing-cpp averaged 0.189 ms per code.

The comparisons, all from the same run:

Libraryms per codeRelative to zxing-cpp
zxing-cpp0.189
segno3.249~17× slower
qrcode4.048~21× slower
qrcodegen7.209~38× slower
pyqrcode10.264~54× slower

So: roughly 17× faster than the fastest pure-Python encoder and ~54× faster than the slowest measured here. This is the one performance gap in the category that is a difference in kind rather than degree. The pure-Python field spans about 3.2× top to bottom — close enough that speed is rarely the reason to move between them. The step to compiled code is an order of magnitude and change, and no amount of Python-level optimization crosses it.

When this matters: at one code per web request it is irrelevant, because the surrounding I/O dwarfs 4 ms. At high batch volume it is the difference between a job that finishes and a job that needs a work queue. The threshold is a volume question, and the number above is the input to it.

The same caveat as everywhere in this pass: the measurement is encoder-only. A third-party benchmark that measured end-to-end PNG generation reported zxing-cpp in a median range of 0.55–14 ms and also ranked it fastest overall — that is a vendor-authored source (see §8), but it happens to agree with our independent result on this specific point, and “zxing-cpp is by far the fastest” is a claim this survey is willing to make on its own probe.

7. Maintenance reality#

zxing-cpp carries the most recent release in the survey: 2026-07-29, with the repository last pushed 2026-08-13, four days before verification. Only pyStrich also has a 2026 release (2026-07-23, repository pushed 2026-08-17); qrcode and segno last shipped in 2025 and qrcodegen in 2022. Together with pyStrich, this is one of only two libraries here where the released version and the repository are close together — which matters, because for the rest of this category “actively maintained” and “recently released” are separate claims with different answers.

1,968 stars, Apache-2.0 — a clean, well-understood, patent-granting license, and one of the unambiguous license classifications in the set. qrcode’s and pyStrich’s both come back as “Other” from GitHub’s classifier despite carrying BSD 3-clause and Apache-2.0 license text respectively — in pyStrich’s case because of a bundled SIL OFL notice for the Courier Prime glyphs — and qrcodegen’s repository has no LICENSE file at all.

The maintenance profile is therefore the strongest here, with the caveat from §4 that active development also means an API that is still being reshaped.

8. The third-party benchmark#

A 26-case correctness corpus published 2026-07-16 and updated 2026-07-31 scored zxing-cpp at 24 of 26 cases passed, called it “fastest overall”, and reported its install size at 31.9 MB.

The benchmark’s author is the maintainer of pyStrich, which it ranks first, so every figure from it carries a conflict of interest and this survey states that wherever it is cited. Its correctness ranking is not reproduced here. Its speed verdict is the one claim we corroborated independently (§6), and on that point we rely on our own measurement rather than its.

The 31.9 MB install figure does not match our 1.89 MB and the difference is almost certainly methodological — a different environment, and quite possibly one where a shared Pillow or numpy install is attributed to each package measured. We report our own figure and note the discrepancy rather than trying to reconcile two measurements whose methods we cannot compare.

8a. The compiled-artifact question, taken seriously#

Every other library in this survey is source you can read, vendor, patch and run anywhere CPython runs. This one is a wheel. That difference is usually a non-event and occasionally decisive.

What actually changes. Installation stops being platform-independent. A wheel must exist for your platform, Python version and C runtime, or the install becomes a compile — which needs a C++ toolchain present at install time, in whatever environment is doing the installing, including build containers and CI images that may not have one. Debugging changes too: a bug in the encoder is a bug in C++, not something you step into from a Python debugger or patch in a vendored copy.

What does not change. The distribution declares no Python dependencies at all, so there is no transitive tree to audit — in dependency-review terms it is simpler than qrcode[pil], which brings in a native image library with its own chain. The 1.89 MB installed size is modest. And the compiled half is a long-established barcode engine rather than bespoke code, so the “who else has run this” question has a good answer.

Where it bites. Unusual architectures, free-threaded or pre-release CPython builds, musl-based minimal containers, restricted-network build environments that cannot fetch a toolchain, air-gapped installs that need every wheel mirrored, and policies that treat binary artifacts as a distinct supply-chain category from source. In each of those the question is not “is it good” but “will it install”, and it is answerable in advance in about five minutes.

8b. Comparison within the category#

Versus segno. These two split the Micro QR capability between them and split almost nothing else. segno is 0.30 MB of pure Python with thirteen serialisers, the standards claim and the test corpus; zxing-cpp is a compiled engine with rMQR, a decoder, and a 17× speed advantage on our measurement. The dividing question is rarely features — it is whether a compiled dependency is acceptable, and if it is, whether the requirement names rMQR or decoding.

Versus qrcode. Barely comparable. qrcode exists to make codes that look like something and has an extension seam for renderers; zxing-cpp has no styling layer of any kind. The one place they meet is bulk generation of plain symbols, where zxing-cpp is roughly 21× faster on encoding alone.

Versus pyStrich. Both are multi-symbology. pyStrich reaches DXF, which zxing-cpp does not, and is pure Python at the cost of an unconditional Pillow requirement; zxing-cpp is compiled, has no Python dependency, and creates symbol families pyStrich was not observed to. For a multi-symbology system the deciding factors are DXF on one side and speed plus Micro/rMQR on the other.

Versus qrcodegen. Opposite ends of the same axis. Both decline to be Python-idiomatic QR libraries — qrcodegen by shipping only a matrix, zxing-cpp by shipping a generic barcode API — and both are chosen for reasons outside Python: cross-language identity in one case, engine maturity and speed in the other.

9. Where this library is the wrong answer#

  • Wrong where a compiled dependency is unacceptable — a pure-source deployment, an exotic platform or Python build with no wheel, an environment where a C++ toolchain is not available at install time, or a supply-chain policy that treats binary wheels differently from source packages.
  • Wrong when styled or branded output is the requirement. There is no module drawer, no color mask, no artwork embedding. It renders symbols, correctly and quickly, and does not decorate them.
  • Wrong as a QR conformance instrument. It makes no ISO edition claim of the kind segno and qrcodegen make; its evidence is a long-lived barcode engine’s track record, which is a different (and not obviously weaker) argument, but not the same one.
  • Wrong when a stationary API is worth more than an improving one. A deprecation in the current release is a real signal for a codebase that expects to pin once and not look again for five years.
  • Wrong when the symbol size you report will be compared against another library’s and nobody on the team knows about §5.

And where it is straightforwardly right: rMQR, Micro QR without pure-Python constraints, high-volume encoding, any system that must also decode, and any system whose real subject is barcodes generally rather than QR specifically.

10. Summary#

zxing-cpp is the native option and the category’s capability outlier. It creates Micro QR and rMQR — the latter uniquely in this survey — encodes at 0.189 ms per code against 3.249 ms for the fastest pure-Python alternative under identical conditions, decodes as well as encodes (which is why it served as this survey’s round-trip oracle, confirming that every library here produces readable symbols), installs at 1.89 MB with no Python dependencies, and carries the freshest release and repository activity in the set. What you accept in exchange is a compiled extension, a general barcode API rather than a QR-specialist one, an entry point that was renamed in the current release, no styling layer whatsoever, and a quiet-zone-inclusive image default that will look like a size discrepancy to anyone comparing outputs without reading the documentation.

S3: Need-Driven

S3 Need-Driven Discovery — Approach#

Purpose#

S1 mapped the field and S2 took the libraries apart. S4 asks which of them will still be a safe bet years from now. S3 answers the orthogonal question: who actually needs to generate QR codes, and why does that need select a different library for different people?

These are personas — WHO and WHY — not build instructions. There is no code in this pass, and no step-by-step anything. A reader should be able to find themselves in one of the six profiles and understand which constraints are doing the work in their situation.

The insight that shapes every persona#

The hands-on probe (2026-08-17, CPython 3.12.3, one machine) produced one result that reorganises this entire category. Given the payload https://example.com/a-moderately-long-url?id=12345&ref=survey at error correction M, all six libraries produced the identical 33-module, version-4 symbol — segno (via make_qr), qrcode, qrcodegen, pyqrcode, pyStrich and zxing-cpp alike. The same agreement held on a 200-digit numeric payload, where all four pure-Python encoders selected version 5. And a round-trip check found that a conformant decoder read every library’s own PNG back byte-identically: nothing in this set is broken in the basic case.

That is the finding: the encoder is commoditised. QR is a frozen, well-specified symbology, the reference algorithms have been public for decades, and the interesting engineering left in this category is not “does it encode correctly.”

The consequence for a reader is blunt. Almost nobody choosing among these six is actually choosing an encoder. They are choosing:

  • Renderer — what comes out the other end. A matrix of booleans, a PNG through Pillow, a byte-efficient SVG, an EPS for a press, a DXF for a laser cutter, or nothing at all (qrcodegen ships no file output whatsoever).
  • Footprint — what the dependency costs. In the probe venv, segno measured 0.30 MB and qrcodegen 0.04 MB, while Pillow alone measured 22.95 MB. Anything that reaches raster output through Pillow pays that tax before its own code is counted.
  • Symbology breadth — whether QR is the whole job. Micro QR, rMQR, Data Matrix, PDF417, Aztec and the 1-D family are all different questions, and the set splits sharply on them.
  • Failure mode — what goes wrong quietly. The most consequential example in this category is that segno.make() with default arguments returns a Micro QR for short payloads (probed: 1234 gives M1, HELLO gives M2-M, https://a.co gives M4-M), a documented behavior with a documented fix (segno.make_qr()), and one that consumer phone scanners handle far less reliably than a standard symbol.

So each persona below is organized around one of those four axes, not around encoding quality. Where a persona does care about speed or symbol version, the figure is given with its conditions, because the conditions are usually what decides whether the figure matters.

The six personas#

  1. The high-volume code issuer — a service or batch job minting a unique code per record. Selects on per-code latency, memory predictability, and raster throughput.
  2. The print and packaging engineer — output goes to a press, a label, or a machined part. Selects on true vector output, vector byte-efficiency, quiet-zone control, and physical module size.
  3. The brand and marketing engineer — the code has to look like the brand. Selects on renderer extensibility, and pays for it in scannability margin.
  4. The constrained-deployment engineer — small container, serverless function, or a security review that counts dependencies. Selects on footprint and dependency count above everything else.
  5. The industrial and logistics engineer — marking parts, totes and documents. Selects on symbology breadth, including the small-symbol variants, and often needs to read codes back as well as write them.
  6. The polyglot platform team — the same symbol must come out of more than one runtime, or one service’s output must be checked against another’s. Selects on cross-language implementation identity.

None of these is the default reader#

State it plainly: no persona here is the survey’s organising principle.

A survey is a neutral comparison of a whole category for any reader. Concrete scenarios are allowed in S3 and only in S3, as peers. If a reader arrives with a need that matches persona four and none of the others, the other five are still correct — they are simply not about them. If a reader’s need spans three personas, the recommendation matrix at the end of this pass is where those tensions get resolved.

What each persona file contains#

  • Who they are — the role, and what a working day actually looks like.
  • What hurts — the problem in their own terms, not the library’s terms.
  • Why particular options fit — requirements tied to verified capabilities.
  • Constraints and trade-offs — what adopting that option costs them.
  • Decision criteria — the crossover point where the answer flips, named explicitly rather than left as “it depends.”

S3 Recommendation — How Need Maps to Option#

There is no category default#

That is the first and most useful thing to say. The six personas in this pass do not converge on a winner, and any survey of this category that names one is answering a narrower question than it admits.

The reason is structural. QR is a frozen, tightly specified symbology, and the probe on 2026-08-17 found that all six libraries produced the identical 33-module version-4 symbol for the same URL payload at error correction M, that all four pure-Python encoders chose version 5 for a 200-digit numeric payload, and that an independent decoder read every library’s own PNG back byte-identically. Nothing here is broken in the basic case.

So the encoder — the part a reader assumes they are choosing — is commoditised, and the choice falls entirely to the surrounding properties: renderer, footprint, symbology breadth, and failure mode. That is the strongest reusable finding in this pass, and it generalises past QR to any category built on a mature, published specification: once the specification stops moving, the libraries stop competing on the thing the specification covers and start competing on everything around it.

Persona to option#

PersonaDominant needFirst choiceCredible alternativeWhy the choice turns
High-volume code issuerPer-code latency and predictable throughputzxing-cppsegnoCompiled encoder at 0.189 ms vs 3.249 ms; but only above the volume where 3 ms per code shows up in wall-clock
Print and packaging engineerTrue vector, compact vector, physical fidelitysegnopyStrich (for DXF)13 formats with no dependency, and SVG ~16× smaller than qrcode’s DEFAULT per-module emitter — ~4× against its SvgPathImage, which is the fair comparison if the reader optimises
Brand and marketing engineerRenderer extensibilityqrcodesegno + qrcode-artisticPluggable image factories; rounded modules and gradient masks confirmed working
Constrained-deployment engineerFootprint and dependency countsegnoqrcodegen (matrix only)0.30 MB, effectively zero dependencies, native PNG that skips Pillow’s 22.95 MB
Industrial and logistics engineerSymbology breadth, small symbols, read-backzxing-cpppyStrich, or segno for Micro QR onlyOnly option creating both Micro QR and rMQR, and the only reader in the set
Polyglot platform teamCross-runtime implementation identityqrcodegenany good per-runtime library plus a shared test corpusOne project across six languages — but the convergence finding weakens the premise

What the mapping reveals#

segno is first choice for two personas and credible alternative for two more, which makes it the closest thing here to a safe default — but it is a default by breadth, not by dominance. It wins the print and constrained-deployment cases on renderer and footprint, and it loses the styling case on architecture and the rMQR case on capability. A reader with no strong constraint will do well with it; a reader with a strong constraint should check which one they have.

zxing-cpp wins the two personas with hard technical requirements — raw speed and symbology breadth including rMQR — and loses everywhere a compiled dependency is unwelcome. That is a clean split, and it means the zxing-cpp decision is usually made by a supply-chain policy rather than by an engineer.

qrcode’s enormous installed base does not translate into persona wins. It recorded 29,511,144 downloads in the last month against segno’s 4,177,918 — a ratio of roughly seven to one — and it is first choice for exactly one persona here. That is not a contradiction. Popularity in this category reflects being the obvious first search result for a very common, very easy task, and PyPI counts include CI runs, container rebuilds and transitive installs, so they measure diffusion rather than deliberate choice. Where qrcode does win, it wins on a real architectural merit: its pluggable rendering design is the only one built for styling.

Every persona’s decision was settled by rendering, footprint, symbology or default behavior. Not one was settled by encoding quality. That is the finding worth carrying out of this pass.

Who should NOT pick what#

Do not pick qrcodegen if you need a file. It produces no output of any kind — its QrCode object exposes a matrix and metadata, nothing more. This is the most common mismatch available in the category, and it is not a limitation that can be worked around cheaply: it is a renderer you now have to write and maintain. Also do not pick it if your payloads include Kanji or CJK text — the Python port 1.8.0, released 2022-04-17, has no make_kanji, whatever its sibling ports in other languages can do.

Do not pick pyStrich because it is described as pure Python. Its encoders are, but its installed metadata declares Pillow as an unconditional runtime dependency, so a pyStrich install carries the largest single package in the comparison — 22.95 MB against pyStrich’s own 0.72 MB — whether or not you ever ask for a raster image. Pick pyStrich for DXF output, which is unique to it here, or for its 2-D-plus-1-D symbology span. Do not pick it to save space.

Do not pick qrcode if the deployment is size-sensitive and the output is raster. Core qrcode is small at 0.14 MB, but PNG requires an extra: either pypng at 0.17 MB or Pillow at 22.95 MB, and most teams do not know which one their install pulled. If PNG is required and the image is tight, segno writes PNG natively with neither.

Do not pick segno without deciding about Micro QR. segno.make() with default arguments returns the smallest symbol including Micro QR variants — the probe saw M1 for 1234, M2-M for HELLO, and M4-M for a short URL. Consumer phone scanners support Micro QR far less reliably than standard QR. The behavior is documented and the fix is a documented one-call change to make_qr(), so this is not a defect in segno; it is the single highest-consequence thing a segno adopter can get wrong, and it belongs in a code review checklist rather than a README.

Do not pick PyQRCode for anything new. It has had no PyPI release since 2016-06-20 — its repository was pushed 2024-04-15, so it is not literally untouched, but no artifact has shipped in about a decade. It was the slowest encoder measured, at 10.264 ms per code. It has no Micro QR, no structured append, and no styling. And yet it recorded 628,262 downloads in the last month, which is the most striking single number in this survey: a decade-dead package still being installed at scale, largely by inertia and by transitive dependency rather than by anyone’s decision.

If you already hold PyQRCode, the retirement path is short. A compatibility shim is sometimes suggested as the migration route to segno; that package is not on PyPI — the index returns 404 as of 2026-08-17 — and exists only as a GitHub repository with one star, last pushed 2020-08-01. Do not take that route. Migrate to segno or qrcode directly on the strength of the convergence finding: for ordinary payloads, the symbol you get out the other side is the same symbol.

Do not pick anything in this set on the strength of the published 26-case correctness ranking — pyStrich 26, zxing-cpp 24, segno 24, qrcode 22 — without noting that the benchmark is published by pyStrich’s own maintainer and was not reproduced by this survey. Its methodology is described and its corpus is stated, so it is not to be dismissed; it is simply vendor-authored, and two of its findings that this survey did independently corroborate (zxing-cpp is fastest, qrcodegen has no file output) are the two safest to rely on.

Do not treat “ISO-compliant” as current in this category. ISO/IEC 18004:2024 is the fourth edition, published 2024-08, and it cancels and replaces the 2015 edition, which is withdrawn. No library in this survey claims conformance to the 2024 edition — segno’s own README still cites 18004:2015(E). Codes built to the older edition still scan and this is not an accusation of brokenness, but a reader with a genuine conformance obligation must check the edition rather than the word.

Handoff to S4#

S3 established who needs what. It leaves S4 the durability question, and this category makes that question unusually sharp: release cadence here is slow across the board — qrcode’s latest release is 2025-05-01, segno’s is 2025-03-12, and qrcodegen’s Python port is from 2022-04-17 — while the repositories behind them are more recently active. In most categories a stale release is a warning. In a category built on a frozen specification, it may be a sign that the work is finished. Distinguishing those two cases is S4’s job.


Use Case: The Brand and Marketing Engineer#

Who They Are#

A developer sitting close to a design function, whose job includes making a machine-readable square look like it belongs to a brand. They work with a design team that has a palette, a corner radius, and opinions. The codes they produce appear on a poster, a landing page, a conference badge, an ad, a menu, a product insert — places where a plain black-and-white square is not rejected because it fails to scan but because it looks like it was pasted in by accident.

Their day is a negotiation. A designer hands over a comp with the code rendered in brand colors, with rounded modules, a gradient, and the company mark sitting in the center. The engineer’s job is to make something close enough to that ship, and to be the person who says which parts of it are safe. They are also usually the person who gets the call when someone in the field says the code will not scan.

What Actually Hurts#

The library boundary is in the wrong place for this work. Most QR libraries treat rendering as an afterthought to encoding: they emit a correct symbol in a plain style and consider the job done. This persona needs the opposite emphasis — the encoding is trivially correct in every option, and all their remaining problems are rendering problems.

Every styling decision spends scannability margin, and the budget is invisible. Error correction exists to tolerate damage and occlusion, and styling consumes that tolerance: a logo covers modules, a gradient reduces contrast between light and dark, rounded modules soften the edges a decoder relies on. None of this produces a warning. The code either scans or it does not, and whether it does depends on the reading device, the lighting, the print quality and the distance — none of which the engineer controls. There is no compile-time error for “too stylish.”

The review loop is human and slow. Verifying a styled code means physically scanning it with several phones, at several sizes, under several lighting conditions. That loop does not automate away, and it is the reason this persona values a library that lets them iterate quickly on appearance.

A design system wants consistency across many codes. One hand-tuned code is craft. Forty codes across a campaign, all matching, all still scanning, is engineering — and it wants a rendering API that can be parameterised, not a one-off graphic.

Why Particular Options Fit#

This is the persona where qrcode’s design wins. Its rendering is built around pluggable image factories: the styling machinery is a composition of a module drawer and a color mask rather than a fixed set of options. The probe confirmed this works — a styled PNG produced with StyledPilImage combined with a rounded-module drawer and a radial-gradient color mask succeeded, writing 58,289 bytes for the same payload whose plain PNG was 815 bytes. Rounded modules and gradient colouring are therefore verified capabilities, not marketing copy.

That architecture is what matters more than any individual effect. A pluggable drawer/mask design means a brand’s look can be expressed once and applied consistently, and that an effect the library does not ship can be added without forking it. For a team maintaining a visual system across many codes, that is a different proposition from a library with a fixed styling menu.

qrcode also brings the largest community in the category by a wide margin — 29,511,144 downloads in the last month and 4,928 stars as of 2026-08-17 — which for this persona translates into the practical benefit that most styling questions have already been asked by somebody else. (PyPI download counts include CI runs, container rebuilds and transitive installs, so they measure diffusion rather than deliberate adoption; the ranking is still informative, the magnitude less so.)

segno’s answer to styling is a plugin. segno’s core is a standards-focused encoder with an unusually broad but plain renderer. Artistic output comes via qrcode-artistic (3.0.2, released 2023-11-27), a separate segno plugin. That is not a defect — it is a coherent design choice that keeps the core dependency-free — but for this persona it means the styling capability lives outside the maintained core and carries its own release cadence, and the most recent release of that plugin is older than the core library’s.

Nothing else in the set is a candidate. zxing-cpp emits a plain SVG and an in-memory image and is oriented toward correctness and speed, not appearance. pyStrich’s confirmed output surface is a set of technical formats. qrcodegen produces no rendered output at all. pyqrcode has shipped nothing since 2016.

Constraints and Trade-offs#

Choosing qrcode means paying the Pillow tax, unconditionally, for styled output. Styled rendering runs through Pillow, which measured 22.95 MB in the probe venv against qrcode’s own 0.14 MB. For a marketing site or an asset pipeline this is usually irrelevant — Pillow is often already present. For a tightly constrained deployment it is the single largest cost in the decision, and it is why this persona and the constrained-deployment persona point in opposite directions from the same starting question.

The styled output is large. 58,289 bytes for one styled PNG against 815 for the plain equivalent, on the probe payload. That is the expected cost of gradient raster output and is unremarkable on a web page, but noticeable once hundreds are embedded in a document or an email.

Logo embedding is the thing this survey could not verify, and it should not be faked. StyledPilImage.__init__ accepts only *args and **kwargs, so signature introspection could not confirm the parameter that carries a center image. This survey therefore makes no claim about how logo embedding is invoked in qrcode, and a reader who needs that feature must check the library’s own current documentation rather than trusting a parameter name copied from a survey or a blog post. What was confirmed is the styling architecture and two concrete effects: rounded modules and a radial gradient color mask.

Everything gained in appearance is spent from the scannability budget, and this persona owns that spend. Nothing in this survey quantifies how much margin a given effect consumes — that figure was not measured and is not stated here. What can be said is directional and reliable: contrast between light and dark modules is what a decoder needs, occlusion is what error correction absorbs, and both are finite. The practical discipline is to treat physical scan testing as part of the definition of done for a styled code, on more than one device, at the size it will actually appear.

A styled code is a worse candidate for size reduction. Where a plain code can be printed small because it is high-contrast and hard-edged, a styled one usually cannot. Styling and small physical size pull against each other, and this persona frequently has to tell a designer that the code cannot be both a subtle accent and readable at that size.

Decision Criteria — Where the Crossover Is#

If styled output is a requirement rather than a wish, qrcode is the default in this set. It is the only option whose rendering architecture was built for the job, and the probe confirmed non-trivial styling works.

The crossover away from qrcode is when styling turns out to be a wish. A surprising number of projects that start with a brief for a branded code end up shipping black on white, because the field testing came back mixed or because the code has to appear at a small physical size. If that happens, the decision should be revisited rather than inherited: with styling out of scope, this persona’s requirements collapse into the print persona’s, and segno’s dependency-free thirteen-format renderer becomes the better fit for what remains.

The crossover toward segno-plus-plugin is when the same codebase already needs segno’s other properties — vector breadth, Micro QR, structured append, or a near-zero footprint — and styling is a secondary requirement. Adding qrcode-artistic to an existing segno install is a smaller change than running two QR libraries side by side, and running two is a real outcome worth avoiding if a single one can carry both jobs adequately.

The crossover toward two libraries is legitimate when the two jobs are separate — a marketing surface that needs branded raster codes and a production pipeline that needs plain vector at volume are different systems with different constraints, and forcing both through one library to satisfy a tidiness preference is how one of them ends up compromised.

One criterion outranks all of the above: if the code cannot be scan-tested before it ships, styling should be reduced, whatever the library. A code printed on a run of ten thousand cartons that fails on a common phone model is not a rendering bug that can be patched. That risk, not the library choice, is this persona’s real exposure.


Use Case: The Constrained-Deployment Engineer#

Who They Are#

Someone who ships Python into an environment where the size of the artifact is a first-class concern. Several distinct roles land here and share the same arithmetic:

  • A developer packaging a serverless function, where the deployment bundle has a hard ceiling and cold-start time scales with what has to be unpacked and imported.
  • A platform engineer maintaining container images that are built dozens of times a day in CI and pulled onto many nodes, where every megabyte is multiplied by build frequency and fleet size.
  • Someone shipping to an edge device or an appliance with a fixed flash budget.
  • Someone whose organization runs a dependency review — a supply-chain policy, an SBOM obligation, a vendor questionnaire — where each new transitive package is a line item that a human has to justify.

Their day includes staring at layer sizes, arguing about base images, and answering the question “why does this service need an imaging library” from somebody who is entitled to ask.

What Actually Hurts#

The cost is almost never the QR library. That is the whole finding for this persona. Every QR library in this comparison is small. What is not small is what some of them pull in behind them, and the person adding “generate a QR code” to a service is rarely the person who notices that the image grew by an order of magnitude more than the feature warranted.

Dependency count is a governance cost, not just a size cost. In a review-heavy organization, adding one package with three transitive dependencies is meaningfully more expensive than adding one package with none — not in megabytes but in paperwork, in CVE surface to monitor, and in the number of upstream projects whose maintenance status somebody now has to track.

“Pure Python” is a claim people trust without checking. It sounds like it means “no dependencies,” and it frequently does not. This persona has been burned by that gap before and reads the metadata rather than the README.

Cold start is measured on the unhappy path. An import that adds a fraction of a second is invisible in a warm process and very visible on the first request after a scale-up event, which is exactly the request a user is waiting on.

The Verified Footprints#

Measured as the sum of installed distribution files in the probe venv on 2026-08-17, CPython 3.12.3:

DistributionMB
qrcodegen0.04
PyQRCode0.12
qrcode0.14
pypng0.17
segno0.30
pyStrich0.72
zxing-cpp1.89
Pillow22.95

And the declared dependencies, read from installed distribution metadata rather than from documentation:

  • segno declares importlib-metadata only when the Python version is below 3.10. On any modern Python it is effectively zero-dependency.
  • qrcodegen declares none at all.
  • zxing-cpp declares none — it ships a compiled extension instead.
  • qrcode declares colorama on win32 only; Pillow and pypng are optional extras. The core install has no non-Windows runtime dependency, but PNG output requires one of those extras.
  • PyQRCode declares pypng under a png extra.
  • pyStrich declares Pillow unconditionally, plus typing-extensions.

That last line is the trap this persona most needs. pyStrich’s own repository description calls it “a fast pure-Python module,” and its encoders are indeed pure Python — but the installed metadata makes Pillow a hard requirement, not an extra. A team that adopts pyStrich expecting a dependency-free install gets the single largest package in the comparison, and gets it whether or not they ever ask for a raster image.

Why Particular Options Fit#

segno is the strongest general answer for this persona. 0.30 MB, effectively no dependencies on a modern interpreter, and — decisively — it writes PNG, SVG, EPS, PDF and nine other formats natively, with no imaging stack behind any of them. That combination is unusual: most ways of getting raster output in Python route through Pillow, and segno simply does not. It means a service can produce both a bitmap and a vector artifact for roughly a third of a megabyte, total.

qrcodegen is the smallest thing that could possibly work, at 0.04 MB with no dependencies. But it produces no file output at all — it exposes a matrix and nothing else. Adopting it as a size play means committing to write and maintain a renderer, which is real code with real bugs. For a persona whose whole motivation is reducing what they own, that is often a bad trade; for a persona that only needs the matrix (writing modules into an existing canvas, an existing PDF, or an existing drawing surface) it is exactly right.

qrcode is fine if — and only if — the output is SVG. Core qrcode is 0.14 MB with no non-Windows runtime dependency, which is small. Its SVG path needs nothing extra. The moment PNG is required, the install acquires either Pillow at 22.95 MB or pypng at 0.17 MB via extras, and which of those two it acquires is a configuration decision most teams make by accident. A constrained deployment on qrcode should know which extra it installed and why.

zxing-cpp at 1.89 MB with no declared dependencies is a reasonable middle, and it is by far the fastest encoder measured (0.189 ms per code, matrix construction only, CPython 3.12.3, one machine, single payload). Its cost to this persona is not size but nature: a compiled extension means platform-specific wheels, and a supply-chain reviewer treats compiled code differently from pure Python.

Constraints and Trade-offs#

Choosing segno means accepting a plain renderer. No styling in core; branded output requires the qrcode-artistic plugin (3.0.2, released 2023-11-27), which is an additional dependency and therefore reopens exactly the question this persona is trying to close. A constrained deployment that also needs branded codes should expect to pay for one or the other.

Choosing segno also means handling its default symbol type deliberately. segno.make() returns the smallest symbol including Micro QR variants — probed as M1 for 1234 and M4-M for a short URL. In a small service that emits codes for consumer phones, this is the highest-consequence mistake available, and it costs nothing to avoid by forcing a standard symbol. The behavior is documented; the default is simply surprising.

Choosing qrcodegen means owning a renderer. Also worth weighing: its most recent PyPI release, 1.8.0, is dated 2022-04-17, and its Python port has no make_kanjiQrSegment exposes numeric, alphanumeric, bytes, ECI and segment composition only. If the payloads are ASCII URLs and identifiers, neither of those matters. If they might not be, the 0.04 MB is not the relevant number.

Choosing pyStrich in a size-constrained deployment is usually a mistake, and specifically so if it was chosen because it is “pure Python.” 0.72 MB of pyStrich plus 22.95 MB of Pillow is a worse footprint than any alternative here that does the same job. There are excellent reasons to run pyStrich — DXF output, breadth of symbologies — but small footprint is not one of them, and this is the persona most likely to adopt it for a reason that is not true.

Nobody here should be on PyQRCode. 0.12 MB is attractive and nothing else about it is: no PyPI release since 2016-06-20, and the slowest encoder measured at 10.264 ms per code. Its 628,262 downloads in the last month are the most striking number in the whole survey — a package with no shipped artifact in about a decade, still being installed at scale — and a dependency review is exactly the process that ought to catch it. (PyPI counts include CI and transitive installs, so this measures diffusion rather than deliberate adoption, which if anything makes the point sharper: much of that volume is nobody’s decision.)

A note on third-party install-size figures. A published benchmark reports install sizes of segno 9.2 MB, qrcode 30.3 MB, pyStrich 31.8 MB, zxing-cpp 31.9 MB and treepoem 101.5 MB. Those are much larger than the figures above because they measure a different thing — a whole environment rather than the sum of distribution files — and that benchmark is published by pyStrich’s own maintainer. The two sets of numbers are not comparable and should not be mixed. The ordering they agree on is the part to trust: segno small, anything Pillow-backed large.

Decision Criteria — Where the Crossover Is#

The single crossover that decides this persona is the output format.

  • If SVG (or any of segno’s text-based formats) is acceptable as the only output, the Pillow tax disappears entirely and this becomes a very cheap feature. segno at 0.30 MB with no dependencies does the whole job. So does qrcode’s SVG path at 0.14 MB, at the cost of a much larger SVG for the same symbol. Either way, the service does not acquire an imaging stack, and the dependency review is a short conversation.

  • If PNG is required, the question becomes how the PNG is produced. segno writes PNG natively and remains at 0.30 MB. qrcode with the png extra reaches PNG via pypng for 0.17 MB more. qrcode with the pil extra, or pyStrich in any configuration, costs 22.95 MB. Those are three very different answers to the same requirement, and the difference is roughly two orders of magnitude.

  • If Pillow is already in the image for other reasons, this entire analysis collapses and the persona should choose on other grounds. The tax is paid; qrcode’s ecosystem and styling become free benefits. Check before optimising, because plenty of services already carry Pillow and their engineers do not know it.

The secondary crossover is between segno and qrcodegen, and it is about ownership rather than size. The gap between them, 0.26 MB, is not worth a hand-written renderer for almost anybody. qrcodegen wins only where the consuming code already has a drawing surface and wants a matrix — in which case that is the correct shape of dependency, not a compromise.

The crossover toward zxing-cpp is speed under a size budget, not size itself. At 1.89 MB it is the largest pure-install option here but still an order of magnitude below Pillow, and it is roughly seventeen times faster than the fastest pure-Python encoder. A constrained deployment that is also latency-sensitive can have both; what it cannot have is a supply chain that refuses compiled artifacts.


Use Case: The High-Volume Code Issuer#

Who They Are#

An engineer responsible for a service or a scheduled job that mints a unique QR code per record. The shape varies — a ticketing backend issuing one code per seat, an asset-tracking system stamping one per inventory item, a nightly job that regenerates codes for every active row in a table, an API endpoint that returns a code on demand and is called by something else in a loop. What unites them is that the code is not a design artifact, it is a field in a record, and there are a great many records.

Their day is spent on the boring parts of throughput: watching p99 latency on an endpoint, sizing a worker pool, arguing about whether a batch that has to finish before the warehouse opens will actually finish. Nobody in their organization thinks about the QR library at all until the day it becomes the reason a job runs long. It is a component chosen once and then expected to disappear.

What Actually Hurts#

Three things, in roughly this order.

Per-code cost multiplied by a large number. A few milliseconds is nothing until it is a few milliseconds a million times, at which point it is an hour of wall-clock and a decision about how many machines to rent. This persona is one of the very few in this survey for whom raw encoding speed is a genuine requirement rather than a benchmark curiosity.

Predictability, not just average speed. A batch that usually takes forty minutes and occasionally takes four hours is worse than one that always takes an hour. They care about variance across payload sizes, about memory that does not grow with the run, and about a library that behaves the same on the ten thousandth call as on the first.

Raster output at scale. Most of these systems emit PNG, because PNG is what gets embedded in a PDF, attached to an email, or served to a phone. That means the rendering step is part of the per-code cost, and in this category the rendering path is frequently a bigger deal than the encoding path.

The Verified Numbers, With Their Conditions#

The probe ran 1,000 encodes of the same URL payload, matrix construction only, no file I/O, on CPython 3.12.3 on a single machine (Linux under WSL2), reported as a simple mean:

Libraryms per code
zxing-cpp0.189
segno3.249
qrcode4.048
qrcodegen7.209
pyqrcode10.264

Read those conditions carefully before acting on them, because they constrain what the numbers mean. One payload, one machine, one interpreter version, and — critically — no file writing. A production issuer almost always writes something. The measurement isolates the encoder so the encoder can be compared; it does not predict end-to-end throughput for a pipeline that also serialises a PNG.

What the numbers do support: zxing-cpp is roughly 17 times faster than the fastest pure-Python option (segno) and roughly 54 times faster than pyqrcode on this workload. Among the pure-Python encoders there is about a 3.2-fold spread from segno at the fast end to pyqrcode at the slow end, with qrcode close behind segno and qrcodegen noticeably slower than both.

The gap between zxing-cpp and everything else is not a tuning difference. It is the difference between a compiled extension and a Python loop, and no amount of micro-optimization inside a pure-Python encoder closes an order of magnitude and a half.

A third-party benchmark reports figures in the same direction, measuring zxing-cpp at 0.55 to 14 ms per PNG and pyStrich at 1.1 to 61 ms across its corpus. That benchmark is published by pyStrich’s own maintainer and must be read as vendor-authored; its speed ranking of zxing-cpp first is, however, independently corroborated by the probe above, so this persona can rely on the direction of the result if not on the specific figures.

Why Particular Options Fit#

zxing-cpp is the option that exists for this persona. It is the only member of the set with a compiled encoder, it measured fastest by a wide margin, it declares no runtime dependencies of its own (it ships the compiled extension), and it measured 1.89 MB installed. It produces both SVG and an in-memory image object, so a high-volume raster path does not have to route through Pillow at all. For a service where code generation is a measurable line item in the latency budget, this is the answer and the others are compromises.

segno is the right pure-Python fallback. It was the fastest of the four pure Python encoders in the probe, it measured 0.30 MB installed, and it is effectively dependency-free on any modern Python — its only declared dependency applies below Python 3.10. It writes PNG directly, without Pillow, which matters enormously to this persona: it means the raster path is one library rather than two, and the container does not carry a 22.95 MB imaging stack purely to serve black squares.

qrcode is defensible if it is already in the stack. It measured 4.048 ms, close enough to segno that the difference will rarely decide anything on its own, and it is by an enormous margin the most widely installed option (29,511,144 downloads in the last month as of 2026-08-17, against segno’s 4,177,918 — a ratio of roughly seven to one). But its PNG path goes through Pillow, which drags in the largest single component in the whole comparison.

Constraints and Trade-offs#

Choosing zxing-cpp means accepting a compiled dependency. That is a real cost, not a formality. It means wheels for every platform the service deploys to, a build path when a wheel is missing, and a component that a security review will treat differently from pure Python. It also means the library is a barcode toolkit rather than a QR library — a larger surface than the job strictly needs.

Choosing segno means accepting that the ceiling is where it is. A 3.249 ms encoder is a 3.249 ms encoder; at large volumes the only remaining lever is parallelism, and parallelism costs machines. It also means being deliberate about the Micro QR default: segno.make() chooses the smallest symbol including Micro QR variants, and an issuer whose codes are scanned by consumer phones should be forcing a standard symbol rather than letting short payloads silently become M1 or M2 symbols across a whole batch. This is documented behavior with a documented fix, but it is exactly the kind of thing that ships unnoticed and is discovered by a customer.

Choosing qrcode means paying the Pillow tax on every deployment. Pillow measured 22.95 MB against qrcode’s own 0.14 MB. If the service already processes images, that cost is already paid and the argument evaporates. If it does not, this is a large dependency acquired to draw squares.

pyqrcode is not a candidate here. It was the slowest encoder measured, at 10.264 ms, and it has had no PyPI release since 2016-06-20 — despite which it was still installed 628,262 times in the last month. A high-volume issuer running on it is paying roughly a threefold speed penalty against segno for no compensating benefit.

qrcodegen is not a candidate here either, for a reason unrelated to its 7.209 ms: it produces no file output at all. It exposes a matrix and nothing else. An issuer choosing it is signing up to write and maintain their own renderer, which for this persona is scope they did not ask for.

Decision Criteria — Where the Crossover Is#

The question this persona has to answer is: at what volume does a compiled dependency stop being worth avoiding?

Frame it in wall-clock rather than in principle. On the probe figures, the difference between segno and zxing-cpp is roughly 3.06 ms per code, encoder only. That is about three seconds per thousand codes, about fifty minutes per million.

  • Below roughly a thousand codes per batch or per request-second, the difference is invisible next to database round-trips, serialization and network. Take the pure-Python option. The operational simplicity of a zero-dependency, one-wheel install is worth more than three seconds. This is where most readers who think they are high-volume actually sit.

  • In the middle band — tens to hundreds of thousands of codes in a run — the answer is decided by what else is in the loop. If the job is already I/O-dominated, the encoder is noise and segno wins on simplicity. If the job is CPU-bound and encoding is a visible fraction of the profile, measure it on your own payloads before deciding; the probe used one URL, and your payload mix is not that.

  • Above roughly a million codes per run, or wherever encoding shows up in a latency budget with a hard ceiling, the compiled option earns its keep. Fifty minutes of saved wall-clock per million codes buys back the wheel management and the security-review conversation, and it does so repeatedly.

Two secondary crossovers are worth naming because they cut across the volume question.

If raster output is required and Pillow is not already present, segno’s direct PNG writing is worth more to this persona than qrcode’s larger ecosystem. The 22.95 MB is paid at every image build and every cold start, not once.

If the codes are consumer-scanned, correctness of symbol type outranks speed entirely. A batch of a million codes that are all Micro QR because the default was left alone is a far more expensive failure than a batch that took an extra hour to produce.


Use Case: The Industrial and Logistics Engineer#

Who They Are#

Someone marking physical things so that other machines can identify them later: components on a line, totes moving through a warehouse, cartons entering a distribution network, instruments and tools that must be traced through a service history, printed documents that a scanner has to route. Their codes are read by fixed-mount scanners, handheld terminals, and vision systems far more often than by phones.

Their day is governed by other people’s requirements. A customer’s receiving system dictates a symbology. A trading-partner standard dictates the data structure inside the symbol. A part’s available marking area — a few square millimetres on a connector housing — dictates the symbol size, and therefore sometimes the symbology. They rarely get to choose the format; they get to implement it.

What Actually Hurts#

QR is often not the requirement. It is one symbology among several, and the one the requirement lands on is decided elsewhere. Data Matrix dominates direct part marking because it survives etching onto small metal surfaces. PDF417 shows up on identity documents and shipping labels. Aztec appears in transit ticketing. The 1-D family persists everywhere legacy scanners do. An engineer in this role who adopts a QR-only library will be back within a year.

Physical space runs out before data does. The most acute version of this persona’s problem is a part too small for a standard QR symbol. The QR family’s answers are Micro QR — four sizes, M1 through M4 — and rMQR, a later rectangular variant designed for narrow strips where a square will not fit. Both are answers to a physical constraint, not a preference, and if the requirement names one, no amount of tuning a standard symbol substitutes for it.

Data structure matters as much as the symbology. GS1 conventions govern how identifiers are laid out inside a symbol so that a receiving system can parse them. “Data Matrix” and “GS1 Data Matrix” are not interchangeable to the system on the other end, and a library that produces the former when the requirement says the latter produces a symbol that scans and is then rejected.

Verification is part of the job. In print-and-marketing work, a code that does not scan is embarrassing. Here, a code that does not scan stops a line or strands a pallet. This persona routinely needs to read a code back — to check what was actually encoded, to validate a marking process, to build an automated gate that rejects bad symbols before product ships. Generation alone is half a solution.

Why Particular Options Fit#

zxing-cpp is the only member of the set that can create both Micro QR and rMQR. The probe confirmed that BarcodeFormat.MicroQRCode and BarcodeFormat.RMQRCode both exist and both appear in its creatable set, and that creating each succeeded — the Micro QR at error-correction level L, the rMQR at H. For an engineer whose requirement names rMQR, that is the end of the evaluation: nothing else here does it.

zxing-cpp is also the only option in this set that reads codes, which speaks directly to the verification need. In the round-trip probe it read back the PNG output of every other library in the comparison — segno, qrcode, pyqrcode and pyStrich — byte-identically. A team that both marks and verifies can do both with one dependency, which is a meaningful reduction in the number of things that can disagree with each other. It is also comfortably the fastest encoder measured, at 0.189 ms per code (matrix construction only, single payload, CPython 3.12.3, one machine), which matters on a line running at rate.

segno covers Micro QR but not rMQR. The probe confirmed that segno.make("12345", micro=True) yields an M1 symbol of 15 by 15 modules, and confirmed the absence of any rMQR or rectangular symbol support in segno.make’s signature or module namespace. So segno is a genuine option for the small-symbol case right up until the requirement says rectangular, at which point it stops being one.

segno brings two other things this persona uses. Structured Append works — the probe split a 600-character payload into four symbols, all version 7 — which is how an over-long payload is carried across multiple physical symbols when a single one will not fit the marking area. And its thirteen output formats include EPS and PDF, which are the formats label and marking systems tend to want.

pyStrich is the breadth play, and in pure Python. Its repository description states support for Code 39, Code 128 including GS1-128, EAN-13, ITF-14 and Interleaved 2 of 5, Data Matrix including GS1 Data Matrix, QR Code, PDF417 and Aztec, with PNG, SVG, EPS, DXF and terminal outputs. That is the widest symbology span in this comparison and the only one that reaches across both 2-D and 1-D families — which matches this persona’s reality, where a single site often runs several symbologies for different purposes. Its DXF output is unique in this set and connects directly to engraving and direct-part-marking workflows. Its QR PNG output was independently decoded back correctly in the probe, from both its file-saving path and its in-memory image.

qrcode, qrcodegen and pyqrcode are out of scope for this persona. All three are QR-only. None offers Micro QR or rMQR. qrcodegen additionally produces no file output. This is not a criticism — a QR library that does QR well is a reasonable thing to be — but it is a mismatch with a requirement that names three symbologies before lunch.

Constraints and Trade-offs#

Choosing zxing-cpp means a compiled dependency in an industrial stack. At 1.89 MB installed with no declared Python dependencies it is not heavy, but it is a native extension: platform wheels, a build path when one is missing, and a different conversation with whoever signs off on the software running near the line. In environments with long-lived, tightly-controlled images this is a real consideration and not a formality. Against that, it is under active development — release 3.1.1 dated 2026-07-29, repository pushed 2026-08-13 — which for industrial use cuts both ways: alive is good, and change is something a validated system has to manage.

Choosing zxing-cpp also means accepting a general barcode toolkit rather than a QR library. Its surface is larger than a QR-only need requires. For this persona that is the point; for anyone else it is overhead.

Choosing segno means accepting a hard boundary at rMQR, and it means being deliberate about symbol type in the other direction too. segno.make() selects the smallest symbol including Micro QR variants — the probe saw M1, M2-M and M4-M for short payloads — so a system that intends standard QR can silently emit Micro QR, which fixed-mount scanners and consumer devices support unevenly. The fix is documented (make_qr() forces a standard symbol); the risk is that in an industrial pipeline the mistake is discovered downstream, on hardware, after product has moved.

Choosing pyStrich means weighing project scale against production criticality. It is small by community signal — 106 stars, 76,806 downloads in the last month as of 2026-08-17 — though actively worked on, with release 0.19 dated 2026-07-23 and its repository pushed 2026-08-17. It also declares Pillow as an unconditional runtime dependency despite its “pure Python” description, which puts about 23 MB behind it. Its ancestry is longer than its star count suggests: it descends from huBarcode, with contributions dating from around 2007 to 2013 and stewardship by Method B Ltd from 2015.

And pyStrich’s headline correctness claim must be read carefully. A published 26-case benchmark ranks pyStrich first at 26 of 26, ahead of zxing-cpp and segno at 24 and qrcode at 22 — but that benchmark is published by pyStrich’s own maintainer, and this survey did not reproduce its corpus. It is a vendor-authored claim with a described methodology, not an independent finding, and this persona in particular should not select on it without testing against their own payloads.

A conformance caveat that lands hardest here. No library in this survey claims conformance to the current edition of the QR standard. ISO/IEC 18004:2024 is the fourth edition, published 2024-08, and it cancels and replaces ISO/IEC 18004:2015, which is withdrawn. segno’s own published README still states conformance to 18004:2015(E); qrcodegen’s README claims conformance to the QR Code Model 2 standard. That is not an accusation of brokenness — QR symbol geometry is stable and codes built to the 2015 edition still scan — but a reader carrying a genuine contractual or regulatory conformance obligation must check the edition rather than the word “compliant,” and this is the persona most likely to have such an obligation in writing.

Decision Criteria — Where the Crossover Is#

If the requirement names rMQR, the decision is made: zxing-cpp. It is the only option here that creates one.

If the requirement names Micro QR and nothing else exotic, the choice is between zxing-cpp and segno, and it turns on whether a compiled dependency is acceptable. If it is, zxing-cpp brings decoding and speed as well. If it is not — a pure-Python constraint, an environment where wheels are painful — segno covers the Micro QR case cleanly and stays dependency-free.

If the requirement spans several 2-D symbologies plus 1-D, pyStrich is the option that covers the span in one dependency. The alternative is running several libraries, which is a defensible answer but multiplies the number of projects whose maintenance status has to be tracked.

If verification — reading codes back — is part of the deliverable, zxing-cpp enters the decision regardless of what else was chosen for generation. It is the only reader in the set. Note that this can be an addition rather than a replacement: generating with segno or pyStrich and verifying with zxing-cpp is a coherent architecture, and arguably a better one, since an independent decoder checking an encoder’s output is a stronger test than a library validating itself.

If none of the above applies and the job really is only QR, this persona has mis-scoped itself and should read the other profiles in this pass — most likely the print-and-packaging one, whose requirements around vector output and physical module size overlap heavily with industrial marking.


Use Case: The Polyglot Platform Team#

Who They Are#

A team that has to produce the same symbol from more than one runtime. The situation arises in a few recognisable ways: a backend in one language and a mobile or web client in another, both of which render codes; a Python service that is gradually being rewritten in something else, with both versions live during the transition; a platform group that owns a shared capability and has to expose it to teams working in different stacks; or a compliance-adjacent situation where one system’s output must be checked against another’s and the two are not written in the same language.

Their day is spent on consistency problems that nobody else in the organization finds interesting. Their instinct — a good one, generally — is to reduce the number of independent implementations of anything that has to agree, because every independent implementation is a place where two systems can diverge and nobody notices for six months.

What Actually Hurts#

Divergence is invisible until it is expensive. Two services generating codes for the same data with two different libraries will agree almost all the time. The failure mode is not “wrong output” but “occasionally different output” — a different symbol version, a different mask, a different mode selection for an edge-case payload — and the discovery mechanism is usually a support ticket rather than a test.

Test fixtures do not travel. If the Python service and the Kotlin client are expected to produce identical symbols, a fixture generated by one is only a valid assertion for the other if the two implementations agree. Teams routinely discover that their golden files encode one library’s choices rather than the standard’s requirements.

Reasoning about behavior has to be done once per language. Every additional implementation is another set of defaults, another set of documentation, another maintainer, another release cadence, and another set of quirks that somebody has to learn before they can debug an incident at eleven at night.

Why qrcodegen Exists For This#

Nayuki’s QR-Code-generator is described by its repository as a “High-quality QR Code generator library in Java, TypeScript/JavaScript, Python, Rust, C++, C.” That is the entire proposition and it is a real one: one project, one design, six language ports, one place where the behavior was decided. For a team that needs the same logic in a JVM backend and a browser client and a Python job, that is a materially different offer from assembling three unrelated libraries chosen by three different teams on three different days.

It is also the most-starred repository in this comparison at 6,717 stars as of 2026-08-17, which reflects the multi-language project rather than the Python package specifically — the Python distribution recorded 12,853 downloads in the last month, the smallest figure in the set by a wide margin. Those two numbers tell the story together: a well-regarded reference implementation whose Python port is a minor tributary of its overall use.

The library’s README claims conformance to the QR Code Model 2 standard, support for all forty versions and four error-correction levels, manual and automatic mask selection, error-level boosting, and ECI segments. The probe corroborated the ECI part directly: QrSegment exposes make_eci alongside make_alphanumeric, make_bytes, make_numeric and make_segments.

The Costs, Stated Plainly#

No file output at all. This is the largest single fact about qrcodegen and it is easy to under-weight. Its QrCode object exposes get_size, get_version, get_module, get_mask and get_error_correction_level. That is the whole surface. There is no PNG, no SVG, no anything — a caller gets a matrix and writes their own renderer.

For this persona that is not automatically disqualifying. If each runtime already has its own drawing surface — a canvas in the browser, a graphics context on mobile, an existing document generator on the server — then a matrix is arguably the right interface, and a file-writing library would be delivering something each platform then has to undo. The cost lands elsewhere: a renderer written per platform is exactly the thing this persona was trying to avoid duplicating. Consistency of the encoder is bought and consistency of the output is given back.

The Python port is not the full library. The probe found that qrcodegen 1.8.0 for Python has no make_kanjiQrSegment offers numeric, alphanumeric, bytes, ECI and segment composition, and nothing for Kanji mode. This directly corrects segno’s own published comparison table, which lists qrcodegen as supporting Kanji: true of the Java and C++ ports, not of the port that pip install qrcodegen delivers. That is a sharp warning for this persona specifically, because “one implementation everywhere” is only true to the extent the ports actually match, and here they demonstrably do not on at least one feature. Any team relying on cross-port identity should verify the specific features they use, per port, rather than assuming the shared repository implies shared capability. (qrcodegen’s own README makes no Kanji claim, which is consistent with the probe — the error is in the third-party table, not in the project’s own documentation.)

The Python release is old. qrcodegen 1.8.0 was released 2022-04-17. The repository was last pushed 2025-01-23. Both facts should be read together: the project is not abandoned, but the Python artifact a team installs today is several years old. Slow release cadence is not unique here — qrcode’s most recent release is 2025-05-01 and segno’s is 2025-03-12, in both cases well behind their repositories’ latest activity. This category as a whole ships infrequently, which is unsurprising for a frozen symbology.

It is the slowest of the pure-Python encoders except pyqrcode, measured at 7.209 ms per code against segno’s 3.249 and qrcode’s 4.048 (matrix construction only, single payload, CPython 3.12.3, one machine). If the multi-language argument is the reason for adoption, this is a price worth knowing about rather than a reason to reject.

The Finding That Weakens the Argument#

Here is the uncomfortable part, and it belongs in this file rather than buried in a footnote.

The probe encoded the payload https://example.com/a-moderately-long-url?id=12345&ref=survey at error correction M in all six libraries and got the same 33-module, version-4 symbol from every one of them — segno via make_qr, qrcode, qrcodegen, pyqrcode, pyStrich and zxing-cpp alike. On a 200-digit numeric payload, all four pure-Python encoders selected version 5. And a round-trip check found every library’s PNG decoded back byte-identically through an independent decoder.

So the premise “we need one implementation everywhere or our services will disagree” is, for ordinary payloads, largely not true. QR is a tightly specified symbology and the mainstream implementations converge because there is very little room not to. A team that adopts qrcodegen purely to guarantee agreement is paying a real cost — no file output, a stale Python release, a missing mode in the Python port, a slower encoder — to buy a guarantee they mostly already had.

Two caveats on that finding, in both directions. It was measured on ordinary numeric and byte payloads; it says nothing about exotic edge cases, and one probe row on a Kanji payload was explicitly not comparable because two of the libraries had made different mode and symbol-type choices. And symbol version agreement is not the same as byte-identical image agreement — mask selection, module rendering and quiet-zone handling are separate questions that this probe did not compare across libraries. A team whose requirement is a pixel-identical artifact from two runtimes has a harder problem than the one this finding closes.

Decision Criteria — Where the Crossover Is#

If the requirement is that ordinary URLs and identifiers encode consistently across runtimes, that requirement is probably already met by whatever good library each platform prefers. Verify it with a shared fixture corpus of your own real payloads rather than adopting a shared implementation on faith. This is the cheaper answer and it is the right one more often than teams expect.

qrcodegen earns its place when at least two of these are true: more than two runtimes need the capability; each runtime already has its own drawing surface, so a matrix is the interface you actually want; the payloads are ASCII-ish, so the missing Kanji mode in the Python port is irrelevant; and having one project to reason about is worth more than having file output handed to you.

The crossover away from qrcodegen is any of these: you need a file out of the Python side and do not want to own a renderer; your payloads include Kanji or CJK text, where the Python port simply cannot do what its sibling ports can; you need Micro QR, rMQR, or structured append, none of which it offers; or speed matters, where it is roughly twice as slow as segno and roughly thirty-eight times slower than zxing-cpp on the probe workload.

There is a third path worth naming, because it is often the best one: use the right library in each runtime and enforce agreement with a cross-language test corpus rather than with a shared implementation. Generate a set of representative payloads, encode them everywhere, decode them with an independent decoder, and assert on the decoded content and the symbol version. That gets the guarantee the persona actually wants — the systems agree, and you find out in CI when they stop agreeing — without constraining every platform to one project’s release schedule or accepting a port that is missing a mode. zxing-cpp is a natural verifier in that arrangement, since it reads codes and read back every library’s output correctly in the probe.

And if the polyglot requirement turns out to be aspirational — one runtime today, another “eventually” — choose on the needs you have now. The convergence finding means a future migration to a different library is far less risky in this category than in most, because the artifact the two libraries produce is, for ordinary payloads, the same artifact.


Use Case: The Print and Packaging Engineer#

Who They Are#

Someone whose output ends up on a physical surface produced by a machine that is not a screen. The variants are many: a packaging engineer placing a code on a carton that will be flexo-printed on a run of two hundred thousand; a prepress-adjacent developer generating artwork that goes to a commercial printer as PDF or EPS; a labels developer feeding thermal transfer printers; someone producing files for a laser engraver or a CNC that will cut or etch a code into metal, acrylic, or wood.

Their day includes conversations that most software engineers never have: whether the substrate holds ink at that resolution, what the press tolerance is, whether the code survives being printed on a curved surface, and what happens when a customer’s phone camera meets a code at a slightly wrong angle in bad light. They think in millimetres and in dots per inch, not in pixels. They send files to a third party who will not send them back for a revision cheaply.

What Actually Hurts#

Raster output is the wrong artifact and quietly ruins work. A PNG has a fixed pixel grid. Sent to a press or a plotter, it gets resampled to the output device, and QR modules — which are hard-edged squares whose entire function is to have unambiguous edges — are exactly the geometry that resampling damages. The symptoms are soft edges, unequal module widths, and a code that scans on the proof and fails on the shelf. Every engineer in this persona has either lived through this or heard the story.

Vector output that is technically vector but practically unusable. Emitting one drawing primitive per module produces a file that is legitimately SVG and legitimately enormous, and that lands badly downstream — slow to open in illustration software, slow to RIP, awkward to place in a layout where dozens of codes appear on one imposition sheet.

Quiet zone and module size are physical facts, not styling. The specification requires a quiet zone of four modules around a standard symbol. On screen, a generous margin is a nicety; on a carton where the code sits three millimetres from a fold, it is the difference between scanning and not. Similarly, module size is dictated by what the printing process can actually resolve, and it is set in physical units, not in “box size” pixels.

The file has to survive a handoff. It goes to a printer, a co-packer, or a fabrication shop, opened in software the engineer does not control. Formats that are boring and universal — EPS, PDF, plain SVG, DXF — are worth more here than formats that are clever.

Why Particular Options Fit#

segno is the strongest fit in this set, and the reason is its renderer, not its encoder. From a single symbol object, the probe successfully wrote thirteen formats with zero failures and no third-party dependency: PNG, SVG, SVGZ, EPS, PDF, TXT, ANS, PAM, PPM, XBM, XPM, TEX and PBM. Three of those — SVG, EPS and PDF — are the currency of print handoff, and getting all three from one library with no imaging stack behind it removes an entire class of integration problem.

The byte-efficiency finding matters more than it first appears. For the same payload, segno’s SVG measured 1,934 bytes and qrcode’s measured 30,816 bytes — roughly a sixteen-fold difference, attributable to segno using a path-based emitter where qrcode emits one <rect> element per module. On a single code this is trivia. On an imposition sheet carrying many codes, or in a document template that embeds a code per page across a long document, it is the difference between a file that opens instantly and one that makes the prepress operator wait. It is also, incidentally, why segno’s compressed SVGZ measured 527 bytes: the path-based output compresses well because it is small to begin with.

pyStrich is the option for fabrication rather than print, and it is unique in this set for exactly one reason: it produces DXF. No other library here does. DXF is the interchange format of CAD and of laser and CNC workflows, and a developer who needs a code as vector geometry inside a drawing — to be etched, cut, or engraved — has effectively one choice in this comparison. Its confirmed output surface also includes EPS and SVG alongside PNG, so it is not a DXF-only tool. Its repository was last pushed 2026-08-17 and its current release, 0.19, is dated 2026-07-23, so it is actively worked on.

qrcode is usable for print but is the weakest of the three on this axis. It does emit SVG. It is also, by a very large margin, the most widely installed option in the category. But its per-module <rect> emitter produces the bulky vector output described above, and its PNG path requires Pillow, which measured 22.95 MB — a substantial dependency to acquire in a workflow whose real output format is vector.

Constraints and Trade-offs#

Choosing segno means giving up styling in core. This persona usually does not care — a code destined for a carton is black on white because that is what scans reliably at press tolerances — but if the same codebase also has to produce a branded code for a campaign, segno’s answer is a separate plugin (qrcode-artistic, version 3.0.2 dated 2023-11-27) rather than a core feature. Adopting segno for print and reaching for something else for marketing is a perfectly reasonable outcome, but it should be a decision, not a surprise.

Choosing segno also means handling the Micro QR default deliberately. With default arguments, segno.make() returns the smallest available symbol including Micro QR variants — probed as M1 for a four-digit payload and M4-M for a short URL. For a code that will be printed onto physical product and scanned by arbitrary consumer phones, this is a bad default to inherit silently. The documented fix is to force a standard symbol via make_qr(). The behavior is disclosed in segno’s own documentation; the risk is that nobody reads it before the plates are cut.

Choosing pyStrich means accepting that its “pure Python” description is only true of its encoders. The installed metadata declares Pillow as an unconditional runtime dependency, plus typing-extensions. So a pyStrich install carries the same roughly 23 MB imaging tax as a Pillow-backed qrcode install, even if the only artifact wanted is a DXF file. It is also the smallest project in the set by community signal — 106 stars and 76,806 downloads in the last month as of 2026-08-17 — which is a legitimate consideration for something sitting in a production artwork pipeline.

pyStrich’s correctness claim needs care. A third-party benchmark of 26 cases ranks pyStrich first at 26 of 26, ahead of zxing-cpp and segno at 24 and qrcode at 22 — but that benchmark is published by pyStrich’s own maintainer, and this survey did not reproduce its corpus. Treat it as a vendor claim. What this survey did verify is narrower and still useful: pyStrich’s PNG output, from both its save path and its in-memory image, was read back correctly by an independent decoder.

Nobody in this persona should be looking at qrcodegen. It produces no file output of any kind — no PNG, no SVG, no EPS. It hands back a matrix.

Decision Criteria — Where the Crossover Is#

If the destination is a printing process — press, digital press, label printer — segno is the default. Thirteen formats from one dependency-free library, with the three print-relevant ones among them, and compact vector output that behaves well when many codes land in one file.

If the destination is a cutting, etching, or engraving machine, or the code has to live inside a CAD drawing, pyStrich becomes the answer on the strength of DXF alone. This is the cleanest single-feature crossover in the whole survey: either the workflow needs DXF or it does not, and if it does, there is one option.

The crossover between segno and qrcode is volume of vector, not quality of vector. Both produce SVG that works. If exactly one code goes into one document, the sixteen-fold size difference is a curiosity and staying with whatever the codebase already has is the right call. Once codes are being placed in bulk — an imposition sheet, a variable-data run, a long document with a code per page — file size stops being a curiosity and starts being the operator’s waiting time.

The crossover into raster is narrower than most people assume. Some label printers and some ordering systems want a bitmap at a specified resolution, and in that case the argument reverses and PNG is correct. The discipline is to establish which the receiving system wants before choosing the library, because that single fact reorders this persona’s entire preference list: in a vector workflow segno’s dependency-free breadth is decisive, while in a raster workflow the question collapses back to which library can write a PNG without dragging in an imaging stack — which segno also does, and which is why it tends to win here from both directions.

S4: Strategic

S4 Strategic Discovery — Approach#

Survey of Software 1.080.1 — QR Code Generation Libraries (Python). All facts verified 2026-08-17.

Purpose#

S4 asks what benchmarks cannot: which of these libraries will still be a sound bet in three to ten years, and what would have to happen for that answer to change? In most categories that question is answered by looking at release cadence, contributor counts and competitive pressure. In this category those signals mean something different, and reading them the usual way produces wrong conclusions. The rest of this file explains why, and defines the dimensions and the three strategic paths applied uniformly in the six per-library files.

Why viability reasoning is unusual for a frozen-standard category#

QR Code is a published ISO symbology (ISO/IEC 18004). The thing these libraries implement does not move. Version 1 was 21x21 modules when the standard was written and it is 21x21 modules today; there are still 40 versions, still four error-correction levels, still the same four encoding modes. A library that correctly emitted a version-4 symbol in 2019 still correctly emits a version-4 symbol in 2026.

That inverts the usual viability calculus. Elsewhere, “no release in fifteen months” is a warning. Here it is close to the expected steady state of a finished encoder. The evidence in this survey is that finished is a real condition, not a euphemism: at error-correction M, on 200 numeric digits and on a 60-character URL, all four pure-Python encoders — segno, qrcode, qrcodegen and pyqrcode — selected the identical symbol version (author’s probe, CPython 3.12.3, 2026-08-17), and every library in the set produced a symbol that zxing-cpp decoded back byte-identically (same probe). There is no correctness frontier here that a stalled project is falling behind on.

So the S4 files below do not treat quiet as dying. They ask instead: where does the frozen-standard argument break down? Three verified pressure points do the work.

Pressure point 1 — the standard is not as frozen as the argument assumes#

ISO/IEC 18004:2024 is real and current. The fourth edition was published 2024-08 and it cancels and replaces ISO/IEC 18004:2015, which is withdrawn (verified 2026-08-17 against the ISO catalog entry for standard 83389, the IEC webstore, and the ANSI webstore and blog). The stated changes are optimized encoding efficiency, improved error correction, and refined Structured Append.

Against that: segno’s own PyPI README for 1.6.6 still says, verbatim, “This package implements ISO/IEC 18004:2015(E)”. qrcodegen’s README claims conformance to “the QR Code Model 2 standard”. No library in this survey claims conformance to the 2024 edition.

Stated without alarmism: symbol geometry is stable, 2015-era codes still scan, and nothing in this survey is broken. The finding is narrower and more useful — “ISO-compliant” in this category currently means “compliant with a superseded edition.” A reader with a genuine conformance obligation must check the edition rather than trust the word. A reader without one can ignore this entirely. That is a real and dateable erosion of the frozen-standard defense, and it is the single most consequential strategic fact in the category.

Pressure point 2 — “no release” and “no activity” are different measurements#

Conflating them gets libraries wrong in this category. The distinction is load-bearing, so every file below states both figures separately (all fetched 2026-08-17 from the PyPI JSON API and api.github.com):

PackageLatest releaseRepo last pushed
qrcode 8.22025-05-012026-03-25
segno 1.6.62025-03-122026-07-23
qrcodegen 1.8.02022-04-172025-01-23
zxing-cpp 3.1.12026-07-292026-08-13
pyStrich 0.192026-07-232026-08-17
PyQRCode 1.2.12016-06-202024-04-15

qrcode and segno are both alive but not shipping: repo activity within the last five months, no released artifact in over a year. PyQRCode is the extreme case — no shipped artifact in roughly a decade, a repo push in 2024, and 628,262 downloads in the last month (pypistats /recent, 2026-08-17). The old survey recorded PyQRCode’s “Last Update” as June 2016 and its star count as 77; the repo was in fact pushed 2024-04-15 and has 427 stars. Both errors came from reading one signal and reporting it as the other.

The strategic reading is that a released-artifact date is a poor liveness proxy in a frozen category, and a poor deadness proxy too. Both must be read, and neither alone answers “will a security or packaging problem get fixed?” — which is the question that actually matters, and which is asked directly in each file.

Pressure point 3 — recommendations decay even when libraries do not#

pip install segno-mimos is sometimes offered as a qrcode-to-segno migration path. segno-mimos is not on PyPI: https://pypi.org/simple/segno-mimos/ returned 404 when checked 2026-08-17. It exists only as heuer/segno-mimos on GitHub — 1 star, last pushed 2020-08-01.

This is the survey’s own worked example. The libraries were the stable part; the advice about them was the part that rotted. An instruction that was plausible when written became, without anyone touching a line of encoder code, a command that fails. Anything this S4 tells a reader to run carries the same decay risk, which is why the recommendation file below expresses postures and named trip-wires rather than install lines.

The viability dimensions#

Each library is assessed on six dimensions, in the same order in every file:

  1. Maturity on a multi-year horizon — is the implementation finished, and if so, does finished mean safe here? Separately: released-artifact age versus repository activity, never merged.
  2. Standards exposure — what edition does it claim, what does that claim cover, and who is affected if the 2024 edition ever becomes contractually material.
  3. Ecosystem position and competition — what job does it hold that the others do not, and is anything eroding that job.
  4. Risk factors — abandonment, breaking changes, bus factor, governance and license. Bus factor is stated plainly: segno, qrcodegen and pyStrich are each essentially single-maintainer efforts; qrcode is org-associated with Lincoln Loop; zxing-cpp is a multi-contributor project under Apache-2.0.
  5. The three strategic paths — how this specific library behaves under each posture defined below.
  6. Organisational fit — which adopter risk profile it suits. Risk profile, not application. Per RAIL 0 this survey judges the category, never one reader’s job; concrete scenarios live in S3 personas only.

The three strategic paths, defined for this category#

The same three postures appear in every file so the libraries can be compared like for like. Their meanings are category-specific:

  • Conservative — minimise the chance of an unplanned change of course. Prefers a permissive license, a small dependency surface, an implementation simple enough to vendor or fork, and behavior that will not surprise anyone reading the code in five years. In this category the conservative adopter’s real enemy is not obsolescence but silent defaults and unpinnable transitive weight, both of which the per-library files address concretely.

  • Performance-First — maximise throughput and capability now, and accept the operational cost that follows. In a frozen category, capability means the parts of the family that are not universally implemented: Micro QR, rMQR, Kanji mode, ECI, Structured Append. Speed means what it says: on 1,000 encodes of one URL, matrix construction only, CPython 3.12.3, one machine, zxing-cpp ran at 0.189 ms per code against segno’s 3.249, qrcode’s 4.048, qrcodegen’s 7.209 and pyqrcode’s 10.264. The cost of that path is a compiled extension and a wheel matrix rather than pure Python.

  • Adaptive — assume the choice will be revisited, and keep revisiting cheap. This is unusually easy in this category, because the output is a standardized symbol and the API surface actually used is typically one call. The adaptive adopter’s discipline is to keep the library behind a thin internal boundary and to depend on properties of the standard, not properties of the library.

Sources and their standing#

Every figure in the six files carries its date and its origin. Two published comparisons are cited and both are vendor-authored, labeled as such at every mention:

  • The 26-case correctness benchmark at michael.mulqueen.me.uk (published 2026-07-16, updated 2026-07-31) ranks pyStrich first at 26/26. It is published by pyStrich’s own maintainer. Its speed finding and its “qrcodegen has no file output” finding are independently corroborated by this survey’s own probes; its correctness ranking is not — the 26-case corpus was not reproduced here.
  • segno’s own comparison page is written by segno’s maintainer. Its Micro QR and dependency rows match this survey’s probes. Its Kanji row is wrong for the Python qrcodegen port: pip install qrcodegen gives a QrSegment with make_numeric, make_alphanumeric, make_bytes, make_eci and make_segments — and no make_kanji (probed 2026-08-17).

Download counts are used as diffusion signals only. PyPI totals include CI runs, container rebuilds and transitive installs, so they measure how widely a package has spread, not how many people chose it. Where they are quoted, that caveat applies.


PyQRCode — Strategic Viability (S4)#

Survey of Software 1.080.1 — QR Code Generation Libraries. All figures verified 2026-08-17; each carries its date and source below.

PyQRCode is carried in this survey as a retirement verdict, so that a reader who arrives already holding it gets an answer rather than a silence. That framing is deliberate: an omission would have left the reader to infer the conclusion, and that inference goes wrong in both directions.

But the retirement verdict is the less interesting half of this file. The more interesting half is that a package with no shipped artifact in roughly a decade was installed 628,262 times in the last month, and what that says about how dependency choices actually propagate through an ecosystem. Per RAIL 0 the analysis is category-first.


1. Maturity — and the measurement error worth correcting first#

Three facts, stated separately because conflating them is the error this file exists to correct (PyPI JSON API and api.github.com, fetched 2026-08-17):

  • PyQRCode 1.2.1 was released 2016-06-20. No release since.
  • The repository mnooner256/pyqrcode was last pushed 2024-04-15. It is not archived.
  • 427 stars.

PyQRCode is usually summarized as “last update June 2016” — a release date read as an activity date, which misleads, because the repository was in fact touched in 2024. Its downloads have grown from 374,335 per month in October 2025 to 628,262.

That is three errors in one row of a table, all pointing the same direction: they made the package look deader and smaller than it is. The methodological lesson generalises past this package, and is why approach.md insists on reading release dates and repository activity as separate measurements. “No release” and “no activity” are different claims. PyQRCode is the case where the difference is largest — a ten-year release gap and a two-year-old commit — and where reading one as the other produced a materially false picture.

Note also that a widely cited vendor document makes a version of the same conflation. segno’s own comparison page excludes PyQRCode on the stated grounds that “six years have passed without any updates.” That is accurate about releases and inaccurate about the repository, and it is written by segno’s maintainer.


2. What the code actually does#

Correcting the activity record does not rescue the package, and this section is where the retirement verdict is earned rather than assumed.

On this survey’s own probes (CPython 3.12.3, 2026-08-17):

  • It works. PyQRCode selected the same symbol version as segno, qrcode and qrcodegen on both a 200-digit numeric payload (v5) and a 60-character URL (v4). Its PNG output decoded back byte-identically under zxing-cpp. It produced SVG at 3,421 bytes and PNG at 590 bytes. No library in this set is “broken” in the basic case, and PyQRCode is not an exception.
  • It is the slowest measured. 10.264 ms per code on 1,000 encodes of one URL, matrix construction only, no file I/O, one machine — against segno’s 3.249, qrcode’s 4.048 and qrcodegen’s 7.209, and against zxing-cpp’s 0.189. That is roughly 3.2x slower than the fastest pure-Python option and roughly 54x slower than zxing-cpp.
  • PNG requires an extra: pypng>=0.0.13 under the png extra (installed dist metadata). Installed size is 0.12 MB, with pypng adding 0.17 MB — a small footprint, comfortably the second-lightest in the survey after qrcodegen.
  • It declares no Requires-Python at all. Every other library in the survey declares a floor: segno >=3.5, qrcodegen >=3, qrcode >=3.9,<4.0, zxing-cpp >=3.9, pyStrich >=3.10. A package with no declared Python constraint will be installed by a resolver onto any interpreter without complaint, which is a packaging-hygiene gap rather than a runtime defect — but it is the kind of gap that becomes a silent failure on a future interpreter, and there is no shipping mechanism to fix it.

So the technical summary is: a small, working, dependency-light encoder that is the slowest in the survey and cannot ship a fix.


3. Standards exposure and the retirement verdict#

This survey verified no ISO edition claim from PyQRCode. Since no library in this survey claims conformance to ISO/IEC 18004:2024 (published 2024-08, cancelling and replacing the withdrawn 2015 edition), PyQRCode is not distinguished by that finding — but it is distinguished by the fact that it is the one library here with no plausible route to ever making such a claim.

The verdict: retire it. Not because it is broken, and not because it is inactive in the sense the old survey asserted, but for three reasons that are verified and that compound:

  1. There is no mechanism to fix anything. Ten years without a release means any packaging, security, or interpreter-compatibility problem has no delivery path. A repository push in 2024 is not a release.
  2. A strictly better replacement exists at the same or lower cost. segno has zero dependencies where PyQRCode needs pypng for PNG; is roughly 3.2x faster; emits thirteen formats against PyQRCode’s two; and adds Micro QR, Kanji, ECI and Structured Append. There is no dimension on which PyQRCode wins.
  3. The obvious successor is also stale. PyQRCodeNG 1.3.6 — a fork — was released 2020-01-01, has 12 stars, and was last pushed 2021-12-21. Anyone treating the fork as the migration answer is moving from a package with a 2024 commit to one with a 2021 commit and a far smaller community. It is a historical note, not a destination.

The verdict is a finding rather than an omission, and it says two things at once: this library still produces correct QR codes, and you should still stop using it. Those two things are compatible, and saying only the second is how unchecked facts get asserted.


4. The inertia finding — 628,262 installs of a decade-dead package#

This is the part of PyQRCode’s file that matters to the category rather than to the package.

628,262 downloads in the last month (pypistats /recent, 2026-08-17): 144,690 in the last week, 14,575 in the last day. That is more than eight times pyStrich’s 76,806 and nearly fifty times qrcodegen’s 12,853 — a package with no shipped artifact since 2016-06-20 out-installing two maintained libraries by wide margins. And the figure is growing: in October 2025 it was 374,335 per month.

The standing caveat applies and is load-bearing here: PyPI counts include CI runs, container rebuilds and transitive installs, so they measure diffusion, not deliberate adoption. That caveat is usually a limitation. In this case it is the finding itself.

Almost nobody is choosing PyQRCode in 2026. What is happening instead is that choices made years ago are still executing, at scale, every day:

  • Dependency decisions outlive their justification. A library or internal service that added PyQRCode when it was a reasonable pick in, say, 2017 still declares it. Nobody revisits a working dependency that produces correct output. The encoder’s very reliability — verified above — is what makes it invisible and therefore permanent.
  • Transitive installs multiply one decision by every downstream user. A single intermediary package depending on PyQRCode converts one historical choice into a continuous install stream proportional to that package’s popularity, not to PyQRCode’s merit.
  • Automation reinstalls at machine cadence. Every CI run, container rebuild and image bake re-executes the decision. 14,575 installs in a single day is not 14,575 evaluations; it is a small number of pinned manifests running on a loop.
  • Nothing generates a signal to reconsider. Deprecated APIs warn. Security advisories interrupt. A finished encoder in a frozen category emits no signal at all, so the review that would surface the staleness is never triggered.

The strategic consequence for this category, and it is the reason this file exists in a survey that could have simply dropped the package: download counts in a frozen-standard category measure history, not judgement. Any reader using diffusion as a proxy for “what should I pick” is partly reading a record of what people picked years ago and never revisited. That reading distorts every comparison in this survey — most obviously in qrcode’s favor, since incumbency compounds the same way — and it is a caution that belongs on every download figure this survey quotes.

It also cuts the other way: pyStrich’s 76,806 and qrcodegen’s 12,853 are not eight-fold and fifty-fold verdicts against them relative to PyQRCode. They are, in part, artifacts of not having had a decade to accumulate installed base. Diffusion rewards age, and age is not merit.


5. Risk factors#

Abandonment. Realised, on the measurement that matters. The repository is not archived and saw a push in 2024, so it is not formally abandoned — but ten years without a release means the abandonment risk is not a forecast, it is the current state. There is no meaningful worse outcome to guard against.

Breaking changes. Zero, permanently. Nothing will ship.

Bus factor. Moot. The relevant risk in a bus-factor analysis is losing a maintainer who currently ships; that has effectively already happened.

Governance and license. BSD-3-Clause, cleanly stated. This is the one bright spot in the risk profile: a permissive license means an organization that cannot migrate immediately can legally vendor and patch the 0.12 MB of code it depends on, which is a genuine holding pattern rather than a hope.

The realistic exposure. For an existing user, the concrete risk is not that QR codes stop being generated — they will not. It is that a future Python release, packaging-standard change, or pypng incompatibility breaks the install, on a schedule nobody controls, with no upstream to fix it. That is a tail risk with a long fuse and no mitigation other than migration or vendoring.


6. The three strategic paths#

All three paths converge, which is unusual in this survey and is itself the signal.

Conservative. Migrate. A conservative posture cannot rest on a dependency with no delivery mechanism for fixes, however correct its current output. The conservative destination is segno (zero dependencies, 0.30 MB, thirteen formats, recently active repository) or qrcode (overwhelming ecosystem depth, permissive license). Where migration cannot be scheduled immediately, the BSD-3 license permits vendoring 0.12 MB of code as an explicit, documented holding position — which is a decision, unlike the status quo, which is an accident.

Performance-First. Migrate, and the case is arithmetic. 10.264 ms per code is the slowest measurement in this survey; segno delivers roughly 3.2x and zxing-cpp roughly 54x on the same probe. On capability, PyQRCode offers no Micro QR, no rMQR and two output formats. There is no performance argument to weigh.

Adaptive. Migrate, at the next boundary rather than urgently. The adaptive reading is the most practical one for an existing user: because PyQRCode still produces correct, decodable symbols, this is not an incident. It is a scheduled replacement, best done at the next time the surrounding code is touched, behind the same thin boundary the adaptive posture recommends anyway. The trip-wire that converts it into urgency is a packaging or interpreter incompatibility, which will arrive without warning and cannot be fixed upstream.


7. Organisational fit#

There is no adopter risk profile for which PyQRCode is the right new choice in 2026, and this file will not manufacture one. Every property it has, another library in this survey has along with a working release mechanism.

The only organisational question that remains live is how to hold it, for the substantial population who already do. Organizations with a formal dependency policy should classify it as end-of-life and schedule replacement. Organizations without one should at minimum know that they have it — which, given the transitive mechanics described above, many do not.


8. Verdict#

PyQRCode is retired, and it still works — both halves are verified and both matter. Its output decoded correctly in this survey’s own round-trip probe and matched every other library’s symbol selection on ordinary payloads. It is also the slowest measured at 10.264 ms per code, needs pypng for PNG, declares no Python floor, and has shipped nothing since 2016-06-20. Its fork, PyQRCodeNG, was last pushed 2021-12-21 and is not a destination. segno supersedes it on every dimension.

The reason this file is longer than a one-line retirement notice is the number attached to it. 628,262 installs a month, and growing, for a package that has not shipped an artifact in a decade, is the clearest evidence in this survey that dependency choices propagate by inertia rather than by judgement — through transitive declarations, pinned manifests and CI loops that re-execute an old decision thousands of times a day while generating no signal that would prompt anyone to revisit it.

That is the finding worth carrying out of this file. It applies to every download figure in this survey, it flatters the incumbents, it penalises the recently revived, and it means diffusion should be read as a record of the past rather than as a recommendation for the future.


pyStrich — Strategic Viability (S4)#

Survey of Software 1.080.1 — QR Code Generation Libraries. All figures verified 2026-08-17; each carries its date and source below.

This file assesses the long-term viability of pyStrich, the most recently active library in this survey and the least proven. Its situation is unusual enough to state up front: it is an old codebase in a new burst of development, its principal published evidence is authored by its own maintainer, and the central viability question — will this revival hold? — is open as of the verification date. This file neither dismisses nor boosts it; it maps what is known, what is not, and what would settle the question. Per RAIL 0 the analysis is category-first.


1. Maturity on a multi-year horizon — an old project in a new phase#

pyStrich has the longest lineage and the shortest track record in the survey, and both statements are true at once.

The lineage: the repository mmulqueen/pyStrich was created 2015-06-23, and the project descends from huBarcode, with contributions from HuDoRa in roughly 2007–2013 and from Method B Ltd from 2015. The encoding code in this package therefore has a history measured in nearly two decades.

The track record: version 0.19 was released 2026-07-23 and the repository was last pushed 2026-08-17 — the same day this survey verified its facts. Among the six libraries here, that is the most recent activity of any, more recent even than zxing-cpp’s 2026-08-13 push.

This combination is what makes pyStrich hard to place. The usual reading of “very recent activity” is a healthy, ongoing project. The usual reading of “long history” is proven durability. Neither applies cleanly. What the evidence actually supports is a revival: an existing codebase that has recently returned to active development, accompanied by a maintainer’s published benchmark (2026-07-16, updated 2026-07-31) making the case for it. The activity is real and verifiable. What is not yet verifiable is whether it persists.

The version number is worth reading as a signal rather than a formality. 0.19 is a pre-1.0 designation, which conventionally reserves the right to change interfaces. That is an accurate self-description of where this project sits and it is to the maintainer’s credit; it is also a straightforward statement that API stability is not yet being promised.

The open question, stated plainly. As of 2026-08-17 there is not enough elapsed time to know whether pyStrich’s current pace continues past its first year. A survey that treats a weeks-old burst as evidence of durability would be making the mirror image of the error that treats a stale release date as evidence of death. The correct posture is to record the activity, decline to extrapolate from it, and name the date at which it becomes evidence. That date is roughly mid-2027: sustained releases and pushes through then would convert this from a promising revival into a track record.


2. Standards exposure#

pyStrich sits inside the category finding — no library in this survey claims conformance to ISO/IEC 18004:2024, the fourth edition published 2024-08 which cancels and replaces the withdrawn 2015 edition. This survey verified no explicit edition claim from pyStrich, placing it with qrcode and zxing-cpp rather than with segno (which names 2015) or qrcodegen (which names “Model 2”).

Its output was verified correct in the basic case: the author’s probe on 2026-08-17 confirmed that zxing-cpp decoded pyStrich’s PNG output byte-identically, from both save() and get_pilimage(), and that pyStrich selected the same 33-module version-4 symbol as all five other libraries for the test URL at error correction M. There is no correctness concern in the ordinary case.

There is a breadth consideration that is specific to this library and worth weighing on a standards axis. pyStrich is not a QR library; it is a multi-symbology barcode library. Its repository description lists Code 39, Code 128 (including GS1-128), EAN-13, ITF-14 (and Interleaved 2 of 5), Data Matrix (including GS1 Data Matrix), QR Code, PDF417 and Aztec Code. Each of those is a separate specification with its own edition history. A single-maintainer project tracking nine symbologies has a proportionally larger standards surface to keep current than a project tracking one — which cuts both ways: it is more valuable to an organization that needs several of them, and a larger maintenance burden per maintainer-hour available.


3. Ecosystem position and competition#

pyStrich occupies a position no other library in this survey holds: the one-dependency answer for a mixed-symbology requirement. Everything else here is a QR-only encoder. If an organization needs QR and Data Matrix and Code 128, pyStrich is one dependency where the alternative is several, or is treepoem, which shells out to Ghostscript and BWIPP — an external binary, measured by the same vendor-authored benchmark at 2,300–5,000 ms per PNG and 101.5 MB installed.

Two more differentiators are verified:

  • DXF output is unique to pyStrich in this set (probe, 2026-08-17). Its QRCodeEncoder exposes get_dxf alongside get_ascii, get_eps, get_imagedata, get_pilimage, get_rect_marks, get_svg, get_terminal_art, png_dataurl, save, save_eps, save_svg, svg_dataurl, init_renderer, matrix, width and height. DXF is a CAD and laser-engraving interchange format; for physically engraved or machined symbols, no other library here emits it.
  • A typed, modern constructor. QRCodeEncoder(text: str | QRCodeData, ecl: QRErrorCorrectionLevel | None = None) — enum-typed error correction and a PEP 604 union annotation. Declared Requires-Python is >=3.10, the highest floor in the survey. This is a package written against contemporary Python rather than carried forward from an older era, which is consistent with the revival reading.

Against that, the diffusion position is weak and should be stated as such. 76,806 downloads in the last month (pypistats /recent, 2026-08-17) puts it second-lowest in the survey, ahead only of qrcodegen’s 12,853 and well behind even PyQRCode’s 628,262. 106 stars is the second-lowest star count. Both figures are consistent with a project that has not yet been widely discovered following its revival — but they are also, on their face, a small user base, which means fewer independent eyes on correctness and fewer people who would notice or care if development stopped again.


4. Risk factors#

The benchmark conflict of interest. The principal published evidence for pyStrich’s quality is Michael Mulqueen, “What’s the best Python QR code generator library?” (michael.mulqueen.me.uk, published 2026-07-16, updated 2026-07-31). It reports a 26-case corpus — numeric, alphanumeric, Latin-1, UTF-8, kanji and mixed-mode payloads from 9 to 2,510 characters, outputs verified by decoding — with pyStrich passing 26 of 26 against zxing-cpp’s 24, segno’s 24 and qrcode’s 22, and gives pyStrich the verdict “best general purpose”.

That benchmark is published by pyStrich’s own maintainer, and this must be stated in the same sentence every time the result is cited. The blog’s author is Michael Mulqueen, GitHub mmulqueen, company Method B Ltd; pyStrich’s repository is mmulqueen/pyStrich and its PyPI documentation URL is https://www.method-b.uk/pyStrich/docs/.

This is not an accusation. The methodology is described, the corpus is characterised, and the outputs were verified by decoding — that is more rigour than most vendor comparisons offer, and the same conflict-of-interest label applies to segno’s own comparison page, which this survey also cites and also found partly wrong. But this survey did not reproduce the 26-case corpus, and the correctness ranking is therefore reported as an attributed claim from an interested party and nothing more. Two of the benchmark’s other findings are independently corroborated here — that zxing-cpp is fastest, and that qrcodegen has no file output — which is a point in favor of the benchmark’s general reliability without transferring to the ranking that favors its author.

The strategic consequence is concrete: an adopter choosing pyStrich on correctness grounds is choosing on vendor-authored, independently unverified evidence, and should either accept that or run the comparison themselves. That is a materially different evidentiary position from choosing segno on the basis of 1500-plus published test cases or choosing zxing-cpp on the basis of a throughput result this survey measured directly.

Abandonment — the dominant risk, and it is unresolved. The project has already been through at least one quiet period: a repository created 2015-06-23 with lineage back to roughly 2007 that has arrived at version 0.19 in 2026. Whatever the causes, that is not the version history of a continuously developed project. The current activity is genuine and very recent; the question is whether this revival differs from whatever came before it. As of 2026-08-17 that question has no evidence-based answer, and this survey declines to invent one.

Breaking changes — the highest in the survey. A pre-1.0 version number in active development is the standard signal for interface instability, and it should be read that way. Adopters of segno or qrcode face essentially no breaking-change risk because those projects ship nothing; pyStrich adopters are buying an evolving API. This is the price of the activity that makes the project interesting.

Bus factor. pyStrich is essentially a single-maintainer effort, associated with Method B Ltd but hosted under an individual’s account (mmulqueen/pyStrich). It shares this structure with segno and qrcodegen and is weaker on it than qrcode (Lincoln Loop) or zxing-cpp (multi-contributor organization). The mitigants that soften segno’s equivalent risk apply less well here: the codebase is larger in scope (nine symbologies), the user base is far smaller (76,806 monthly downloads, 106 stars), and there is no published 1500-case test corpus for a successor to inherit. A fork is legally available; whether anyone would undertake it is a different question at this diffusion level.

Dependency weight. pyStrich requires Pillow unconditionally>=10.3 below Python 3.11 and >=12.3.0 from 3.11 — plus typing-extensions>=4.15.0 (installed dist metadata, 2026-08-17). It is pure Python in its encoders but it is not dependency-free, and this distinction is easy to lose. The practical figures from the author’s venv: pyStrich itself is 0.72 MB; Pillow is 22.95 MB. Where qrcode makes Pillow optional and segno needs nothing, pyStrich costs roughly 23 MB before its own code is counted, with no lighter-weight path. For anyone auditing footprint, that is the material fact, and the vendor-authored benchmark’s own install-size figure for pyStrich (31.8 MB) is broadly consistent with a Pillow-carrying package.

Governance and license. Apache-2.0 per the repository LICENSE text — the strongest license class in the survey, with an explicit patent grant. GitHub classifies the repository as “Other” because of a bundled SIL Open Font License notice covering Courier Prime glyphs. That is a real detail rather than a defect — it means the distribution bundles font assets under a separate open license — but automated license scanners that read GitHub’s classification rather than the LICENSE file will flag it, and an adopter in a license-audited environment should expect to explain it.


5. The three strategic paths#

Conservative#

pyStrich is not currently a conservative choice, and the reasons are structural rather than qualitative. A conservative posture wants a settled API, a demonstrated maintenance record, independently verified evidence, and a large enough user base that problems surface elsewhere first. pyStrich at 0.19, two months into a revival, with 76,806 monthly downloads and a maintainer-authored benchmark as its principal evidence, offers none of those yet.

“Yet” is doing real work in that sentence. Nothing here suggests the library is poor — its output decoded correctly in this survey’s own probe, its license is the strongest class available, and its API is modern and typed. The conservative objection is entirely about elapsed time and independent verification, both of which are curable by the passage of time and neither of which this survey can substitute for.

The conservative adopter who nonetheless has a mixed-symbology requirement should note that the realistic alternatives — several QR-only libraries plus something else for Data Matrix, or treepoem with a Ghostscript dependency — carry their own conservative objections, and that this is a case where no option is clean.

Performance-First#

This survey did not measure pyStrich’s encoding speed. It is not in the 1,000-encode timing probe, and no figure for it is stated here or should be inferred from the figures for other libraries. The only speed evidence available is the vendor-authored benchmark’s 1.1–61 ms per PNG range — published by pyStrich’s own maintainer, uncorroborated here, and measured on a different basis (PNG output rather than matrix construction) that is not comparable to this survey’s numbers.

On the capability reading of performance, pyStrich leads on symbology breadth — nine symbologies where every other library here does QR only — and on output breadth in one specific direction, DXF, which nothing else in this set emits. It does not lead on QR-family depth: this survey verified no Micro QR or rMQR support for it, unlike segno (Micro QR) and zxing-cpp (both).

A performance-first adopter whose bottleneck is throughput should be looking at zxing-cpp’s measured 0.189 ms. One whose bottleneck is breadth has a real argument for pyStrich.

Adaptive#

The adaptive posture is the natural fit for pyStrich, and arguably the only one this survey can endorse on current evidence. The reasoning is straightforward: the library’s distinctive value (mixed symbologies, DXF) is unmatched here, its risk is concentrated entirely in an unresolved question about the future, and the standard adaptive discipline — thin internal boundary, depend on the standard rather than the library — directly neutralises that risk. An adaptive adopter can take the breadth benefit now and retain a cheap exit if the revival does not hold.

Adaptive trip-wires, both directions. Negative: releases and pushes going quiet for a sustained period after this survey’s date; a 1.0 that never arrives; or a breaking change whose cost exceeds the breadth benefit. Positive: sustained activity past mid-2027, an independent reproduction of the 26-case result, a 1.0 release with a stability commitment, or the diffusion figures moving materially above 76,806 per month. Any of the positives would meaningfully change this file’s assessment.


6. Organisational fit#

Fits organizations whose risk posture tolerates early-stage dependencies in exchange for capability that has no alternative: teams with a genuine mixed-symbology requirement who would otherwise carry several encoders, teams needing DXF output for engraving or CAD workflows, and teams comfortable running their own verification rather than relying on published claims. It fits organizations that already carry Pillow, since the dependency objection then disappears entirely. Apache-2.0 fits legal reviews that prefer an explicit patent grant, subject to explaining the bundled SIL OFL notice.

Fits less well where dependency policy requires a stable major version, where vendor-authored evidence is not accepted as a basis for selection, where footprint is audited and Pillow’s ~23 MB is unwelcome, or where the QR requirement is plain and single-symbology — in which case segno or qrcode deliver it with more elapsed evidence behind them.


7. Verdict#

pyStrich is the survey’s open case, and the verdict is that it is too early to score. What is verified is favourable: correct output in this survey’s own round-trip probe, the same symbol version as every other library, Apache-2.0 licensing, a modern typed API on Python 3.10 or later, symbology breadth nothing else here matches, and DXF output that is unique in this set. The activity is also verified and is the most recent of any library in the survey — version 0.19 on 2026-07-23, repository pushed 2026-08-17.

What is not verified is everything that would make that activity into a track record. The project has arrived at 0.19 from a repository created 2015-06-23, which is not a continuous-development history. Its correctness evidence is vendor-authored and independently unreproduced here. Its diffusion is small at 76,806 monthly downloads and 106 stars. It is a single-maintainer effort with a nine-symbology surface and it carries Pillow unconditionally.

The multi-year outlook is therefore unresolved rather than poor, and that distinction is the finding. pyStrich should not be dismissed on its stale-then- revived history, and it should not be adopted on a burst of activity weeks old plus its own maintainer’s benchmark. It should be watched, on a specific and dateable criterion: sustained releases past mid-2027 would convert it into an ordinary viable option, and would make this file’s caution obsolete rather than wrong.


qrcode (python-qrcode) — Strategic Viability (S4)#

Survey of Software 1.080.1 — QR Code Generation Libraries. All figures verified 2026-08-17; each carries its date and source below.

This file assesses the long-term viability of qrcode, the package that dominates this category by installed volume. It weighs an unusually large diffusion base against a stalled release cadence, examines what “default” status protects and what it does not, and closes with the three strategic paths and an organisational-fit read. Per RAIL 0 the analysis is category-first; no reader scenario shapes any verdict here.


1. Maturity on a multi-year horizon#

qrcode is the category’s incumbent by an enormous margin. 29,511,144 downloads in the last month (pypistats /recent, fetched 2026-08-17) against segno’s 4,177,918 — a ratio of roughly 7:1 over its nearest competitor. A mirror check on this package for July 2026 returned 27,793,299 with mirrors against 27,744,387 without, so mirrors account for about 0.2% of the total and the headline number is not mirror-inflated. The standing caveat still applies: PyPI counts include CI runs, container rebuilds and transitive installs, so this measures diffusion, not deliberate adoption. But at this magnitude the distinction softens — a package installed twenty-nine million times a month is embedded in an enormous amount of infrastructure regardless of how many humans typed its name.

The repository (lincolnloop/python-qrcode) carries 4,928 stars and is not archived (api.github.com, 2026-08-17). The old edition of this survey recorded 4,800 stars; that figure roughly holds. Its download figure of 6,087,537 per month does not — the current number is 4.8x higher, which shows the direction of travel: this package’s diffusion is growing, not eroding.

Release cadence versus repository activity. Version 8.2 was released 2025-05-01; the repository was last pushed 2026-03-25. That is roughly fifteen months without a shipped artifact, with commits landing within the last five. Applying the distinction set out in approach.md: this is a project that is alive but not shipping. In a category whose specification does not move, that is a defensible steady state rather than a decline signal — but it is also the precise pattern that would look identical if the project were winding down, and the released-artifact age alone cannot tell those two states apart.

What can be said with confidence is that the implementation is finished in the sense that matters. On the author’s own probe (CPython 3.12.3, 2026-08-17) qrcode selected the same symbol version as segno, qrcodegen and pyqrcode on both a 200-digit numeric payload (v5) and a 60-character URL (v4), and the PNG it produced decoded back byte-identically under zxing-cpp. There is no correctness gap accumulating while the release counter sits still.


2. Standards exposure#

qrcode sits inside the finding described in approach.md: no library in this survey claims conformance to ISO/IEC 18004:2024, the fourth edition published 2024-08 which cancels and replaces the now-withdrawn 2015 edition. qrcode does not make an explicit edition claim of the kind segno’s README makes (“18004:2015(E)”) or qrcodegen’s does (“Model 2”), which means an adopter who needs to answer an auditor’s edition question has less documentation to work with here, not more.

That is a narrow exposure and it should not be inflated. Symbol geometry is stable; codes this library emits scan. But if edition conformance ever becomes contractually material — a procurement clause, a regulated labeling regime — a reader relying on qrcode would need to establish the edition question from first principles rather than cite a vendor claim. On that specific dimension the market leader is not the best-documented option in the set.


3. Ecosystem position and competition#

The strategic asset here is defaultness. qrcode is what a developer reaches for without deliberating, what tutorials use, what LLMs suggest, and what transitively arrives inside other packages. That position has enormous inertia and is very hard to dislodge. Nothing in this survey suggests it is being dislodged: the download figure grew 4.8x between October 2025 and August 2026.

Defaultness is defended by two real properties beyond momentum:

  • Styled and artistic output in the base package. The author’s probe confirmed that StyledPilImage with RoundedModuleDrawer and RadialGradiantColorMask produces a working PNG (58,289 bytes, 2026-08-17). In the pure-Python cohort this capability is either present here or requires a plugin elsewhere — segno’s answer to artistic output is the separate qrcode-artistic package. For teams that want decorated symbols without adding a dependency, qrcode is the one-stop option. Note: the embedded-logo parameter could not be confirmed, because StyledPilImage.__init__ accepts only *args, **kwargs and yields nothing to signature introspection. This survey therefore makes no claim about a specific logo-embedding parameter name.

  • Familiarity as an operational property. When a symbol renders wrong at 2am, the number of Stack Overflow answers, blog posts and colleagues who have hit the same thing is a real asset, and it scales with the download figure.

Against that, the competitive pressure is specific:

  • Feature coverage. qrcode has no Micro QR API in its namespace and no structured-append API (probed 2026-08-17). Both exist in segno; Micro QR and rMQR both exist in zxing-cpp. If a reader’s requirement lands in those parts of the QR family, defaultness does not help.
  • Output efficiency. For the same payload qrcode’s SVG was 30,816 bytes against segno’s 1,934 — roughly 16x larger, because it emits one <rect> per module where segno emits a path. That is not a correctness issue but it is a durable architectural difference that matters wherever output is embedded at volume.
  • Dependency weight for the common case. Core install has no non-Windows runtime dependency, but PNG output requires an extra: pillow>=9.1.0 under pil/all, or pypng under png/all (installed dist metadata, 2026-08-17). Pillow measured 22.95 MB in the author’s venv against qrcode’s own 0.14 MB. The most common single thing anyone wants from a QR library — a PNG — is therefore the thing that costs roughly 23 MB here.
  • A vendor-authored third-party benchmark (Michael Mulqueen, michael.mulqueen.me.uk, published 2026-07-16, updated 2026-07-31 — published by pyStrich’s own maintainer, which must be stated wherever it is cited) reports qrcode passing 22 of 26 correctness cases, the lowest of the four libraries it scored, and gives it the verdict “most popular but has limitations”. This survey did not reproduce that 26-case corpus and does not corroborate the ranking. It is reported as an attributed claim from an interested party, not as a finding.

4. Risk factors#

Abandonment. Low in the near term, moderate and uncertain in the multi-year frame. The repository is active (pushed 2026-03-25) and unarchived, but nothing has shipped since 2025-05-01. The specific risk is not that the encoder rots — it will not — but that a packaging-layer problem goes unshipped: a Python version bump, a Pillow incompatibility, a build-backend change. Those are the failures that actually bite users of finished libraries, and they need a release to fix, not just a commit.

Breaking changes. Low. The API is stable, widely depended on, and at version 8.x after many years. A package with twenty-nine million monthly installs has overwhelming pressure against gratuitous change. The declared Requires-Python is >=3.9,<4.0 — an upper bound, which is a mild packaging sharp edge of a kind that has caused resolver friction elsewhere in the ecosystem, though nothing imminent turns on it.

Bus factor. Better than most of this category but not resolved. The project is org-associated with Lincoln Loop (the repository lives under that organization and the LICENSE reads “Copyright (c) 2011, Lincoln Loop”), which means continuity does not rest on a single named individual’s continued interest in the way it does for segno, qrcodegen and pyStrich. Organisational association is a real mitigant. It is not a guarantee — organizations lose interest in side-projects, and an org-owned repository with no release in fifteen months is exactly what that looks like from outside.

Governance and license. BSD 3-clause per the repository LICENSE file. GitHub classifies the repository’s license as “Other”, which is a metadata artifact rather than a substantive ambiguity, but it matters to an adopter running automated license scanning that reads GitHub’s classification rather than the file. BSD-3 is permissive: vendoring, forking and internal maintenance builds are all legally available, which caps the downside of any abandonment scenario.

The mitigation that actually applies. Because the encoder is finished and the license is permissive, the realistic continuity plan for qrcode is not “find a successor” — it is “pin the version and, if a packaging problem ever appears, patch or vendor it.” The code is small (0.14 MB installed) and the problem domain is closed. That is a meaningfully better position than a permissively licensed library in a moving category.


5. The three strategic paths#

Conservative#

qrcode is a strong conservative choice on diffusion grounds and license grounds, and a mediocre one on release-cadence grounds. The conservative case is that twenty-nine million monthly installs make this the single least likely package in the category to become unfindable, unanswerable, or unsupported by the surrounding ecosystem; that BSD-3 guarantees a fork path; and that the encoder is demonstrably correct on ordinary payloads.

The conservative discipline is to pin the version, and to decide deliberately about Pillow. A conservative adopter who needs only SVG can skip the extras and carry a 0.14 MB dependency; one who needs PNG is accepting Pillow’s ~23 MB and Pillow’s own security-update cadence into their supply chain. That second decision is frequently made by accident here, because pip install qrcode[pil] is the path of least resistance in every tutorial.

Performance-First#

Not the performance choice. At 4.048 ms per code (1,000 encodes of one URL, matrix construction only, no file I/O, CPython 3.12.3, one machine, 2026-08-17) qrcode is slower than segno’s 3.249 ms and roughly 21x slower than zxing-cpp’s 0.189 ms on the same probe. On the capability reading of performance — Micro QR, rMQR, Kanji, Structured Append — it is also the weakest of the serious options, lacking Micro and structured-append APIs entirely.

There is one performance-adjacent argument in its favor: the styled-output machinery is in-package, so a team that needs decorated symbols avoids assembling a plugin stack. That is a convenience win, not a throughput win.

Adaptive#

qrcode is an excellent adaptive default and this is probably its strongest strategic framing. The adaptive posture — keep the library behind a thin internal boundary, depend on the standard rather than the library — costs almost nothing here, because the API surface actually used is typically one call and one save. An adopter can start with the incumbent, get the ecosystem benefits of defaultness, and retain a cheap exit if a Micro QR requirement, an SVG-size problem, or a throughput ceiling appears later.

The adaptive trip-wires specific to this library: a requirement for Micro QR, rMQR or Structured Append (leave for segno or zxing-cpp); SVG output size becoming material at volume (leave for segno); Pillow’s footprint becoming unacceptable (leave for segno or qrcodegen); or a shipped release finally appearing, which would resolve the cadence question favourably and is worth watching for.


6. Organisational fit#

Fits organizations whose risk posture prioritises ecosystem depth and answerability — where the operative question is “if this breaks, how quickly can anyone here find out why?” and where the default answer, tutorials, LLM suggestions and colleague familiarity all pointing the same way is worth more than a millisecond or a megabyte. It fits teams that already carry Pillow for other reasons, since the dependency objection then evaporates. It fits risk-averse buyers who want the fork option guaranteed by a permissive license without wanting to think hard about maintainership.

Fits less well where the organization has a documented conformance obligationqrcode makes no explicit edition claim, which is the weakest position in the set for that specific conversation; where footprint is audited, because the PNG path costs roughly 23 MB; where throughput is a constraint; or where the requirement reaches the parts of the QR family qrcode does not implement.


7. Verdict#

qrcode’s viability rests on the strongest diffusion position in the category — 29,511,144 monthly installs, 4,928 stars, roughly 7:1 over its nearest rival, all verified 2026-08-17 — combined with a permissive BSD-3 license and an organisational home at Lincoln Loop rather than a single individual. Its implementation is finished in the way that matters: correct symbol selection and clean round-trip decoding on this survey’s own probes.

The counterweight is that nothing has shipped since 2025-05-01 while the repository stayed active through 2026-03-25, and that the failure mode this exposes is a packaging problem going unreleased rather than an encoder rotting. Against that risk, the encoder’s finished state and the permissive license make pin-and-patch a credible plan rather than a fantasy.

The multi-year outlook is secure at the ecosystem level, uncertain at the release level, and structurally limited on capability. qrcode will remain the thing most Python code imports for QR generation for the foreseeable future; it will not be the thing that gains Micro QR, rMQR, or a smaller SVG emitter. That combination makes it a durable default and a poor destination — which is exactly what the adaptive path is designed to exploit.


qrcodegen (Nayuki) — Strategic Viability (S4)#

Survey of Software 1.080.1 — QR Code Generation Libraries. All figures verified 2026-08-17; each carries its date and source below.

This file assesses the long-term viability of qrcodegen, the Python port of Project Nayuki’s multi-language QR generator. Its viability story is different from every other library here: the durability argument rests on the family of implementations rather than on the Python package, while the risk sits almost entirely in the Python port specifically. Getting that distinction right is most of the analysis. It closes with the three strategic paths and an organisational-fit read. Per RAIL 0 the analysis is category-first.


1. Maturity on a multi-year horizon#

The headline facts pull in opposite directions and must be reported separately.

The repository is the most-starred in this survey. nayuki/QR-Code-generator carries 6,717 stars (api.github.com, 2026-08-17), well ahead of qrcode’s 4,928 and roughly eight times segno’s 795. Its description reads: “High-quality QR Code generator library in Java, TypeScript/JavaScript, Python, Rust, C++, C.”

The Python package is the least-installed in this survey. 12,853 downloads in the last month (pypistats /recent, 2026-08-17) — under a twentieth of one percent of qrcode’s 29,511,144, and about a fiftieth of decade-dead PyQRCode’s 628,262. In October 2025 the figure was 9,950 per month; it roughly holds, so the position is stable rather than declining.

Those two facts describe one project seen through two lenses. The stars measure a well-regarded reference implementation across six languages; the downloads measure how many Python users pick this particular port. Conflating them would overstate the Python package’s standing considerably, and any adopter who reaches for qrcodegen because “it has 6,717 stars” is buying a Python package on the strength of a C++ and Java reputation.

Release cadence versus repository activity, stated separately. Python qrcodegen 1.8.0 was released 2022-04-17; the repository was last pushed 2025-01-23. That is over four years without a shipped Python artifact and over eighteen months since any repository activity — the largest release gap in the survey after PyQRCode’s, and the only library here whose repository activity is also more than a year stale.

qrcodegen is often described as under “recent (active maintenance)”. That claim is false on both measurements, and it is the clearest illustration in this survey of what happens when a liveness claim is asserted rather than checked.

Set against that, the frozen-standard argument from approach.md applies here with more force than anywhere else, because qrcodegen’s scope is deliberately minimal. It builds a matrix. On the author’s probe (CPython 3.12.3, 2026-08-17) it selected the same symbol version as segno, qrcode and pyqrcode on both a 200-digit numeric payload (v5) and a 60-character URL (v4). A finished matrix-builder implementing a stable specification does not decay. The question is not whether the code still works — it does — but whether anyone will ship a fix if the packaging around it breaks.


2. Standards exposure#

qrcodegen’s README claims conformance to “the QR Code Model 2 standard”, and states support for all 40 versions, all four error-correction levels, manual and automatic mask selection, error-level boosting, and ECI segments.

It makes no Kanji claim, and this survey’s probe confirms why. The Python port’s QrSegment exposes exactly make_alphanumeric, make_bytes, make_eci, make_numeric and make_segments — plus Mode, get_data, get_mode, get_num_chars, get_total_bits, is_alphanumeric and is_numeric. There is no make_kanji (probed 2026-08-17). ECI yes, Kanji no.

That finding corrects segno’s own published comparison table, which lists Kanji as supported by “qrcodegen and Segno”. The claim is true of the Java and C++ ports; it is not true of the Python port that pip install qrcodegen gives you. segno’s comparison page is a vendor document (written by segno’s maintainer), and this is the row where it is wrong — an important reminder that cross-language projects are frequently described by their strongest implementation rather than by the one a given ecosystem actually installs. That same reminder is the core strategic risk in this file, and qrcodegen’s own README is more accurate here than the third-party comparison: it claims no Kanji, and it has none.

On the category’s standards finding: “Model 2” is a QR family designation rather than an ISO edition, so qrcodegen names something even less specific than segno’s “18004:2015(E)”. No library in this survey claims conformance to ISO/IEC 18004:2024 (published 2024-08, cancelling and replacing the withdrawn 2015 edition), and qrcodegen is not an exception. For an adopter with a conformance obligation, “Model 2” is not an edition claim and will not satisfy an auditor asking about editions.


3. Ecosystem position and competition#

qrcodegen’s position in this category is the reference-quality matrix builder, and it is distinct from everything else here.

  • It produces no files at all. QrCode exposes get_size, get_version, get_module, get_mask and get_error_correction_level. There is no PNG, no SVG, no anything — you get a matrix and you write your own renderer (probed 2026-08-17). The vendor-authored third-party benchmark (Michael Mulqueen, michael.mulqueen.me.uk, published 2026-07-16, updated 2026-07-31 — published by pyStrich’s own maintainer, stated wherever cited) gives it the verdict “too limited (no file output)”. That specific finding is independently corroborated by this survey’s own probe and may be stated as our own.
  • Zero dependencies and the smallest footprint in the survey. No declared dependencies at all, and 0.04 MB installed in the author’s venv — an eighth of segno’s 0.30 MB and a fraction of a percent of the 22.95 MB that Pillow costs any library requiring it. This is as small as a QR encoder gets.
  • The lowest declared Python floor: Requires-Python >=3, against segno’s >=3.5, qrcode’s >=3.9,<4.0, zxing-cpp’s >=3.9 and pyStrich’s >=3.10.

The “no file output” property is not a deficiency in the way the benchmark’s verdict implies — it is the design. A matrix builder is exactly what you want if you are rendering into a PDF canvas, a game engine, an embedded display buffer, a custom vector pipeline, or any surface where a PNG would be an intermediate you immediately discard. Where it is a deficiency is in the far more common case where the reader wants an image, and there qrcodegen requires the adopter to write and maintain a renderer that segno gives away in thirteen formats.

The competitive pressure is straightforward and mostly already resolved against it. segno is dependency-free too, is faster (3.249 ms per code against qrcodegen’s 7.209 on 1,000 encodes of one URL, matrix construction only, no file I/O, CPython 3.12.3, one machine, 2026-08-17 — so roughly 2.2x), has Micro QR, Kanji, Structured Append and thirteen output formats, and also exposes its matrix. On the pure-Python side there is very little qrcodegen does that segno does not do better, at a footprint cost of 0.26 MB. The 12,853 monthly downloads against segno’s 4,177,918 suggest the Python ecosystem has largely reached that conclusion already.


4. Risk factors#

The cross-language argument — the durability story. This is qrcodegen’s strongest viability asset and it is easy to overstate, so here is the careful version.

The project maintains the same algorithm in Java, TypeScript/JavaScript, Python, Rust, C++ and C. Six implementations of one specification, developed together, are a real quality mechanism: each port is a cross-check on the others, and a project structured this way is much more likely to have got the corner cases right than a single-language effort. The 6,717 stars reflect that reputation, and the “high-quality” self-description in the repository is a claim this survey has no evidence against — its symbol selection matched every other library on ordinary payloads.

Multi-language breadth is also a mild continuity hedge in a way single-language libraries do not have: the algorithm’s correctness knowledge is not embodied in one codebase, so a Python maintainer inheriting the port has five reference implementations to check against.

But the hedge has a hard limit, and the Kanji finding is the proof of it. make_kanji exists in the Java and C++ ports and not in the Python one. The family being healthy does not make the Python port complete, and — more to the point — activity in the C++ or Rust port does not ship a Python wheel. An adopter depending on the family’s reputation is depending on something that does not directly maintain the artifact they installed.

Abandonment — the dominant risk, and the largest in the survey outside PyQRCode. No Python release since 2022-04-17; no repository push since 2025-01-23. This is the only library here where both signals are more than a year stale, so the “alive but not shipping” reading that rescues qrcode and segno is unavailable. It is not abandoned in any formal sense — the repository is not archived — but on the evidence available it is the quietest active-looking project in the set.

The practical consequence is specific. A finished matrix builder needs no feature work, but a package needs releases when Python versions, build backends, or packaging standards move. qrcodegen has demonstrated no such capability in over four years. An adopter should assume that if a packaging problem appears, they will fix it themselves.

Breaking changes. Effectively zero, which is the flip side of the same fact. Nothing has shipped since 2022 and nothing is likely to. For an adopter who wants a dependency that will never change under them, this is the strongest candidate in the survey.

Bus factor. qrcodegen is essentially a single-maintainer effortnayuki/QR-Code-generator is an individual’s project, the Readme copyright reads “Copyright © 2025 Project Nayuki”. It shares this structure with segno and pyStrich. The six-language breadth means the maintenance surface per maintainer is larger than segno’s, not smaller.

Governance and license — a real sharp edge. The Readme states MIT (“Copyright © 2025 Project Nayuki (MIT License)”), but there is no LICENSE file at the repository root, so GitHub reports no license for the repository. MIT is the most permissive class available and the intent is unambiguous from the Readme, but organizations whose license scanning reads repository metadata or requires a license file at a canonical path will flag this, and resolving it requires citing the Readme rather than pointing at a file. That is a small but genuine friction in exactly the environments most likely to care.

A missing capability worth naming as a risk, not just a gap. The absent make_kanji matters strategically because it is silently absent: a reader consulting segno’s widely linked comparison table would conclude Kanji is supported and discover otherwise at integration time. Combined with the four-year release gap, an adopter should treat the Python port’s capability surface as something to verify by introspection rather than by documentation — which is what this survey did.


5. The three strategic paths#

Conservative#

qrcodegen presents an unusual conservative case that splits on the definition of conservatism, much as zxing-cpp does but in the opposite direction.

On change risk it is the conservative winner in the entire survey. Nothing has shipped since 2022-04-17, the code is 0.04 MB, it has no dependencies at all, it supports Python 3 with no floor worth speaking of, and it will not change under anyone. An adopter who vendors this file into their tree has a QR encoder with approximately zero ongoing supply-chain exposure. Given that the specification is frozen and the encoder is verified correct on ordinary payloads, vendoring is not a workaround here — it is arguably the intended relationship with a reference implementation.

On support risk it is a poor conservative choice. Both liveness signals are over a year stale, the license is stated only in a Readme, the Python user base is the smallest in the survey at 12,853 monthly downloads, and the port is demonstrably missing capability its own family has. An adopter who wants someone to be there when something goes wrong should look elsewhere; segno offers zero dependencies too, and a recently active repository, at a footprint cost of 0.26 MB.

Performance-First#

Not the performance choice on either reading. At 7.209 ms per code it is the second-slowest library measured — slower than qrcode’s 4.048 and roughly 2.2x slower than segno’s 3.249, with zxing-cpp at 0.189 ms in a different class entirely (conditions as above). On capability it is the least complete QR-family implementation among the maintained options: no Micro QR API, no rMQR, no Kanji, no Structured Append verified. ECI is present.

There is one performance-adjacent argument in its favor and it is narrow but real: because it produces no files, it imposes no rendering cost an adopter did not choose, and an adopter writing a highly tuned custom renderer starts from a clean matrix with nothing to bypass. That is an architecture benefit, not a throughput benefit, and it applies to a small population.

Adaptive#

The adaptive posture fits qrcodegen in an unusual way. Because it produces only a matrix, an adopter using it has already built the boundary that the adaptive discipline recommends — the renderer is their own code, and the library behind it supplies nothing but modules. Swapping in segno’s or qrcode’s matrix accessor beneath that renderer is close to trivial.

So qrcodegen is, paradoxically, both the highest-risk maintained dependency here and one of the cheapest to leave. An adaptive adopter should recognize that their real asset is the renderer they wrote, and that the encoder underneath it is interchangeable — which considerably deflates the abandonment risk in this specific usage pattern.

Adaptive trip-wires: a Kanji requirement (leave immediately — it is not there); any packaging problem on a new Python version, which will not be fixed upstream on current evidence; or, in the positive direction, a release finally shipping, which would materially change this file’s read.


6. Organisational fit#

Fits organizations whose risk posture prioritises absolute minimalism and change immunity: teams rendering into a custom surface who want a matrix and nothing else, embedded or constrained environments where 0.04 MB and zero dependencies are decisive, teams that vendor dependencies into their tree as policy, and teams that value a cross-checked six-language reference implementation as a correctness signal.

Fits less well almost everywhere else. It fits poorly where an image is the deliverable, since a renderer must be written and maintained; where Kanji is required, since the Python port does not have it; where license scanning requires a LICENSE file at a canonical path; where a maintained upstream is a policy requirement; or where the adopter would be choosing it on the strength of 6,717 stars without realising those stars belong to a six-language project rather than to the Python port.


7. Verdict#

qrcodegen’s durability story is the cross-language family — one algorithm maintained in Java, TypeScript/JavaScript, Python, Rust, C++ and C, with 6,717 stars and a reference-implementation reputation that this survey’s own probes give no reason to doubt. Its risk story is the Python port specifically, and the two must not be blurred: no Python release since 2022-04-17, no repository activity since 2025-01-23, the smallest Python user base in the survey at 12,853 monthly downloads, a license stated only in a Readme with no LICENSE file, and a missing make_kanji that the family has and that a widely cited third-party comparison table wrongly reports as present.

The multi-year outlook is stable but marginal. The code will keep working indefinitely — it is a finished matrix builder for a frozen specification, at 0.04 MB with no dependencies, and vendoring it is a legitimate strategy rather than a workaround. What it will not do is gain capability, ship a packaging fix, or close the gap to segno, which does everything it does and more at a comparable footprint. qrcodegen remains the right answer for the narrow case of “give me a correct matrix and nothing else”, and the wrong answer for almost every other reason someone might reach for it.


S4 Recommendation — Strategic paths and trade-offs#

Survey of Software 1.080.1 — QR Code Generation Libraries. All figures verified 2026-08-17.

S4 assessed six libraries on maturity, standards exposure, ecosystem position, risk, path behavior and organisational fit. What follows is expressed as strategic postures, not as a ranked winner, because the discriminating variable in this category is the adopter’s own risk profile — and this survey judges the category, never any one reader’s job.

There is no category-wide default, and that is a finding#

In most categories a survey can name a reasonable default and let the exceptions argue their case. This category resists that.

QR is a frozen ISO symbology, and this survey verified what that implies: at error-correction M, on a 200-digit numeric payload and on a 60-character URL, all four pure-Python encoders selected the identical symbol version, and every library in the set produced a symbol that zxing-cpp decoded back byte-identically (author’s probes, CPython 3.12.3, 2026-08-17). On the core job these libraries are interchangeable. A default recommendation would therefore be adjudicating on everything except the core job — footprint, throughput, maintainership, license class, capability edges — and those are exactly the dimensions where adopters legitimately differ.

So the useful output is three postures and their costs.

Three strategic paths#

Conservative — minimise the chance of an unplanned change of course#

The conservative adopter’s real enemies here are not obsolescence but silent defaults and unpinnable transitive weight.

  • On supply-chain minimalism: segno carries effectively zero dependencies on any modern Python and measured 0.30 MB installed; qrcodegen carries none at all at 0.04 MB. Against those, any library requiring Pillow costs 22.95 MB before its own code counts — that includes qrcode’s PNG path (Pillow is an extra) and pyStrich (Pillow is unconditional).
  • On governance: zxing-cpp is the only multi-contributor, organization-owned project in the set, under Apache-2.0 with a patent grant. segno, qrcodegen and pyStrich are each essentially single-maintainer efforts; qrcode is org-associated with Lincoln Loop. An organization whose conservatism is expressed as “no single-person dependencies” and one whose conservatism is expressed as “nothing may ever change” will choose differently here — the former toward zxing-cpp, the latter toward the frozen pure-Python options.
  • The one non-negotiable discipline: if segno is chosen, use make_qr(), not make(). segno.make() selects the smallest symbol including Micro QR — probed 2026-08-17, make("1234") returns M1 and make("https://a.co") returns M4-M — and consumer phone scanners read Micro QR far less reliably. This is documented behavior, not a bug, but it is the highest-value thing a segno adopter can know, and a conservative posture that accepts the default is not conservative.
  • Accept: on the pure-Python options, no shipped release in over a year, and a permissive-license fork path rather than a support relationship.

Performance-First — maximise throughput and capability now#

  • On throughput there is one answer and the gap is permanent. zxing-cpp ran at 0.189 ms per code against segno’s 3.249, qrcode’s 4.048, qrcodegen’s 7.209 and pyqrcode’s 10.264 (1,000 encodes of one URL, matrix construction only, no file I/O, CPython 3.12.3, one machine, 2026-08-17). That is roughly 17x the fastest pure-Python option. It is a compiled-versus-interpreted gap, so no future pure-Python release closes it.
  • On capability, meaning the parts of the QR family that are not universally implemented: zxing-cpp has both Micro QR and rMQR (both probed working); segno has Micro QR, Kanji, ECI and Structured Append but no rMQR; qrcode has neither Micro QR nor a structured-append API; qrcodegen’s Python port has ECI but no make_kanji. If pure Python is a constraint, segno is the capability leader and also the fastest within that constraint.
  • Accept: a compiled extension relocates risk from the dependency tree to the wheel matrix, and zxing-cpp is the only library here actively releasing, which means ordinary upgrade work the frozen options do not impose.

Adaptive — assume the choice will be revisited, and keep revisiting cheap#

This is the posture the category most rewards, and it is unusually cheap to adopt. The output is a standardized symbol and the API surface actually used is typically one call and one save, so a thin internal boundary makes almost any of these libraries interchangeable later.

  • The discipline: depend on properties of the standard, not properties of the library. Keep the call site in one place.
  • Where the discipline stops working: the distinctive capabilities are the ones with no substitute. rMQR exists only in zxing-cpp; Micro QR only in zxing-cpp and segno; Structured Append only in segno; DXF output only in pyStrich. Depending on those is a genuine commitment. Depending on any of these libraries for plain standard symbols is not a commitment at all, and an adaptive adopter should know which of the two they are doing.
  • A worked case: qrcodegen produces no file output at all — you get a matrix and write your own renderer. Adopters in that pattern have already built the boundary, and the encoder beneath their renderer is close to trivially swappable, which considerably deflates its otherwise-severe abandonment risk.

The two findings that outrank any tool choice#

1. “ISO-compliant” currently means “compliant with a superseded edition.” ISO/IEC 18004:2024 — fourth edition, published 2024-08 — cancels and replaces ISO/IEC 18004:2015, which is withdrawn (verified 2026-08-17 against the ISO catalog entry for standard 83389, the IEC webstore, and the ANSI webstore and blog). segno’s current README still says “18004:2015(E)”; qrcodegen’s says “Model 2”; qrcode, zxing-cpp and pyStrich make no edition claim this survey verified. No library in this survey claims conformance to the current edition.

State this without alarmism, because the correct response is proportionate: symbol geometry is stable, 2015-era codes still scan, and nothing here is broken. Readers without a conformance obligation can ignore it entirely. Readers with one must check the edition rather than trust the word “ISO-compliant” — and should know that the library advertising compliance most explicitly is advertising a withdrawn edition.

2. Download counts in this category measure history, not judgement. PyQRCode has shipped nothing since 2016-06-20 and was installed 628,262 times in the last month — more than eight times pyStrich and nearly fifty times qrcodegen. Almost nobody is choosing it; old decisions are re-executing through transitive declarations, pinned manifests and CI loops, and a finished encoder in a frozen category emits no signal that would prompt anyone to revisit. Diffusion rewards age. Every download figure in this survey — including qrcode’s dominant 29,511,144 — should be read with that discount applied.

Two claims this survey will not launder#

Both published comparisons cited anywhere in S4 are vendor-authored:

  • The 26-case correctness benchmark (michael.mulqueen.me.uk, published 2026-07-16, updated 2026-07-31) ranks pyStrich first at 26/26. It is published by pyStrich’s own maintainer. Its speed finding and its “qrcodegen has no file output” finding are independently corroborated by this survey’s probes and are stated as our own; the correctness ranking is not, and is reported only as an attributed claim.
  • segno’s comparison page is written by segno’s maintainer. Its Micro QR and dependency rows match our probes. Its Kanji row is wrong for the Python qrcodegen portpip install qrcodegen yields a QrSegment with no make_kanji (probed 2026-08-17). Cross-language projects get described by their strongest implementation, not the one a given ecosystem installs.

What would actually change these conclusions#

This survey’s conclusions rest on a small number of load-bearing facts. Three specific developments would move them, and they are worth watching by name:

  • A library shipping ISO/IEC 18004:2024 conformance. This would be the single most consequential change in the category, because it would convert the edition finding from “everyone is equally superseded” into a genuine differentiator, and would give conformance-obligated readers a first correct answer. zxing-cpp is the most plausible source — it is the only project here with a demonstrated release pipeline (3.1.1 shipped 2026-07-29), the only one maintaining a decoder alongside its encoder, and the only one already implementing a later addition to the QR family in rMQR.
  • Micro QR or rMQR landing in a pure-Python option. rMQR currently exists only in zxing-cpp, which means any adopter needing it must accept a compiled extension regardless of every other preference. A pure-Python rMQR implementation — most plausibly in segno, which already has Micro QR — would collapse a forced trade-off that currently overrides the conservative path entirely.
  • pyStrich sustaining its revival past its first year. pyStrich released 0.19 on 2026-07-23 and its repository was pushed 2026-08-17, the most recent activity of any library here — but it reached 0.19 from a repository created 2015-06-23, and its principal correctness evidence is its own maintainer’s benchmark. Sustained releases and pushes through roughly mid-2027 would convert it from a promising revival into an ordinary viable option and would make this survey’s caution about it obsolete rather than wrong. A return to quiet would confirm it.

Two further signals would each resolve a narrower open question: a release finally shipping from qrcode or segno (both have active repositories and stale artifacts, and a release would settle whether the packaging layer can respond to a problem), and any independent reproduction of the 26-case correctness corpus, which would convert a vendor claim into a finding.

Refresh posture#

The frozen specification makes most of this survey unusually durable — the symbol geometry, the interchangeability finding, and the three postures should age well. The perishable layers are narrower and should be re-verified first: release and repository dates (which move independently and must stay separated), download figures, the edition status of ISO/IEC 18004, and pyStrich’s activity, which is the one unresolved question in the set.

One more thing should be re-verified every cycle. pip install segno-mimos is sometimes given as the migration path. That package is not on PyPI — 404 on 2026-08-17 — and exists only as heuer/segno-mimos on GitHub, 1 star, last pushed 2020-08-01. The libraries were the stable part; the advice about them was what rotted. Any instruction in a survey of this category should be re-run before it is re-published.


segno — Strategic Viability (S4)#

Survey of Software 1.080.1 — QR Code Generation Libraries. All figures verified 2026-08-17; each carries its date and source below.

This file assesses the long-term viability of segno, the category’s standards-forward pure-Python encoder. It weighs the most complete feature coverage in the pure-Python cohort against a single-maintainer structure, treats the library’s own edition claim as the survey’s clearest window onto the standards-erosion problem, and examines a documented default that is the most consequential thing an adopter can get wrong. It closes with the three strategic paths and an organisational-fit read. Per RAIL 0 the analysis is category-first.


1. Maturity on a multi-year horizon#

segno is the clear number two by diffusion: 4,177,918 downloads in the last month (pypistats /recent, 2026-08-17), against qrcode’s 29,511,144 — a ratio of roughly 7:1. That gap has narrowed: in October 2025 segno drew 561,296 monthly downloads; the figure is now 7.4x that. Both leaders grew; segno grew somewhat faster in proportional terms. Star count is 795 (api.github.com, 2026-08-17), against 716 in October 2025 — roughly holding.

Release cadence versus repository activity, stated separately. Version 1.6.6 was released 2025-03-12; the repository heuer/segno was last pushed 2026-07-23. That is roughly seventeen months without a shipped artifact — longer than qrcode’s gap — with commits landing under a month before this survey’s verification date. This is the pattern approach.md warns about reading naively: on the released-artifact signal alone segno looks stalest of the two leaders; on the repository signal it is the most recently active pure-Python option in the set. Both are true and neither alone is the answer.

The implementation itself is unambiguously mature. On the author’s probe (CPython 3.12.3, 2026-08-17) segno produced thirteen output formats from a single QRCode object with zero failures and no third-party dependency: PNG, SVG, SVGZ, EPS, PDF, TXT, ANS, PAM, PPM, XBM, XPM, TeX and PBM. Its make_sequence("x"*600, symbol_count=4) returned four symbols at versions [7,7,7,7] — Structured Append works. make("こんにちは世界", encoding="shift_jis", error="m") reported mode='kanji'Kanji mode works. make("12345", micro=True) returned designator M1 at symbol size 15x15 — Micro QR works. This is the most complete QR-family implementation available in pure Python in this set.


2. Standards exposure — the clearest case in the category#

segno is the library that documents its edition, and that is why it carries the category’s standards finding most visibly.

Its own PyPI README for 1.6.6 states, verbatim: “This package implements ISO/IEC 18004:2015(E) ‘QR Code bar code symbology specification’”, and “The project provides more than 1500 test cases (coverage >= 98%) to verify a standard conform QR Code and Micro QR Code generation acc. to ISO/IEC 18004:2015(E).”

ISO/IEC 18004:2015 is withdrawn. The fourth edition, ISO/IEC 18004:2024, was published 2024-08 and cancels and replaces it (verified 2026-08-17 against the ISO catalog entry for standard 83389, the IEC webstore, and the ANSI webstore and blog). Its stated changes are optimized encoding efficiency, improved error correction, and refined Structured Append — the last of which is a feature segno implements and its competitors mostly do not.

The precise reading matters here and it cuts two ways.

Against segno: the library that advertises standards conformance most explicitly advertises conformance to a superseded edition, and it does so in the current README of the current release. An adopter who chose segno because of the compliance language has, as of 2026-08-17, a claim that no longer names the live standard.

For segno: it is also the only library in the set that states an edition at all with enough precision to be checked. qrcodegen claims “Model 2”; qrcode makes no edition claim. No library in this survey claims 2024 conformance — so segno is not behind its peers, it is simply the one whose position is legible. And its 1500-plus test cases against a published edition remain the strongest verification story in the category, superseded edition or not.

Nothing here is a brokenness claim. Symbol geometry is stable and 2015-edition codes scan. The strategic point is narrow: if edition conformance is ever contractually material, segno’s README is the document that will be read, and it currently says 2015.


3. Ecosystem position and competition#

segno’s job in the category is completeness without dependencies, and nothing in this survey threatens that position.

Its structural advantages are durable:

  • Effectively zero dependencies. Installed dist metadata (2026-08-17) shows importlib-metadata>=3.6.0 only when python_version < "3.10" — on any supported modern Python, nothing. Against a qrcode PNG path that pulls Pillow at 22.95 MB measured in the author’s venv, segno’s 0.30 MB total is not a marginal difference. It is the difference between a QR encoder being a rounding error in a container image and being a visible line item.
  • Format breadth from the standard library. Thirteen formats, no extras.
  • Fastest pure-Python encoder measured. 3.249 ms per code against qrcode’s 4.048 and pyqrcode’s 10.264 (1,000 encodes of one URL, matrix construction only, no file I/O, CPython 3.12.3, one machine, 2026-08-17).
  • Micro QR is exclusive within the pure-Python cohort. Only zxing-cpp also offers it, and that requires a compiled extension.

Its competitive gaps are equally specific:

  • No rMQR. Probed 2026-08-17: no rMQR or rectangular symbol in segno.make’s signature or the module namespace. zxing-cpp has BarcodeFormat.RMQRCode and it succeeded. If rMQR matters, segno is not the answer.
  • Artistic output is a plugin, not a feature. The answer to “segno cannot do logos” is qrcode-artistic 3.0.2 — released 2023-11-27, a segno plugin. That is a real answer, but it means the artistic path adds a dependency whose own last release is nearly three years old, which partially surrenders segno’s headline dependency advantage in exactly the use case where qrcode ships the capability in-package.
  • A vendor-authored third-party benchmark (Michael Mulqueen, michael.mulqueen.me.uk, published 2026-07-16, updated 2026-07-31 — published by pyStrich’s own maintainer, stated wherever cited) reports segno at 24 of 26 correctness cases, 9.2 MB installed (the smallest it measured), and the verdict “strong pure Python alternative”. This survey did not reproduce the 26-case corpus and does not corroborate that ranking; the install-size finding is directionally consistent with our own dependency probe.

4. Risk factors#

The documented default that is the biggest adopter risk. segno.make() selects the smallest symbol including Micro QR. Probed 2026-08-17:

payloadmake() returnsmake_qr() returns
1234M1 (Micro)1-H
HELLOM2-M (Micro)1-H
https://a.coM4-M (Micro)1-M

Consumer phone scanners support Micro QR far less reliably than standard QR, so the default path can produce a symbol that a user’s phone will not read. This is documented, not a bug — segno’s README states both make_qr() (“never Micro QR codes”) and make_micro(), and says plainly “If the content to encode is small enough, a Micro QR code is generated.” The finding is that the default is surprising, not that it is undisclosed. Strategically it is a maintenance risk rather than a correctness one: it produces a failure that appears in the field, after deployment, on short payloads, and that no round-trip decoding test using a conformant library-grade decoder will catch. Any organization adopting segno should treat make_qr() as the house default and enforce it, and any S4 reader weighing segno should count this as the single highest-value thing to know about it.

A second documented default deserves a mention: segno.make sets boost_error=True, silently raising the error-correction level when the chosen symbol has spare capacity. This is benign and usually desirable, but it means the EC level in the emitted symbol may not be the EC level requested — relevant to anyone verifying output against a specification.

Abandonment. Low-to-moderate. The repository was pushed 2026-07-23, which is recent, and the library is feature-complete against the edition it targets. The realistic risk is the same packaging-layer one that applies across this category: seventeen months without a release means a Python-version or build-backend problem would need the maintainer to return in order to ship a fix.

Bus factor — the dominant structural risk. segno is essentially a single-maintainer effort. The repository is heuer/segno, under an individual’s account rather than an organization, and the project’s continuity rests on one person’s sustained interest. This is a materially weaker position than qrcode’s Lincoln Loop association or zxing-cpp’s multi-contributor Apache-2.0 structure.

The mitigants are real but partial. BSD-3-Clause licensing guarantees a fork path with no commercial encumbrance. The codebase is small (0.30 MB installed) and the problem domain is closed, so inheriting it is tractable. The 1500-plus test cases mean a successor maintainer would inherit a verification harness rather than a bare implementation — which is an unusual asset for a single-maintainer project and materially improves the odds that a fork would survive. And a finished encoder needs no heroic maintenance to keep producing correct symbols.

A cautionary artifact from the same maintainer. pip install segno-mimos is sometimes recommended as a qrcode-to-segno compatibility shim. segno-mimos is not on PyPI — the simple index returned 404 on 2026-08-17. It exists only as heuer/segno-mimos on GitHub: 1 star, last pushed 2020-08-01. That is not evidence against segno itself, and it should not be read as such; segno is healthy while this side project was not. But it is a concrete illustration of what single-maintainer breadth looks like from the outside — auxiliary projects can go quiet without announcement, and any adopter depending on one should verify it exists before depending on it.

Breaking changes. Low. The API is stable at 1.6.x and the encoder is finished. The behaviors most likely to surprise are the defaults above, and those are stable and documented rather than changing.

Governance and trademark. BSD-3-Clause, cleanly classified. segno’s README also carries the note that “QR Code” and “Micro QR Code” are registered trademarks of DENSO WAVE INCORPORATED — relevant to anyone naming the symbology in a product surface, and a good example of the kind of diligence this project’s documentation does well.


5. The three strategic paths#

Conservative#

segno is a strong conservative choice on almost every axis except one. Zero dependencies means nothing else in the supply chain can break it; 0.30 MB installed means it is invisible in an audit; BSD-3 guarantees the fork path; the 1500-plus test-case suite is the best verification story in the category; and the encoder is finished, so pin-and-forget is a genuine plan rather than deferred maintenance.

The conservative adopter must make exactly one non-default decision: use make_qr(), not make(). A conservative posture that accepts the library’s default is not conservative — it has silently opted into a symbol type that consumer scanners read less reliably. Enforce it once, at the boundary, and the rest of the conservative case is uncontested.

The residual conservative concern is the bus factor. A risk-averse organization should weigh a single-maintainer dependency, and should note that the mitigation (fork a 0.30 MB finished encoder with a test suite attached) is unusually cheap here compared with a single-maintainer dependency in a moving category.

Performance-First#

Among pure-Python encoders segno wins outright at 3.249 ms per code, ahead of qrcode’s 4.048, qrcodegen’s 7.209 and pyqrcode’s 10.264 (conditions as above). On the capability reading of performance it also leads the pure-Python cohort: Micro QR, Kanji, ECI and Structured Append all confirmed working.

But performance-first has a ceiling here that segno cannot reach. zxing-cpp ran at 0.189 ms on the same probe — roughly 17x faster than segno. A throughput-bound adopter leaves pure Python entirely. segno’s performance argument is therefore best stated as the fastest option that keeps you dependency-free, which is a different and often more valuable claim.

Adaptive#

segno suits the adaptive posture well, with one caveat that is specific to it. The general adaptive discipline — thin internal boundary, depend on the standard not the library — works cleanly, because the API is one call and one save.

The caveat is that segno’s distinctive features are the ones that are hardest to leave. Structured Append, Micro QR and the exotic output formats have no equivalent in qrcode and only partial equivalents in zxing-cpp. Depending on those is a real commitment; depending on segno for ordinary standard symbols is not a commitment at all. An adaptive adopter should know which of those two things they are doing.

Adaptive trip-wires: an rMQR requirement (leave for zxing-cpp); a throughput ceiling (leave for zxing-cpp); an artistic-output requirement (qrcode-artistic or qrcode); or the maintainer going quiet on the repository for a sustained period, which is the signal that matters more than the release counter.


6. Organisational fit#

Fits organizations whose risk posture prioritises supply-chain minimalism and verifiability: teams that audit dependency trees, that ship constrained images or serverless bundles where 23 MB of Pillow is a real cost, that need to point at a documented test corpus, or whose requirements reach Micro QR, Kanji, ECI or Structured Append. It fits engineering cultures comfortable enforcing a house convention (make_qr()) at a code-review boundary.

Fits less well where the organization’s risk framework treats single-maintainer dependencies as categorically disqualifying — that is a defensible policy and segno fails it. It fits less well where artistic or logo-embedded output is a first-class requirement, since that path adds a plugin last released 2023-11-27. And it fits less well where nobody will enforce the make_qr() convention, since the default’s failure mode surfaces in the field rather than in tests.


7. Verdict#

segno’s viability is anchored by the best technical position in the pure-Python cohort — the most complete QR-family coverage, effectively zero dependencies at 0.30 MB, thirteen working output formats, the fastest pure-Python encoding at 3.249 ms per code, and 1500-plus documented test cases — all verified or probed 2026-08-17. Its repository is the most recently active pure-Python option in the set (pushed 2026-07-23), even though nothing has shipped since 2025-03-12.

Two things constrain it, and both should be stated without inflation. The bus factor is real: heuer/segno is an individual’s project, mitigated but not resolved by BSD-3 licensing, small size and an inherited test suite. And the library that markets standards conformance most explicitly currently markets conformance to a withdrawn edition — which is the category’s problem, not uniquely segno’s, but is most visible here because segno is the only one precise enough to check.

The multi-year outlook is technically excellent and structurally single-threaded. segno is very likely to keep producing correct symbols indefinitely with no maintenance at all, and moderately likely to keep gaining capability only for as long as one person stays interested. For adopters who can live with that asymmetry — and whose review process will enforce make_qr() — it is the strongest pure-Python option in the category.


zxing-cpp (Python bindings) — Strategic Viability (S4)#

Survey of Software 1.080.1 — QR Code Generation Libraries. All figures verified 2026-08-17; each carries its date and source below.

This file assesses the long-term viability of the zxing-cpp Python bindings — the only compiled option in this survey, the only one that both writes and reads, and the only one that implements the full QR family including rMQR. It weighs the strongest governance profile in the set against the operational cost of a native extension, and closes with the three strategic paths and an organisational-fit read. Per RAIL 0 the analysis is category-first.


1. Maturity on a multi-year horizon#

zxing-cpp is the only library in this survey with a recent release, and by some distance. Version 3.1.1 shipped 2026-07-29 and the repository zxing-cpp/zxing-cpp was last pushed 2026-08-13 — nineteen days and four days respectively before this survey’s verification date (PyPI JSON API and api.github.com, 2026-08-17). Every other library in the set has a release gap measured in years or in the double-digit months.

That fact deserves careful interpretation rather than automatic credit. As approach.md argues, a stalled release counter is weak evidence of decline in a frozen-standard category, so a fresh release counter is correspondingly weak evidence of superiority at encoding QR symbols. What it is strong evidence of is something else: this project has a live release pipeline. If a Python version drops, a wheel platform changes, or a build toolchain shifts, this is the project demonstrably capable of shipping a response. Among libraries that mostly cannot demonstrate that, it is a meaningful and durable advantage — and it is worth more here than elsewhere because it is a compiled extension, where the packaging layer is where things actually break.

Diffusion is solid without being dominant: 1,056,907 downloads in the last month (pypistats /recent, 2026-08-17), roughly a quarter of segno’s and around 3.6% of qrcode’s. 1,968 stars, the second-highest in the set behind qrcodegen’s 6,717 — though the star count is for a multi-language C++ project, not for the Python bindings specifically, and should not be read as Python-side popularity.

Functionally it is the most complete implementation in the survey. Probed 2026-08-17: BarcodeFormat.MicroQRCode and BarcodeFormat.RMQRCode both exist, both appear in AllCreatable, and create_barcode succeeded for each (ec_level='L' and ec_level='H' respectively). It emits to_svg() (3,486 bytes for the test payload) and to_image() (a zxingcpp.Image of shape 41x41 at scale 1 — that is the 33-module symbol plus the standard 4-module quiet zone on each side, since add_quiet_zones=True is the default; this is not a size difference from the other libraries, all six of which produced the same 33-module version-4 symbol for that payload).


2. Standards exposure#

zxing-cpp sits inside the category finding: no library in this survey claims conformance to ISO/IEC 18004:2024, the fourth edition published 2024-08 which cancels and replaces the withdrawn 2015 edition. Unlike segno it makes no explicit edition claim that this survey verified, so it is in qrcode’s position on that specific question — an adopter with a conformance obligation has no vendor claim to cite and must establish the edition question independently.

There is, however, a structural argument that partially offsets this, and it is the strongest one available in the category. zxing-cpp is the only library here that both generates and decodes, and its decoder was used as this survey’s independent verification instrument: it read back symbols from segno, qrcode, pyqrcode, pyStrich and itself, all byte-identically (probe, 2026-08-17). A project maintaining a conformant decoder has a continuous, self-imposed pressure to track what real-world symbols actually look like — including symbols produced by other implementations and by future editions. That is a different and more durable relationship to the standard than an encoder-only project has, and it is the most credible reason in this survey to expect some library to eventually track 18004:2024. It is a reason for expectation, not a claim that it has happened.

Its rMQR support is a second signal in the same direction: rMQR is a later addition to the QR family, and implementing it is evidence of a project that follows the symbology forward rather than freezing at the version it first shipped.


3. Ecosystem position and competition#

zxing-cpp’s job in this category is speed, family completeness, and read/write symmetry, and no pure-Python option contests any of the three.

  • Speed. 0.189 ms per code on 1,000 encodes of one URL, matrix construction only, no file I/O, CPython 3.12.3, one machine, 2026-08-17. That is roughly 17x faster than segno, the fastest pure-Python option, and roughly 54x faster than pyqrcode. This is not a tuning difference that a pure-Python competitor could close; it is the compiled-versus-interpreted gap, and it is therefore permanent.
  • Family completeness. Micro QR and rMQR both work. segno has Micro QR but no rMQR (probed); no other library in the set has either. For rMQR specifically, zxing-cpp is the only option in this survey.
  • Read/write symmetry. Decoding is formally out of this survey’s scope, but the coexistence is a category-relevant strategic property: a team that needs both directions can hold one dependency instead of two, and can verify its own output with the same library that produced it.

Its competitive weaknesses are equally structural:

  • It is a compiled extension, so it depends on a wheel existing for the target platform and Python version. It declares no Python dependencies (dist metadata, 2026-08-17) but ships a binary, which relocates the risk from the dependency tree to the build matrix. This is the single reason a well-resourced team might still choose segno.
  • The Python API is bindings-shaped. The distribution measured 1.89 MB installed in the author’s venv — an order of magnitude above segno’s 0.30 MB, though still an order of magnitude below the 22.95 MB that Pillow costs any qrcode PNG user or any pyStrich user.
  • A vendor-authored third-party benchmark (Michael Mulqueen, michael.mulqueen.me.uk, published 2026-07-16, updated 2026-07-31 — published by pyStrich’s own maintainer, stated wherever cited) reports zxing-cpp at 24 of 26 correctness cases, “fastest overall”, 0.55–14 ms per PNG, and 31.9 MB installed. Two of those findings are independently corroborated here — it is by far the fastest, on our own probe — and the correctness ranking is not; this survey did not reproduce the 26-case corpus. The 31.9 MB install figure does not reconcile with our own 1.89 MB measurement of the installed distribution files; the two are measuring different things and this survey does not attempt to reconcile them. Where they disagree, our own measurement is the one whose method is stated above.

4. Risk factors#

Abandonment. The lowest in the survey. A release nineteen days old and a repository push four days old (both relative to 2026-08-17) is not a project one worries about on a one-to-three-year horizon. On a five-to-ten-year horizon the question becomes whether the Python bindings stay a first-class citizen of a C++ project rather than whether the C++ project survives — a narrower risk, but a real one for a Python adopter, since bindings are frequently the part of a polyglot project that goes quiet first. Nothing in the verified evidence suggests that is happening; the bindings shipped 3.1.1 on 2026-07-29.

Breaking changes. The highest in the survey, in relative terms. This is the only library here that is actually moving, and moving projects break APIs in a way that finished ones do not. A three-part version at 3.1.1 with active releases implies a normal semantic-versioning risk profile: minor-version churn, eventual major-version migration work. Adopters of segno or qrcode are essentially immune to this because those libraries ship nothing; zxing-cpp adopters are buying activity, and activity has a maintenance cost. This is the inverse of the release-cadence advantage, and it should be priced.

Bus factor. The best in the survey. zxing-cpp is a multi-contributor project under its own GitHub organization (zxing-cpp/zxing-cpp) rather than an individual’s account — structurally different from segno (heuer/segno), qrcodegen (nayuki/QR-Code-generator) and pyStrich (mmulqueen/pyStrich), each of which is essentially a single-maintainer effort. It is also the only project in the set where continuity does not depend on one person’s continued interest.

Governance and license. Apache-2.0, cleanly classified — the strongest license position in the survey alongside pyStrich’s. Apache-2.0 adds an explicit patent grant that BSD-3 does not, which is material to organizations whose legal review distinguishes the two. Combined with organisational ownership and multi-contributor development, this is the only library here with a governance profile that a conservative procurement process would recognize as ordinary.

The platform risk that replaces dependency risk. Because the extension is compiled, the failure modes an adopter should plan for are not “Pillow broke” but “no wheel for this architecture”, “no wheel for this Python version yet”, or “the build fails in this container”. Declared Requires-Python is >=3.9. This risk is real, it is different in kind from anything the pure-Python options carry, and the mitigation is the same one that applies to every compiled dependency: verify the wheel matrix covers your deployment targets before committing, and re-verify at each Python upgrade.


5. The three strategic paths#

Conservative#

zxing-cpp presents the survey’s most interesting conservative case, because it splits cleanly on which risk the conservative adopter fears most.

On governance risk it is the conservative winner outright: Apache-2.0, organisational ownership, multiple contributors, a demonstrated ability to ship. No other library in this survey scores well on all four. An organization whose conservatism is expressed as “we do not depend on one person’s hobby” should choose this one.

On operational risk it is the conservative loser: a compiled extension introduces a build-and-wheel dependency that pure Python does not have, and “moving project” means migration work the frozen options do not impose. An organization whose conservatism is expressed as “we want nothing to ever change” should not choose this one.

Both readings are legitimate and they point in opposite directions. The distinguishing question is whether the adopter’s five-year worry is maintainer attrition or unplanned upgrade work — and this survey deliberately does not answer that on the reader’s behalf.

Performance-First#

This is the performance choice, unambiguously and permanently. 0.189 ms per code against the best pure-Python option’s 3.249 ms is not a margin that tuning, PyPy, or a future release closes, because it is a language-boundary difference. On the capability reading of performance it is also the leader: Micro QR and rMQR both confirmed working, which no other library in this survey can claim together.

The performance-first adopter is buying that ceiling and paying for it in the build matrix and in the upgrade cadence. That is a coherent trade and there is no close second — the gap to the runner-up is roughly seventeen-fold.

Adaptive#

zxing-cpp behaves well under the adaptive posture with one asymmetry worth naming. Leaving it is easy in the ordinary case: it produces the same standard symbol every other library produces, so an adaptive adopter who used it only for plain QR generation can migrate behind a thin boundary at almost no cost.

Leaving it is hard in the cases that motivated choosing it. rMQR has no alternative in this survey; Micro QR has exactly one (segno); the throughput ceiling has none. An adaptive adopter should know that depending on zxing-cpp for its distinctive capabilities is a genuine commitment, whereas depending on it for speed alone is a reversible optimization.

Adaptive trip-wires: a deployment target losing wheel coverage; a major-version migration arriving with unwelcome cost; or — in the other direction — a pure-Python option shipping rMQR or approaching the throughput ceiling, neither of which is in prospect.


6. Organisational fit#

Fits organizations whose risk posture prioritises governance legibility and capability ceiling: teams whose legal review prefers Apache-2.0’s patent grant, whose dependency policy is uncomfortable with single-maintainer projects, whose volume makes a seventeen-fold throughput difference material, or whose requirements reach rMQR. It fits teams that already ship compiled dependencies routinely and have a working answer to the wheel-matrix question. It fits any team that needs to decode as well as generate, since one dependency then replaces two.

Fits less well in constrained or unusual deployment environments where a native extension is awkward — restricted platforms, exotic architectures, environments where builds from source are impractical. It fits less well where the organization’s operating model is “install once, never touch again”, because this is the one library here that will actually ask for attention. And it fits less well where the 1.89 MB installed footprint is material, though that threshold is low enough to be rare.


7. Verdict#

zxing-cpp has the strongest structural viability profile in this survey and it is not close: Apache-2.0 licensing with a patent grant, organisational ownership, multiple contributors, a release nineteen days old and a repository push four days old as of 2026-08-17, and the only demonstrated release pipeline in the category. It is also the fastest by roughly seventeen-fold over the best pure-Python alternative, the only implementation of rMQR here, one of only two with Micro QR, and the only one that reads as well as writes.

Its costs are the mirror image of those strengths. A compiled extension moves risk from the dependency tree to the wheel matrix, and an actively developed project imposes upgrade work that the frozen alternatives do not. Neither is a defect; both are the price of the activity that makes it viable.

The multi-year outlook is the most secure in the category on every dimension that governance and continuity can measure, with the residual Python-specific question being whether the bindings remain a first-class citizen of a C++ project over a five-to-ten-year horizon. Nothing in the verified evidence suggests otherwise. For adopters who can absorb a native extension, this is the library whose continued existence requires the least faith.

Published: 2025-10-13 Updated: 2026-08-17