On Mon, Mar 22, 2021 at 3:14 PM Guido van Rossum
On Sun, Mar 21, 2021 at 3:35 PM Chris Angelico
wrote: On Mon, Mar 22, 2021 at 7:49 AM Ben Rudiak-Gould
wrote: In the "Object Lifetime" section you say "registers should be cleared upon last reference". That isn't safe, since there can be hidden dependencies on side effects of __del__, e.g.:
process_objects = create_pipeline() output_process = process_objects[-1] return output_process.wait()
If the process class terminates the process in __del__ (PyQt5's QProcess does), then implicitly deleting process_objects after the second line will break the code.
Hang on hang on hang on. After the second line, there are two references to the last object, and one to everything else. (If create_pipeline returns two objects, one for each end of the pipe, then there are two references to the second one, and one to the first.) Even if you dispose of process_objects itself on the basis that it's not used any more (which I would disagree with, since it's very difficult to manage that well), it shouldn't terminate the process, because one of the objects is definitely still alive.
In the hypothetical scenario, presumably create_pipeline() returns a list of process objects, where the process class somehow kills the process when it is finalized. In that case dropping the last reference to process_objects[0] would kill the first process in the pipeline. I don't know if that's good API design, but Ben states that PyQt5 does this, and it could stand in for any number of other APIs that legitimately destroy an external resource when the last reference is dropped. (E.g., stdlib temporary files.)
The question is really whether process_objects ceases to exist after the last time it's referenced. I may have misinterpreted the thin example here, but let's just focus on process_objects[0] (hereunder "po0" for simplicity), and assume that there's at least two elements in the list. A list begins to exist somewhere inside create_pipeline(), and at the point where that list is returned, it has a reference to po0. That list is returned, and assigned to process_objects, which we assume is a function-local variable. So the function's locals reference process_objects, which references po0. So far, so good. Then we get a new variable output_process, and we lift something unrelated from the list. Then we call a method on an unrelated object, and return from the function. At what point does the process_objects list cease to be referenced? After the last visible use of it, or at the end of the function? My understanding of Python's semantics is that the list object MUST continue to exist all the way up until the function exits, or wording that another way, that the function's call frame has a reference to ALL of its locals, not just the ones that can visibly be seen to be used. Allowing an object to be disposed of early if there are no future uses of it would be quite surprising. It would be different if, before the return statement, "process_objects = None" were inserted. Then the list would cease to be referenced, and po0 would cease to be referenced, and regardless of the exact type of GC being used, it would be legit to ditch it before the wait() call. If *that* version is broken, then there's a problem with the objects in the list depending on each other in a non-Python-visible way, and that's a bug in the library. Can a PyQT user clarify, please? ChrisA