Quais são os bons usos para as "Anotações de Função" do Python3


159

Anotações de Função: PEP-3107

Corri através de um trecho de código demonstrando as anotações de função do Python3. O conceito é simples, mas não consigo pensar por que eles foram implementados no Python3 ou se há bons usos para eles. Talvez o SO possa me esclarecer?

Como funciona:

def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):
    ... function body ...

Tudo o que segue os dois pontos após um argumento é uma 'anotação' e as informações a seguir ->são uma anotação para o valor de retorno da função.

foo.func_annotations retornaria um dicionário:

{'a': 'x',
 'b': 11,
 'c': list,
 'return': 9}

Qual é o significado de ter isso disponível?



6
@ SilentGhost: infelizmente, muitos dos links com os casos de uso reais estão quebrados. Existe algum lugar onde o conteúdo pode ter sido armazenado ou desaparecido para sempre?
max

16
não deveria foo.func_annotations estar foo.__annotations__em python3?
Zhangxaochen

2
As anotações não têm significado especial. A única coisa que o Python faz é colocá-los no dicionário de anotações . Qualquer outra ação é sua.
N Randhawa

o que def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):significa
Ali SH

Respostas:


90

Eu acho que isso é realmente ótimo.

Vindo de uma formação acadêmica, posso dizer-lhe que as anotações provaram ser inestimáveis ​​para permitir analisadores estáticos inteligentes para linguagens como Java. Por exemplo, você pode definir semânticas como restrições de estado, threads com permissão de acesso, limitações de arquitetura etc., e existem algumas ferramentas que podem lê-las e processá-las para fornecer garantias além do que você obtém dos compiladores. Você pode até escrever coisas que verifiquem pré-condições / pós-condições.

Sinto que algo como isso é especialmente necessário no Python por causa de sua digitação mais fraca, mas realmente não havia construções que tornassem isso simples e parte da sintaxe oficial.

Existem outros usos para anotações além da garantia. Eu posso ver como eu poderia aplicar minhas ferramentas baseadas em Java ao Python. Por exemplo, eu tenho uma ferramenta que permite atribuir avisos especiais aos métodos e fornece indicações quando você os chama para ler a documentação deles (por exemplo, imagine que você tem um método que não deve ser chamado com um valor negativo, mas é intuitivo do nome). Com anotações, eu poderia escrever algo assim para Python. Da mesma forma, uma ferramenta que organiza métodos em uma classe grande com base em tags pode ser escrita se houver uma sintaxe oficial.


34
ISTM, esses são benefícios teóricos que só podem ser alcançados se a biblioteca padrão e os módulos de terceiros usarem anotações de funções e usá-las com significado consistente e usar sistemas de anotações bem pensados. Até aquele dia (que nunca chegará), os principais usos das anotações de função do Python serão os usos únicos descritos nas outras respostas. Por enquanto, você pode esquecer analisadores estáticos inteligentes, garantias de compiladores, cadeias de ferramentas baseadas em java etc.
Raymond Hettinger

4
Mesmo sem tudo usando anotações de função, você ainda pode usá-las para análise estática no código que as contém em suas entradas e está chamando outro código que é anotado da mesma forma. Em um projeto ou base de código maior, ainda pode ser um corpo de código significativamente útil para executar análises estáticas baseadas em anotações.
gps

1
AFAICT, você pode fazer tudo isso com decoradores, que antecedem anotações; portanto, ainda não vejo o benefício. Eu tenho uma opinião um pouco diferente sobre esta questão: stackoverflow.com/questions/13784713/…
allyourcode

9
Avançando para 2015, python.org/dev/peps/pep-0484 e mypy-lang.org estão começando a provar que todos os opositores estão errados.
Mauricio Scheffer

1
Também revela a influência do Python no Swift ainda mais.
Uchuugaka 3/11

92

As anotações de função são o que você faz delas.

Eles podem ser usados ​​para documentação:

def kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second'):
     ...

Eles podem ser usados ​​para verificação de condição prévia:

def validate(func, locals):
    for var, test in func.__annotations__.items():
        value = locals[var]
        msg = 'Var: {0}\tValue: {1}\tTest: {2.__name__}'.format(var, value, test)
        assert test(value), msg


