Python3の「関数アノテーション」の良い使い方は何ですか


159

関数アノテーション:PEP-3107

Python3の関数アノテーションを示すコードのスニペットに出くわしました。コンセプトは単純ですが、なぜこれらがPython3で実装されたのか、またはそれらの適切な使用法が何であるのか、私には思いつきません。たぶん、SOが私を啓発できるでしょうか?

使い方:

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

引数の後のコロンに続くものはすべて「注釈」であり、それに続く情報->は関数の戻り値の注釈です。

foo.func_annotationsは辞書を返します。

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

これを利用できることの意味は何ですか?



6
@SilentGhost:残念ながら、実際の使用例とのリンクの多くは壊れています。コンテンツが保存された可能性がある場所、または永久になくなった場所はありますか?
最大

16
ならないfoo.func_annotations ことfoo.__annotations__のpython3に?
zhangxaochen 2014

2
注釈には特別な意味はありません。Pythonが行う唯一のことは、それらを注釈ディクショナリに入れることです。他のアクションはあなた次第です。
Nランダワ

どういうdef foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):意味ですか?
Ali SH

回答:


90

これは本当に素晴らしいと思います。

学問的な経歴から、アノテーションはJavaのような言語でスマートな静的アナライザーを有効にするために非常に貴重であることが証明されたと言えるでしょう。たとえば、状態の制限、アクセスが許可されているスレッド、アーキテクチャの制限などのセマンティクスを定義できます。これらを読み取って処理し、コンパイラーから得られる以上の保証を提供できるツールはかなり多くあります。前提条件/事後条件をチェックするものを書くこともできます。

型付けが弱いため、Pythonではこのようなものが特に必要だと思いますが、これを簡単で公式の構文の一部にする構成要素は実際にはありませんでした。

アノテーションには、保証以外にも用途があります。JavaベースのツールをPythonに適用する方法がわかります。たとえば、私はメソッドに特別な警告を割り当て、それらを呼び出すときにそれらのドキュメントを読む必要があることを示すツールを持っています(たとえば、負の値で呼び出されてはならないメソッドがあるとしますが、それは名前から直感的ではありません)。注釈があれば、Pythonでこのようなものを技術的に書くことができます。同様に、公式の構文があれば、タグに基づいてメソッドを大きなクラスに編成するツールを作成できます。


34
ISTMはこれらの理論上の利点であり、標準ライブラリとサードパーティモジュールがすべて関数アノテーションを使用し、一貫した意味でそれらを使用し、十分に検討されたアノテーションシステムを使用する場合にのみ実現できます。その日まで(これから来ることはありません)、Pythonの関数アノテーションの主な用途は、他の回答で説明されている1回限りの用途です。当分の間、あなたはなど、スマート静的アナライザ、コンパイラの保証、Javaベースのツールチェーン、忘れることができます
レイモンド・ヘッティンガー

4
関数アノテーションをすべて使用していなくても、それらを入力に使用し、同様にアノテーションが付けられた他のコードを呼び出すコード内で静的分析に使用できます。より大きなプロジェクトまたはコードベース内では、これはまだ、アノテーションベースの静的分析を実行するための非常に有用なコードの本体である可能性があります。
gps

1
AFAICT、アノテーションよりも古いデコレータでこれらすべてを行うことができます。したがって、まだメリットはわかりません。:私はこの質問に若干異なるテイク持っstackoverflow.com/questions/13784713/...
allyourcode

9
2015年に早送りし、python.org / dev / peps / pep - 0484mypy-lang.orgは、すべての否定者の間違いを証明し始めています。
Mauricio Scheffer 2015

1
また、PythonがSwiftに与える影響がさらに明らかになります。
uchuugaka 2015年

92

関数アノテーションは、それらを使用して作成したものです。

ドキュメントに使用できます。

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

これらは事前条件チェックに使用できます。

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

タイプチェックを実装する方法については、http://www.python.org/dev/peps/pep-0362/も参照してください


18
これはドキュメンテーションのdocstringまたは関数の明示的な型チェックよりも優れていますか?これは理由もなく言語を複雑にするようです。
エンドリス2013

