O camiño para verificar 4 millóns de liñas de código Python. Parte 1

Hoxe chamamos á vosa atención a primeira parte da tradución do material sobre como aborda Dropbox o control de tipos do código Python.

O camiño para verificar 4 millóns de liñas de código Python. Parte 1

Dropbox escribe moito en Python. É unha linguaxe que usamos moi amplamente, tanto para servizos de back-end como para aplicacións cliente de escritorio. Tamén usamos moito Go, TypeScript e Rust, pero Python é a nosa linguaxe principal. Tendo en conta a nosa escala, e estamos a falar de millóns de liñas de código Python, resultou que a tipificación dinámica deste código complicou innecesariamente a súa comprensión e comezou a afectar seriamente a produtividade do traballo. Para mitigar este problema, comezamos a transición gradual do noso código á comprobación de tipo estático usando mypy. Este é probablemente o sistema de verificación de tipos autónomo máis popular para Python. Mypy é un proxecto de código aberto, os seus principais desenvolvedores traballan en Dropbox.

Dropbox foi unha das primeiras empresas en implementar a verificación de tipos estáticos en código Python a esta escala. Mypy utilízase en miles de proxectos nestes días. Esta ferramenta innumerables veces, como din, "probada na batalla". Percorremos un longo camiño para chegar a onde estamos agora. Ao longo do camiño, houbo moitas empresas sen éxito e experimentos fracasados. Esta publicación abarca a historia da comprobación de tipos estáticos en Python, desde os seus comezos rocosos como parte do meu proxecto de investigación, ata os nosos días, cando a verificación de tipos e as indicacións de tipo se converteron en algo común para incontables desenvolvedores que escriben en Python. Estes mecanismos agora son compatibles con moitas ferramentas como IDE e analizadores de código.

Le a segunda parte

Por que é necesaria a verificación de tipos?

Se algunha vez usaches Python de tipo dinámico, quizais teñas algunha confusión sobre por que hai tanto alboroto pola escritura estática e mypy ultimamente. Ou quizais che guste Python precisamente pola súa escritura dinámica, e o que está a suceder simplemente te molesta. A clave do valor da dixitación estática é a escala das solucións: canto maior sexa o seu proxecto, máis se inclina pola escritura estática e, ao final, máis o necesita.

Supoñamos que un determinado proxecto alcanzou o tamaño de decenas de miles de liñas e resultou que varios programadores están a traballar nel. Mirando un proxecto similar, baseándonos na nosa experiencia, podemos dicir que comprender o seu código será a clave para manter a produtividade dos desenvolvedores. Sen anotacións de tipo, pode ser difícil descubrir, por exemplo, que argumentos pasar a unha función ou que tipos pode devolver unha función. Aquí tes preguntas típicas que adoitan ser difíciles de responder sen usar anotacións de tipo:

  • Pode volver esta función None?
  • Cal debe ser este argumento? items?
  • Cal é o tipo de atributo id: int é, str, ou quizais algún tipo personalizado?
  • Este argumento debería ser unha lista? É posible pasarlle unha tupla?

Se miras o seguinte fragmento de código con anotación de tipo e intentas responder a preguntas semellantes, resulta que esta é a tarefa máis sinxela:

class Resource:
    id: bytes
    ...
    def read_metadata(self, 
                      items: Sequence[str]) -> Dict[str, MetadataItem]:
        ...

  • read_metadata non volve None, xa que o tipo de retorno non é Optional[…].
  • argumento items é unha secuencia de liñas. Non se pode iterar ao azar.
  • Atributo id é unha cadea de bytes.

Nun mundo ideal, cabería esperar que todas esas sutilezas fosen descritas na documentación integrada (docstring). Pero a experiencia dá moitos exemplos do feito de que tal documentación moitas veces non se observa no código co que tes que traballar. Aínda que tal documentación estea presente no código, non se pode contar coa súa absoluta corrección. Esta documentación pode ser vaga, inexacta e aberta a malentendidos. En grandes equipos ou grandes proxectos, este problema pode chegar a ser extremadamente agudo.

