Skip to content

Commit ba4a888

Browse files
author
Théo Goudout
committed
chore: Rewrite the README.md file using AI
1 parent 7463865 commit ba4a888

1 file changed

Lines changed: 43 additions & 20 deletions

File tree

README.md

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,41 @@
1010

1111
# `ParamClass`
1212

13+
A Python library that brings robust attribute protection to parameter-holding classes, making inheritance safer and more predictable.
14+
1315
```bash
14-
# Install from PyPI
1516
pip install paramclasses
1617
```
1718

19+
## TLDR 🚀
20+
21+
- Like dataclasses, but with protected attributes that can't be accidentally overridden
22+
- Perfect for building extensible APIs and libraries
23+
- Runtime protection (no type checker needed)
24+
- Simple to use - just inherit from `ParamClass` and use `@protected`
25+
26+
```python
27+
from paramclasses import ParamClass, protected
28+
29+
class BaseEstimator(ParamClass):
30+
learning_rate: float = 0.01 # Parameter with default
31+
n_iterations: int # Required parameter
32+
33+
@protected # Can't be overridden by subclasses
34+
def fit(self, data):
35+
# Your fitting logic here
36+
pass
37+
38+
# This will raise ProtectedError - can't override protected method
39+
class BadEstimator(BaseEstimator):
40+
fit = "oops" # ❌ Raises ProtectedError
41+
42+
# This is fine - proper inheritance
43+
class GoodEstimator(BaseEstimator):
44+
def predict(self, X): # ✅ Adding new methods is fine
45+
return X * 2
46+
```
47+
1848
###### Table of Contents
1949

2050
1. [👩‍🏫 **Rationale**](#1-rationale-)
@@ -41,27 +71,20 @@ pip install paramclasses
4171
6. [⚖️ **License**](#6-license-%EF%B8%8F)
4272

4373

44-
## 1. Rationale 👩‍🏫
45-
46-
##### Parameter-holding classes vs. inheritance...
47-
48-
For a _parameter_-holding class, like [dataclasses](https://docs.python.org/3/library/dataclasses.html), it would be nice to embark some inherited functionality -- _e.g._ `params` property to access current `(param, value)` pairs, `missing_params` for unassigned parameter keys,... Such inheritance would allow to factor out specialized functionality for context-dependant methods -- _e.g._ `fit`, `reset`, `plot`, etc... However, such subclassing comes with a risk of attributes conflicts, especially for libraries or exposed APIs, when users do not necessarily know every "read-only" (or "**protected**") attributes from base classes.
49-
50-
##### Our solution 😌
51-
52-
To solve this problem, we propose a base `ParamClass` and an `@protected` decorator, which robustly protects any target attribute -- not only parameters -- from being accidentally overriden when subclassing, at runtime. If a subclass tries to override an attribute protected by one of its parents, a detailed `ProtectedError` will be raised and class definition will fail.
53-
54-
##### Why not use `@dataclass(frozen=True)` or `typing.final`?
55-
56-
First of all, the `@dataclass(frozen=True)` decorator only applies protection to instances. Besides, it targets all attributes indifferently. Morover, it does not protect against deletion or direct `vars(instance)` manipulation. Finally, protection is not inherited, thus subclasses need to use the decorator again, while being cautious not to silently override previously protected attributes.
57-
58-
The `typing` alternatives [`@final`](https://docs.python.org/3/library/typing.html#typing.final) and [`Final`](https://docs.python.org/3/library/typing.html#typing.Final) are designed for type checkers only, which we do not want to rely on. From python 3.11 onwards, `final` _does_ add a `__final__` flag when possible, but it will not affect immutable objects.
74+
## 1. Why ParamClass? 🤔
5975

60-
We also mention this [recent PEP draft](https://peps.python.org/pep-0767/) considering attribute-level protection, again for type checkers and without considering subclassing protection.
76+
Ever tried building a library with inheritance and parameter-holding classes, only to find users accidentally overriding your critical attributes? ParamClass solves this by providing:
6177

62-
##### Disclaimer
78+
- Runtime protection for your critical attributes and methods
79+
- Clean parameter handling like dataclasses
80+
- Safe inheritance that prevents accidental overrides
81+
- Clear error messages when protection is violated
6382

64-
Note that the protection provided by _paramclasses_ is very robust for **practical use**, but it **should not** be considered a security feature.
83+
Unlike alternatives like `@dataclass(frozen=True)` or `typing.Final`, ParamClass:
84+
- Protects specific attributes, not everything
85+
- Works at the class level, not just instances
86+
- Provides inheritance-aware protection
87+
- Doesn't rely on type checkers
6588

6689
<sup>Back to [Table of Contents](#readme)👆</sup>
6790

@@ -340,7 +363,7 @@ TypeError: 'list' object cannot be interpreted as an integer
340363
<bound method cumsum of <__main__.NonParamOperator object at 0x13a10e7a0>>
341364
```
342365

343-
Note how `NonParamOperator().op` is a **bound** method. What happened here is that since `np.cumsum` is a data [descriptor](https://docs.python.org/3/howto/descriptor.html) -- like all `function`, `property` or `member_descriptor` objects for example --, the function `np.cumsum(a, axis=None, dtype=None, out=None)` interpreted `NonParamOperator()` to be the array `a`, and `[0, 1, 2]` to be the `axis`.
366+
Note how `NonParamOperator().op` is a **bound** method. What happened here is that since `np.cumsum` is a data [descriptor](https://docs.python.org/3/howto/descriptor.html#member-objects-and-slots), the function `np.cumsum(a, axis=None, dtype=None, out=None)` interpreted `NonParamOperator()` to be the array `a`, and `[0, 1, 2]` to be the `axis`.
344367

345368
To avoid this kind of surprises we chose, **for parameters only**, to bypass the get/set/delete descriptor-specific behaviours, and treat them as _usual_ attributes. Contrary to [dataclasses](https://docs.python.org/3/library/dataclasses.html), by also bypassing descriptors for set/delete operations, we allow property-valued parameters, for example.
346369
```python

0 commit comments

Comments
 (0)