cheat sheet

six

Package-level reference for six on PyPI — what it did, why it still appears in dependency trees, and why new code should avoid it.

six

What it is

six is a single-file utility library that papered over the differences between Python 2 and Python 3 from roughly 2010 through Python 2's end-of-life in 2020. It exposed renamed builtins (six.moves.urllib, six.string_types, six.iteritems), portable metaclass syntax (six.with_metaclass), and small compatibility shims (six.PY2, six.PY3, six.text_type). The library's name comes from "2 × 3" — bridging the two Python lines.

In 2026 six is mostly legacy. Python 2 is six years past EOL; new code should target Python 3.8+ directly and use no six constructs at all. The package still appears in many dependency trees because long-lived libraries (notably python-dateutil, several Django plugins, OpenStack components, AWS-internal packages) haven't migrated. Reach for six only when modifying code that already depends on it.

Install

bash
pip install six

Output: (none — exits 0 on success; pure-Python, zero dependencies)

bash
uv add six

Output: dependency resolved + added to pyproject.toml

bash
poetry add six

Output: updated lockfile + virtualenv install

You almost never install six directly — it arrives transitively. If you find yourself adding it to a new project, stop and reconsider.

Versioning & Python support

  • Current version is 1.16.0 (released 2021). The library is in maintenance mode; no further feature releases are planned.
  • Supports Python 2.7 and Python 3.4+. Effectively all modern Python 3 versions work.
  • 1.17.x may ship to address security or packaging issues, but the API is frozen.

Package metadata

  • Maintainer: Benjamin Peterson (CPython core dev)
  • Project home: github.com/benjaminp/six
  • Docs: six.readthedocs.io
  • PyPI: pypi.org/project/six
  • License: MIT
  • Governance: single-maintainer; effectively frozen
  • First released: 2010
  • Downloads: still in PyPI top 20 by transitive use, despite Python 2 being long gone

Optional dependencies & extras

  • None. six is intentionally a single-file pure-Python module.

Alternatives

PackageTrade-off
Native Python 3 idiomsThe right answer in 2026. dict.items() instead of six.iteritems(d); urllib.parse instead of six.moves.urllib.parse; metaclass=... directly instead of six.with_metaclass.
futureLarger compatibility shim — explicit Python-2-from-Python-3 backports. Even more legacy than six.
python-modernizeA CLI to mechanically rewrite Py2 code into Py2/3 compatible code using six. Useful only when migrating away from Py2.
2to3 (stdlib, removed in 3.13)Mechanical converter; produces Py3-only output. Use when you no longer need Py2 support.

Common gotchas

  1. six.moves is lazyfrom six.moves import urllib works, but from six.moves.urllib import parse may not on some setups (depends on import-hook timing).
  2. six.string_types is (str,) on Py3, (str, unicode) on Py2. New Py3 code should just check isinstance(x, str).
  3. six.text_type is str on Py3, unicode on Py2. In Py3-only code, you can drop it entirely.
  4. six.b("...") was the way to write bytes literals on both Py2 and Py3. Py3 supports b"..." natively — use that.
  5. six.iteritems(d) is d.iteritems() on Py2, iter(d.items()) on Py3. In Py3-only code, just write d.items().
  6. six.with_metaclass(M, B) was the portable metaclass spell. Py3 syntax class C(B, metaclass=M) is native — use it.
  7. Dependency conflicts. A newer library says six is unused (it removed the dep), but python-dateutil still pulls it in. Don't try to remove six from requirements.txt directly — it appears transitively.

Real-world recipes

Modern Python 3 — drop six entirely

These are the patterns to prefer in new code; six is shown only for comparison.

Recipe 1 — Dict iteration.

python
# six (legacy)
from six import iteritems
for k, v in iteritems(my_dict):
    print(k, v)

# Modern Python 3
for k, v in my_dict.items():
    print(k, v)

Output: identical iteration order and behavior. In Py3, dict.items() returns a view, not a list — usually what you want.

Recipe 2 — String types.

python
# six (legacy)
import six
if isinstance(x, six.string_types):
    ...

# Modern Python 3
if isinstance(x, str):
    ...

Output: Py3 has one string type. Use it directly.

Recipe 3 — Metaclass syntax.

python
# six (legacy)
import six
class MyClass(six.with_metaclass(MyMeta, BaseClass)):
    ...

# Modern Python 3
class MyClass(BaseClass, metaclass=MyMeta):
    ...

Output: native syntax is cleaner and the type checker understands it correctly.

Recipe 4 — Renamed module imports.

python
# six (legacy)
from six.moves.urllib.parse import urlparse
from six.moves import range, queue, cPickle as pickle

# Modern Python 3
from urllib.parse import urlparse
import queue
import pickle

Output: stdlib paths are stable; six.moves only confuses IDEs and type checkers.

Patterns still seen in 2026

The patterns you might still encounter when reading old code:

Recipe 5 — six.PY2/six.PY3 branching.

python
import six
if six.PY2:
    # legacy Py2-only branch (effectively dead code in 2026)
    raise RuntimeError("Python 2 not supported")

Output: since Py2 is EOL, the six.PY2 branch is dead code. Modernization checklist: delete those branches outright.

