Introduction
Resource Acquisition and Initialization means the following:
- When we create an object, we make sure it gets everything it needs.
- When we don’t need that thing anymore, we clean up and give back any resources it was using.
There are several ways to implement this pattern in Python:
- By using context managers, usually using the
with
statement - Manually using the
del
statement - Using the
try-finally
construct.
We will discuss all three in this post. Don’t worry, it’s not as complicated as it sounds.
For our example we will build a very simple window system in three slighly different windows.
Using context managers and the ‘with’ statement.
Let’s look at this short program:
import traceback
from typing import Self
class Window:
_title: str = None
def __init__(self, title):
self._title = title
@property
def title(self) -> str:
return self._title
def open(self):
print(f"Opening window with title {self._title}")
def close(self):
print(f"Closing window with title {self._title}")
def __enter__(self) -> Self:
print(f"Window with title {self._title} acquired")
return self
def __exit__(self, exc_type: type | None, exc_val: Exception | None, exc_tb: traceback):
print(f"Window with title {self._title} released")
if __name__ == "__main__":
with Window("My windows") as w:
w.open()
w.close()
A short summary:
- Our
Window
class only has one attribute, a title - Besides the usual open() and close() methods, there are two new methods: enter(), which is called when you start a ‘with’ block, and exit(), when the block ends. This method is always called, no matter how the block exits, normally or with an exception.
- In the main program we use the
with
statement to make sure the resources are properly acquired and released.
Releasing resources manually using the ‘del’ statement
Let’s have a look at this code:
class Window:
_title: str = None
def __init__(self, title):
self._title = title
@property
def title(self) -> str:
return self._title
def open(self):
print(f"Opening window with title {self.title}")
def close(self):
print(f"Closing window with title {self.title}")
def __del__(self):
print(f"Deleting window with title {self.title}")
if __name__ == "__main__":
w = Window("My window")
w.open()
w.close()
del w
A short summary:
- We define a
Window
class with a constructor andopen()
andclose()
methods - The
__del()__
method is called when thedel
statement is called on the object. - In the main program, we create a window, open and close it, and delete it.
Using the ‘try-finally’ method
This method is a little different but can be elegant in some situations:
class Window:
_title: str = None
def __init__(self, title):
self._title = title
@property
def title(self) -> str:
return self._title
def open(self):
print(f"Opening window with title {self.title}")
def close(self):
print(f"Closing window with title {self.title}")
def release(self):
print(f"Releasing window with title {self.title}")
if __name__ == "__main__":
w = Window("My window")
try:
w.open()
w.close()
finally:
w.release()
A short explanation:
- We define the same
Window
class, but now we add arelease()
method - In the main program, we create a window
- In the
try-finally
block we open and close the window. After that, even when an exception is raised, the finally block is executed and the window is released.
Conclusion
This pattern is not very hard to understand. It’s important to remember it to make sure you acquire and release resources properly.
As I have shown there are at least three ways to implement this pattern. Which pattern you use depends on your personal preference. My personal preference would be the with
-method: it is the clearest, and the flow is the easiest to understand.
In a later pos I will have a look at this pattern in a multi-threaded environment, because the implementation as they stand are not thread-safe.