Aínda que Python sobresae nas etapas iniciais ou intermedias dos proxectos, nalgún momento os proxectos exitosos e as empresas que usan Python poden enfrontarse á pregunta vital: "¿Debemos reescribir todo nunha linguaxe de tipo estático?".

Os sistemas de verificación de tipos como mypy solucionan o problema anterior proporcionando ao desenvolvedor unha linguaxe formal para describir os tipos e verificando que as declaracións de tipo coinciden coa implementación do programa (e, opcionalmente, comprobando a súa existencia). En xeral, podemos dicir que estes sistemas poñen á nosa disposición algo así como documentación coidadosamente revisada.

O uso de tales sistemas ten outras vantaxes e xa non son triviais:

  • O sistema de verificación de tipos pode detectar algúns erros pequenos (e non tan pequenos). Un exemplo típico é cando se esquecen de procesar un valor None ou algunha outra condición especial.
  • A refactorización do código simplifícase moito porque o sistema de verificación de tipos adoita ser moi preciso sobre o código que hai que cambiar. Ao mesmo tempo, non cómpre esperar unha cobertura de código ao 100% con probas, o que, en todo caso, adoita non ser viable. Non necesitamos afondar nas profundidades da traza da pila para descubrir a causa do problema.
  • Mesmo en proxectos grandes, mypy adoita facer a comprobación completa de tipos nunha fracción de segundo. E a execución das probas adoita levar decenas de segundos ou mesmo minutos. O sistema de verificación de tipos dálle ao programador un feedback instantáneo e permítelle facer o seu traballo máis rápido. Xa non necesita escribir probas unitarias fráxiles e difíciles de manter que substitúan entidades reais por simulacros e parches só para obter resultados das probas de código máis rápido.

Os IDE e os editores como PyCharm ou Visual Studio Code usan o poder das anotacións de tipos para proporcionar aos desenvolvedores completar código, resaltar erros e compatibilizar con construcións de linguaxes de uso habitual. E estes son só algúns dos beneficios de escribir. Para algúns programadores, todo este é o principal argumento a favor de escribir. Isto é algo que se beneficia inmediatamente despois da implementación. Este caso de uso para tipos non require un sistema de verificación de tipos separado como mypy, aínda que hai que ter en conta que mypy axuda a manter as anotacións de tipos coherentes co código.

Fondo de mypy

A historia de mypy comezou no Reino Unido, en Cambridge, uns anos antes de incorporarme a Dropbox. Estiven implicado, como parte da miña investigación de doutoramento, na unificación das linguaxes de tipo estático e dinámico. Inspiroume un artigo sobre dixitación incremental de Jeremy Siek e Walid Taha e o proxecto Typed Racket. Tentei atopar formas de usar a mesma linguaxe de programación para varios proxectos, desde pequenos scripts ata bases de código que consisten en moitos millóns de liñas. Ao mesmo tempo, quería asegurarme de que nun proxecto de calquera escala non tivese que facer compromisos demasiado grandes. Unha parte importante de todo isto foi a idea de pasar gradualmente dun proxecto prototipo non tipificado a un produto acabado de tipo estático probado exhaustivamente. Hoxe en día, estas ideas son en gran parte descontadas, pero en 2010 era un problema que aínda se estaba explorando activamente.

O meu traballo orixinal de verificación de tipos non estaba dirixido a Python. Pola contra, usei un pequeno idioma "caseiro". Alore. Aquí tes un exemplo que che permitirá entender do que estamos a falar (as anotacións de tipo son opcionais aquí):

def Fib(n as Int) as Int
  if n <= 1
    return n
  else
    return Fib(n - 1) + Fib(n - 2)
  end
end

