Using the Picamera2 library and Camera Module 3 is one of the many ways to take pictures with Raspberry PI. Once the hardware module is installed you can try the code proposed by the official documentation (The Picamera 2 Manual, cap 2.3):

from picamera2 import Picamera2, Preview
import time
picam2 = Picamera2()
camera_config = picam2.create_preview_configuration()
picam2.configure(camera_config)
picam2.start_preview(Preview.QTGL)
picam2.start()
time.sleep(2)
picam2.capture_file("test.jpg")

This program launches a preview window, so in a headless Raspberry Pi 4 board (without monitor and keyboard) it can be simplified:

from picamera2 import Picamera2
cam = Picamera2()
cam.start()
cam.capture_file("test_1.jpg")

If the program is complex and photo processing takes time, you can take advantage of concurrent execution on the processor cores of the Raspberry board. Python offers the asyncio library (Asynchronous I/O) which allows the use of the async/await syntax.

When the program has to carry out some heavy tasks, it launches them in parallel, waiting for the end of the execution only when it needs the results, as in the figure:

picam2.start()picam2.capture_file()more code ...code ...image elaboration ...always code ...

In the following example, I only use the coroutines startup and sync apis: concurrent functions are declared as async and are launched in parallel with asyncio.create_task(). This method returns a Task object, which can then be used for a later sync using the await command:

#!/usr/bin/python3
import asyncio
from picamera2 import Picamera2

async def shot(camera):
    # … some more code …
    camera.capture_file("test_2.jpg")
    # … some more code …
   
async def main():
    cam = Picamera2()
    cam.start()
    task1 = asyncio.create_task(shot(cam))
    # … some more code …
    await task1

asyncio.run(main())

Note that asyncio requires to launch the main function with asyncio.run(...).

References