簡體   English   中英

Python函數如何處理你傳入的參數類型?

[英]How do Python functions handle the types of parameters that you pass in?

除非我弄錯了,否則在 Python 中創建 function 的工作方式如下:

def my_func(param1, param2):
    # stuff

但是,您實際上並沒有給出這些參數的類型。 另外,如果我記得的話,Python 是一種強類型語言,因此,似乎 Python 不應該讓您傳入與 function 創建者預期的類型不同的參數。 但是,Python 如何知道 function 的用戶傳遞的是正確的類型? 假設 function 實際使用了參數,如果類型錯誤,程序會死掉嗎? 你必須指定類型嗎?

其他答案在解釋鴨子打字和tzot 的簡單答案方面做得很好:

Python 沒有變量,就像其他語言一樣,變量有類型和值; 它有指向對象的名稱,這些對象知道它們的類型。

但是,自 2010 年(首次提出問題時)以來,一件有趣的事情發生了變化,即PEP 3107的實現(在 Python 3 中實現)。 您現在可以實際指定參數的類型和函數的返回類型的類型,如下所示:

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

我們可以在這里看到pick接受 2 個參數,一個列表l和一個整數index 它還應該返回一個整數。

所以這里暗示l是一個整數列表,我們可以毫不費力地看到它,但是對於更復雜的函數,對於列表應該包含的內容可能有點混亂。 我們也希望的默認值index為0。為了解決這個問題,你可以選擇寫pick喜歡這個:

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

請注意,我們現在放入一個字符串作為l的類型,這在語法上是允許的,但它不適合以編程方式解析(我們稍后會回來討論)。

重要的是要注意,如果您將浮點數傳遞給index ,Python 不會引發TypeError ,其原因是 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 中得到了更多的關注,它引入了一個用於類型提示的標准模塊。

這些類型提示來自類型檢查器mypy ( GitHub ),它現在符合PEP 484

打字模塊附帶了一個非常全面的類型提示集合,包括:

  • List , Tuple , Set , Map - 分別用於list , tuple , setmap
  • Iterable - 對生成器有用。
  • Any - 當它可以是任何東西時。
  • Union - 當它可以是一組指定類型中的任何東西時,而不是Any
  • Optional - 當它可能是 None 時。 Union[T, None]簡寫。
  • TypeVar - 與泛型TypeVar使用。
  • Callable - 主要用於函數,但也可用於其他可調用對象。

這些是最常見的類型提示。 可以在打字模塊文檔中找到完整列表。

這是使用類型模塊中引入的注釋方法的舊示例:

from typing import List

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

一個強大的功能是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變得更加精確,但這已經留給讀者作為練習,因為我相信我已經用太多關於類型提示啟用的精彩新功能的信息填充了我的答案.


以前,當一個文檔化的 Python 代碼,例如Sphinx 時,可以通過編寫如下格式的文檔字符串來獲得上述某些功能:

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]

正如您所看到的,這需要多行一些行(確切的數量取決於您想要的明確程度以及您如何格式化文檔字符串)。 但是現在您應該清楚PEP 3107如何提供一種在許多(所有?)方面都優越的替代方案。 PEP 484結合使用時尤其如此,正如我們所見, PEP 484提供了一個標准模塊,該模塊定義了這些類型提示/注釋的語法,可以以明確、精確但靈活的方式使用,從而使強大的組合。

在我個人看來,這是 Python 有史以來最偉大的特性之一。 我迫不及待地希望人們開始利用它的力量。 抱歉回答太長,但這就是我興奮時會發生的情況。


可以在此處找到大量使用類型提示的 Python 代碼示例。

Python 是強類型的,因為每個對象都有一個類型,每個對象都知道它的類型,不可能意外或故意使用一個類型的對象“好像”它是一個不同類型的對象,並且對象上的所有基本操作都是委托給它的類型。

這與名稱無關。 Python 中的名稱沒有“類型”:如果定義了名稱,則名稱指的是對象,並且對象確實具有類型(但這實際上並不強制名稱上有類型:a名字是一個名字)。

Python 中的名稱可以完美地在不同時間引用不同的對象(就像在大多數編程語言中一樣,盡管不是全部)——並且對名稱沒有限制,如果它曾經引用過 X 類型的對象,然后它永遠被限制為僅引用類型為 X 的其他對象。名稱上的約束不是“強類型”概念的一部分,盡管一些靜態類型的愛好者(名稱確實受到約束,並且在靜態中,AKA 編譯 -時間、時尚也一樣)確實會以這種方式誤用這個詞。