def is_int(x):
    return isinstance(x, int)

def between(lo, hi):
    def _between(x):
            return lo <= x <= hi
    return _between

def f(x: between(3, 10), y: is_int):
    validate(f, locals())
    print(x, y)


>>> f(0, 31.1)
Traceback (most recent call last):
   ... 
AssertionError: Var: y  Value: 31.1 Test: is_int

Consulte também http://www.python.org/dev/peps/pep-0362/ para obter uma maneira de implementar a verificação de tipo.


18
Como isso é melhor do que uma documentação para documentação ou verificação explícita de tipo na função? Isso parece complicar o idioma sem motivo.
Endolith

10
@ endolith Certamente podemos ficar sem anotações de função. Eles apenas fornecem uma maneira padrão de acessar as anotações. Isso os torna acessíveis para help () e para dicas de ferramentas e os tornam disponíveis para introspecção.
Raymond Hettinger

4
Ao invés de passar em torno de números, você pode criar tipos Masse Velocityem vez disso.
rightfold

1
para demonstrar isso totalmente, eu também teria def kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second') -> float:que mostrar o tipo de retorno. Esta é a minha resposta favorita aqui.
Tommy

Usando seu código, há uma maneira de verificar a returnanotação? Não parece aparecer em #locals
user189728

46

Essa é uma resposta bastante tardia, mas AFAICT, o melhor uso atual de anotações de função é PEP-0484 e MyPy .

Mypy é um verificador de tipo estático opcional para Python. Você pode adicionar dicas de tipo aos seus programas Python usando o próximo padrão para anotações de tipo introduzido no Python 3.5 beta 1 (PEP 484) e usar mypy para digitá-las estaticamente.

Usado assim:

from typing import Iterator

def fib(n: int) -> Iterator[int]:
    a, b = 0, 1
    while a < n:
        yield a
        a, b = b, a + b


Veja também pytype - o outro analisador estático sendo construído com o PEP-0484 em mente.
gps

Infelizmente, o tipo não é imposto. Se eu digitar list(fib('a'))com sua função de exemplo, o Python 3.7 aceita o argumento com satisfação e reclama que não há como comparar uma string e um int.
Denis de Bernardy

@DenisdeBernardy Como o PEP-484 explica, o Python fornece apenas anotações de tipo. Para impor tipos, você deve usar mypy.
Dustin Wyatt

23

Apenas para adicionar um exemplo específico de um bom uso da minha resposta aqui , juntamente com os decoradores, um mecanismo simples para vários métodos pode ser feito.

# This is in the 'mm' module

registry = {}
import inspect

class MultiMethod(object):
    def __init__(self, name):
        self.name = name
        self.typemap = {}
    def __call__(self, *args):
        types = tuple(arg.__class__ for arg in args) # a generator expression!
        function = self.typemap.get(types)
        if function is None:
            raise TypeError("no match")
        return function(*args)
    def register(self, types, function):
        if types in self.typemap:
            raise TypeError("duplicate registration")
        self.typemap[types] = function

def multimethod(function):
    name = function.__name__
    mm = registry.get(name)
    if mm is None:
        mm = registry[name] = MultiMethod(name)
    spec = inspect.getfullargspec(function)
    types = tuple(spec.annotations[x] for x in spec.args)
    mm.register(types, function)
    return mm

e um exemplo de uso:

from mm import multimethod

@multimethod
def foo(a: int):
    return "an int"

@multimethod
def foo(a: int, b: str):
    return "an int and a string"

if __name__ == '__main__':
    print("foo(1,'a') = {}".format(foo(1,'a')))
    print("foo(7) = {}".format(foo(7)))

Isso pode ser feito adicionando os tipos ao decorador, como mostra a postagem original de Guido , mas anotar os próprios parâmetros é melhor, pois evita a possibilidade de correspondência incorreta de parâmetros e tipos.

Nota : No Python, você pode acessar as anotações function.__annotations__e não function.func_annotationscomo o func_*estilo foi removido no Python 3.


