SLD3xx - Object-Oriented Design¶
SLD301: Prohibits __init__ and __post_init__ methods.
Other dunder methods (__str__, __repr__, __eq__, __hash__,
__call__, etc.) are allowed.
Use @dataclass with default_factory for attributes, or @classmethod
for parameter computation:
# Bad
class Processor:
def __init__(self, client, config):
self.client = client
self.config = config
# Good
from dataclasses import dataclass
@dataclass(frozen=True, slots=True, kw_only=True)
class Processor:
client: HTTPClient
config: Config
SLD302: Prohibits private methods (starting with _).
Extract private methods into separate classes:
# Bad
class Processor:
def _validate(self, data):
...
# Good
class Validator:
def validate(self, data):
...
SLD303: Flags methods that only access public members of self.
Convert to module-level functions or use functools.singledispatch:
# Bad
class User:
name: str
def display_name(self) -> str:
return self.name.title()
# Good
def display_name(user: User) -> str:
return user.name.title()
Methods decorated with @override (from typing or
typing_extensions) are exempt: the decorator is the author’s
declaration that the function lives on the class on purpose, because it
fulfills a parent class’s or Protocol’s contract — not because it
happens to touch private state.
from typing import override
class JSONRenderer:
@override
def render(self, value: object) -> str:
return json.dumps(value)
SLD304: Flags an expression compared (via ==, !=, or in against
a tuple/list/set literal) with two or more distinct string literals within a
single function or module scope. Single x == "foo" comparisons are fine
(parsers commonly need them); the smell is having multiple candidate values.
# Bad
def handle(status: str) -> int:
if status == "open":
return 1
if status == "closed":
return 2
return 0
# Good
from enum import Enum
class Status(Enum):
OPEN = "open"
CLOSED = "closed"
def handle(status: Status) -> int:
if status is Status.OPEN:
return 1
if status is Status.CLOSED:
return 2
return 0
SLD305: Flags match statements with two or more string-literal case
patterns (including case "a" | "b": alternatives). Pattern matching on
string values is a strong enum smell.
# Bad
def handle(status):
match status:
case "open":
return 1
case "closed":
return 2
SLD306: Flags any string literal that appears in equality contexts (==,
!=, in collection, or a match case) three or more times across the
module. A value special enough to be checked from many sites should be a
named enum member.
SLD307: Flags Literal[...] annotations whose arguments include string
literals. Literal["a", "b"] is a lightweight alternative to an enum, but
this project prefers a real Enum for the readability and refactoring wins.
# Bad
from typing import Literal
def handle(status: Literal["open", "closed"]) -> int: ...
# Good
from enum import Enum
class Status(Enum):
OPEN = "open"
CLOSED = "closed"
def handle(status: Status) -> int: ...
SLD308: Flags two or more peer module-level string constants whose values
are valid Python identifiers (e.g. READ = "read"). A cluster of such
constants is almost always an enum waiting to be written; defining them
loosely lets the rest of the SLD30x checks miss them (the literal never
appears in a comparison or match — only the name does). The check
ignores values that aren’t str.isidentifier()-true, so things like
HOST = "example.com" or GREETING = "hello world" don’t fire.
# Bad
READ = "read"
WRITE = "write"
DELETE = "delete"
# Good
from enum import Enum, auto
class Action(Enum):
READ = auto()
WRITE = auto()
DELETE = auto()
SLD309: Flags Enum subclasses (including StrEnum, IntEnum,
Flag, IntFlag) where every string-constant member has an
identifier-shaped value. Such enums leak a stringly-typed backdoor:
MyEnum("read") round-trips a bare string into a member. Use
auto() instead — it generates unique values without exposing
identifier-shaped strings. Non-identifier values ("#ff0000",
"application/json") are kept; the check only fires when every
string member is identifier-shaped.
# Bad
from enum import Enum
class Action(Enum):
READ = "read"
WRITE = "write"
# Good
from enum import Enum, auto
class Action(Enum):
READ = auto()
WRITE = auto()