Suppose I do:
myfoo = Foo('grapes', 'oranges')
And in the __init__() of Foo, there is
a real problem with the consumption of fruit.
Is there a clean way to ensure that myfoo
will be None after the call?
I don't believe so.
Generally, in the event of an error, you should raise an exception:
class Foo():
def __init__(self, *fruits):
if "grapes" in fruits:
raise AllergyError("I'm allergic to grapes")
# process fruits
Then handle the exception:
try:
myfoo = Foo('grapes', 'oranges')
except AllergyError:
# recover gracefully
handle_failed_instance()
If you wish, you can wrap it in a function:
def safe_foo(*fruits):
try:
return Foo(*fruits)
except AllergyError:
return None
myfoo = safe_foo('grapes', 'oranges')
The disadvantage of this is now your code has to be sprinkled with a
million tests "if myfoo is not None: process(myfoo)".
Would the
__init__() just do del(self), or is there
a better way to think about this?
An alternative is to use a propagating "not a Foo" Foo object, like NaNs
and INFs in floating point.
class Foo():
def __init__(self, *fruits):
if "grapes" in fruits:
self._state = "failed" # NaNs use special bit patterns
else:
# process fruits
self._state = "okay"
def method(self, *args):
if self._state != "failed":
# process instance
else:
pass # do nothing
def __add__(self, other):
if self._state == "failed":
return self
elif other._state == "failed":
return other
else:
# process Foo addition
return something
Now you can call Foo methods regardless of whether the instance is valid
or not, errors will propagate cleanly, and you only need to check whether
the instance is valid at the very end of your code.
However, unless there is a clear advantage of doing this, you're creating
a fair bit of extra work for yourself.