2
Aplicativo interessante, embora eu tenha medo de function = self.typemap.get(types)não funcionar quando subclasses estiverem envolvidas. Nesse caso, você provavelmente teria que typemapusar o loop isinnstance. Pergunto-me se @overloadlida com isso corretamente
Tobias KIENZLER

Eu acho que isso é quebrada se a função tem um tipo de retorno
Zenna

1
O __annotations__ é um dictque não garante a ordem dos argumentos; portanto, esse fragmento às vezes falha. Eu recomendaria mudar o types = tuple(...)para spec = inspect.getfullargspec(function)então types = tuple([spec.annotations[x] for x in spec.args]).
Xoolive

Você está certo, @xoolive. Por que você não edita a resposta para adicionar sua correção?
Muhammad Alkarouri

@ xoolive: eu notei. Às vezes, os editores usam muito a mão para gerenciar as edições. Eu editei a pergunta para incluir sua correção. Na verdade, tive uma discussão sobre isso, mas não há como cancelar a correção. Obrigado pela ajuda pelo caminho.
Muhammad Alkarouri

22

Uri já deu uma resposta adequada, então aqui está uma resposta menos séria: para que você possa reduzir seus textos.


2
adoro. +1. no entanto, no final, escrever docstrings ainda é a principal maneira de tornar meu código legível; no entanto, se você implementar algum tipo de verificação estática ou dinâmica, é bom ter isso. Talvez eu possa encontrar um uso para isso.
9139 Warren P

8
Não recomendo o uso de anotações como substituto de uma seção Args: ou linhas @param ou similares em suas instruções (qualquer que seja o formato que você escolher). Embora as anotações da documentação sejam um bom exemplo, ela mancha o poder potencial das anotações, pois pode atrapalhar outros usos mais poderosos. Além disso, você não pode omitir anotações em tempo de execução para reduzir o consumo de memória (python -OO), como é possível com as instruções e declarações de documentos.
gps

2
@ gps: Como eu disse, foi uma resposta menos séria.
JAB

2
Com toda a seriedade, é uma maneira muito melhor de documentar os tipos que você espera, enquanto ainda segue o DuckTyping.
Marc

1
@ gps Não sei se o consumo de memória de documentos é algo para se preocupar em 99,999% dos casos.
Tommy

13

A primeira vez que vi anotações, pensei "ótimo! Finalmente, posso optar por alguma verificação de tipo!" Obviamente, eu não havia notado que as anotações não são realmente aplicadas.

Decidi escrever um decorador de funções simples para aplicá-los :

def ensure_annotations(f):
    from functools import wraps
    from inspect import getcallargs
    @wraps(f)
    def wrapper(*args, **kwargs):
        for arg, val in getcallargs(f, *args, **kwargs).items():
            if arg in f.__annotations__:
                templ = f.__annotations__[arg]
                msg = "Argument {arg} to {f} does not match annotation type {t}"
                Check(val).is_a(templ).or_raise(EnsureError, msg.format(arg=arg, f=f, t=templ))
        return_val = f(*args, **kwargs)
        if 'return' in f.__annotations__:
            templ = f.__annotations__['return']
            msg = "Return value of {f} does not match annotation type {t}"
            Check(return_val).is_a(templ).or_raise(EnsureError, msg.format(f=f, t=templ))
        return return_val
    return wrapper

@ensure_annotations
def f(x: int, y: float) -> float:
    return x+y

print(f(1, y=2.2))

>>> 3.2

print(f(1, y=2))

>>> ensure.EnsureError: Argument y to <function f at 0x109b7c710> does not match annotation type <class 'float'>

Eu o adicionei à biblioteca Assegurar .


Eu tenho a mesma decepção depois que saí acreditando que finalmente o Python finalmente tem verificação de tipo. Finalmente, será necessário continuar com a implementação de verificação de tipo feita em casa.
precisa

3

Há muito tempo que isso foi perguntado, mas o exemplo de trecho fornecido na pergunta é (como também foi mencionado) no PEP 3107 e no final desse exemplo de PEP também são fornecidos casos de uso que podem responder à pergunta do ponto de vista do PEP. Visão ;)

O seguinte é citado em PEP3107

Casos de Uso

