8

I am new to Python. I come from C++.

In some code reviews, I've had several peers wanting me to move things from init and del to a start and stop method. Most of them time, this goes against the RAII that was beaten into my head with decades of C++.

https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization

Is RAII not a thing in Python? Shouldn't it be?

After all, we can throw exceptions and we'd want to release resources when we do, no?

If it isn't. Can someone give some insight as to why things are done differently? Is there a language feature that I don't understand?

if I have:

class Poop:
    def __init__:
        # Get some Windows Resource
    def __del__:
        #Release some Windows Resource

def foo():
    poop = Poop()
    raise Exception("Poop happens")

The Windows Resource is released, right?

Christopher Pisz
  • 3,757
  • 4
  • 29
  • 65
  • See [garbage collection](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) which python relies upon instead of `delete` whether implied or explicit. C++ has been infested rethought with considerable amounts of religion in recent decades. – wallyk May 09 '19 at 00:24
  • I would just add that languages are different. Don't expect that *anything* you know from C++ is a given in Python. Yes, there are similarities, but there are also "false cognates": constructs that *seem* like they should be the same thing, but aren't. Programming languages are like human language that way. Don't try to "transliterate" C++ into Python, you'll only end up frustrated. Instead, learn the idioms and meaning in the new language. As you learn them you will see how they relate to *concepts* you already know from C++, not *constructs* of that language. – Daniel Pryden May 09 '19 at 00:55

3 Answers3

10

RAII works in C++ because destruction is deterministic.

In garbage collected languages like Python, your object could theoretically never be destroyed, even if you call del on it.

Anyway, the idiomatic way to handle resources in Python is not with RAII, nor with start/stop, but with context managers.

The simplest example is with a file object:

with open('this_file.txt') as f:
    #  ... do stuff with f ...

# ... back to code that doesn't touch f ...

The with statement is, more or less, a try-finally block that creates a resource and ensures that the resource is cleaned up when the block ends; something like this:

try:
    f = open('this_file.txt')
    #  ... do stuff with f ...

finally:
    f.close()

# ... back to code that doesn't touch f ...

I don't know Java, but I believe that the JVM also uses garbage collection, and similarly try-finally is an idiom for resource management in Java.

Anyway, the with statement takes a context manager, which is an instance of a class defining the __enter__ and __exit__ methods (see the docs).

For completeness, there may be cases where you want a context manager, but don't want to define a whole class just for that. In that case, contextlib may help.

A worked example; say you have a resource:

class Resource:

    def method(self):
        pass

get_resource = Resource
release_resource = lambda x: None

A RAII-like class might look something like this:

class RAIILike:

    def __init__(self):
        self.resource = get_resource()

    def __del__(self):
        release_resource(self.resource)

    def do_complex_thing(self):
        #  do something complex with resource
        pass

raii_thingy = RAIILike()

And you would use the resource like this:

raii_thingy.resource.method()

On the other hand, a context managed resource could look like this...

class ContextManagedResource:

    def __enter__(self):
        self._resource = get_resource()
        return self._resource

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is not None:
            #  handle exception here
            pass

        else:
            pass

        release_resource(self._resource)
        return True

...and be used like this:

with ContextManagedResource() as res:
    res.method()

Once the with block ends, the resource will be automatically released, regardless of whether the object that obtained it has been garbage collected.

gmds
  • 19,325
  • 4
  • 32
  • 58
  • If memory isn't one of your concerns, it's still RAII. Speaking strictly, yes, the context manager itself is not destroyed on leaving the scope, but the context itself is. – a small orange May 09 '19 at 00:33
  • I am not really clear on how to use the with keyword to manage a resource for the lifetime of a class. Can you incorperate the example code I just edited into the original question? – Christopher Pisz May 09 '19 at 00:38
  • @ChristopherPisz your class could implement a staticmethod that is a context manager handling that resource, then individual method calls would request the resource using the context manager. – Adam Smith May 09 '19 at 00:43
  • Can the `atexit.register` functionality be used to implement RAII? Many object may have the responsibility to handle the life cycle of resources and not of them can be used as context manager from my own perspective – Jean-Marc Volle Jun 24 '20 at 10:06
1

Your own reference to wikipedia says:

Perl, Python (in the CPython implementation), and PHP manage object lifetime by reference counting, which makes it possible to use RAII. Objects that are no longer referenced are immediately destroyed or finalized and released, so a destructor or finalizer can release the resource at that time. However, it is not always idiomatic in such languages, and is specifically discouraged in Python (in favor of context managers and finalizers from the weakref package).

Mats Wichmann
  • 800
  • 6
  • 6
1

You can do RAII in python, or get pretty close. However, unlike C++ where you do the work in the constuctor and destructor, in python you need to use the dunder functions of enter and exit. This post has a excellent write up of how to write the functions and how they will behave in the presence of exceptions: https://preshing.com/20110920/the-python-with-statement-by-example/

Les Brody
  • 11
  • 1