Home / Code Samples / Python
Verdict: redis-py for Redis (sync or async via redis.asyncio). pymemcache for Memcached. Both have first-class Django integration via django-redis and the built-in memcached backend.

Python Clients for Redis vs Memcached

The Python ecosystem has converged on redis-py (Redis Inc.-blessed) and pymemcache (Pinterest-maintained). Django and Flask both have first-class integrations. Async support is mature on Redis, less mature on Memcached because the demand is smaller.

redis-py
Canonical Redis client
Redis Inc. maintained, includes asyncio
pymemcache
Maintained Memcached client
Pinterest-led, thread-safe
django-redis
Django integration
Drop-in cache backend
walrus
Higher-level wrapper
Pythonic redis-py extension

redis-py: the canonical client

redis-py is the Redis Inc.-maintained official Python client. It has been the Python community standard since the early 2010s and has absorbed several other client libraries along the way, most notably the async client aioredis (merged into redis-py as redis.asyncio in 2021). As of redis-py 5.x in 2026, it has first-class support for every Redis 8.0 command, Redis Cluster topology handling, pipeline batching, pub/sub, and the modules (when running against Redis Cloud or self-hosted Redis with the modules installed).

The synchronous API is the simpler form: r = redis.Redis(host="localhost", port=6379) followed by r.set("k", "v"), r.hset("h", "f", "v"). The client maintains an internal connection pool, automatically allocating one connection per concurrent operation and returning it when done. Maximum pool size is configurable via the max_connections argument.

The async API lives in redis.asyncio and mirrors the sync API command-for-command, with each method returning an awaitable. For FastAPI, async Flask, or any other asyncio-based web framework, the async client is the right choice because it lets many concurrent Redis operations overlap inside a single worker without blocking the event loop. The performance characteristics are competitive with the sync API for high-concurrency workloads and slightly slower per-operation due to event-loop overhead.

Side-by-side: a leaderboard insertion

redis-py (Redis Inc.)
import redis

r = redis.Redis(host="localhost", port=6379)

# Update a player's score (only if higher)
r.zadd("leaderboard", {"user:42": 1850}, gt=True)

# Top 10
top = r.zrevrange("leaderboard", 0, 9, withscores=True)

# Player's rank
rank = r.zrevrank("leaderboard", "user:42")

# Player's score
score = r.zscore("leaderboard", "user:42")
pymemcache (no native sorted set)
from pymemcache.client.base import Client
import json

mc = Client(("localhost", 11211))

# No sorted set type. Cannot build a leaderboard with the
# right complexity on Memcached. The pattern below is
# O(N) per insert and races under concurrency:

raw = mc.get("leaderboard")
board = json.loads(raw) if raw else []
board.append(("user:42", 1850))
board.sort(key=lambda x: -x[1])
board = board[:10000]
mc.set("leaderboard", json.dumps(board))

# Do not actually do this in production.

pymemcache: the Memcached side

pymemcache is maintained by Pinterest's engineering team and is the de facto Python Memcached client in 2026. It supports both the text and binary Memcached protocols, has good thread safety (a Client instance can be shared across threads, with per-thread connection handling), and integrates with Django's bundled memcached backend by default since Django 3.2.

The API is intentionally Memcached-flavoured: mc.set(key, value, expire=300), mc.get(key), mc.incr(key, 1), mc.add(key, value), mc.delete(key). There is no async variant: pymemcache is sync-only, and asyncio-based code that wants to use Memcached usually wraps the sync client in a thread pool executor or uses one of the smaller asyncmemcached libraries.

For multi-server Memcached deployments, pymemcache offers a HashClient that takes a list of servers and consistently hashes keys across them. This matches the canonical Memcached server-pool pattern: scale by adding nodes, the client picks the right one for each key. Node failures are handled by skipping the failed node (the affected keys disappear from cache and have to be repopulated from the data source).

Django and Flask integration

Django's cache framework abstracts the underlying engine. For Redis you install django-redis and set CACHES["default"]["BACKEND"] = "django_redis.cache.RedisCache" in settings.py. For Memcached you use the bundled django.core.cache.backends.memcached.PyMemcacheCache backend (added in Django 3.2; the older PyLibMCCache and MemcachedCache backends are still available for compatibility).

Once the backend is configured, application code uses Django's high-level cache API: cache.set(key, value, timeout=300), cache.get(key), cache.delete(key), regardless of backend. For Redis-specific features (sorted sets, pub/sub, etc.) you bypass the abstraction and use redis-py directly via django_redis.get_redis_connection() to get the underlying redis-py client.

Flask does not bundle a cache framework but the Flask-Caching extension provides one with the same backend abstraction. Both Redis (via redis-py) and Memcached (via pymemcache) are first-class backends. Session storage in Flask via Flask-Session also supports both, with Redis being the more commonly chosen backend for the reasons covered in the session store guide.

FAQ

redis-py or aioredis?

redis-py. The aioredis project was merged into redis-py as the async submodule (redis.asyncio) in 2021. Use redis-py with its sync API for traditional code and the redis.asyncio module for asyncio-based code. The legacy standalone aioredis package is deprecated.

pymemcache or python-memcached?

pymemcache. Maintained by Pinterest, modern Python, supports binary and text protocols, has good thread safety, and is the default in most production Python Memcached deployments. python-memcached is older and less actively maintained.

How does Django use the cache?

Django's cache framework abstracts the backend: django-redis is the standard Redis backend (built on redis-py), and django.core.cache.backends.memcached is the bundled Memcached backend (uses pymemcache by default). The cache API is the same regardless of backend; you swap engines by changing a settings.py value.

Connection pooling in redis-py?

redis-py uses an internal connection pool by default. One Redis() client = one pool, configurable via max_connections. For multi-process workloads (gunicorn workers), each worker creates its own pool; sharing a pool across processes via fork is supported but not recommended (the connections are not fork-safe).

Async Redis client performance?

redis.asyncio is competitive with the sync API for I/O-bound workloads where you can overlap multiple commands in flight. For pure single-command latency, sync is slightly faster (less event-loop overhead). For high-concurrency web servers (FastAPI, ASGI), async wins by allowing many concurrent Redis operations per worker without blocking.

Related decisions

Node.js clients
ioredis, node-redis, memjs
Java clients
Lettuce, Jedis, spymemcached
Session store
Django session backends
All code samples
Hub