10
@endolith確かに関数アノテーションがなくても可能です。これらは、注釈にアクセスするための標準的な方法を提供するだけです。これにより、help()とツールチップにアクセスできるようになり、イントロスペクションに使用できるようになります。
レイモンドヘッティンガー2013

4
数値を渡すのではなくMassVelocity代わりに型を作成できます。
14

1
これを完全に実証するにはdef kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second') -> float:、戻り値の型も示す必要があります。これが私のお気に入りの答えです。
Tommy

コードを使用して、return注釈を検証する方法はありますか?表示されないようですlocals
user189728 '10 / 07/18

46

これは遅い回答ですが、AFAICT、関数アノテーションの現在の最良の使用法はPEP-0484MyPyです。

Mypyは、Python用のオプションの静的型チェッカーです。Python 3.5ベータ1(PEP 484)で導入されたタイプアノテーションの次期標準を使用してPythonプログラムにタイプヒントを追加し、mypyを使用してそれらを静的にタイプチェックできます。

そのように使用:

from typing import Iterator

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

2
その他の例はこちらMypyの例とここ型ヒントからメリットを得る方法
El

pytype -PEP-0484を念頭に置いて構築されている他の静的アナライザーも参照してください。
gps

残念ながら、タイプは強制されていません。私list(fib('a'))があなたの例の関数でタイプすると、Python 3.7は引数を喜んで受け入れ、文字列とintを比較する方法がないと文句を言います。
Denis de Bernardy

@DenisdeBernardy PEP-484が説明するように、Pythonは型注釈のみを提供します。タイプを強制するには、mypyを使用する必要があります。
ダスティンワイアット

23

ここでの私の答えからの良い使用の特定の例を追加するだけ、デコレータと組み合わせて、マルチメソッドの簡単なメカニズムを実行できます。

# 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

と使用例:

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)))

これは、Guidoの元の投稿に示されているように、デコレータに型を追加することで実行できますが、パラメータ自体に注釈を付けると、パラメータと型の誤った一致の可能性を回避できるため、優れています。

:ではPythonはあなたのように注釈にアクセスできるfunction.__annotations__のではなくfunction.func_annotationsfunc_*、スタイルはPython 3で除去しました。


2
興味深いアプリケーションですが、function = self.typemap.get(types)サブクラスが関係していると動作しません。その場合は、おそらくをtypemap使用してループする必要がありますisinnstance@overloadこれを正しく処理できるかどうか疑問に思います
Tobias Kienzler '28

関数に戻り値の型がある場合、これは壊れていると思います
zenna

1
__annotations__ dict、このスニペットが時々失敗するので、引数の順序を保証するものではないこと。私は変更をお勧めtypes = tuple(...)spec = inspect.getfullargspec(function)、その後types = tuple([spec.annotations[x] for x in spec.args])
xoolive

@xoolive、正解です。回答を編集して修正を追加してみませんか?
Muhammad Alkarouri 2016

@xoolive:気づいた。時々、編集者は編集の管理に重い手を使います。修正を含めるために質問を編集しました。実際、私はこれについて議論しましたが、修正を却下する方法はありません。ちなみに助けてくれてありがとう。
Muhammad Alkarouri 2016

22

Uriはすでに適切な回答を提供しているので、それほど深刻ではありません。Docstringを短くすることができます。


2
大好きです。+1。ただし、結局のところ、docstringsを書くことは、コードを読みやすくするための一番の方法ですが、静的または動的なチェックを実装する場合は、これを使用すると便利です。おそらく私はそれの使用法を見つけるかもしれません。
ウォーレンP

8
docstringsでArgs:セクションまたは@param行などの代わりにアノテーションを使用することはお勧めしません(使用する形式に関係なく)。ドキュメンテーションアノテーションは良い例ですが、他のより強力な使用の邪魔になる可能性があるため、アノテーションの潜在的な力を弱めます。また、docstringsやassertステートメントとは異なり、実行時にアノテーションを省略してメモリ消費を減らすことはできません(python -OO)。
gps

2
@gps:私が言ったように、それはそれほど深刻ではない答えでした。
JAB 2012

