Reputation: 311
In JVM languages there is a data type called Optional which says value can be Null/None. By using the data type of a function as Option(for example Option(Int)). Calling statement of function can take action.
How does one implement a similar approach in Python. I want to create a function and the return value of function should tell me 1. Function was successful so I have a value returned. 2. Function was not successful and so there is nothing to return.
Upvotes: 7
Views: 23604
Reputation: 312
One approach would be to return either ()
or (<type>,)
(int
in your case). You could then come up with some helper functions like this one:
def get_or_else(option: tuple, default):
if option:
return option[0]
else:
return default
One benefit of this approach is that you don't need to depend on a third-party library to implement this. As a counterpart, you basically have to make your own little library.
Upvotes: 0
Reputation: 1968
I also wanted to tackle this problem and made a library called optional.py to do so. It can be installed using pip install optional.py
. It is fully test covered and supports python2 and python3. Would love some feedback, suggestions, contributions.
To address the concerns of the other answer, and to head off any haters, yes, raising exceptions is more idiomatic of python, however it leads to ambiguity between exceptions for control-flow and exceptions for actual exceptional reasons.
There are large discussions about preventing defensive programming which mask the core logic of an application and smart people on both sides of the conversation. My personal preference is towards using optionals and so I provided the library to support that preference. Doing it with exceptions (or returning None
) are acceptable alternatives, just not my preference.
Upvotes: 6
Reputation: 11358
To your original question: Raise an exception instead of returning None
. This is an idiom in Python and therefore well understood by users. For example
def get_surname(name):
if name == "Monty":
return "Python"
else:
raise KeyError(name)
You'll see this usage pattern a lot in Python:
try:
print(get_surname("foo"))
except KeyError:
print("Oops, no 'foo' found")
Based on your feedback, it also seemed like you wanted to make sure that certain return values are actually used. This is quite tricky, and I don't think there is an elegant way to enforce this in Python, but I'll give it a try.
First, we'll put the return value in a property so we can track if it has indeed been read.
class Result(object):
def __init__(self, value):
self._value = value
self.used = False
@property
def value(self):
self.used = True # value was read
return self._value
Secondly, we require that the Result
object must be retrieved in a with
-block. When the block is exited (__exit__
), we check if the value has been read. To handle cases where a user doesn't use the with
-statement, we also check that the value has been read when it is being garbage collected (__del__
). An exception there will be converted into a warning. I usually shy away from __del__
though.
class RequireUsage(object):
def __init__(self, value):
self._result = Result(value)
def __enter__(self):
return self._result
def __exit__(self, type, value, traceback):
if type is None: # No exception raised
if not self._result.used:
raise RuntimeError("Result was unused")
def __del__(self):
if not self._result.used:
raise RuntimeError("Result was unused (gc)")
To use this system, simply wrap the return value in RequireUsage
like so:
def div2(n):
return RequireUsage(n/2)
To use such functions, wrap them in a block and extract the value
:
with div2(10) as result:
print(result.value)
If you don't invoke result.value
anywhere, you will now get an exception:
with div2(10) as result:
pass # exception will be thrown
Likewise, if you just call div2
without with
at all, you will also get an exception:
div2(10) # will also raise an exception
I wouldn't particularly recommend this exact approach in real programs, because I feel it makes usage more difficult while adding little. Also, there are still edge-cases that aren't covered, like if you simply do res = div2(10)
, because a held reference prevents __del__
from being invoked.
Upvotes: 1