Python関数は、渡すパラメーターのタイプをどのように処理しますか?


305

私が間違っていない限り、Pythonで関数を作成すると次のようになります。

def my_func(param1, param2):
    # stuff

ただし、これらのパラメーターのタイプを実際に指定するわけではありません。また、覚えていると思いますが、Pythonは厳密に型指定された言語であるため、Pythonでは関数の作成者が期待するものとは異なる型のパラメーターを渡すべきではないようです。しかし、Pythonは、関数のユーザーが適切な型で渡されていることをどのようにして知るのでしょうか。関数が実際にパラメーターを使用していると仮定して、プログラムが間違ったタイプである場合、プログラムは単に終了しますか?タイプを指定する必要がありますか?


15
この質問で受け入れられた答えは、Pythonが提供する現在の機能とより一致するように更新する必要があると思います。この答えはうまくいくと思います。
code_dredd 2017

回答:


173

Pythonは強く、すべてのオブジェクトがあるため入力されているタイプを、すべてのオブジェクトが知っているそのタイプを、それが対象だった「あるかのように」偶然または故意型のオブジェクトを使用することは不可能だ異なるタイプ、およびオブジェクト上の全ての基本操作がありますそのタイプに委任されました。

これは名前とは関係ありません。名前 Pythonでは「タイプを持っていない」:場合は、いつ名前の定義された、名前がを指し、オブジェクト、およびオブジェクトのタイプを持っています(それは実際には上のタイプ強制するものではありません名前:Aを名前は名前です)。

Pythonでの名前は、さまざまなオブジェクトをさまざまなタイミングで(ほとんどのプログラミング言語ではすべてではありませんが)完全に参照できます。また、名前に制約がないため、X型のオブジェクトを一度参照した場合、それは、とこしえのみのタイプX.制約の他のオブジェクトを参照するように制約だ名前のいくつかの愛好家が、「強い型付け」のコンセプトの一部ではない静的の名前がタイピング(ない制約を取得し、静的で、AKA、コンパイル時間、ファッションも)このように用語を誤用します。


71
だから、強い型付けはそれほど強くないようです、この特定の場合、それは静的型付けよりも弱いです私見、名前/変数/参照のコンパイル時の型付け制約は実際には非常に重要です、したがって私は大胆にPythonが静的型付けほど良くないと主張しますこの面で。私が間違っていたら訂正してください。
リャン2013年

19
@liangそれは意見なので、あなたが正しいか間違っていることはできません。それは確かに私の意見でもあり、私は多くの言語を試してきました。IDEを使用してパラメーターのタイプ(およびメンバー)を見つけることができないという事実は、Pythonの主な欠点です。この欠点がダックタイピングの利点より重要であるかどうかは、あなたが尋ねる人に依存します。
Maarten Bodewes 2013年

6
しかし、これはどの質問にも答えません。「しかし、Pythonは、関数のユーザーが適切な型で渡されていることをどのようにして知るのですか?関数が実際にパラメーターを使用すると仮定して、プログラムが間違った型である場合、プログラムはただ死ぬのでしょうか?タイプを指定する必要がありますか?」または..
qPCR4vir 2016年

4
@ qPCR4vir、任意のオブジェクトを引数として渡すことができます。(参照、それをキャッチするためにコード化されています場合は例外、プログラムはなりません「ダイ」エラーがtry/ except操作は、オブジェクトがサポートしていないことをしようとしているときと場合)が発生します。Python 3.5では、オプションで引数の「タイプを指定」できるようになりましたが、仕様に違反してもエラー自体は発生しません。タイピング表記は、分析などを実行する個別のツールを支援することのみを目的としており、Python自体の動作を変更しません。
Alex Martelli 2016年

2
@AlexMartelli。感謝!私にとってこれは正しい答えです:「エラー(例外、プログラムがそれをキャッチするようにコーディングされている場合、プログラムは "死ぬ"ことはありません
。try

753

他の回答は、アヒルのタイピングとtzotによる簡単な回答を説明するのに優れています。

Pythonには、変数が型と値を持つ他の言語のように、変数はありません。タイプを知っているオブジェクトを指す名前が付けられています。

ただし、2010年以降(質問が最初に質問されたとき)興味深い変更が1つあります。それは、PEP 3107(Python 3で実装)の実装です。次のように、実際にパラメータのタイプと関数の戻り値のタイプを指定できます。

def pick(l: list, index: int) -> int:
    return l[index]

ここではpick、リストlと整数の2つのパラメーターを受け取ることがわかりますindex。また、整数を返す必要があります。

したがって、ここではl、それが多くの努力なしで見ることができる整数のリストであることを暗示していますが、より複雑な関数の場合、リストに何を含める必要があるかについて少し混乱する可能性があります。また、のデフォルト値をindex0にする必要があります。これを解決するにはpick、代わりに次のように記述します。

def pick(l: "list of ints", index: int = 0) -> int:
    return l[index]

ここでは、のタイプとして文字列を入れていますがl、これは構文的には許可されていますが、プログラムによる解析には適していません(これについては後で説明します)。

Pythonが提起しないことに注意することが重要であるTypeErrorあなたにフロートを渡した場合index、この理由は、Pythonの設計哲学の主なポイントの一つである、:「我々は、すべてここに同意成人している」あなたがすることが期待されている手段、関数に渡すことができるものとできないものに注意してください。TypeErrorsをスローするコードを本当に作成したい場合は、isinstance関数を使用して、渡された引数が次のように適切なタイプまたはそのサブクラスであることを確認できます。

def pick(l: list, index: int = 0) -> int:
    if not isinstance(l, list):
        raise TypeError
    return l[index]

あなたがめったにこれをするべきではない理由と、代わりに何をすべきかについての詳細は、次のセクションとコメントで説明されています。

PEP 3107はコードの読みやすさを向上させるだけでなく、ここで読むことができるいくつかの適切な使用例もあります。


型注釈は、型ヒントの標準モジュールを導入するPEP 484の導入により、Python 3.5でより多くの注目を集めました。

これらの型ヒントは、現在PEP 484に準拠している型チェッカーmypyGitHub)からのものです。

型付けモジュールには、次のような型のヒントのかなり包括的なコレクションが付属しています。

  • ListTupleSetMap-のためにlisttuplesetおよびmapそれぞれ。
  • Iterable -発電機に便利です。
  • Any -それが何かであるとき。
  • Union-とは対照的に、指定されたタイプのセット内の何かである可能性がある場合Any
  • Optional-ときそれは可能性が Noneに。の略記Union[T, None]
  • TypeVar -ジェネリックで使用されます。
  • Callable -主に関数に使用されますが、他の呼び出し可能オブジェクトにも使用できます。

これらは最も一般的な型のヒントです。完全なリストは入力モジュールのドキュメントにあります

これは、型付けモジュールで導入されたアノテーションメソッドを使用した古い例です。

from typing import List

def pick(l: List[int], index: int) -> int:
    return l[index]

強力な機能の1つは、Callable関数を引数として取るアノテーションメソッドを入力できることです。例えば:

from typing import Callable, Any, Iterable

def imap(f: Callable[[Any], Any], l: Iterable[Any]) -> List[Any]:
    """An immediate version of map, don't pass it any infinite iterables!"""
    return list(map(f, l))

上記の例は、のTypeVar代わりにを使用するとより正確になる可能性がありAnyますが、タイプヒントによって有効化されたすばらしい新機能に関する情報が多すぎてすでに回答が埋まっていると思うので、これは読者への課題として残しました。


以前は、たとえばSphinxなどのドキュメント化されたPythonコードの場合、上記の機能の一部は、次のような形式のdocstringを記述することで取得できました。

def pick(l, index):
    """
    :param l: list of integers
    :type l: list
    :param index: index at which to pick an integer from *l*
    :type index: int
    :returns: integer at *index* in *l*
    :rtype: int
    """
    return l[index]

ご覧のように、これは追加の行数を必要とします(正確な数は、どの程度明示的になりたいか、およびdocstringのフォーマット方法によって異なります)。しかし、PEP 3107が多くの(すべての)方法で優れた代替手段を提供する方法が明らかになったはずです。これは、PEP 484と組み合わせた場合に特に当てはまります。これは、これまで見てきたように、これらのタイプのヒント/注釈の構文を定義する標準モジュールを提供します。強力な組み合わせ。

私の個人的な意見では、これはPythonの最大の機能の1つです。私は人々がそれの力を利用し始めるのを待つことができません。長い答えで申し訳ありませんが、これは私が興奮したときに起こることです。


型ヒントを多用するPythonコードの例は、こちらにあります


2
@rickfoosusa:機能が追加されたPython 3を実行していないと思います。
erb

26
ちょっと待って!パラメータと戻り値の型を定義してもが発生しない場合TypeErrorpick(l: list, index: int) -> int1行の定義のように使用する意味は何ですか?それとも間違っていたのか、わかりません。
Erdin Eray、2016

24
@Eray Erdin:それはよくある誤解であり、決して悪い質問ではありません。ドキュメント化の目的で使用でき、IDEがオートコンプリートを改善し、静的分析を使用してランタイム前にエラーを見つけるのに役立ちます(回答で述べたmypyのように)。ランタイムが情報を利用してプログラムを実際に高速化できることが期待されますが、実装されるまでに非常に長い時間がかかる可能性があります。あなたは可能性も(情報が保存されているあなたのためTypeErrorsをスローデコレータ作成することができる__annotations__関数オブジェクトの属性)。
erb 2016

2
@ErdinEray TypeErrorsをスローすることは悪い考えです(意図した例外がどれほど発生しても、デバッグは決して楽しいものではありません)。しかし、恐れることはありません。私の回答に記載されている新機能の利点により、より良い方法が実現します。実行時にチェックに依存せず、mypyを使用して実行前にすべてを実行するか、PyCharmなどの静的分析を行うエディターを使用します。 。
erb

2
@Tony:2つ以上のオブジェクトを返す場合、実際にはタプルを返すため、タプル型注釈を使用する必要があります。つまり、def f(a) -> Tuple[int, int]:
erb

14

タイプは指定しません。このメソッドは、渡されたパラメーターで定義されていない属性にアクセスしようとした場合にのみ(実行時に)失敗します。

したがって、この単純な関数:

def no_op(param1, param2):
    pass

... 2つの引数が渡されても失敗しません。

ただし、この機能:

def call_quack(param1, param2):
    param1.quack()
    param2.quack()

... という呼び出し可能属性が両方param1param2もない場合、実行時に失敗しますquack


+1:属性とメソッドは静的に決定されません。この「適切なタイプ」または「間違ったタイプ」がどのように機能するかという概念は、タイプが関数で適切に機能するかどうかによって確立されます。
S.Lott、2010年

11

多くの言語には、特定の型で値を持つ変数があります。Pythonには変数がありません。オブジェクトがあり、名前を使用してこれらのオブジェクトを参照します。

他の言語で言うと:

a = 1

次に、(通常は整数)変数は、その内容を値1に変更します。

Pythonでは、

a = 1

「名前aを使用してオブジェクト1を参照する」を意味します。インタラクティブなPythonセッションで次のことができます。

>>> type(1)
<type 'int'>

関数typeはオブジェクトで呼び出されます1。すべてのオブジェクトはその型を知っているので、その型typeを見つけて返すのは簡単です。

同様に、関数を定義するときはいつでも

def funcname(param1, param2):

この関数は2つのオブジェクトを受け取り、名前、それらをparam1し、param2その種類にかかわらず、。受け取ったオブジェクトが特定のタイプであることを確認したい場合は、必要なタイプであるかのように関数をコード化し、そうでない場合にスローされる例外をキャッチします。スローされる例外は、通常TypeError(無効な操作を使用した場合)およびAttributeError(存在しないメンバーにアクセスしようとした場合(メソッドもメンバーです))です。


8

Pythonは、静的またはコンパイル時の型チェックの意味で強く型付けされていません。

ほとんどのPythonコードは、いわゆる「ダックタイピング」に該当します。たとえば、readオブジェクトのメソッドを探します。オブジェクトがディスク上のファイルであるかソケットであるかは関係ありません。単にNを読みたいだけです。それからのバイト。


21
Python 強く型付けされています。また、動的に型付けされます。
Daniel Newby

1
しかし、これはどの質問にも答えません。「しかし、Pythonは、関数のユーザーが適切な型で渡されていることをどのようにして知るのですか?関数が実際にパラメーターを使用すると仮定して、プログラムが間違った型である場合、プログラムはただ死ぬのでしょうか?タイプを指定する必要がありますか?」または..
qPCR4vir 2016年

6

同様にアレックスマルテッリは説明し

通常のPythonicの推奨される解決策は、ほぼ常に「ダックタイピング」です。特定の目的の型であるかのように引数を使用してみてください。実際に引数がなかった場合に発生する可能性のあるすべての例外をキャッチするtry / exceptステートメントで使用してくださいその型(または他の任意の型がうまくダックを模倣する;-)であり、except句で他のことを試してください(引数を「あたかも」他の型と同じように使用して)。

役立つ情報については、彼の投稿の残りを読んでください。


5

Pythonは、関数に何を渡してもかまいません。を呼び出すmy_func(a,b)と、param1およびparam2変数はaとbの値を保持します。Pythonは、関数が適切な型で呼び出されていることを認識していないため、プログラマがそれを処理することを期待しています。関数が異なるタイプのパラメーターで呼び出される場合は、try / exceptブロックでそれらにアクセスするコードをラップし、パラメーターを任意の方法で評価できます。


11
Pythonには、変数が型と値を持つ他の言語のように変数はありません。それが持っている名前を指しているオブジェクト自分のタイプを知っています、。
tzot

2

