Skip to content

Commit 5901ad8

Browse files
authored
Merge pull request #5 from cbefus/abc
Rework to use an abstract base class.
2 parents 782f973 + 041dc16 commit 5901ad8

File tree

7 files changed

+138
-134
lines changed

7 files changed

+138
-134
lines changed

README.md

Lines changed: 10 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -82,25 +82,7 @@ So we present you with an **Optional** object as an alternative.
8282
print(thing)
8383
```
8484

85-
6. But you **can't** get the value without first checking for presence:
86-
```python
87-
thing = some_func_returning_an_optional()
88-
print(thing.get()) # **will raise an exception**
89-
90-
```
91-
but:
92-
```python
93-
thing = some_func_returning_an_optional()
94-
if thing.is_present(): # could use is_empty() as alternative
95-
print(thing.get()) # **does not throw**
96-
```
97-
instead of:
98-
```python
99-
if thing is not None:
100-
print(thing)
101-
```
102-
103-
7. You **can't** get the value if its empty:
85+
6. You **can't** get the value if its empty:
10486
```python
10587
thing = some_func_returning_an_optional()
10688
if thing.is_empty():
@@ -112,7 +94,7 @@ So we present you with an **Optional** object as an alternative.
11294
print(None) # very odd
11395
```
11496

115-
8. **__Best Usage:__** You can chain on presence:
97+
7. **__Best Usage:__** You can chain on presence:
11698
```python
11799
thing = some_func_returning_an_optional()
118100
thing.if_present(lambda thing: print(thing))
@@ -123,7 +105,7 @@ So we present you with an **Optional** object as an alternative.
123105
print(thing)
124106
```
125107

126-
9. **__Best Usage:__** You can chain on non presence:
108+
8. **__Best Usage:__** You can chain on non presence:
127109
```python
128110
thing = some_func_returning_an_optional()
129111
thing.if_present(lambda thing: print(thing)).or_else(lambda _: print("PANTS!"))
@@ -136,45 +118,33 @@ So we present you with an **Optional** object as an alternative.
136118
print("PANTS!")
137119
```
138120

139-
10. **__Best Usage:__** You can map a function:
121+
9. **__Best Usage:__** You can map a function:
140122
```python
141123
def mapping_func(thing):
142124
return thing + "PANTS"
143-
125+
144126
thing_to_map = Optional.of("thing")
145127
mapped_thing = thing_to_map.map(mapping_func) # returns Optional.of("thingPANTS")
146128
```
147129
Note that if the mapping function returns `None` then the map call will return `Optional.empty()`. Also
148130
if you call `map` on an empty optional it will return `Optional.empty()`.
149-
150-
11. **__Best Usage:__** You can flat map a function which returns an Optional.
131+
132+
10. **__Best Usage:__** You can flat map a function which returns an Optional.
151133
```python
152134
def flat_mapping_func(thing):
153135
return Optional.of(thing + "PANTS")
154-
136+
155137
thing_to_map = Optional.of("thing")
156138
mapped_thing = thing_to_map.map(mapping_func) # returns Optional.of("thingPANTS")
157139
```
158-
Note that this does not return an Optional of an Optional. __Use this for mapping functions which return optionals.__
140+
Note that this does not return an Optional of an Optional. __Use this for mapping functions which return optionals.__
159141
If the mapping function you use with this does not return an Optional, calling `flat_map` will raise a
160142
`FlatMapFunctionDoesNotReturnOptionalException`.
161143

162-
12. You can compare two optionals:
144+
11. You can compare two optionals:
163145
```python
164146
Optional.empty() == Optional.empty() # True
165147
Optional.of("thing") == Optional.of("thing") # True
166148
Optional.of("thing") == Optional.empty() # False
167149
Optional.of("thing") == Optional.of("PANTS") # False
168150
```
169-
170-
171-
172-
173-
174-
175-
176-
177-
178-
179-
180-

optional.py

Lines changed: 0 additions & 83 deletions
This file was deleted.

optional/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .optional import *

optional/compatible_abc.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""compatible_abc
2+
3+
This module exports a single class, CompatibleABC.
4+
It is necessary to provide the same behavior in
5+
Python 2 and Python 3.
6+
7+
The implementation was taken from https://stackoverflow.com/a/38668373
8+
"""
9+
from abc import ABCMeta
10+
11+
12+
CompatibleABC = ABCMeta('ABC', (object,), {'__slots__': ()})

optional/optional.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
from abc import abstractmethod
2+
3+
from .compatible_abc import CompatibleABC
4+
5+
6+
class Optional(object):
7+
@staticmethod
8+
def of(thing):
9+
return _Nothing() if thing is None else _Something(thing)
10+
11+
@staticmethod
12+
def empty():
13+
return _Nothing()
14+
15+
16+
class _AbstractOptional(CompatibleABC):
17+
18+
@abstractmethod
19+
def is_present(self):
20+
pass
21+
22+
def is_empty(self):
23+
return not self.is_present()
24+
25+
@abstractmethod
26+
def get(self):
27+
pass
28+
29+
@abstractmethod
30+
def if_present(self, consumer):
31+
pass
32+
33+
@abstractmethod
34+
def or_else(self, procedure):
35+
pass
36+
37+
@abstractmethod
38+
def map(self, func):
39+
pass
40+
41+
@abstractmethod
42+
def flat_map(self, func):
43+
pass
44+
45+
46+
class _Nothing(_AbstractOptional):
47+
def is_present(self):
48+
return False
49+
50+
def get(self):
51+
raise OptionalAccessOfEmptyException(
52+
"You cannot call get on an empty optional"
53+
)
54+
55+
def if_present(self, consumer):
56+
return self
57+
58+
def or_else(self, procedure):
59+
return procedure()
60+
61+
def map(self, func):
62+
return self
63+
64+
def flat_map(self, func):
65+
return self
66+
67+
def __eq__(self, other):
68+
return isinstance(other, _Nothing)
69+
70+
71+
class _Something(_AbstractOptional):
72+
def __init__(self, value):
73+
self.__value = value
74+
75+
def is_present(self):
76+
return True
77+
78+
def get(self):
79+
return self.__value
80+
81+
def if_present(self, consumer):
82+
consumer(self.get())
83+
return self
84+
85+
def or_else(self, procedure):
86+
return self
87+
88+
def map(self, func):
89+
return Optional.of(func(self.get()))
90+
91+
def flat_map(self, func):
92+
res = func(self.get())
93+
if not isinstance(res, _AbstractOptional):
94+
raise FlatMapFunctionDoesNotReturnOptionalException(
95+
"Mapping function to flat_map must return Optional."
96+
)
97+
98+
return res
99+
100+
def __eq__(self, other):
101+
return isinstance(other, _Something) and self.get() == other.get()
102+
103+
104+
class OptionalAccessOfEmptyException(Exception):
105+
pass
106+
107+
108+
class FlatMapFunctionDoesNotReturnOptionalException(Exception):
109+
pass

test/__init__.py

Whitespace-only changes.

test_optional.py renamed to test/test_optional.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import unittest
22

3-
from optional import Optional, OptionalAccessWithoutCheckingPresenceException, OptionalAccessOfEmptyException, FlatMapFunctionDoesNotReturnOptionalException
3+
from optional import (
4+
Optional,
5+
OptionalAccessOfEmptyException,
6+
FlatMapFunctionDoesNotReturnOptionalException
7+
)
48

59

610
class TestOptional(unittest.TestCase):
711

812
def test_can_instantiate(self):
9-
Optional(None)
13+
Optional.of(None)
1014

1115
def test_instantiate_empty(self):
1216
optional = Optional.empty()
@@ -28,11 +32,6 @@ def test_is_not_present_with_empty(self):
2832
optional = Optional.of(None)
2933
self.assertFalse(optional.is_present())
3034

31-
def test_cannot_get_without_checking_presence(self):
32-
optional = Optional.of("thing")
33-
with self.assertRaises(OptionalAccessWithoutCheckingPresenceException):
34-
optional.get()
35-
3635
def test_cannot_get_from_empty_even_after_checking(self):
3736
optional = Optional.empty()
3837
self.assertTrue(optional.is_empty())
@@ -156,7 +155,3 @@ def test_non_empty_optionals_with_non_equal_content_are_not_equal(self):
156155

157156
def test_non_empty_optionals_with_equal_content_are_equal(self):
158157
self.assertEqual(Optional.of("PANTS"), Optional.of("PANTS"))
159-
160-
161-
if __name__ == '__main__':
162-
unittest.main()

0 commit comments

Comments
 (0)