RFC: Proposal: Deterministic Object Destruction

Chris Angelico rosuav at gmail.com
Mon Mar 5 09:21:21 EST 2018


On Tue, Mar 6, 2018 at 12:58 AM, Ooomzay <ooomzay at gmail.com> wrote:
> Then that is indeed a challenge. From CPython back in 2.6 days up to Python36-32 what I see is:-
>
> a Opened
> b Opened
> Traceback (most recent call last):
> ...
> AttributeError: 'C' object has no attribute 'dostuff'
> a Closed
> b Closed
>
>> Maybe exceptions aren't as easy to handle as you think?
>
> Well there is a general issue with exceptions owing to the ease
> with which one can create cycles that may catch out newbs. But
> that is not the case here.
>
>> Or maybe you
>> just haven't tried any of this (which is obvious from the bug in your
>> code
>
> Or maybe I just made a typo when simplifying my test case and failed to retest?
>
> Here is my fixed case, if someone else could try it in CPython and report back that would be interesting:-
>
> class RAIIFileAccess():
>     def __init__(self, fname):
>         print("%s Opened" % fname)
>         self.fname = fname
>
>     def __del__(self):
>         print("%s Closed" % self.fname)
>
> class A():
>     def __init__(self):
>         self.res = RAIIFileAccess("a")
>
> class B():
>     def __init__(self):
>         self.res = RAIIFileAccess("b")
>
> class C():
>     def __init__(self):
>         self.a = A()
>         self.b = B()
>
> def main():
>     c = C()
>     c.dostuff()
>
> main()

Tried this in the interactive interpreter again.

>>> main()
a Opened
b Opened
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in main
AttributeError: 'C' object has no attribute 'dostuff'
>>>

Same problem! If you can't handle this situation, there's something
fundamentally wrong with your system.

Here's how I'd do it with context managers.

from contextlib import contextmanager

@contextmanager
def file_access(fname):
    try:
        print("%s Opened" % fname)
        yield
    finally:
        print("%s Closed" % fname)

@contextmanager
def c():
    try:
        print("Starting c")
        with file_access("a") as a, file_access("b") as b:
            yield
    finally:
        print("Cleaning up c")

def main():
    with c():
        dostuff() # NameError

>>> main()
Starting c
a Opened
b Opened
b Closed
a Closed
Cleaning up c
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in main
NameError: name 'dostuff' is not defined
>>>

And if you want different semantics, you can lay out c() differently -
maybe have the try/finally not contain within the 'with' but be
contained within it, or whatever else you like. Exceptions move
through the stack exactly the way you'd expect them to. Instead of
having an object to represent each resource, you simply have a
function with the code needed to allocate and deallocate it. They nest
perfectly.

ChrisA



More information about the Python-list mailing list