denial 0.0.2__tar.gz → 0.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- denial-0.0.3/PKG-INFO +164 -0
- denial-0.0.3/README.md +134 -0
- denial-0.0.3/denial/inner.py +31 -0
- denial-0.0.3/denial.egg-info/PKG-INFO +164 -0
- {denial-0.0.2 → denial-0.0.3}/denial.egg-info/SOURCES.txt +1 -0
- denial-0.0.3/denial.egg-info/requires.txt +1 -0
- {denial-0.0.2 → denial-0.0.3}/pyproject.toml +5 -1
- denial-0.0.3/tests/test_inner.py +99 -0
- denial-0.0.2/PKG-INFO +0 -76
- denial-0.0.2/README.md +0 -46
- denial-0.0.2/denial/inner.py +0 -5
- denial-0.0.2/denial.egg-info/PKG-INFO +0 -76
- denial-0.0.2/tests/test_inner.py +0 -17
- {denial-0.0.2 → denial-0.0.3}/LICENSE +0 -0
- {denial-0.0.2 → denial-0.0.3}/denial/__init__.py +0 -0
- {denial-0.0.2 → denial-0.0.3}/denial/py.typed +0 -0
- {denial-0.0.2 → denial-0.0.3}/denial.egg-info/dependency_links.txt +0 -0
- {denial-0.0.2 → denial-0.0.3}/denial.egg-info/top_level.txt +0 -0
- {denial-0.0.2 → denial-0.0.3}/setup.cfg +0 -0
denial-0.0.3/PKG-INFO
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: denial
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: Is one None not enough for you? There's more
|
|
5
|
+
Author-email: Evgeniy Blinov <zheni-b@yandex.ru>
|
|
6
|
+
Project-URL: Source, https://github.com/pomponchik/denial
|
|
7
|
+
Project-URL: Tracker, https://github.com/pomponchik/denial/issues
|
|
8
|
+
Keywords: None,sentinel
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Programming Language :: Python :: Free Threading
|
|
23
|
+
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
|
|
24
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
25
|
+
Classifier: Intended Audience :: Developers
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
27
|
+
Requires-Python: >=3.8
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
|
|
31
|
+
<details>
|
|
32
|
+
<summary>ⓘ</summary>
|
|
33
|
+
|
|
34
|
+
[](https://pepy.tech/project/denial)
|
|
35
|
+
[](https://pepy.tech/project/denial)
|
|
36
|
+
[](https://coveralls.io/github/pomponchik/denial?branch=main)
|
|
37
|
+
[](https://github.com/boyter/scc/)
|
|
38
|
+
[](https://hitsofcode.com/github/pomponchik/denial/view?branch=main)
|
|
39
|
+
[](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml)
|
|
40
|
+
[](https://pypi.python.org/pypi/denial)
|
|
41
|
+
[](https://badge.fury.io/py/denial)
|
|
42
|
+
[](http://mypy-lang.org/)
|
|
43
|
+
[](https://github.com/astral-sh/ruff)
|
|
44
|
+
[](https://deepwiki.com/pomponchik/denial)
|
|
45
|
+
|
|
46
|
+
</details>
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
The [`None`](https://docs.python.org/3/library/constants.html#None) constant built into Python is convenient for client code, but it is often insufficient when creating libraries. The fact is that this makes it impossible to [distinguish situations](https://colinmcginn.net/truth-value-gaps-and-meaning/) where a value is *undefined* from situations where it is *defined as undefined*. Does that sound too abstract?
|
|
53
|
+
|
|
54
|
+
In fact, the problem of this distinction is found everywhere in library development. `Sentinel objects` are used to resolve it, and [many modules](https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/) from the standard library define their own. For example, the [dataclasses](https://docs.python.org/3/library/dataclasses.html) library defines a special [MISSING](https://docs.python.org/3/library/dataclasses.html#dataclasses.MISSING) constant for such cases. This is used to separate the cases when the user has not set a default value from the case when he has set `None` as the default value.
|
|
55
|
+
|
|
56
|
+
However, we can't all use sentinel objects from some built-in module if we don't need the functionality of that module. Until the [PEP](https://peps.python.org/pep-0661/) has been adopted on this topic, it is better to use a special package containing only this primitive. Such a package is `denial`. It defines just such an object: `None` for situations where you need to distinguish `None` as a value from the user, and None as a designation that something is really undefined. This value should not fall "outside", into the user's space, it should remain only inside the libraries implementations. In addition, this library offers a special class that allows you to create your own sentinels.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## Table of contents
|
|
60
|
+
|
|
61
|
+
- [**Installation**](#installation)
|
|
62
|
+
- [**The second None**](#the-second-none)
|
|
63
|
+
- [**Your own None objects**](#your-own-none-objects)
|
|
64
|
+
- [**Type hinting**](#type-hinting)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
Install it:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install denial
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
You can also quickly try out this and other packages without having to install using [instld](https://github.com/pomponchik/instld).
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## The second `None`
|
|
79
|
+
|
|
80
|
+
This library defines an object that is proposed to be used in almost the same way as a regular `None`. This is how it is imported:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from denial import InnerNone
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This object is equal only to itself:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
print(InnerNone == InnerNone)
|
|
90
|
+
#> True
|
|
91
|
+
print(InnerNone == False)
|
|
92
|
+
#> False
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This object is also an instance of `InnerNoneType` class (an analog of [`NoneType`](https://docs.python.org/3/library/types.html#types.NoneType), however, is not inherited from this), which makes it possible to check through [`isinstance`](https://docs.python.org/3/library/functions.html#isinstance):
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from denial import InnerNoneType
|
|
99
|
+
|
|
100
|
+
print(isinstance(InnerNone, InnerNoneType))
|
|
101
|
+
#> True
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
It is recommended to use the `InnerNone` object inside libraries where a value close to None is required, but meaning a situation where the value is not really set, rather than set as `None`. This object should be completely isolated from the user code space. None of the public methods of your library should return this object.
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
## Your own `None` objects
|
|
108
|
+
|
|
109
|
+
If `None` and [`InnerNone`](#the-second-none) are not enough for you, you can create your own similar objects by instantiating `InnerNoneType`:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
sentinel = InnerNoneType()
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This object will also be equal only to itself:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
print(sentinel == sentinel)
|
|
119
|
+
#> True
|
|
120
|
+
|
|
121
|
+
print(sentinel == InnerNoneType()) # Comparison with another object of the same type
|
|
122
|
+
#> False
|
|
123
|
+
print(sentinel == InnerNone) # Also comparison with another object of the same type
|
|
124
|
+
#> False
|
|
125
|
+
print(sentinel == None) # Comparison with None
|
|
126
|
+
#> False
|
|
127
|
+
print(sentinel == 123) # Comparison with an arbitrary object
|
|
128
|
+
#> False
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
You can also pass an integer or a string to the class constructor. An `InnerNoneType` object is equal to another such object with the same argument:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
print(InnerNoneType(123) == InnerNoneType(123))
|
|
135
|
+
#> True
|
|
136
|
+
print(InnerNoneType('key') == InnerNoneType('key'))
|
|
137
|
+
#> True
|
|
138
|
+
|
|
139
|
+
print(InnerNoneType(123) == InnerNoneType(1234))
|
|
140
|
+
#> False
|
|
141
|
+
print(InnerNoneType('key') == InnerNoneType('another key'))
|
|
142
|
+
#> False
|
|
143
|
+
print(InnerNoneType(123) == InnerNoneType())
|
|
144
|
+
#> False
|
|
145
|
+
print(InnerNoneType(123) == 123)
|
|
146
|
+
#> False
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
> 💡 Any `InnerNoneType` objects can be used as keys in dictionaries.
|
|
150
|
+
|
|
151
|
+
## Type hinting
|
|
152
|
+
|
|
153
|
+
> When used in a type hint, the expression `None` is considered equivalent to `type(None)`.
|
|
154
|
+
|
|
155
|
+
> *[Official typing documentation](https://typing.python.org/en/latest/spec/special-types.html#none)*
|
|
156
|
+
|
|
157
|
+
`None` is a special value for which Python type checkers make an exception, allowing it to be used as an annotation of its own type. Unfortunately, this behavior cannot be reproduced without changing the internal implementation of existing type checkers, which I would not expect until the [PEP](https://peps.python.org/pep-0661/) is adopted.
|
|
158
|
+
|
|
159
|
+
Therefore, it is suggested to use class `InnerNoneType` as a type annotation:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
def function(default: int | InnerNoneType):
|
|
163
|
+
...
|
|
164
|
+
```
|
denial-0.0.3/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<details>
|
|
2
|
+
<summary>ⓘ</summary>
|
|
3
|
+
|
|
4
|
+
[](https://pepy.tech/project/denial)
|
|
5
|
+
[](https://pepy.tech/project/denial)
|
|
6
|
+
[](https://coveralls.io/github/pomponchik/denial?branch=main)
|
|
7
|
+
[](https://github.com/boyter/scc/)
|
|
8
|
+
[](https://hitsofcode.com/github/pomponchik/denial/view?branch=main)
|
|
9
|
+
[](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml)
|
|
10
|
+
[](https://pypi.python.org/pypi/denial)
|
|
11
|
+
[](https://badge.fury.io/py/denial)
|
|
12
|
+
[](http://mypy-lang.org/)
|
|
13
|
+
[](https://github.com/astral-sh/ruff)
|
|
14
|
+
[](https://deepwiki.com/pomponchik/denial)
|
|
15
|
+
|
|
16
|
+
</details>
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
The [`None`](https://docs.python.org/3/library/constants.html#None) constant built into Python is convenient for client code, but it is often insufficient when creating libraries. The fact is that this makes it impossible to [distinguish situations](https://colinmcginn.net/truth-value-gaps-and-meaning/) where a value is *undefined* from situations where it is *defined as undefined*. Does that sound too abstract?
|
|
23
|
+
|
|
24
|
+
In fact, the problem of this distinction is found everywhere in library development. `Sentinel objects` are used to resolve it, and [many modules](https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/) from the standard library define their own. For example, the [dataclasses](https://docs.python.org/3/library/dataclasses.html) library defines a special [MISSING](https://docs.python.org/3/library/dataclasses.html#dataclasses.MISSING) constant for such cases. This is used to separate the cases when the user has not set a default value from the case when he has set `None` as the default value.
|
|
25
|
+
|
|
26
|
+
However, we can't all use sentinel objects from some built-in module if we don't need the functionality of that module. Until the [PEP](https://peps.python.org/pep-0661/) has been adopted on this topic, it is better to use a special package containing only this primitive. Such a package is `denial`. It defines just such an object: `None` for situations where you need to distinguish `None` as a value from the user, and None as a designation that something is really undefined. This value should not fall "outside", into the user's space, it should remain only inside the libraries implementations. In addition, this library offers a special class that allows you to create your own sentinels.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Table of contents
|
|
30
|
+
|
|
31
|
+
- [**Installation**](#installation)
|
|
32
|
+
- [**The second None**](#the-second-none)
|
|
33
|
+
- [**Your own None objects**](#your-own-none-objects)
|
|
34
|
+
- [**Type hinting**](#type-hinting)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
Install it:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install denial
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
You can also quickly try out this and other packages without having to install using [instld](https://github.com/pomponchik/instld).
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## The second `None`
|
|
49
|
+
|
|
50
|
+
This library defines an object that is proposed to be used in almost the same way as a regular `None`. This is how it is imported:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from denial import InnerNone
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
This object is equal only to itself:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
print(InnerNone == InnerNone)
|
|
60
|
+
#> True
|
|
61
|
+
print(InnerNone == False)
|
|
62
|
+
#> False
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This object is also an instance of `InnerNoneType` class (an analog of [`NoneType`](https://docs.python.org/3/library/types.html#types.NoneType), however, is not inherited from this), which makes it possible to check through [`isinstance`](https://docs.python.org/3/library/functions.html#isinstance):
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from denial import InnerNoneType
|
|
69
|
+
|
|
70
|
+
print(isinstance(InnerNone, InnerNoneType))
|
|
71
|
+
#> True
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
It is recommended to use the `InnerNone` object inside libraries where a value close to None is required, but meaning a situation where the value is not really set, rather than set as `None`. This object should be completely isolated from the user code space. None of the public methods of your library should return this object.
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
## Your own `None` objects
|
|
78
|
+
|
|
79
|
+
If `None` and [`InnerNone`](#the-second-none) are not enough for you, you can create your own similar objects by instantiating `InnerNoneType`:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
sentinel = InnerNoneType()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This object will also be equal only to itself:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
print(sentinel == sentinel)
|
|
89
|
+
#> True
|
|
90
|
+
|
|
91
|
+
print(sentinel == InnerNoneType()) # Comparison with another object of the same type
|
|
92
|
+
#> False
|
|
93
|
+
print(sentinel == InnerNone) # Also comparison with another object of the same type
|
|
94
|
+
#> False
|
|
95
|
+
print(sentinel == None) # Comparison with None
|
|
96
|
+
#> False
|
|
97
|
+
print(sentinel == 123) # Comparison with an arbitrary object
|
|
98
|
+
#> False
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
You can also pass an integer or a string to the class constructor. An `InnerNoneType` object is equal to another such object with the same argument:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
print(InnerNoneType(123) == InnerNoneType(123))
|
|
105
|
+
#> True
|
|
106
|
+
print(InnerNoneType('key') == InnerNoneType('key'))
|
|
107
|
+
#> True
|
|
108
|
+
|
|
109
|
+
print(InnerNoneType(123) == InnerNoneType(1234))
|
|
110
|
+
#> False
|
|
111
|
+
print(InnerNoneType('key') == InnerNoneType('another key'))
|
|
112
|
+
#> False
|
|
113
|
+
print(InnerNoneType(123) == InnerNoneType())
|
|
114
|
+
#> False
|
|
115
|
+
print(InnerNoneType(123) == 123)
|
|
116
|
+
#> False
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
> 💡 Any `InnerNoneType` objects can be used as keys in dictionaries.
|
|
120
|
+
|
|
121
|
+
## Type hinting
|
|
122
|
+
|
|
123
|
+
> When used in a type hint, the expression `None` is considered equivalent to `type(None)`.
|
|
124
|
+
|
|
125
|
+
> *[Official typing documentation](https://typing.python.org/en/latest/spec/special-types.html#none)*
|
|
126
|
+
|
|
127
|
+
`None` is a special value for which Python type checkers make an exception, allowing it to be used as an annotation of its own type. Unfortunately, this behavior cannot be reproduced without changing the internal implementation of existing type checkers, which I would not expect until the [PEP](https://peps.python.org/pep-0661/) is adopted.
|
|
128
|
+
|
|
129
|
+
Therefore, it is suggested to use class `InnerNoneType` as a type annotation:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
def function(default: int | InnerNoneType):
|
|
133
|
+
...
|
|
134
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from itertools import count
|
|
2
|
+
from typing import Any, Optional, Union
|
|
3
|
+
|
|
4
|
+
from printo import descript_data_object
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InnerNoneType:
|
|
8
|
+
id: Optional[Union[int, str]] # pragma: no cover
|
|
9
|
+
counter = count()
|
|
10
|
+
|
|
11
|
+
def __init__(self, id: Optional[Union[int, str]] = None) -> None: # noqa: A002
|
|
12
|
+
if id is None:
|
|
13
|
+
self.id = next(self.counter)
|
|
14
|
+
else:
|
|
15
|
+
self.id = id
|
|
16
|
+
|
|
17
|
+
def __eq__(self, other: Any) -> bool:
|
|
18
|
+
if not isinstance(other, type(self)):
|
|
19
|
+
return False
|
|
20
|
+
return self.id == other.id
|
|
21
|
+
|
|
22
|
+
def __hash__(self) -> int:
|
|
23
|
+
return hash(self.id)
|
|
24
|
+
|
|
25
|
+
def __repr__(self) -> str:
|
|
26
|
+
if not self.id:
|
|
27
|
+
return 'InnerNone'
|
|
28
|
+
return descript_data_object(type(self).__name__, (self.id,), {})
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
InnerNone = InnerNoneType()
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: denial
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: Is one None not enough for you? There's more
|
|
5
|
+
Author-email: Evgeniy Blinov <zheni-b@yandex.ru>
|
|
6
|
+
Project-URL: Source, https://github.com/pomponchik/denial
|
|
7
|
+
Project-URL: Tracker, https://github.com/pomponchik/denial/issues
|
|
8
|
+
Keywords: None,sentinel
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Programming Language :: Python :: Free Threading
|
|
23
|
+
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
|
|
24
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
25
|
+
Classifier: Intended Audience :: Developers
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
27
|
+
Requires-Python: >=3.8
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
|
|
31
|
+
<details>
|
|
32
|
+
<summary>ⓘ</summary>
|
|
33
|
+
|
|
34
|
+
[](https://pepy.tech/project/denial)
|
|
35
|
+
[](https://pepy.tech/project/denial)
|
|
36
|
+
[](https://coveralls.io/github/pomponchik/denial?branch=main)
|
|
37
|
+
[](https://github.com/boyter/scc/)
|
|
38
|
+
[](https://hitsofcode.com/github/pomponchik/denial/view?branch=main)
|
|
39
|
+
[](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml)
|
|
40
|
+
[](https://pypi.python.org/pypi/denial)
|
|
41
|
+
[](https://badge.fury.io/py/denial)
|
|
42
|
+
[](http://mypy-lang.org/)
|
|
43
|
+
[](https://github.com/astral-sh/ruff)
|
|
44
|
+
[](https://deepwiki.com/pomponchik/denial)
|
|
45
|
+
|
|
46
|
+
</details>
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
The [`None`](https://docs.python.org/3/library/constants.html#None) constant built into Python is convenient for client code, but it is often insufficient when creating libraries. The fact is that this makes it impossible to [distinguish situations](https://colinmcginn.net/truth-value-gaps-and-meaning/) where a value is *undefined* from situations where it is *defined as undefined*. Does that sound too abstract?
|
|
53
|
+
|
|
54
|
+
In fact, the problem of this distinction is found everywhere in library development. `Sentinel objects` are used to resolve it, and [many modules](https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/) from the standard library define their own. For example, the [dataclasses](https://docs.python.org/3/library/dataclasses.html) library defines a special [MISSING](https://docs.python.org/3/library/dataclasses.html#dataclasses.MISSING) constant for such cases. This is used to separate the cases when the user has not set a default value from the case when he has set `None` as the default value.
|
|
55
|
+
|
|
56
|
+
However, we can't all use sentinel objects from some built-in module if we don't need the functionality of that module. Until the [PEP](https://peps.python.org/pep-0661/) has been adopted on this topic, it is better to use a special package containing only this primitive. Such a package is `denial`. It defines just such an object: `None` for situations where you need to distinguish `None` as a value from the user, and None as a designation that something is really undefined. This value should not fall "outside", into the user's space, it should remain only inside the libraries implementations. In addition, this library offers a special class that allows you to create your own sentinels.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## Table of contents
|
|
60
|
+
|
|
61
|
+
- [**Installation**](#installation)
|
|
62
|
+
- [**The second None**](#the-second-none)
|
|
63
|
+
- [**Your own None objects**](#your-own-none-objects)
|
|
64
|
+
- [**Type hinting**](#type-hinting)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
Install it:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install denial
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
You can also quickly try out this and other packages without having to install using [instld](https://github.com/pomponchik/instld).
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## The second `None`
|
|
79
|
+
|
|
80
|
+
This library defines an object that is proposed to be used in almost the same way as a regular `None`. This is how it is imported:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from denial import InnerNone
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This object is equal only to itself:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
print(InnerNone == InnerNone)
|
|
90
|
+
#> True
|
|
91
|
+
print(InnerNone == False)
|
|
92
|
+
#> False
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This object is also an instance of `InnerNoneType` class (an analog of [`NoneType`](https://docs.python.org/3/library/types.html#types.NoneType), however, is not inherited from this), which makes it possible to check through [`isinstance`](https://docs.python.org/3/library/functions.html#isinstance):
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from denial import InnerNoneType
|
|
99
|
+
|
|
100
|
+
print(isinstance(InnerNone, InnerNoneType))
|
|
101
|
+
#> True
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
It is recommended to use the `InnerNone` object inside libraries where a value close to None is required, but meaning a situation where the value is not really set, rather than set as `None`. This object should be completely isolated from the user code space. None of the public methods of your library should return this object.
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
## Your own `None` objects
|
|
108
|
+
|
|
109
|
+
If `None` and [`InnerNone`](#the-second-none) are not enough for you, you can create your own similar objects by instantiating `InnerNoneType`:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
sentinel = InnerNoneType()
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This object will also be equal only to itself:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
print(sentinel == sentinel)
|
|
119
|
+
#> True
|
|
120
|
+
|
|
121
|
+
print(sentinel == InnerNoneType()) # Comparison with another object of the same type
|
|
122
|
+
#> False
|
|
123
|
+
print(sentinel == InnerNone) # Also comparison with another object of the same type
|
|
124
|
+
#> False
|
|
125
|
+
print(sentinel == None) # Comparison with None
|
|
126
|
+
#> False
|
|
127
|
+
print(sentinel == 123) # Comparison with an arbitrary object
|
|
128
|
+
#> False
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
You can also pass an integer or a string to the class constructor. An `InnerNoneType` object is equal to another such object with the same argument:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
print(InnerNoneType(123) == InnerNoneType(123))
|
|
135
|
+
#> True
|
|
136
|
+
print(InnerNoneType('key') == InnerNoneType('key'))
|
|
137
|
+
#> True
|
|
138
|
+
|
|
139
|
+
print(InnerNoneType(123) == InnerNoneType(1234))
|
|
140
|
+
#> False
|
|
141
|
+
print(InnerNoneType('key') == InnerNoneType('another key'))
|
|
142
|
+
#> False
|
|
143
|
+
print(InnerNoneType(123) == InnerNoneType())
|
|
144
|
+
#> False
|
|
145
|
+
print(InnerNoneType(123) == 123)
|
|
146
|
+
#> False
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
> 💡 Any `InnerNoneType` objects can be used as keys in dictionaries.
|
|
150
|
+
|
|
151
|
+
## Type hinting
|
|
152
|
+
|
|
153
|
+
> When used in a type hint, the expression `None` is considered equivalent to `type(None)`.
|
|
154
|
+
|
|
155
|
+
> *[Official typing documentation](https://typing.python.org/en/latest/spec/special-types.html#none)*
|
|
156
|
+
|
|
157
|
+
`None` is a special value for which Python type checkers make an exception, allowing it to be used as an annotation of its own type. Unfortunately, this behavior cannot be reproduced without changing the internal implementation of existing type checkers, which I would not expect until the [PEP](https://peps.python.org/pep-0661/) is adopted.
|
|
158
|
+
|
|
159
|
+
Therefore, it is suggested to use class `InnerNoneType` as a type annotation:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
def function(default: int | InnerNoneType):
|
|
163
|
+
...
|
|
164
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
printo==0.0.8
|
|
@@ -4,11 +4,15 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "denial"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.3"
|
|
8
8
|
authors = [{ name = "Evgeniy Blinov", email = "zheni-b@yandex.ru" }]
|
|
9
9
|
description = "Is one None not enough for you? There's more"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.8"
|
|
12
|
+
dependencies = [
|
|
13
|
+
'printo==0.0.8',
|
|
14
|
+
]
|
|
15
|
+
|
|
12
16
|
classifiers = [
|
|
13
17
|
"Operating System :: OS Independent",
|
|
14
18
|
'Operating System :: MacOS :: MacOS X',
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from threading import Thread
|
|
2
|
+
|
|
3
|
+
from denial import InnerNone, InnerNoneType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_inner_none_is_inner_none():
|
|
7
|
+
assert InnerNone is InnerNone # noqa: PLR0124
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_inner_none_is_instance_of_inner_none_type():
|
|
11
|
+
assert isinstance(InnerNone, InnerNoneType)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_str_inner_none():
|
|
15
|
+
assert str(InnerNone) == 'InnerNone'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_repr_inner_none():
|
|
19
|
+
assert repr(InnerNone) == 'InnerNone'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_new_instance_has_id_more_0():
|
|
23
|
+
instance_1 = InnerNoneType()
|
|
24
|
+
instance_2 = InnerNoneType()
|
|
25
|
+
|
|
26
|
+
assert isinstance(instance_1.id, int)
|
|
27
|
+
assert isinstance(instance_2.id, int)
|
|
28
|
+
|
|
29
|
+
assert InnerNone.id == 0
|
|
30
|
+
assert instance_1.id > 0
|
|
31
|
+
assert instance_2.id > 0
|
|
32
|
+
assert instance_2.id == instance_1.id + 1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_new_instance_repr():
|
|
36
|
+
new_instance = InnerNoneType()
|
|
37
|
+
assert repr(new_instance) == f'InnerNoneType({new_instance.id})'
|
|
38
|
+
assert repr(InnerNoneType('kek')) == "InnerNoneType('kek')"
|
|
39
|
+
assert repr(InnerNoneType(123)) == "InnerNoneType(123)"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_eq():
|
|
43
|
+
new_instance = InnerNoneType()
|
|
44
|
+
|
|
45
|
+
assert InnerNone == InnerNone # noqa: PLR0124
|
|
46
|
+
assert InnerNone != new_instance
|
|
47
|
+
assert InnerNone != InnerNoneType('kek')
|
|
48
|
+
assert InnerNone != InnerNoneType(123)
|
|
49
|
+
|
|
50
|
+
assert new_instance == new_instance # noqa: PLR0124
|
|
51
|
+
assert new_instance != InnerNoneType()
|
|
52
|
+
assert InnerNoneType() != InnerNoneType()
|
|
53
|
+
|
|
54
|
+
assert InnerNoneType(123) == InnerNoneType(123)
|
|
55
|
+
assert InnerNoneType('kek') == InnerNoneType('kek')
|
|
56
|
+
|
|
57
|
+
assert InnerNoneType(123) != InnerNoneType(124)
|
|
58
|
+
assert InnerNoneType('kek') != InnerNoneType(123)
|
|
59
|
+
assert InnerNoneType('kek') != InnerNoneType('lol')
|
|
60
|
+
|
|
61
|
+
assert InnerNone != None # noqa: E711
|
|
62
|
+
assert InnerNoneType() != None # noqa: E711
|
|
63
|
+
assert InnerNoneType(123) != None # noqa: E711
|
|
64
|
+
|
|
65
|
+
assert InnerNoneType(123) != 123
|
|
66
|
+
assert InnerNoneType('kek') != 'kek'
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_hashing_and_use_as_key_in_dict():
|
|
70
|
+
assert hash(InnerNone) == hash(InnerNone.id)
|
|
71
|
+
assert hash(InnerNoneType(123)) == hash(123)
|
|
72
|
+
assert hash(InnerNoneType('123')) == hash('123')
|
|
73
|
+
|
|
74
|
+
new_instance = InnerNoneType()
|
|
75
|
+
assert hash(new_instance) == hash(new_instance.id)
|
|
76
|
+
|
|
77
|
+
dict_with_it = {new_instance: 'kek'}
|
|
78
|
+
assert dict_with_it[new_instance] == 'kek'
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_thread_safety():
|
|
82
|
+
number_of_iterations = 10_000
|
|
83
|
+
number_of_threads = 10
|
|
84
|
+
|
|
85
|
+
nones = []
|
|
86
|
+
|
|
87
|
+
def go_increment():
|
|
88
|
+
for _ in range(number_of_iterations):
|
|
89
|
+
nones.append(InnerNoneType())
|
|
90
|
+
|
|
91
|
+
threads = [Thread(target=go_increment()) for _ in range(number_of_threads)]
|
|
92
|
+
|
|
93
|
+
for thread in threads:
|
|
94
|
+
thread.start()
|
|
95
|
+
|
|
96
|
+
for thread in threads:
|
|
97
|
+
thread.join()
|
|
98
|
+
|
|
99
|
+
assert len(set(x.id for x in nones)) == number_of_iterations * number_of_threads
|
denial-0.0.2/PKG-INFO
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: denial
|
|
3
|
-
Version: 0.0.2
|
|
4
|
-
Summary: Is one None not enough for you? There's more
|
|
5
|
-
Author-email: Evgeniy Blinov <zheni-b@yandex.ru>
|
|
6
|
-
Project-URL: Source, https://github.com/pomponchik/denial
|
|
7
|
-
Project-URL: Tracker, https://github.com/pomponchik/denial/issues
|
|
8
|
-
Keywords: None,sentinel
|
|
9
|
-
Classifier: Operating System :: OS Independent
|
|
10
|
-
Classifier: Operating System :: MacOS :: MacOS X
|
|
11
|
-
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
-
Classifier: Operating System :: POSIX
|
|
13
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
-
Classifier: Programming Language :: Python
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
-
Classifier: Programming Language :: Python :: Free Threading
|
|
23
|
-
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
|
|
24
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
25
|
-
Classifier: Intended Audience :: Developers
|
|
26
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
27
|
-
Requires-Python: >=3.8
|
|
28
|
-
Description-Content-Type: text/markdown
|
|
29
|
-
License-File: LICENSE
|
|
30
|
-
|
|
31
|
-
# denial
|
|
32
|
-
|
|
33
|
-
[](https://pepy.tech/project/denial)
|
|
34
|
-
[](https://pepy.tech/project/denial)
|
|
35
|
-
[](https://coveralls.io/github/pomponchik/denial?branch=main)
|
|
36
|
-
[](https://github.com/boyter/scc/)
|
|
37
|
-
[](https://hitsofcode.com/github/pomponchik/denial/view?branch=main)
|
|
38
|
-
[](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml)
|
|
39
|
-
[](https://pypi.python.org/pypi/denial)
|
|
40
|
-
[](https://badge.fury.io/py/denial)
|
|
41
|
-
[](http://mypy-lang.org/)
|
|
42
|
-
[](https://github.com/astral-sh/ruff)
|
|
43
|
-
[](https://deepwiki.com/pomponchik/denial)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
There is a small but annoying misunderstanding in the design of Python as a language. The language defines the constant `None`, which designates a special object that is used as a "stub" when it is not possible to use the "real" value. But sometimes, when implementing libraries, it is not possible to distinguish `None`, passed by the user as the default value, from `None`, which means that the value is *really undefined*. In some rare cases, this distinction is important.
|
|
47
|
-
|
|
48
|
-
For example, the [dataclasses](https://docs.python.org/3/library/dataclasses.html) library defines a special [MISSING](https://docs.python.org/3/library/dataclasses.html#dataclasses.MISSING) constant for such cases. This is used to separate the cases when the user has not set a default value from the case when he has set `None` as the default value. However, the use of `MISSING` is tied to the use of this library, and sometimes this constant may be needed for completely different purposes.
|
|
49
|
-
|
|
50
|
-
This library defines just such an object: `None` for situations where you need to distinguish `None` as a value from the user, and None as a designation that something is really undefined. This value should not fall "outside", into the user's space, it should remain only inside the libraries implementations.
|
|
51
|
-
|
|
52
|
-
Well, how to use it?
|
|
53
|
-
|
|
54
|
-
Let's start with the installation:
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
pip install denial
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
This is how this additional version of `None` and its class are imported (can be used for type hints or checks via isinstance):
|
|
61
|
-
|
|
62
|
-
```python
|
|
63
|
-
from denial import InnerNone, InnerNoneType
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
`InnerNone` is used the same way as `None`, with a couple of additional caveats:
|
|
67
|
-
|
|
68
|
-
1. `InnerNone` is not an instance of [`NoneType`](https://docs.python.org/3/library/types.html#types.NoneType), it has its own parent class.
|
|
69
|
-
|
|
70
|
-
2. `InnerNone` cannot be used as your own type hint. What am I talking about? Let's look at the documentation:
|
|
71
|
-
|
|
72
|
-
> When used in a type hint, the expression `None` is considered equivalent to `type(None)`.
|
|
73
|
-
|
|
74
|
-
> *[Official typing documentation](https://typing.python.org/en/latest/spec/special-types.html#none)*
|
|
75
|
-
|
|
76
|
-
In most type checkers, this is implemented using a special "crutch", an exception in the code that cannot be repeated for any other value. Therefore, use `InnerNoneType` as a type hint.
|
denial-0.0.2/README.md
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# denial
|
|
2
|
-
|
|
3
|
-
[](https://pepy.tech/project/denial)
|
|
4
|
-
[](https://pepy.tech/project/denial)
|
|
5
|
-
[](https://coveralls.io/github/pomponchik/denial?branch=main)
|
|
6
|
-
[](https://github.com/boyter/scc/)
|
|
7
|
-
[](https://hitsofcode.com/github/pomponchik/denial/view?branch=main)
|
|
8
|
-
[](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml)
|
|
9
|
-
[](https://pypi.python.org/pypi/denial)
|
|
10
|
-
[](https://badge.fury.io/py/denial)
|
|
11
|
-
[](http://mypy-lang.org/)
|
|
12
|
-
[](https://github.com/astral-sh/ruff)
|
|
13
|
-
[](https://deepwiki.com/pomponchik/denial)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
There is a small but annoying misunderstanding in the design of Python as a language. The language defines the constant `None`, which designates a special object that is used as a "stub" when it is not possible to use the "real" value. But sometimes, when implementing libraries, it is not possible to distinguish `None`, passed by the user as the default value, from `None`, which means that the value is *really undefined*. In some rare cases, this distinction is important.
|
|
17
|
-
|
|
18
|
-
For example, the [dataclasses](https://docs.python.org/3/library/dataclasses.html) library defines a special [MISSING](https://docs.python.org/3/library/dataclasses.html#dataclasses.MISSING) constant for such cases. This is used to separate the cases when the user has not set a default value from the case when he has set `None` as the default value. However, the use of `MISSING` is tied to the use of this library, and sometimes this constant may be needed for completely different purposes.
|
|
19
|
-
|
|
20
|
-
This library defines just such an object: `None` for situations where you need to distinguish `None` as a value from the user, and None as a designation that something is really undefined. This value should not fall "outside", into the user's space, it should remain only inside the libraries implementations.
|
|
21
|
-
|
|
22
|
-
Well, how to use it?
|
|
23
|
-
|
|
24
|
-
Let's start with the installation:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
pip install denial
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
This is how this additional version of `None` and its class are imported (can be used for type hints or checks via isinstance):
|
|
31
|
-
|
|
32
|
-
```python
|
|
33
|
-
from denial import InnerNone, InnerNoneType
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
`InnerNone` is used the same way as `None`, with a couple of additional caveats:
|
|
37
|
-
|
|
38
|
-
1. `InnerNone` is not an instance of [`NoneType`](https://docs.python.org/3/library/types.html#types.NoneType), it has its own parent class.
|
|
39
|
-
|
|
40
|
-
2. `InnerNone` cannot be used as your own type hint. What am I talking about? Let's look at the documentation:
|
|
41
|
-
|
|
42
|
-
> When used in a type hint, the expression `None` is considered equivalent to `type(None)`.
|
|
43
|
-
|
|
44
|
-
> *[Official typing documentation](https://typing.python.org/en/latest/spec/special-types.html#none)*
|
|
45
|
-
|
|
46
|
-
In most type checkers, this is implemented using a special "crutch", an exception in the code that cannot be repeated for any other value. Therefore, use `InnerNoneType` as a type hint.
|
denial-0.0.2/denial/inner.py
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: denial
|
|
3
|
-
Version: 0.0.2
|
|
4
|
-
Summary: Is one None not enough for you? There's more
|
|
5
|
-
Author-email: Evgeniy Blinov <zheni-b@yandex.ru>
|
|
6
|
-
Project-URL: Source, https://github.com/pomponchik/denial
|
|
7
|
-
Project-URL: Tracker, https://github.com/pomponchik/denial/issues
|
|
8
|
-
Keywords: None,sentinel
|
|
9
|
-
Classifier: Operating System :: OS Independent
|
|
10
|
-
Classifier: Operating System :: MacOS :: MacOS X
|
|
11
|
-
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
-
Classifier: Operating System :: POSIX
|
|
13
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
-
Classifier: Programming Language :: Python
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
-
Classifier: Programming Language :: Python :: Free Threading
|
|
23
|
-
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
|
|
24
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
25
|
-
Classifier: Intended Audience :: Developers
|
|
26
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
27
|
-
Requires-Python: >=3.8
|
|
28
|
-
Description-Content-Type: text/markdown
|
|
29
|
-
License-File: LICENSE
|
|
30
|
-
|
|
31
|
-
# denial
|
|
32
|
-
|
|
33
|
-
[](https://pepy.tech/project/denial)
|
|
34
|
-
[](https://pepy.tech/project/denial)
|
|
35
|
-
[](https://coveralls.io/github/pomponchik/denial?branch=main)
|
|
36
|
-
[](https://github.com/boyter/scc/)
|
|
37
|
-
[](https://hitsofcode.com/github/pomponchik/denial/view?branch=main)
|
|
38
|
-
[](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml)
|
|
39
|
-
[](https://pypi.python.org/pypi/denial)
|
|
40
|
-
[](https://badge.fury.io/py/denial)
|
|
41
|
-
[](http://mypy-lang.org/)
|
|
42
|
-
[](https://github.com/astral-sh/ruff)
|
|
43
|
-
[](https://deepwiki.com/pomponchik/denial)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
There is a small but annoying misunderstanding in the design of Python as a language. The language defines the constant `None`, which designates a special object that is used as a "stub" when it is not possible to use the "real" value. But sometimes, when implementing libraries, it is not possible to distinguish `None`, passed by the user as the default value, from `None`, which means that the value is *really undefined*. In some rare cases, this distinction is important.
|
|
47
|
-
|
|
48
|
-
For example, the [dataclasses](https://docs.python.org/3/library/dataclasses.html) library defines a special [MISSING](https://docs.python.org/3/library/dataclasses.html#dataclasses.MISSING) constant for such cases. This is used to separate the cases when the user has not set a default value from the case when he has set `None` as the default value. However, the use of `MISSING` is tied to the use of this library, and sometimes this constant may be needed for completely different purposes.
|
|
49
|
-
|
|
50
|
-
This library defines just such an object: `None` for situations where you need to distinguish `None` as a value from the user, and None as a designation that something is really undefined. This value should not fall "outside", into the user's space, it should remain only inside the libraries implementations.
|
|
51
|
-
|
|
52
|
-
Well, how to use it?
|
|
53
|
-
|
|
54
|
-
Let's start with the installation:
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
pip install denial
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
This is how this additional version of `None` and its class are imported (can be used for type hints or checks via isinstance):
|
|
61
|
-
|
|
62
|
-
```python
|
|
63
|
-
from denial import InnerNone, InnerNoneType
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
`InnerNone` is used the same way as `None`, with a couple of additional caveats:
|
|
67
|
-
|
|
68
|
-
1. `InnerNone` is not an instance of [`NoneType`](https://docs.python.org/3/library/types.html#types.NoneType), it has its own parent class.
|
|
69
|
-
|
|
70
|
-
2. `InnerNone` cannot be used as your own type hint. What am I talking about? Let's look at the documentation:
|
|
71
|
-
|
|
72
|
-
> When used in a type hint, the expression `None` is considered equivalent to `type(None)`.
|
|
73
|
-
|
|
74
|
-
> *[Official typing documentation](https://typing.python.org/en/latest/spec/special-types.html#none)*
|
|
75
|
-
|
|
76
|
-
In most type checkers, this is implemented using a special "crutch", an exception in the code that cannot be repeated for any other value. Therefore, use `InnerNoneType` as a type hint.
|
denial-0.0.2/tests/test_inner.py
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from denial import InnerNone, InnerNoneType
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def test_inner_none_is_inner_none():
|
|
5
|
-
assert InnerNone is InnerNone # noqa: PLR0124
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def test_inner_none_is_instance_of_inner_none_type():
|
|
9
|
-
assert isinstance(InnerNone, InnerNoneType)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def test_str():
|
|
13
|
-
assert str(InnerNone) == 'InnerNone'
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def test_repr():
|
|
17
|
-
assert repr(InnerNone) == 'InnerNone'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|