2
真剣に考えると、これは、DuckTypingを順守しながら、期待する型を文書化するためのはるかに優れた方法です。
Marc

1
@gps 99.999%のケースで、docstringのメモリ消費が心配になるかどうかはわかりません。
Tommy

13

アノテーションを初めて見たとき、「素晴らしい!最後にタイプチェックを有効にすることができます」と思いました。もちろん、アノテーションが実際に強制されていないことに気づきませんでした。

だから私はそれらを強制するために簡単な関数デコレータ書くことにしました

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'>

それをEnsureライブラリに追加しました。


ついにPythonがようやく型チェックをしていると信じ込んだ後、私は同じような失望を感じました。ついに自社製の型チェックの実装に取り​​掛かる必要があります。
Hibou57 2014

3

これは尋ねられて久しぶりですが、質問に示されているサンプルスニペットは(そこにも述べられているように)PEP 3107からのものであり、このPEPの例の最後に、見る ;)

以下はPEP3107から引用されています

ユースケース

アノテーションについて議論する過程で、いくつかのユースケースが提起されました。これらのいくつかはここに提示され、どのような情報を伝えるかによってグループ化されています。また、注釈を利用できる既存の製品とパッケージの例も含まれています。

  • タイピング情報の提供
    • 型チェック([3]、[4])
    • IDEに、関数が期待して返すタイプを表示させます([17])
    • 関数のオーバーロード/ジェネリック関数([22])
    • 外国語の橋([18]、[19])
    • 適応([21]、[20])
    • 述語論理関数
    • データベースクエリマッピング
    • RPCパラメータマーシャリング([23])
  • その他の情報
    • パラメータと戻り値のドキュメント([24])

特定のポイント(およびそれらの参照)の詳細については、PEPを参照してください。


反対投票者が反対投票の原因となったものについて少なくとも短いコメントを残した場合、私は本当に心から感謝します。これは本当に(少なくとも私にとって)改善に大いに役立ちます。
klaas 2017

2

また、Python 3.X(のみ)は、関数定義を一般化して、引数と戻り値に拡張で使用するオブジェクト値の注釈を付けることを許可します 。

関数の値をより明確にするために説明するMETAデータ。

注釈は:value、引数名の後、デフォルトの前、および->value引数リストの後のようにコーディングされます。

それらは__annotations__関数の属性に収集されますが、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'>}

出典:Python Pocket Reference、Fifth Edition

例:

このtypeannotationsモジュールは、Pythonコードの型チェックと型推論のための一連のツールを提供します。また、関数やオブジェクトに注釈を付けるのに役立つタイプのセットも提供します。

これらのツールは主に、リンター、コード補完ライブラリー、IDEなどの静的アナライザーで使用するように設計されています。さらに、実行時チェックを行うためのデコレーターが提供されます。Pythonでは、実行時の型チェックは必ずしも良い考えではありませんが、場合によっては非常に役立つことがあります。

https://github.com/ceronman/typeannotations

タイピングがより良いコードを書くのにどのように役立つか

タイピングは、コードを本番環境に送信する前に型エラーをキャッチする静的コード分析を行い、明らかなバグを防ぐのに役立ちます。mypyのようなツールがあり、ソフトウェアライフサイクルの一部としてツールボックスに追加できます。mypyは、コードベースに対して部分的または完全に実行することにより、正しいタイプをチェックできます。mypyは、関数から値が返されたときにNoneタイプをチェックするなどのバグを検出するのにも役立ちます。タイピングはコードをよりクリーンにするのに役立ちます。docstringで型を指定するコメントを使用してコードを文書化する代わりに、パフォーマンスを犠牲にすることなく型を使用できます。

Clean Python:PythonでのエレガントなコーディングISBN:ISBN-13(pbk):978-1-4842-4877-5

PEP 526-変数アノテーションの構文

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

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


@BlackJack、「拡張機能で使用するための」は明確ではありませんでしたか?
Demz 2017

