Raising objects

Alex Martelli aleax at aleax.it
Wed Apr 30 19:16:11 EDT 2003


Steven Taschuk wrote:

> Quoth Alex Martelli:
>> Steven Taschuk wrote:
>   [inability to raise new-style objects]
>> > I don't have a real use case either, but it does seem rather
>> > arbitrary.  Is there an implementation reason for this restriction?
>> 
>> In a sense, yes: there is no way to check if something "is a new-style
>> class" as opposed to "any type whatsoever" -- so one could end up
>> raising lists, tuples, strings (shades of the past...), whatever.
> 
> How about this?
> 
>     >>> def newstyle(obj):
>     ...     """Whether obj is an instance of a new-style class."""
>     ...     return hasattr(obj, '__class__') \
>     ...            and issubclass(obj.__class__, object)

This so-called "newstyle" accepts EVERYTHING except old-style classes.  E.g:

>>> hasattr(23, '__class__')
True
>>> issubclass((23).__class__, object)
True
>>>

So, it IS accepting "any type whatsoever" -- just not classic classes.
As I said, this includes lists, tuples, strings -- and also integers,
and so forth.


> It would produce counterintuitive results in such use if it were
> possible for a new-style class object to be an instance of an
> old-style class...  is that possible?

I guess you can try using a classic-class as a metaclass but it
would be a HUGE lot of work.  That's because of the semantic level
confusion in the classic object model that was removed in the
newstyle object model.  E.g.:

>>> class icmeta:
...   def __getattr__(self, name):
...     return getattr(type, name)
...   def __call__(*args, **kwds):
...     print 'BUH!'
...     return type.__call__(*args, **kwds)
...
>>> class weirdo(icmeta): pass
...
>>> ww = weirdo()
>>> ww
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: descriptor '__repr__' of 'type' object needs an argument
>>> ww()
BUH!
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 5, in __call__
TypeError: descriptor '__call__' requires a 'type' object but received a 
'instance'
>>>

See?  Calling the INSTANCE ww suprisingly ends up using the
METACLASS's __call__ -- eeek.

I very much doubt anybody would WANT to use this kind of thing.


> This function does report that strings and lists are new-style.
> Aren't they?

If by "new-style" you mean "anything EXCEPT classic classes" then
yes, of course -- strings, lists, numbers, etc, aren't classic
classes.  But then it's simpler to check for types.ClassType, no?


>> What I hope to see happen eventually is a spec that raise can use:
>> 
>>   -- any subclass of Exception (by that time a new-style class), OR
>>   -- for backwards compatibility any old-style class, OR
>>   -- for VERY backwards compatibility, any string
>> 
>> [and also INSTANCES as well as classes for the first two cases].
> 
> That seems reasonable.
> 
> But is there a problem with being able to raise anything?  Perhaps
> some class of easy-to-produce bugs that I'm not seeing?

When you raised strings (still supported for backwards compatibility)
the except statement worked by IDENTITY -- it didn't catch a string
that was equal but not identical.  This avoided deuced bugs such as:

in module primo.py:
error = 'error'

in module secondo.py, independently developed:
error = 'error'


in module terzo.py, using both the others...:

import primo, secondo

...

try:
    primo.blah()
    secondo.bluh()
except primo.error:
    print "error in blah"


where secondo.bluh might raise secondo,error -- if except had
worked by equality, it would have made an unwanted catch here.


So -- would all objects be caught by identity?  Consider the
horrible, perennial constraints this would impose on obvious
possible future optimizations -- why couldn't a very smart
compiler be able to "fold" equal immutable objects into a
single one?  Answer -- it couldn't do so without breaking
code using catch-by-identity as above.


Now we catch-by-class instead (when class instances are raised)
and the problem evaporates (except for "legacy" code as above).
Moreover, thanks to inheritance, we can easily catch large
"groups" of exceptions either jointly or severally.

Moreover, if what you raise is always derived by Exception, the
Exception base class can ensure all sort of nice error-reporting
etc -- I consider it a minor design bug that in the switch to
class-based exceptions the constraint that classes raises must
subclass Exception (directly or indirectly) wasn't introduced.


> What I'm imagining at the moment is this: suppose you can raise
> anything, and except clauses identify the exceptions to be caught
> by their class.  The only problem I can see with this is that it
> clashes with the present (backwards-compatible) treatment of
> raised strings:

Exactly.

>     try:
>         raise 'foo'
>     except 'foo':
>         print 'wah'
> wouldn't print 'wah', since 'foo' is not an instance of 'foo'.

It might or might not do so today, depending on the id() of the
two 'foo' literals (which Python doesn't guarantee either way).

> You'd have to do something like
>     try:
>         raise 'foo'
>     except str, e:
>         if e == 'foo':
>             print 'wah'
>         else:
>             raise
> which is ugly.

It would also have different semantics (== instead of is).


> So, suppose that strings were special-cased for backwards
> compatibility:
> 
>     def exceptmatch(exception, classtomatch):
>         """Whether an except clause specifying classtomatch should
>         catch exception.
>         """
>         if isinstance(classtomatch, str):
>             return exception == classtomatch
>         else:
>             return isinstance(exception, classtomatch)
> 
> (I ignore for simplicity except clauses which match more than one
> class.)
> 
> Is there a problem with this scheme?

The horrid complexity and specialcasing is problematic enough
for me.  Allowing such generality in what is raised buys you
nothing pragmatically useful AND you pay a price in complexity.
No thanks!!!

Allowing only [a] subclasses of (say) NewException, a hypothetical
new newstyle class; or [b] classic classes, and [c] strings, to
be raised, with b and c there only for backwards compatibility,
is FAR simpler, neater, easier to explain, and more pragmatical.


>> Exactly: even in my hoped-for new spec, you'd have to wrap this
>> in a subclass of Exception.  Facilitating the [ab]use of the
>> exception mechanism for generalized control flow is, I suspect,
>> NOT on the list of priorities in the BDFL's mind;-).  Feel free
>> to open a PEP to settle that beyond any doubt, though...;-)
> 
> Heh.  I imagine you're right.  I'd argue in favour of a scheme
> such as above chiefly on the basis of regularity (rather than for
> making my silly example work).

I think it's LESS simple and thus regular than my a/b/c scheme --
where a is the mainstream and b/c sad legacies of the past.


> I just don't see a good reason not to be able to raise new-style
> objects, so to me it's an arbitrary restriction and as such makes
> the language slightly uglier.  (It is, of course, a very minor
> issue; I hadn't even noticed that Exception is old-style until
> this thread.)

I think it's a restriction that is FAR from arbitrary: on the
contrary, in my opinion it would be quite useful and neat.


Alex





More information about the Python-list mailing list