web-dev-qa-db-ja.com

Python3の「関数注釈」の優れた用途

関数アノテーション: 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}

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

151
agscala

これは実際に素晴らしいと思います。

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

タイピングが弱いため、Pythonでは特にこのようなものが必要であると感じていますが、これを直接的な公式構文の一部にする構成要素は実際にはありませんでした。

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

87
Uri

関数注釈は、あなたが作成したものです。

これらは文書化に使用できます。

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/ も参照してください。

86

これはかなり遅い答えですが、関数注釈の現在の最適な使用法は PEP-0484 および MyPy です。

Mypyは、Pythonのオプションの静的型チェッカーです。 Python 3.5 beta 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
40
Dustin Wyatt

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

# 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 __== '__main__':
    print("foo(1,'a') = {}".format(foo(1,'a')))
    print("foo(7) = {}".format(foo(7)))

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

:Pythonでは、注釈にfunction.__annotations__ のではなく function.func_annotationsとしてfunc_*スタイルは、Python 3。

23

Uriはすでに適切な答えを出しているので、ここではそれほど深刻ではありません。したがって、docstringを短くすることができます。

20
JAB

注釈を初めて見たとき、「素晴らしい!最終的に何らかの型チェックを選択できるようになった!」と思いました。もちろん、私は注釈が実際に強制されないことに気づいていませんでした。

そこで、私は 単純な関数デコレータを作成してそれらを強制する

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 ライブラリに追加しました。

13
weaver

これが尋ねられてから長い時間が経ちましたが、質問に示されているスニペットの例は(同様に述べられているように)PEP 3107からのものであり、PESの例の最後にあります。見る ;)

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

ユースケース

注釈を議論する過程で、いくつかのユースケースが提起されました。これらのいくつかは、それらが伝える情報の種類ごとにグループ化されてここに表示されます。また、注釈を使用できる既存の製品およびパッケージの例も含まれています。

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

特定のポイント(およびその参照)の詳細については、 [〜#〜] pep [〜#〜] を参照してください。

3
klaas

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で型を指定するコメントを使用してコードを文書化する代わりに、パフォーマンスコストなしで型を使用できます。

きれいな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

2
The Demz

少し遅れた回答として、私のパッケージのいくつか(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/PYTHONOPTIMIZE env var)高価な(たとえば、再帰的な)チェックは省略されます。開発中にアプリを適切にテストしたため、本番環境ではチェックは不要です。

1
amcgregor

ここで説明するすべての用途にもかかわらず、注釈の強制可能な、そしておそらく最も強制的な使用は、 type hints のためです。

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

引用 注釈の既存の使用はどうですか?

最終的にはタイプヒントがアノテーションの唯一の用途になることを願っていますが、これには、Python 3.5でのタイピングモジュールの最初のロールアウト後の追加の議論と廃止期間が必要です。 PEPは、Python 3.6がリリースされるまで、暫定的な状態(PEP 411を参照)になります。考えられる最も速いスキームは、3.6の非型ヒントアノテーションのサイレント非推奨、3.7の完全非推奨、および宣言を導入しますPython 3.8。

私はまだ3.6で静かな非推奨を見たことはありませんが、これは代わりに3.7に非常にうまくバンプできます。

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