それは明らかですが、質問IMHOには答えません。それは、「クラスの良い使い方は何ですか?」と「プログラムで使用するために」と答えるようなものです。それは明確で正しいですが、質問する側は、具体的な優れた使い方が何であるかについて本当に賢明ではありません。あなたの答えは、より一般的ではあり得ない答えであり、本質的には既に質問されているものと同じ例です。
BlackJack

1

ここで説明されているすべての使用にもかかわらず、アノテーションの強制可能で、おそらく強制されているのは、型ヒントです。

これは現在どのような方法でも強制されていませんが、PEP 484から判断すると、Pythonの将来のバージョンでは注釈の値として型のみが許可されます。

引用注釈の既存の用途について何を?

型ヒントが最終的にアノテーションの唯一の使用法になることを願っていますが、Python 3.5で型付けモジュールを最初に公開した後は、追加の議論と非推奨期間が必要になります。現在のPEPは、Python 3.6がリリースされるまで暫定的なステータス(PEP 411を参照)になります。考えられる最速のスキームは、3.6では非タイプヒントアノテーションのサイレント非推奨を導入し、3.7では完全に非推奨にし、タイプヒントをPython 3.8で許可される唯一のアノテーションの使用として宣言します。

3.6ではまだ何のサイレントサポートの終了も確認していませんが、代わりに3.7に変更することができます。

したがって、他にもいくつかの良いユースケースがあるかもしれませんが、この制限が適用される将来、すべての変更を回避したくない場合は、タイプヒンティングのためだけにそれらを保持するのが最善です。


1

少し遅れた回答として、私のパッケージのいくつか(marrow.script、WebCoreなど)は、型キャスト(つまり、Webからの受信値の変換、ブールスイッチである引数の検出など)を宣言するために利用できる場合は、アノテーションも使用します。引数の追加のマークアップを実行するように。

Marrow Scriptは、任意の関数とクラスへの完全なコマンドラインインターフェイスを構築し、ドキュメントの定義、キャスト、およびコールバックから派生したデフォルト値を、アノテーションを介して、古いランタイムをサポートするデコレータで定義できるようにします。注釈を使用するすべてのライブラリは、フォームをサポートしています。

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

docstringまたはタイプキャスト関数の「ベア」サポートにより、注釈を認識する他のライブラリとの混合が容易になります。(つまり、たまたまコマンドラインスクリプトとして公開されるタイプキャストを使用するWebコントローラーがあります。)

追加用に編集:開発時のアサーションを使用して検証のためにTypeGuardパッケージを利用し始めました。利点:「最適化」を有効にして実行すると(-O/ PYTHONOPTIMIZEenv var)、コストがかかる(再帰的など)可能性のあるチェックが省略されます。開発時にアプリを適切にテストしたため、本番環境ではチェックは不要になります。


-2

注釈は、コードを簡単にモジュール化するために使用できます。たとえば、私が管理しているプログラムのモジュールは、次のようなメソッドを定義するだけです。

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

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

そして、「カウントに必要」で「int」であるはずの「param1」という名前をユーザーに要求することができます。最後に、ユーザーが指定した文字列を目的のタイプに変換して、最も手間のかからないエクスペリエンスを実現することもできます。

これを支援し、必要な値を自動的に取得して任意のタイプに変換できるオープンソースクラスについては、関数メタデータオブジェクトを参照してください(注釈は変換メソッドであるため)。IDEでさえ、オートコンプリートが正しく表示され、型は注釈に従っていると想定されています-完全に適合しています。


-2

Cythonの利点のリストを見ると、主要なものは、Pythonオブジェクトがどのタイプであるかをコンパイラーに伝える機能です。

Cython(またはPythonコードの一部をコンパイルする同様のツール)が注釈構文を使用して魔法をかける未来を想像できます。


RPythonアノテータは適切Python的に感じるアプローチの一例です。アプリケーションのグラフを生成した後、すべての変数のタイプを計算し、(RPythonの場合)単一タイプの安全性を適用できます。OTOH動的でリッチな値を可能にするには、「ボクシング」またはその他のソリューション/回避策が必要です。が完全に有効なmultiply場合、整数に対してのみ機能するように関数を強制するのは誰'na' * 8 + ' batman!'ですか?;)
amcgregor
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.