it-swarm-pt.tech

Usando o Design by Contract em Python

Estou querendo começar a usar o DBC em um grande número de projetos baseados em Python no trabalho e estou me perguntando que experiências outros tiveram com ele. Até agora, minha pesquisa revelou o seguinte:

Minhas perguntas são: você usou o DBC com Python para código de produção maduro? Quão bem funcionou/valeu a pena o esforço? Quais ferramentas você recomendaria?

46
ipartola

O PEP que você encontrou ainda não foi aceito, portanto, não há uma maneira padrão ou aceita de fazer isso (ainda - você sempre pode implementar o PEP!). No entanto, existem algumas abordagens diferentes, como você descobriu.

Provavelmente, o mais leve é ​​simplesmente usar Python decoradores. Há um conjunto de decoradores para pré/pós-condições na Python Decorator Library que são bastante simples de usar.Aqui está um exemplo dessa página:

  >>> def in_ge20(inval):
  ...    assert inval >= 20, 'Input value < 20'
  ...
  >>> def out_lt30(retval, inval):
  ...    assert retval < 30, 'Return value >= 30'
  ...
  >>> @precondition(in_ge20)
  ... @postcondition(out_lt30)
  ... def inc(value):
  ...   return value + 1
  ...
  >>> inc(5)
  Traceback (most recent call last):
    ...
  AssertionError: Input value < 20

Agora, você menciona invariantes de classe. Isso é um pouco mais difícil, mas a maneira como eu definiria isso é definir uma chamada para verificar a invariante e, em seguida, fazer com que algo como o decorador pós-condição verifique essa invariante no final de cada chamada de método. Como primeiro corte, você provavelmente poderia usar o decorador pós-condição como está.

17
snim2

Na minha experiência, vale a pena fazer o projeto por contrato, mesmo sem o suporte ao idioma. Para métodos que não são asserções substituídas, juntamente com docstrings são suficientes para pré e pós-condições. Para métodos que são substituídos, dividimos o método em dois: um método público que verifica as condições pré e pós, e um método protegido que fornece a implementação e pode ser substituído por subclasses. Aqui está um exemplo deste último:

class Math:
    def square_root(self, number)
        """
        Calculate the square-root of C{number}

        @precondition: C{number >= 0}

        @postcondition: C{abs(result * result - number) < 0.01}
        """
        assert number >= 0
        result = self._square_root(number)
        assert abs(result * result - number) < 0.01
        return result

    def _square_root(self, number):
        """
        Abstract method for implementing L{square_root()}
        """
        raise NotImplementedError()

Eu obtive a raiz quadrada como um exemplo geral de projeto por contrato de um episódio no projeto por contrato no rádio de engenharia de software ( http://www.se-radio.net/2007/03/ episódio-51-projeto-por-contrato / ). Eles também mencionaram a necessidade de suporte ao idioma, porque as asserções não foram úteis para garantir o princípio de substituição de Liskov, embora meu exemplo acima pretenda demonstrar o contrário. Também devo mencionar o idioma pimpl (implementação privada) do C++ como fonte de inspiração, embora isso tenha um propósito totalmente diferente.

Em meu trabalho, refatorei recentemente esse tipo de verificação de contrato em uma hierarquia de classe maior (o contrato já estava documentado, mas não foi testado sistematicamente). Os testes unitários existentes revelaram que os contratos foram violados várias vezes. Só posso concluir que isso deveria ter sido feito há muito tempo e que a cobertura do teste de unidade compensa ainda mais depois que o projeto por contrato é aplicado. Espero que qualquer pessoa que tente essa combinação de técnicas faça as mesmas observações.

Um melhor suporte de ferramentas pode nos oferecer ainda mais poder no futuro, congratulo-me com isso.

13

Como não usei o design por contrato em python, não posso responder a todas as suas perguntas. No entanto, passei algum tempo olhando para a biblioteca contractos , cuja versão mais recente foi lançada recentemente e parece bastante agradável.

Houve alguma discussão sobre esta biblioteca em reddit .

7
jcollado

Queríamos usar pré/pós-condições/invariantes em nosso código de produção, mas descobrimos que todas as bibliotecas atuais de projeto por contrato não possuíam mensagens informativas e herança adequada.

Por isso, desenvolvemos icontract . As mensagens de erro são geradas automaticamente redirecionando o código descompilado da função e avaliando todos os valores envolvidos:

import icontract

>>> class B:
...     def __init__(self) -> None:
...         self.x = 7
...
...     def y(self) -> int:
...         return 2
...
...     def __repr__(self) -> str:
...         return "instance of B"
...
>>> class A:
...     def __init__(self)->None:
...         self.b = B()
...
...     def __repr__(self) -> str:
...         return "instance of A"
...
>>> SOME_GLOBAL_VAR = 13
>>> @icontract.pre(lambda a: a.b.x + a.b.y() > SOME_GLOBAL_VAR)
... def some_func(a: A) -> None:
...     pass
...
>>> an_a = A()
>>> some_func(an_a)
Traceback (most recent call last):
  ...
icontract.ViolationError: 
Precondition violated: (a.b.x + a.b.y()) > SOME_GLOBAL_VAR:
SOME_GLOBAL_VAR was 13
a was instance of A
a.b was instance of B
a.b.x was 7
a.b.y() was 2

Achamos a biblioteca bastante útil tanto na produção (devido a mensagens informativas) quanto durante o desenvolvimento (já que permite detectar erros desde o início).

5
marko.ristin

Embora não seja exatamente projetado por contrato , algumas estruturas de teste que favorecem a abordagem de teste de propriedades são muito próximas conceitualmente.

O teste aleatório para verificar se certas propriedades permanecem no tempo de execução permite verificar facilmente:

  • invariantes
  • domínios de valores de entrada e saída
  • outras pré e pós-condições

Para Python, existem algumas estruturas de teste no estilo QuickCheck:

5
sastanin