[Tutor] raising exceptions in constructor code?
Steven D'Aprano
steve at pearwood.info
Tue Jul 16 18:53:05 EDT 2019
On Tue, Jul 16, 2019 at 04:29:15PM -0500, James Hartley wrote:
> I ask this having more C++ knowledge than sense.
>
> There is an adage in the halls of everything Stroustrup that one needs to
> think about how resource allocation will be unwound if an exception is
> thrown. This gets watered down to the mantra "Don't throw exceptions from
> within constructors." Does this carry over to Python? I'm trying to
> develop a Pythonistic mindset as opposed to carrying over old baggage...
No, it is perfectly safe to raise exceptions from within the Python
constructors, whether you are using __new__ (the true constructor) or
__init__ (the initialiser).
The only tricky part is if you allocate resources external to the
object, like this:
class Weird(object):
openfiles = []
def __new__(cls, fname):
f = open(fname)
cls.openfiles.append(f)
# New instance:
instance = super().__new__(cls)
if condition:
raise ValueError
return instance
Even if the __new__ constructor fails, I've kept a reference to an open
file in the class. (I could have used a global variable instead.) That
would be bad. But notice I had to work hard to make this failure mode,
and write the code in a weird way. The more natural way to write that^1
would be:
class Natural(object):
def __init__(self, fname):
self.openfile = open(fname)
if condition:
raise ValueError
Now if there is an exception, the garbage collector will collect the
instance and close the open file as part of the collection process.
That might not be immediately, for example Jython might not close the
file until interpreter shutdown. But the earlier example will definitely
leak an open file, regardless of which Python interpreter you use, while
the second will only leak if the garbage collector fails to close open
files.
Here's a better example that doesn't depend on the quirks of the garbage
collector:
class Leaky(object):
instances = []
def __init__(self):
self.instance.append(self)
if random.random() < 0.1:
raise ValueError
This will hold onto a reference to the instance even if the initialiser
(constructor) fails. But you normally wouldn't do that.
class NotLeaky(object):
def __init__(self):
if random.random() < 0.1:
raise ValueError
try:
x = NotLeaky()
except ValueError:
pass
Now either the call to NotLeaky succeeds, and x is bound to the
instance, or it fails, and x is *not* bound to the instance. With no
references to the newly-created instance, it will be garbage collected.
^1 Actually that's not too natural either. It is not usually a good idea
to hold onto an open file when you aren't actively using it, as the
number of open files is severely constrained on most systems.
--
Steven
More information about the Tutor
mailing list