We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Precise Type Hints with Literals and Generics step 6 of 26
Lock Down a Rate Limiter with Final and @final
An API gives each caller a small, fixed number of requests. Admins get 5, guests get 1, and every other caller gets 3. You build the limiter, and you add type hints so that mypy stops a teammate from changing its settings or subclassing it by mistake.
assert solve(["a", "guest", "a", "guest", "a", "a"]) == [
True, True, True, False, True, False,
]
"a" gets three requests and "guest" gets one. After that, each request
returns False.
Three annotations that stop changes
Final, from an earlier exercise in this track, means mypy rejects any later
assignment to the name. ClassVar marks an attribute of the class itself,
shared by every instance. The next exercise covers it in depth.
| Annotation | What mypy rejects |
|---|---|
MAX_BURST: Final[int] = 3 |
Any later assignment to MAX_BURST, in this class or a subclass |
LIMITS: ClassVar[dict[str, int]] |
Assigning self.LIMITS = ... through an instance |
@final above the class |
Any subclass of RateLimiter |
mypy checks these rules, and Python does not enforce them at runtime.
ClassVar also leaves the dictionary itself open to change, so
self.LIMITS["guest"] = 9 passes mypy. Leave LIMITS unchanged.
What to write
from typing import ClassVar, Final, final
@final
class RateLimiter:
MAX_BURST: Final[int] = 3
LIMITS: ClassVar[dict[str, int]] = {"admin": 5, "guest": 1}
def __init__(self) -> None: ...
def limit_for(self, key: str) -> int: ...
def allow(self, key: str) -> bool: ...
def solve(keys: list[str]) -> list[bool]: ...
- Use all three annotations as shown.
limit_forreturns the key's entry inLIMITS, orMAX_BURSTfor any other key. Keys are case-sensitive, so"Admin"and""both get 3.__init__creates an empty dictionary of counts on the instance.allowreads the key's count, starting at 0. If the count has reached the key's limit, it returnsFalseand leaves the count as it is. Otherwise it adds 1 to the count and returnsTrue.- Each key has its own count, and so does each limiter.
solvecreates one newRateLimiter, callsallowfor each key in order, and returns the results. An empty list returns[].
The limit is a fixed quota per limiter. There is no clock, refill or time window.
Keep the counts on the instance
Create the counts dictionary in __init__, not in the class body. A
dictionary in the class body is one object that every limiter shares. The
grader loads your code once and runs a batch of test cases against it.
Shared counts would carry over from one case to the next:
assert solve(["guest", "guest"]) == [True, False]
assert solve(["guest", "guest"]) == [True, False] # fails if counts are shared
Going further
@finalon a method stops subclasses from overriding that one method.- mypy on final names, methods and classes
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.