[Python-Dev] PEP 442 clarification for type hierarchies

Stefan Behnel stefan_ml at behnel.de
Tue Aug 6 17:18:59 CEST 2013


Antoine Pitrou, 06.08.2013 14:12:
> Le Mon, 05 Aug 2013 22:30:29 +0200,
> Stefan Behnel a écrit :
>>
>> Hmm, it's a bit unfortunate that tp_finalize() maps so directly to
>> __del__(), but I think this can be fixed. In any case, each
>> tp_finalize() function must only ever be called once, so if a subtype
>> inherited the tp_finalize() slot from its parent, it mustn't be
>> called again.
> 
> This is already dealt with by a custom bit in the GC header (cf.
> _PyGC_IS_FINALIZED, IIRC).

But that's only at an instance level. If a type in the hierarchy inherited
the slot function for tp_finalize() from its parent, then the child must
skip its parent in the call chain to prevent calling the same slot function
twice. No instance flag can help you here.


>>>> An obvious open question is how to deal with exceptions during
>>>> finalisation. Any break in the execution chain would mean that a
>>>> part of the type wouldn't be finalised.
>>>
>>> Let's come back to pure Python:
>>>
>>> class A:
>>>     def __del__(self):
>>>         1/0
>>>
>>> class B(A):
>>>     def __del__(self):
>>>         super().__del__()
>>>         self.cleanup_resources()
>>
>> What makes you think it's a good idea to call the parent type's
>> finaliser before doing the local finalisation, and not the other way
>> round? What if the subtype needs access to parts of the super type
>> for its cleanup?
> 
> I'm not saying it's a good idea. I'm just saying that to reason about
> the C API, it is a good idea to reason about equivalent pure Python
> code. Since exceptions aren't implicitly silenced in pure Python code,
> they probably shouldn't in C code.
> 
>> In other words, which makes more sense (at the C level):
>>
>>     try:
>>         super().tp_finalize()
>>     finally:
>>         local_cleanup()
>>
>> or
>>
>>     try:
>>         local_cleanup()
>>     finally:
>>         super().tp_finalize()
>>
>> Should that order be part of the protocol or not? (well, not for
>> __del__() I guess, but maybe for tp_finalize()?)
> 
> No, it is left to the user's preference. Since tp_finalize() is meant
> to be equivalent to __del__(), I think it's better if the protocols
> aren't subtly different (to the extent to which it is possible, of
> course).

Ok, fine with me. If the calls are done recursively anyway, then the child
can decide when to calls into its parent.


>> Coming back to the __del__() vs. tp_finalize() story, if tp_finalize()
>> first recursed into the super types, the top-most one of which then
>> calls __del__() and returns, we'd get an execution order that runs
>> Python-level __del__() methods before C-level tp_finalize()
>> functions, but loose the subtype-before-supertype execution order for
>> tp_finalize() functions.
> 
> Well... to get that, you'd have to subclass a pure Python class with a
> C extension type.

Maybe I'm wrong here. It's the default implementation of tp_finalize() that
calls __del__, right? If a Python class with a __del__ inherits from an
extension type that implements tp_finalize(), then whose tp_finalize() will
be executed first? The one of the Python class or the one of the extension
type?

Stefan




More information about the Python-Dev mailing list