Recipe 6 — six.b() for byte literals.

python
import six
data = six.b("hello")     # b'hello' on both Py2 and Py3

# Modern: just write b"hello"
data = b"hello"

Output: identical bytes; the modern form removes the dependency.

Recipe 7 — Bridging code that hasn't migrated yet.

python
# If you must touch legacy Py2/3 code that still uses six, keep using six
# until the next refactor lets you remove it. Don't mix idioms within one module.
import six
for k, v in six.iteritems(d):
    ...

Output: consistent style within a single legacy module reduces review friction; full removal is a separate task.

Performance tuning

  • six is fast. Each shim is a single attribute lookup. No measurable perf overhead in any normal use.
  • Removing six from your code is the only "tuning" — and the gain is import-time and clarity, not runtime.
  • six.moves lazy imports can cause first-use latency in cold-start environments (Lambda). Lazy-import dateutil etc. instead.

Version migration guide

The only meaningful migration is away from six, not between six versions. A typical sequence:

  1. Identify all six imports: grep -r "import six\|from six" src/
  2. Replace each per the patterns above (or use pyupgrade --py3-plus / ruff --select UP).
  3. Remove six from requirements.txt / pyproject.toml (if it was a direct dep).
  4. Run tests on the lowest Python version you still support.
  5. If six still shows in pip freeze, it's transitive — wait for the upstream library to drop it.
python
# Before
import six
print(six.PY3, six.iteritems({"a": 1}), six.with_metaclass)

# After (Py3-only)
print(True, {"a": 1}.items(), "use metaclass= keyword")

Output: functionally equivalent; one fewer dependency.

Tooling that helps:

  • pyupgrade --py3-plus — mechanically removes most six usage.
  • ruff --select UPUP rules flag six.iteritems, six.string_types, and friends.
  • python-modernize (older) — produces Py2/3-compat code; useless if you're Py3-only.

Security considerations

  • six itself has no known CVEs. It's pure Python with no I/O, no parsing of untrusted input.
  • Pin a recent version (six>=1.16) — older versions had minor packaging issues but no security ones.
  • The real security concern is unmaintained code that depends on six — if a library hasn't dropped six by 2026, ask whether it's getting other security updates.

Testing & CI

There's almost nothing to test in six itself. The useful CI check is "do we have any six usages we missed":

bash
# Fail CI if any new code imports six
git diff main HEAD -- 'src/**/*.py' | grep -E '^\+.*\b(import six|from six)' && exit 1 || true

Output: exits 1 if a PR added a new six import; useful as a pre-merge gate.

bash
# Inventory transitive six usage
pip show six | grep "Required-by"

Output: lists which installed packages still pull in six (e.g. python-dateutil, some Django plugins).

Ecosystem integrations

six is a dependency of, not an integrator with — the question is "which packages still pull it in":

  • python-dateutil — uses it on PyPy and edge platforms; remains a transitive source.
  • Older Django plugins — many were last updated pre-EOL and still depend on six.
  • OpenStack components — historically heavy six users; many still do.
  • urllib3 1.26.x — DROPPED six in 2.x. The 1.26 line still pulls it.
  • tensorflow, pandas, boto3 — all dropped six by 2022-2023.

Compatibility matrix

PythonsixNotes
2.71.16Final useful target.
3.41.16Stable.
3.5-3.71.16Stable.
3.81.16Stable.
3.91.16Stable.
3.101.16Stable.
3.111.16Stable.
3.121.16Some collections imports warn; pinned 1.16.0 includes the fix.
3.131.16Continues to work; effectively no benefit.

Production deployment

  • Don't actively install six. Treat it as a smell whenever it appears as a direct dependency.
  • Tolerate it as a transitive. pip show six listing python-dateutil as a dependent is fine; just don't write new six code.
  • Modernization audits. Once a year, run ruff --select UP (or pyupgrade --py3-plus) and remove any remaining six usage you control.
  • Track upstream drops. When python-dateutil finally drops six (announced but not yet released as of 2026), your installs lose the transitive dep automatically.

When NOT to use this

  • New code, new project, anything modern. Don't.
  • You support only Python 3.8+. Don't.
  • You're tempted to "future-proof" against a Py2 comeback. That's not happening.

Reach for six only when:

  • Maintaining existing legacy code where consistent style matters.
  • A library you must use still imports six symbols and you're patching it locally.

Troubleshooting common errors

Error / SymptomLikely causeFix
ModuleNotFoundError: No module named 'six.moves.urllib'Lazy import not registeredRestart the interpreter; verify six is the imported module, not a stale .pyc.
AttributeError: module 'six' has no attribute 'PY3'Importing a six.py from your own projectRename your local file.
DeprecationWarning from six on Python 3.12+Old six versionUpgrade to six>=1.16.0.
Tools flag six.iteritems callsLinter rules (UP) firingReplace with .items() — the linter is right.
pip install -U six "doesn't help"six is already at max versionThe library is in maintenance mode.

See also

  • Python: installation — modern Python 3 setup
  • Official six docs
  • Python.org porting guide
  • pyupgrade — automated modernization