O uso dunha lingua nativa simplificada é un enfoque común empregado na investigación científica. Isto é así, sobre todo porque permite realizar experimentos rapidamente, e tamén debido a que o que non ten nada que ver co estudo pode ignorarse facilmente. As linguaxes de programación do mundo real tenden a ser fenómenos a gran escala con implementacións complexas, e isto ralentiza a experimentación. Porén, calquera resultado baseado nunha linguaxe simplificada parece un pouco sospeitoso, xa que ao obter estes resultados o investigador puido sacrificar consideracións importantes para o uso práctico das linguas.

O meu verificador de tipos para Alore parecía moi prometedor, pero quería probalo experimentando con código real, que, podería dicirse, non estaba escrito en Alore. Por sorte para min, a linguaxe Alore baseouse en gran parte nas mesmas ideas que Python. Foi o suficientemente sinxelo refacer o verificador de tipos para que puidese funcionar coa sintaxe e a semántica de Python. Isto permitiunos probar a verificación de tipos no código Python de código aberto. Ademais, escribín un transpiler para converter o código escrito en Alore a código Python e useino para traducir o meu código de verificación de tipos. Agora tiña un sistema de verificación de tipos escrito en Python que admitía un subconxunto de Python, algún tipo desa linguaxe! (Algunhas decisións arquitectónicas que tiñan sentido para Alore eran pouco adecuadas para Python, e isto aínda se nota en partes da base de código mypy).

De feito, a linguaxe admitida polo meu sistema de tipos non se podía chamar Python neste momento: era unha variante de Python debido a algunhas limitacións da sintaxe de anotación de tipo Python 3.

Parecía unha mestura de Java e Python:

int fib(int n):
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Unha das miñas ideas naquel momento era usar anotacións de tipos para mellorar o rendemento compilando este tipo de Python en C, ou quizais bytecode JVM. Cheguei á fase de escribir un prototipo de compilador, pero abandonei esta idea, xa que a verificación de tipos parecía bastante útil.

Acabei presentando o meu proxecto na PyCon 2013 en Santa Clara. Tamén falei disto con Guido van Rossum, o benévolo ditador de Python de por vida. Convenceume de deixar a miña propia sintaxe e seguir coa sintaxe estándar de Python 3. Python 3 admite anotacións de funcións, polo que o meu exemplo podería reescribirse como se mostra a continuación, dando como resultado un programa Python normal:

def fib(n: int) -> int:
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Tiven que facer algúns compromisos (en primeiro lugar, quero notar que inventei a miña propia sintaxe por iso mesmo). En particular, Python 3.3, a versión máis recente da linguaxe naquel momento, non admitía anotacións variables. Comentei con Guido por correo electrónico varias posibilidades para o deseño sintáctico deste tipo de anotacións. Decidimos usar comentarios de tipo para as variables. Isto serviu para o propósito previsto, pero foi algo engorroso (Python 3.6 deunos unha sintaxe máis agradable):

products = []  # type: List[str]  # Eww

Os comentarios de tipo tamén foron útiles para admitir Python 2, que non ten soporte incorporado para anotacións de tipos:

f fib(n):
    # type: (int) -> int
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Resultou que estas (e outras) compensacións non importaban realmente: os beneficios da dixitación estática fixeron que os usuarios pronto se esqueceran da sintaxe menos que ideal. Dado que non se utilizaron construcións sintácticas especiais no código de Python comprobado por tipo, as ferramentas existentes de Python e os procesos de procesamento de código continuaron funcionando normalmente, o que facilitaba moito a aprendizaxe da nova ferramenta para os desenvolvedores.

Guido tamén me convenceu de unirme a Dropbox despois de rematar a miña tese de posgrao. Aquí é onde comeza a parte máis interesante da historia mypy.

Continuar ...

Queridos lectores! Se usas Python, indícanos a escala dos proxectos que desenvolves nesta linguaxe.

O camiño para verificar 4 millóns de liñas de código Python. Parte 1
O camiño para verificar 4 millóns de liñas de código Python. Parte 1

Fonte: www.habr.com

Engadir un comentario