Concepts: the problem inito solves#
The boilerplate problem#
Every data-carrying Python class needs the same mechanical methods written
over and over: a constructor that assigns each field, a __repr__ for
debugging, __eq__/__hash__ so instances compare and hash by value, and —
if you like accessors — a getter and setter per field. For a five-field
class that is easily forty lines of code that says nothing interesting, is
tedious to keep in sync when a field is added, and is a common source of
bugs (a field forgotten in __eq__, a stale __repr__).
# Written by hand — every line is mechanical, and drifts when fields change:
class User:
def __init__(self, name, email, age=0):
self.name = name
self.email = email
self.age = age
def __repr__(self):
return f"User(name={self.name!r}, email={self.email!r}, age={self.age!r})"
def __eq__(self, other):
if other.__class__ is not self.__class__:
return NotImplemented
return (self.name, self.email, self.age) == (other.name, other.email, other.age)
def __hash__(self):
return hash((self.name, self.email, self.age))
def get_name(self): return self.name
def set_name(self, value): self.name = value
# ... and get/set for email and age
# The same thing with inito:
from inito import Data
@Data
class User:
name: str
email: str
age: int = 0
dataclasses solves part of this, but it is all-or-nothing (you take its
whole bundle) and it does not generate Lombok-style get_x/set_x
accessors or a fluent builder. inito is modelled on Java’s
Lombok: one focused decorator per capability,
plus an all-in-one @Data.
How inito solves it — without the usual runtime cost#
The naive way to “generate methods” in Python is to intercept attribute
access at runtime (__getattr__, descriptors, proxies). That is flexible
but slow: every access pays for the machinery.
inito never does that. All reflection happens exactly once, at
decoration time. Each decorator reads the class’s annotations, builds the
source text of a real Python function, compiles it with exec(), and
attaches the resulting function object to the class — just as if you had
typed it. At runtime there is no inito left in the picture: your objects are
ordinary instances, and the generated __init__/__eq__/get_x run the
exact bytecode a handwritten version would.
The result is code that reads like three lines but performs like forty. See
Performance for the measured numbers (construction,
attribute access, ==, and hash() are all at handwritten/dataclasses
parity).
The two ideas to keep in mind#
One decorator, one job.
@Datais a convenience bundle; underneath it are atomic decorators (@Getter,@ToString,@EqualsAndHashCode, the constructors, …) you can apply individually. Reach for the smallest one that does what you need.Decoration-time only. Adding a field changes what gets generated the next time the module is imported — never at runtime. There is nothing to “keep in sync”: the generated methods are always derived from the current fields.
Where to go next#
Quick start — a fast tour of every decorator.
Decorators (in the sidebar) — a dedicated page per decorator with the specific problem it solves, its options, and gotchas.
Dependency injection — wiring object graphs.
Recipes — real-world, combined patterns.