É uma nova seleção de dicas e truques sobre Python e programação do meu canal Telegram @pythonetc.
←
Publicações anterioresbreak
instrução
break
suprime a exceção se usada na cláusula
finally
, mesmo quando o bloco
except
não é apresentado:
for i in range(10): try: 1 / i finally: print('finally') break print('after try') print('after while')
Saída:
finally after while
O mesmo vale para
continue
, no entanto, ele não pode ser usado
finally
até o Python 3.8:
SyntaxError: 'continue' not supported inside 'finally' clause
Você pode adicionar caracteres Unicode em uma cadeia de caracteres literal, não apenas pelo número, mas também pelo nome.
>>> '\N{EM DASH}' '—' >>> '\u2014' '—'
Também é compatível com strings-f:
>>> width = 800 >>> f'Width \N{EM DASH} {width}' 'Width — 800'
Existem seis métodos mágicos para objetos Python que definem regras de comparação:
__lt__
para <
__gt__
para >
__le__
para <=
__ge__
para >=
__eq__
para ==
__ne__
for !=
Se alguns desses métodos não estiverem definidos ou retornarem
NotImplemented
, as seguintes regras serão aplicadas:
a.__lt__(b)
é o mesmo que b.__gt__(a)
a.__le__(b)
é o mesmo que b.__ge__(a)
a.__eq__(b)
é o mesmo que not a.__ne__(b)
(lembre-se de que b
não são trocados neste caso)
No entanto,
a >= b
e
a != b
não implicam automaticamente
a > b
. O decorador
functools.total_ordering
cria todos os seis métodos baseados em
__eq__
e um dos seguintes:
__lt__
,
__gt__
,
__le__
ou
__ge__
.
from functools import total_ordering @total_ordering class User: def __init__(self, pk, name): self.pk = pk self.name = name def __le__(self, other): return self.pk <= other.pk def __eq__(self, other): return self.pk == other.pk assert User(2, 'Vadim') < User(13, 'Catherine')
Às vezes, você deseja usar as versões decorada e não decorada de uma função. A maneira mais fácil de conseguir isso é renunciar à sintaxe especial do decorador (aquela com
@
) e criar a função decorada manualmente:
import json def ensure_list(f): def decorated(*args, **kwargs): result = f(*args, **kwargs) if isinstance(result, list): return result else: return [result] return decorated def load_data_orig(string): return json.loads(string) load_data = ensure_list(load_data_orig) print(load_data('3'))
Como alternativa, você pode escrever outro decorador, que decora uma função, preservando sua versão original no atributo
orig
do novo:
import json def saving_orig(another_decorator): def decorator(f): decorated = another_decorator(f) decorated.orig = f return decorated return decorator def ensure_list(f): ... @saving_orig(ensure_list) def load_data(string): return json.loads(string) print(load_data('3'))
Se todos os decoradores com os quais você estiver trabalhando forem criados via
functools.wraps
você poderá usar o atributo
__wrapped__
para acessar a função não decorada:
import json from functools import wraps def ensure_list(f): @wraps(f) def decorated(*args, **kwargs): result = f(*args, **kwargs) if isinstance(result, list): return result else: return [result] return decorated @ensure_list def load_data(string): return json.loads(string) print(load_data('3'))
No entanto,
__wrapped__
-se de que ele não funciona para funções decoradas por mais de um decorador: você precisa acessar
__wrapped__
para cada decorador aplicado:
def ensure_list(f): ... def ensure_ints(f): @wraps(f) def decorated(*args, **kwargs): result = f(*args, **kwargs) return [int(x) for x in result] return decorated @ensure_ints @ensure_list def load_data(string): return json.loads(string) for f in ( load_data, load_data.__wrapped__, load_data.__wrapped__.__wrapped__, ): print(repr(f('"4"')))
Saída:
[4] ['4'] '4'
O
@saving_orig
mencionado acima aceita outro decorador como argumento. E se esse decorador puder ser parametrizado? Bem, como o decorador parametrizado é uma função que retorna um decorador real, esse caso é tratado automaticamente:
import json from functools import wraps def saving_orig(another_decorator): def decorator(f): decorated = another_decorator(f) decorated.orig = f return decorated return decorator def ensure_ints(*, default=None): def decorator(f): @wraps(f) def decorated(*args, **kwargs): result = f(*args, **kwargs) ints = [] for x in result: try: x_int = int(x) except ValueError: if default is None: raise else: x_int = default ints.append(x_int) return ints return decorated return decorator @saving_orig(ensure_ints(default=0)) def load_data(string): return json.loads(string) print(repr(load_data('["2", "3", "A"]'))) print(repr(load_data.orig('["2", "3", "A"]')))
O decorador
@saving_orig
realmente não faz o que queremos se houver mais de um decorador aplicado a uma função. Temos que chamar
orig
para cada um desses decoradores:
import json from functools import wraps def saving_orig(another_decorator): def decorator(f): decorated = another_decorator(f) decorated.orig = f return decorated return decorator def ensure_list(f): ... def ensure_ints(*, default=None): ... @saving_orig(ensure_ints(default=42)) @saving_orig(ensure_list) def load_data(string): return json.loads(string) for f in ( load_data, load_data.orig, load_data.orig.orig, ): print(repr(f('"X"')))
Saída:
[42] ['X'] 'X'
Podemos corrigi-lo suportando um número arbitrário de decoradores como argumentos
saving_orig
:
def saving_orig(*decorators): def decorator(f): decorated = f for d in reversed(decorators): decorated = d(decorated) decorated.orig = f return decorated return decorator ... @saving_orig( ensure_ints(default=42), ensure_list, ) def load_data(string): return json.loads(string) for f in ( load_data, load_data.orig, ): print(repr(f('"X"')))
Saída:
[42] 'X'
Outra solução é tornar o
saving_orig
inteligente o suficiente para passar o
orig
de uma função decorada para outra:
def saving_orig(another_decorator): def decorator(f): decorated = another_decorator(f) if hasattr(f, 'orig'): decorated.orig = f.orig else: decorated.orig = f return decorated return decorator @saving_orig(ensure_ints(default=42)) @saving_orig(ensure_list) def load_data(string): return json.loads(string)
Se um decorador que você está escrevendo se tornar muito complicado, pode ser razoável transformá-lo de uma função para uma classe com o método
__call__
class SavingOrig: def __init__(self, another_decorator): self._another = another_decorator def __call__(self, f): decorated = self._another(f) if hasattr(f, 'orig'): decorated.orig = f.orig else: decorated.orig = f return decorated saving_orig = SavingOrig
A última linha permite que você nomeie a classe com caixa de camelo e mantenha o nome do decorador em caixa de cobra.
Em vez de modificar a função decorada, você pode criar outra classe de chamada para retornar suas instâncias em vez de uma função:
class CallableWithOrig: def __init__(self, to_call, orig): self._to_call = to_call self._orig = orig def __call__(self, *args, **kwargs): return self._to_call(*args, **kwargs) @property def orig(self): if isinstance(self._orig, type(self)): return self._orig.orig else: return self._orig class SavingOrig: def __init__(self, another_decorator): self._another = another_decorator def __call__(self, f): return CallableWithOrig(self._another(f), f) saving_orig = SavingOrig
Veja o código completo
aqui