Esta é a sexta coleção de dicas e programação em Python do meu feed @pythonetc.
Seleções anteriores:
Decoradores atípicos
Os decoradores de funções não precisam retornar apenas novas funções, eles podem retornar qualquer outro valor:
def call(*args, **kwargs): def decorator(func): return func(*args, **kwargs) return decorator @call(15) def sqr_15(x): return x * x assert sqr_15 == 225
Isso pode ser útil para criar classes simples com apenas um método redefinível:
from abc import ABCMeta, abstractmethod class BinaryOperation(metaclass=ABCMeta): def __init__(self, left, right): self._left = left self._right = right def __repr__(self): klass = type(self).__name__ left = self._left right = self._right return f'{klass}({left}, {right})' @abstractmethod def do(self): pass @classmethod def make(cls, do_function): return type( do_function.__name__, (BinaryOperation,), dict(do=do_function), ) class Addition(BinaryOperation): def do(self): return self._left + self._right @BinaryOperation.make def Subtraction(self): return self._left - self._right
__length_hint__
O PEP 424 permite que geradores e outros iteráveis que não tenham um tamanho predeterminado específico retornem seu comprimento aproximado. Por exemplo, esse gerador provavelmente retornará cerca de 50 elementos:
(x for x in range(100) if random() > 0.5)
Se você escreve algo iterável e deseja retornar um comprimento aproximado, defina o método
__length_hint__
. E se você souber o comprimento exato, use
__len__
. Se você usa um objeto iterável e deseja saber quanto tempo pode demorar, use
operator.length_hint
.
com gerador
O operador
in
pode ser usado com geradores:
x in g
. Nesse caso, o Python iterará sobre
g
até que
x
ou até que
g
termine.
>>> def g(): ... print(1) ... yield 1 ... print(2) ... yield 2 ... print(3) ... yield 3 ... >>> 2 in g() 1 2 True
range()
, no entanto, funciona um pouco melhor. Possui um método mágico substituído
__contains__
, graças ao qual a complexidade computacional de
in
se torna igual a O (1):
In [1]: %timeit 10**20 in range(10**30) 375 ns ± 10.7 ns per loop
Observe que isso não funcionará com a função
xrange()
do Python 2.
Operadores + = e +
Python possui dois operadores diferentes:
+=
e
+
. Os métodos
__iadd__
e
__add__
são responsáveis por seu comportamento, respectivamente.
class A: def __init__(self, x): self.x = x def __iadd__(self, another): self.x += another.x return self def __add__(self, another): return type(self)(self.x + another.x)
Se
__iadd__
não
__iadd__
definido,
a += b
funcionará como
a = a + b
.
A diferença semântica entre
+=
e
+
é que o primeiro altera o objeto e o segundo cria um novo:
>>> a = [1, 2, 3] >>> b = a >>> a += [4] >>> a [1, 2, 3, 4] >>> b [1, 2, 3, 4] >>> a = a + [5] >>> a [1, 2, 3, 4, 5] >>> b [1, 2, 3, 4]
Funcionar como um atributo de uma classe
Você não pode armazenar uma função como um atributo de classe, porque ela será convertida automaticamente em um método se for acessada por meio de uma instância:
>>> class A: ... CALLBACK = lambda x: x ** x ... >>> A.CALLBACK <function A.<lambda> at 0x7f68b01ab6a8> >>> A().CALLBACK <bound method A.<lambda> of <__main__.A object at 0x7f68b01aea20>> >>> A().CALLBACK(4) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: <lambda>() takes 1 positional argument but 2 were given
Você pode trapacear e agrupar a função em um descritor trivial:
>>> class FunctionHolder: ... def __init__(self, f): ... self._f = f ... def __get__(self, obj, objtype): ... return self._f ... >>> class A: ... CALLBACK = FunctionHolder(lambda x: x ** x) ... >>> A().CALLBACK <function A.<lambda> at 0x7f68b01ab950>
Você também pode sair da situação usando o método de classe em vez do atributo
class A: @classmethod def _get_callback(cls): return lambda x: x ** x