No curso da discussão de anotações, vários casos de uso foram levantados. Alguns deles são apresentados aqui, agrupados por que tipo de informação eles transmitem. Também estão incluídos exemplos de produtos e pacotes existentes que podem fazer uso de anotações.

  • Fornecendo informações de digitação
    • Verificação de tipo ([3], [4])
    • Deixe os IDEs mostrarem que tipos uma função espera e retorna ([17])
    • Sobrecarga de função / funções genéricas ([22])
    • Pontes em línguas estrangeiras ([18], [19])
    • Adaptação ([21], [20])
    • Predicado funções lógicas
    • Mapeamento de Consulta ao Banco de Dados
    • Empacotamento de parâmetros RPC ([23])
  • Outra informação
    • Documentação para parâmetros e valores de retorno ([24])

Consulte o PEP para obter mais informações sobre pontos específicos (bem como suas referências)


Eu realmente apreciaria se os votantes negativos deixassem pelo menos um breve comentário sobre o que causou o voto negativo. Isso realmente ajudaria (pelo menos eu) muito a melhorar.
Klaas

2

O Python 3.X (apenas) também generaliza a definição de função para permitir que argumentos e valores de retorno sejam anotados com valores de objetos para uso em extensões .

Seus dados META para explicar, para ser mais explícito sobre os valores da função.

As anotações são codificadas como :valueapós o nome do argumento e antes de um padrão e como ->valueapós a lista de argumentos.

Eles são coletados em um __annotations__atributo da função, mas não são tratados como especiais pelo próprio Python:

>>> def f(a:99, b:'spam'=None) -> float:
... print(a, b)
...
>>> f(88)
88 None
>>> f.__annotations__
{'a': 99, 'b': 'spam', 'return': <class 'float'>}

Fonte: Referência de Bolso do Python, Quinta Edição

EXEMPLO:

O typeannotationsmódulo fornece um conjunto de ferramentas para verificação de tipo e inferência de tipo de código Python. Ele também fornece um conjunto de tipos úteis para anotar funções e objetos.

Essas ferramentas são projetadas principalmente para serem usadas por analisadores estáticos, como linters, bibliotecas de conclusão de código e IDEs. Além disso, são fornecidos decoradores para fazer verificações em tempo de execução. A verificação do tipo em tempo de execução nem sempre é uma boa ideia no Python, mas em alguns casos pode ser muito útil.

https://github.com/ceronman/typeannotations

Como a digitação ajuda a escrever um código melhor

A digitação pode ajudá-lo a fazer uma análise de código estática para detectar erros de tipo antes de enviar seu código para produção e evitar erros óbvios. Existem ferramentas como mypy, que você pode adicionar à sua caixa de ferramentas como parte do seu ciclo de vida de software. O mypy pode verificar os tipos corretos executando sua base de código parcial ou totalmente. O mypy também ajuda a detectar erros, como verificar o tipo Nenhum quando o valor é retornado de uma função. Digitar ajuda a tornar seu código mais limpo. Em vez de documentar seu código usando comentários, onde você especifica tipos em uma sequência de documentos, você pode usar tipos sem nenhum custo de desempenho.

Python Limpo: Codificação Elegante em Python ISBN: ISBN-13 (pbk): 978-1-4842-4877-5

PEP 526 - Sintaxe para anotações variáveis

https://www.python.org/dev/peps/pep-0526/

https://www.attrs.org/en/stable/types.html


@BlackJack, o "para uso em extensões" não estava claro?
The Demz

É claro, mas não responde à pergunta IMHO. É como responder "Quais são os bons usos das classes?" Com "Para uso em programas". É claro, correto, mas a pessoa que pergunta não é realmente mais sábia quanto ao que são os bons usos concretos . O seu é uma resposta que não pode ser mais genérica, com um exemplo que é essencialmente o mesmo que já está na pergunta .
BlackJack

1

Apesar de todos os usos descritos aqui, o uso obrigatório e, muito provavelmente, obrigatório de anotações será para dicas de tipo .

Atualmente, isso não é imposto de nenhuma maneira, mas, a julgar pelo PEP 484, as versões futuras do Python permitirão apenas tipos como o valor para anotações.