您沒有指定類型。 如果該方法嘗試訪問未在傳入參數上定義的屬性,則該方法只會失敗(在運行時)。

所以這個簡單的函數:

def no_op(param1, param2):
    pass

...無論傳入兩個參數都不會失敗。

然而,這個功能:

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

...如果param1param2都沒有名為quack可調用屬性,則將在運行時失敗。

許多語言都有變量,它們屬於特定類型並具有值。 Python 沒有變量; 它有對象,您可以使用名稱來引用這些對象。

在其他語言中,當您說:

a = 1

然后一個(通常是整數)變量將其內容更改為值 1。

在 Python 中,

a = 1

意思是“使用名稱a來指代對象1 ”。 您可以在交互式 Python 會話中執行以下操作:

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

函數type用對象1調用; 因為每個對象都知道它的類型,很容易為type ,找出所述類型並返回。

同樣,每當你定義一個函數

def funcname(param1, param2):

該函數接收兩個對象,並將它們命名為param1param2 ,無論它們的類型如何。 如果您想確保接收到的對象是特定類型的,請將您的函數編碼為所需類型,並捕獲如果它們不是所拋出的異常。 拋出的異常通常是TypeError (您使用了無效操作)和AttributeError (您嘗試訪問不存在的成員(方法也是成員))。

在靜態或編譯時類型檢查的意義上,Python 不是強類型的。

大多數 Python 代碼都屬於所謂的“Duck Typing” ——例如,你尋找一個read對象的方法——你不關心對象是磁盤上的文件還是套接字,你只想讀取N 個字節。

正如亞歷克斯·馬泰利(Alex Martelli)所解釋的那樣

正常的、Pythonic 的首選解決方案幾乎總是“鴨子類型”:嘗試使用參數,就好像它是某種所需的類型一樣,在 try/except 語句中執行此操作,以捕獲如果參數實際上不是可能出現的所有異常該類型(或任何其他類型很好地模仿它;-),並在except子句中嘗試其他內容(使用參數“好像”它是其他類型)。

閱讀他帖子的其余部分以獲取有用的信息。

如果有人想指定變量類型,我已經實現了一個包裝器。

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

Python 並不關心你傳遞給它的函數的內容。 當您調用my_func(a,b) ,param1 和 param2 變量將保存 a 和 b 的值。 Python 不知道您正在使用正確的類型調用函數,並希望程序員負責。 如果您的函數將使用不同類型的參數調用,您可以使用 try/except 塊包裝訪問它們的代碼,並以您想要的任何方式評估參數。

您從不指定類型; Python 有鴨子類型的概念; 基本上,處理參數的代碼會對它們做出某些假設——也許是通過調用參數預期實現的某些方法。 如果參數類型錯誤,則會拋出異常。

一般來說,確保您傳遞正確類型的對象取決於您的代碼 - 沒有編譯器提前強制執行此操作。

鴨式打字有一個臭名昭著的例外,值得一提。

str函數調用__str__類方法時,它會巧妙地檢查其類型:

>>> 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 提示我們程序在遇到意外類型時應該引發哪個異常。

在 Python 中,一切都有一個類型。 如果參數類型支持,Python 函數將執行要求它執行的任何操作。

示例: foo將添加所有可以__add__ ed ;) 而不必擔心其類型。 所以這意味着,為了避免失敗,你應該只提供那些支持加法的東西。

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

我在其他答案中沒有看到這一點,所以我將其添加到鍋中。

正如其他人所說,Python 不會對函數或方法參數強制類型。 假設您知道自己在做什么,並且如果您真的需要知道傳入的東西的類型,您將檢查它並決定自己做什么。

執行此操作的主要工具之一是 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++ 這樣的靜態語言中,這樣的事情將是一場噩夢。

要有效地使用打字模塊(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 ,...

無論您是否指定類型提示,運行時都會失敗。

但是,您可以為 function arguments 及其返回類型提供類型提示。 例如, def foo(bar: str) -> List[float]暗示 bar 應該是一個字符串,而 function 返回一個浮點值列表。 如果類型不匹配(在使用 function 中的參數或返回類型之前),這將導致在調用方法時出現類型檢查錯誤。 這個 IMOHO 在捕獲此類錯誤方面比在方法調用中某處缺少字段或方法的錯誤更有幫助。 我建議閱讀官方文檔 Python Typing - Support for type hints

此外,如果您使用類型提示,則可以使用 static 類型檢查器來驗證代碼的正確性。 python 內置的此類工具之一是Mypy官方文檔)。 關於 Static 類型檢查的文章的這一部分很好地介紹了如何使用它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM