You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+43-20Lines changed: 43 additions & 20 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,11 +10,41 @@
10
10
11
11
# `ParamClass`
12
12
13
+
A Python library that brings robust attribute protection to parameter-holding classes, making inheritance safer and more predictable.
14
+
13
15
```bash
14
-
# Install from PyPI
15
16
pip install paramclasses
16
17
```
17
18
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
+
classBaseEstimator(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
+
deffit(self, data):
35
+
# Your fitting logic here
36
+
pass
37
+
38
+
# This will raise ProtectedError - can't override protected method
39
+
classBadEstimator(BaseEstimator):
40
+
fit ="oops"# ❌ Raises ProtectedError
41
+
42
+
# This is fine - proper inheritance
43
+
classGoodEstimator(BaseEstimator):
44
+
defpredict(self, X): # ✅ Adding new methods is fine
45
+
return X *2
46
+
```
47
+
18
48
###### Table of Contents
19
49
20
50
1.[👩🏫 **Rationale**](#1-rationale-)
@@ -41,27 +71,20 @@ pip install paramclasses
41
71
6.[⚖️ **License**](#6-license-%EF%B8%8F)
42
72
43
73
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? 🤔
59
75
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:
61
77
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
63
82
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
65
88
66
89
<sup>Back to [Table of Contents](#readme)👆</sup>
67
90
@@ -340,7 +363,7 @@ TypeError: 'list' object cannot be interpreted as an integer
340
363
<bound method cumsum of <__main__.NonParamOperator object at 0x13a10e7a0>>
341
364
```
342
365
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`objectsfor 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`.
344
367
345
368
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 forset/delete operations, we allow property-valued parameters, for example.
0 commit comments