Re: [Tutor] raising exceptions in constructor code?

2019-07-16 Thread Cameron Simpson

On 16Jul2019 23:49, Alan Gauld  wrote:

On 16/07/2019 22:56, Mats Wichmann wrote:
thrown.  This gets watered down to the mantra "Don't throw 
exceptions from

within constructors."  Does this carry over to Python?


If you mean __init__, that's not a constructor, so you should set your
mind at rest :)   It's more properly an "initializer", the instance has
already been constructed when it's called.


FWIW The true constructor is __new__() and its quite rarely overridden
by application programmers. But if you do, it's not common that you'd do
anything that would merit an exception. __new__ pretty much just sets
up the structure of the object ready for initialisation by __init__.

Incidentally, this two stage construction/initialisation is also found
in other OOP languages like Smalltalk and Objective C (and Lisp?).


And to return to the OP's question:

The __init__ method (and arguably __new__ if you touch it - very rare) 
is like other Python code: resource allocation should normally get 
unwound as objects become unreferenced. So raising an exception should 
be a pretty safe thing to do.


That is a simplification. Of course if you implement an object with side 
effects _outside_ the Python object space (maybe it opened a scratch 
file to support something), it is your responsibility to ensure release 
in the object's __del__ method. But an object that just allocates a 
bunch of lists or dicts or the like? Python will clean that up for you.


That said, I try to do cheap initialisation before exspensive 
initialisation. So allocating locks, opening files, starting worker 
threads: these come at the bottom of the __init__ method.


Also, it is _ROUTINE_ to raise exceptions from __init__: like any other 
method we _expect_ you to raise ValueError if the initialiser parameters 
are insane (negatively sized arrays, etc etc).


So in Python, raising exceptions in __init__ is normal: it shouldn't 
happen when you programme is running correctly of course, but it is the 
_preferred_ action when your initialiser cannot complete correctly.


Consider:

 x = Foo()

After this assignment we expect "x" to be a usable instance of Foo. We 
don't put special checks; what would such checks look like? (There are 
some answers for that, but they're all poor.)


So raising an exception is what happens if __init__ fails.

Cheers,
Cameron Simpson 
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] raising exceptions in constructor code?

2019-07-16 Thread Steven D'Aprano
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
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] raising exceptions in constructor code?

2019-07-16 Thread Alan Gauld via Tutor
On 16/07/2019 22:56, Mats Wichmann wrote:

>> thrown.  This gets watered down to the mantra "Don't throw exceptions from
>> within constructors."  Does this carry over to Python?  

> 
> If you mean __init__, that's not a constructor, so you should set your
> mind at rest :)   It's more properly an "initializer", the instance has
> already been constructed when it's called.

FWIW The true constructor is __new__() and its quite rarely overridden
by application programmers. But if you do, it's not common that you'd do
anything that would merit an exception. __new__ pretty much just sets
up the structure of the object ready for initialisation by __init__.

Incidentally, this two stage construction/initialisation is also found
in other OOP languages like Smalltalk and Objective C (and Lisp?).

-- 
Alan G
Author of the Learn to Program web site
http://www.alan-g.me.uk/
http://www.amazon.com/author/alan_gauld
Follow my photo-blog on Flickr at:
http://www.flickr.com/photos/alangauldphotos


___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] raising exceptions in constructor code?

2019-07-16 Thread Mats Wichmann
On 7/16/19 3:29 PM, 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...

If you mean __init__, that's not a constructor, so you should set your
mind at rest :)   It's more properly an "initializer", the instance has
already been constructed when it's called.


___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


[Tutor] raising exceptions in constructor code?

2019-07-16 Thread James Hartley
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...

Thanks!

Jim
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor