The one example I gave in the parent comment already addresses everything you talked about.
Yes you can use a Python context manager (that's what you're referring to) to lock a mutex, but you can't pass that context manager as a return result of a function and be sure its __exit__ method will be called appropriately in the three situations I mentioned (immediately if result thrown away, at the end of the function if stored in a local variable, later still if returned from that outer function).
In fact I don't think you can return a context manager from within a "with" block at all, even accepting that the parent function will have to manually reuse it in another with block, because its __exit__ method will already be called in the inner function. Technically this is true in C++ too of course because the object in the inner function will have its destructor called, but its move constructor will be called first giving you an opportunity to clear out its internal state (this is why my example used a unique_lock rather than a lock_guard). Things are even better in Rust: the bytes of the object are directly copied to the new object's footprint and the original destructor isn't called, so you don't even need to set up a dummy "empty" state.
> The only thing that the c++ method does really well is memory management (e.g. unique_ptr), because free() can't fail.
Mutex unlocking usually can't fail (and if they do, there's usually not much you can do about it). Same with closing network connections. Closing files can fail, and for some programs it's very important to know when that happens, but for many others it's not important at all. Python's chained exceptions are very nice, but not so important that their absence from C++'s exceptions (or Rust's panics) makes those languages useless.