タイプを指定することはありません。Pythonにはダックタイピングの概念があります。基本的に、パラメーターを処理するコードは、パラメーターについて特定の仮定を行います-おそらく、パラメーターが実装すると予想される特定のメソッドを呼び出すことによって。パラメータのタイプが間違っている場合、例外がスローされます。

一般に、適切なタイプのオブジェクトを確実に渡せるようにするのはコードの役割です。これを事前に強制するコンパイラーはありません。


2

このページで言及する価値のあるアヒルのタイピングには、悪名高い例外が1つあります。

ときにstr関数呼び出し__str__クラスメソッドを、それが微妙にその型をсhecks:

>>> class A(object):
...     def __str__(self):
...         return 'a','b'
...
>>> a = A()
>>> print a.__str__()
('a', 'b')
>>> print str(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __str__ returned non-string (type tuple)

まるでGuidoが、プログラムが予期しないタイプに遭遇した場合にどの例外を発生させるべきかを示唆するかのように。


1

Pythonではすべてに型があります。Python関数は、引数のタイプがサポートしている場合、要求されたすべてのことを行います。

例:edにfooできるすべてを追加します__add__;)そのタイプをあまり気にせずに。つまり、失敗を回避するには、追加をサポートするものだけを提供する必要があります。

def foo(a,b):
    return a + b

class Bar(object):
    pass

class Zoo(object):
    def __add__(self, other):
        return 'zoom'

if __name__=='__main__':
    print foo(1, 2)
    print foo('james', 'bond')
    print foo(Zoo(), Zoo())
    print foo(Bar(), Bar()) # Should fail

1

他の回答でこれが言及されていないので、これをポットに追加します。

他の人が言ったように、Pythonは関数やメソッドのパラメーターに型を強制しません。あなたが何をしているのかを知っていて、渡されたもののタイプを本当に知る必要があるなら、あなたはそれをチェックし、自分のために何をするかを決めることが想定されています。

これを行うための主なツールの1つはisinstance()関数です。

たとえば、通常のutf-8エンコードされた文字列ではなく、生のバイナリテキストデータを取得することを期待するメソッドを作成する場合、途中でパラメーターのタイプを確認し、見つけたものに適応するか、拒否する例外。

def process(data):
    if not isinstance(data, bytes) and not isinstance(data, bytearray):
        raise TypeError('Invalid type: data must be a byte string or bytearray, not %r' % type(data))
    # Do more stuff

Pythonには、オブジェクトを掘り下げるためのあらゆる種類のツールも用意されています。勇気があるなら、その場でimportlibを使用して任意のクラスの独自のオブジェクトを作成することもできます。JSONデータからオブジェクトを再作成するためにこれを行いました。このようなことは、C ++のような静的言語では悪夢になります。


1

入力モジュール(Python 3.5の新機能)を効果的に使用するには、すべて(*)を含めます。

from typing import *

そして、あなたは使う準備ができています:

List, Tuple, Set, Map - for list, tuple, set and map respectively.
Iterable - useful for generators.
Any - when it could be anything.
Union - when it could be anything within a specified set of types, as opposed to Any.
Optional - when it might be None. Shorthand for Union[T, None].
TypeVar - used with generics.
Callable - used primarily for functions, but could be used for other callables.

しかし、まだあなたのようなタイプの名前を使用することができintlistdict、...


1

変数型を指定したい場合は、ラッパーを実装しました。

import functools

def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for i in range(len(args)):
            v = args[i]
            v_name = list(func.__annotations__.keys())[i]
            v_type = list(func.__annotations__.values())[i]
            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        v = result
        v_name = 'return'
        v_type = func.__annotations__['return']
        error_msg = 'Variable `' + str(v_name) + '` should be type ('
        error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
        if not isinstance(v, v_type):
                raise TypeError(error_msg)
        return result

    return check

次のように使用します。

@type_check
def test(name : str) -> float:
    return 3.0

@type_check
def test2(name : str) -> str:
    return 3.0

>> test('asd')
>> 3.0

>> test(42)
>> TypeError: Variable `name` should be type (<class 'str'>) but instead is type (<class 'int'>)

>> test2('asd')
>> TypeError: Variable `return` should be type (<class 'str'>) but instead is type (<class 'float'>)

編集

上記のコードは、引数(または戻り値)の型が宣言されていない場合は機能しません。次の編集は役立ちますが、一方で、kwargsに対してのみ機能し、argsをチェックしません。

def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for name, value in kwargs.items():
            v = value
            v_name = name
            if name not in func.__annotations__:
                continue

            v_type = func.__annotations__[name]

            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ') '
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        if 'return' in func.__annotations__:
            v = result
            v_name = 'return'
            v_type = func.__annotations__['return']
            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                    raise TypeError(error_msg)
        return result

    return check
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.