The command with
serves to facilitate the writing of any code block that involves resources that need to be "finalized" (i.e., restored, released, closed, etc... ) after the block is terminated - and it allows this to be done automatically, with the logic of completion within the object used.
So first thing: the completion code of with
always runs - no matter if an error occurred inside the with
or not.
But this could already be done with the block finally
and a clause try...finally
- no matter the outcome of the block within the try
, the finally
always runs.
The great differential of with
is that the programmer who is using the objects that need completion don’t have to worry about calling these methods explicitly.
In the case of a file, in general people know that it has to be called the close
- but the most common, as is already used the close
is that no one ends up programming an excerpt that handles file with call to close
, without using the with
remembering to put this inside a finally
. That is, the with
face encourages the code to be written in a way that behaves in the correct way:
wrong way:
f = open("data.txt", "w")
# codigo usando o arquivo f
f.close()
right way, without the with:
try:
f = open("data.txt", "w")
# codigo usando o arquivo f
finally:
f.close()
right way with the with (recommended):
with open("data.txt", "w") as f:
# codigo usando o arquivo f
(the file is automatically closed at the end of the block).
Then note that the code "right" still gets a shorter line than the "wrong" way. But - as I mentioned above, the close
files is not the coolest thing of with
- because everyone knows and remembers the close
(hopefully) - but is that for any object that needs a shutdown - threads Locks, database transactions, change terminal status to read keys without needing the <enter>
, Monkey patch functions and methods in testing, the with
performs the completion transparently - without the user needing to know if they need to call the method close
, release
, undo
of each object (in fact, it forces programmers of the object classes that work with to implement a common interface, with the methods __enter__
and __exit__
.
What’s more, when you’re programming your own classes that you can use with with
can handle within your function code __exit__
the most common, or expected errors that could happen in the block: the method __exit__
receives information about the exception that occurred within the block, and the __exit__
can choose to treat the error and delete the exception, or simply release the resource and pass the error forward: ie a lot of code that would always be the same as someone would have to write within the clause except
all once you were using your object can stay inside the __exit__
and people use their object without worrying about the code that would go inside the except
.
Here is a simple example of a "bank account" that makes temporary transactions with the balance, but only allows transactions to take place at the end of the with
if there is a positive balance in all accounts:
class ContaBancariaTransacional:
def __init__(self, titular, saldo_inicial=0):
self.titular = titular
self.saldo = saldo_inicial
def deposito(self, valor):
self.temp += valor
def retirada(self, valor):
if self.saldo - self.temp - valor < 0:
raise ValueError("Não há saldo suficiente")
self.temp -= valor
return valor
def __enter__(self):
self.temp = 0
def __exit__(self, tipo_excecao, valor_excecao, traceback):
if tipo_excecao is None:
# Não ocorreu nenhum erro no bloco, e podemos
# atualizar o saldo definitivo
self.saldo += self.temp
del self.temp
def __repr__(self):
return f"conta de {self.titular}. Saldo: {self.saldo:.02f}"
def transferir(conta1, conta2, valor):
with conta1, conta2:
conta2.deposito(conta1.retirada(valor))
And in an interactive session we can see:
In [96]: conta1 = ContaBancariaTransacional("João", 100)
In [97]: conta2 = ContaBancariaTransacional("José", 0)
In [98]: conta1, conta2
Out[98]: (conta de João. Saldo: 100.00, conta de José. Saldo: 0.00)
In [99]: transferir(conta1, conta2, 100)
In [100]: conta1, conta2
Out[100]: (conta de João. Saldo: 0.00, conta de José. Saldo: 100.00)
In [101]: transferir(conta1, conta2, 50)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
(...)
ValueError: Não há saldo suficiente
In [102]: conta1, conta2
Out[102]: (conta de João. Saldo: 0.00, conta de José. Saldo: 100.00)
Disclaimer: that code is not complete to be a "two Phase transaction" - and in fact, it only works because the account the balance comes from is before in charge with
- when the __exit__
of the first account is called, it raises the exception, which then prevents the balance increase in the count2. In addition, to put into production it would be nice to have some Ocks per thread in this Account class as well, no matter how toy it is. But I think you can see how with
can work with classes defined by the programmer himself.
Now, other than for transfer- imagine that this "account" object is used by a system that is inside a withdrawal machine (in case the access to the account object would be remote, with some methods remote calling protocol, of course) - but assuming a Pythonica library to operate the machine hardware - the code for booty could simply be:
def saque(atm, conta, valor):
with conta:
valor = conta.retirada(valor)
atm.disponibilizar_notas(valor)
atm.abrir_gaveta()
atm.esperar(10)
atm.fechar_gaveta()
if not atm.gaveta_vazia():
raise RuntimeError("Usuario nao retirou o dinheiro")
Ready - if any of the methods involving even mechanical parts of the machine give an error, the actual balance of the account is not debited. (Note that if there is no desired value in the account, the withdrawal method itself already aborts the transaction)
I don’t know much about Python, but he looks like
using
C#, which automatically releases the resource at the end of the block.– bfavaretto
was thinking the same thing, it seems using.
– PauloHDSousa
@Solkarped Before continuing with the editions (I saw that you did a lot), please read here and here. Basically, only edit if you actually have substantial improvements to be made (in this case, changing "but" to "but" does not fit this criterion: it does not improve - or worsen - the understanding of the question; if it is to change only This, please don’t edit).
– hkotsubo
@hkotsubo, good afternoon! Understood your remarks. Strong hug!
– Solkarped