Citação E os usos existentes de anotações? :

Esperamos que as dicas de tipo acabem se tornando o único uso para anotações, mas isso exigirá uma discussão adicional e um período de descontinuação após o lançamento inicial do módulo de digitação com o Python 3.5. O PEP atual terá status provisório (consulte PEP 411) até que o Python 3.6 seja lançado. O esquema mais rápido concebível introduziria a descontinuação silenciosa de anotações que não são de tipo dica em 3.6, a depreciação total em 3.7 e declararia as dicas de tipo como o único uso permitido de anotações no Python 3.8.

Embora eu não tenha visto nenhuma depreciação silenciosa na 3.6 ainda, isso pode muito bem ser aumentado para 3.7, em vez disso.

Portanto, mesmo que haja outros bons casos de uso, é melhor mantê-los apenas para obter dicas de tipo, se você não quiser mudar tudo em um futuro em que essa restrição esteja em vigor.


1

Como resposta um pouco atrasada, vários dos meus pacotes (marrow.script, WebCore etc.) usam anotações onde estão disponíveis para declarar tipecasting (ou seja, transformar valores recebidos da web, detectar quais argumentos são opções booleanas, etc.) para realizar marcação adicional de argumentos.

O Marrow Script cria uma interface completa da linha de comandos para funções e classes arbitrárias e permite definir documentação, conversão e valores padrão derivados de retorno de chamada por meio de anotações, com um decorador para suportar tempos de execução mais antigos. Todas as minhas bibliotecas que usam anotações suportam os formulários:

any_string  # documentation
any_callable  # typecast / callback, not called if defaulting
(any_callable, any_string)  # combination
AnnotationClass()  # package-specific rich annotation object
[AnnotationClass(), AnnotationClass(), …]  # cooperative annotation

O suporte "bare" para funções de seqüência de caracteres ou conversão de texto permite uma mistura mais fácil com outras bibliotecas que reconhecem anotações. (Ou seja, temos um controlador da Web usando tipecasting que também é exposto como um script de linha de comando.)

Editado para adicionar: Eu também comecei a usar o pacote TypeGuard usando asserções de tempo de desenvolvimento para validação. Benefício: quando executadas com "otimizações" ativadas ( -O/ PYTHONOPTIMIZEenv var), as verificações, que podem ser caras (por exemplo, recursivas), são omitidas, com a idéia de que você testou corretamente seu aplicativo em desenvolvimento, para que as verificações sejam desnecessárias na produção.


-2

As anotações podem ser usadas para modularizar facilmente o código. Por exemplo, um módulo para um programa que eu estou mantendo poderia apenas definir um método como:

def run(param1: int):
    """
    Does things.

    :param param1: Needed for counting.
    """
    pass

e poderíamos pedir ao usuário uma coisa chamada "param1", que é "Necessário para contar" e deve ser um "int". No final, podemos até converter a string fornecida pelo usuário no tipo desejado para obter a melhor experiência sem complicações.

Veja nosso objeto de metadados de função para obter uma classe de código aberto que ajuda com isso e pode recuperar automaticamente os valores necessários e convertê-los para qualquer tipo desejado (porque a anotação é um método de conversão). Até os IDEs mostram os preenchimentos automáticos corretos e assumem que os tipos estão de acordo com as anotações - um ajuste perfeito.


-2

Se você olhar a lista de benefícios do Cython, um dos principais é a capacidade de dizer ao compilador que tipo de objeto Python é.

Posso imaginar um futuro em que o Cython (ou ferramentas semelhantes que compilam parte do seu código Python) usará a sintaxe da anotação para fazer sua mágica.


O RPython Annotator é um exemplo de uma abordagem que parece adequadamente Pythonic; depois de gerar um gráfico de seu aplicativo, ele pode determinar o tipo de cada variável e (para o RPython) impor segurança de tipo único. OTOH requer "boxe" ou outras soluções / soluções alternativas para permitir valores ricos dinâmicos. Quem sou eu para forçar minha multiplyfunção a trabalhar apenas contra números inteiros, quando 'na' * 8 + ' batman!'é totalmente válido? ;)
amcgregor 19/01/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.