Python函数如何处理您传入的参数类型?

问题:Python函数如何处理您传入的参数类型?

除非我没有记错,否则在Python中创建函数的工作方式如下:

def my_func(param1, param2):
    # stuff

但是,您实际上并未提供这些参数的类型。另外,如果我记得,Python是一种强类型语言,因此,似乎Python不应让您传递与函数创建者所期望的类型不同的参数。但是,Python如何知道函数的用户正在传递正确的类型?假定函数实际上使用了参数,那么如果类型错误,程序会死掉吗?您必须指定类型吗?

Unless I’m mistaken, creating a function in Python works like this:

def my_func(param1, param2):
    # stuff

However, you don’t actually give the types of those parameters. Also, if I remember, Python is a strongly typed language, as such, it seems like Python shouldn’t let you pass in a parameter of a different type than the function creator expected. However, how does Python know that the user of the function is passing in the proper types? Will the program just die if it’s the wrong type, assuming the function actually uses the parameter? Do you have to specify the type?


回答 0

Python是强类型的,因为每个对象都有一个类型,每个对象都知道其类型,不可能无意或故意使用类型“好像”它是不同类型的对象,并且对该对象的所有基本操作都是委托给它的类型。

这与名称无关。Python中的名称没有“具有类型”:如果且在定义名称时,该名称指向一个对象,并且该对象确实具有一个类型(但实际上并不会强制该名称使用类型:名称是一个名称)。

Python中的名称可以很好地在不同时间引用不同的对象(就像在大多数编程语言中一样,尽管不是全部)-并且名称不受任何限制,因此,如果它曾经引用过X类型的对象,这样一来,便永远只能引用其他类型为X的对象。名称的约束不属于“强类型”概念的一部分,尽管一些静态类型的爱好者(名称确实受到约束,并且在静态AKA中会编译-时间,时尚也是如此)请勿以这种方式滥用该术语。

Python is strongly typed because every object has a type, every object knows its type, it’s impossible to accidentally or deliberately use an object of a type “as if” it was an object of a different type, and all elementary operations on the object are delegated to its type.

This has nothing to do with names. A name in Python doesn’t “have a type”: if and when a name’s defined, the name refers to an object, and the object does have a type (but that doesn’t in fact force a type on the name: a name is a name).

A name in Python can perfectly well refer to different objects at different times (as in most programming languages, though not all) — and there is no constraint on the name such that, if it has once referred to an object of type X, it’s then forevermore constrained to refer only to other objects of type X. Constraints on names are not part of the concept of “strong typing”, though some enthusiasts of static typing (where names do get constrained, and in a static, AKA compile-time, fashion, too) do misuse the term this way.


回答 1

其他答案在解释鸭子的类型和tzot的简单答案方面做得很好:

Python没有变量,就像其他语言一样,变量具有类型和值。它具有指向对象的名称,这些对象知道其类型。

但是,自2010年(首次提出该问题)以来,发生了一件有趣的事情,即PEP 3107的实现(在Python 3中实现)。现在,您实际上可以像这样指定参数的类型和函数的返回类型的类型:

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

我们在这里可以看到pick有两个参数,一个列表l和一个整数index。它还应该返回一个整数。

因此,这里暗示的l是一个整数列表,我们可以很轻松地看到它,但是对于更复杂的函数,该列表应包含的内容可能会有些混乱。我们还希望默认值为index0。要解决此问题,您可以选择这样写pick

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

请注意,我们现在在字符串中添加了类型为的字符串l,这在语法上是允许的,但是对于以编程方式进行解析不是很好(我们将在后面介绍)。

重要的是要注意,TypeError如果将float传递给Python,Python不会引发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),它现在符合PEP 484

键入模块随附了非常全面的类型提示集合,包括:

  • ListTupleSetMap-为listtuplesetmap分别。
  • Iterable -对生成器有用。
  • Any -什么时候可以。
  • Union-相对于,它可以是指定类型集中的任何内容Any
  • Optional可能为“无”时。的简写Union[T, None]
  • 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,但是这留给了读者练习,因为我相信我已经在答案中添加了有关类型提示所启用的出色新功能的过多信息。


以前,当编写一个带有Sphinx的文档化Python代码时,可以通过编写如下格式的文档字符串来获得上述某些功能:

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代码示例。

The other answers have done a good job at explaining duck typing and the simple answer by tzot:

Python does not have variables, like other languages where variables have a type and a value; it has names pointing to objects, which know their type.

However, one interesting thing has changed since 2010 (when the question was first asked), namely the implementation of PEP 3107 (implemented in Python 3). You can now actually specify the type of a parameter and the type of the return type of a function like this:

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

We can here see that pick takes 2 parameters, a list l and an integer index. It should also return an integer.

So here it is implied that l is a list of integers which we can see without much effort, but for more complex functions it can be a bit confusing as to what the list should contain. We also want the default value of index to be 0. To solve this you may choose to write pick like this instead:

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

Note that we now put in a string as the type of l, which is syntactically allowed, but it is not good for parsing programmatically (which we’ll come back to later).

It is important to note that Python won’t raise a TypeError if you pass a float into index, the reason for this is one of the main points in Python’s design philosophy: “We’re all consenting adults here”, which means you are expected to be aware of what you can pass to a function and what you can’t. If you really want to write code that throws TypeErrors you can use the isinstance function to check that the passed argument is of the proper type or a subclass of it like this:

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

More on why you should rarely do this and what you should do instead is talked about in the next section and in the comments.

PEP 3107 does not only improve code readability but also has several fitting use cases which you can read about here.


Type annotation got a lot more attention in Python 3.5 with the introduction of PEP 484 which introduces a standard module for type hints.

These type hints came from the type checker mypy (GitHub), which is now PEP 484 compliant.

With the typing module comes with a pretty comprehensive collection of type hints, including:

  • 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.

These are the most common type hints. A complete listing can be found in the documentation for the typing module.

Here is the old example using the annotation methods introduced in the typing module:

from typing import List

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

One powerful feature is the Callable which allows you to type annotate methods that take a function as an argument. For example:

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

The above example could become more precise with the usage of TypeVar instead of Any, but this has been left as an exercise to the reader since I believe I’ve already filled my answer with too much information about the wonderful new features enabled by type hinting.


Previously when one documented Python code with for example Sphinx some of the above functionality could be obtained by writing docstrings formatted like this:

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]

As you can see, this takes a number of extra lines (the exact number depends on how explicit you want to be and how you format your docstring). But it should now be clear to you how PEP 3107 provides an alternative that is in many (all?) ways superior. This is especially true in combination with PEP 484 which, as we have seen, provides a standard module that defines a syntax for these type hints/annotations that can be used in such a way that it is unambiguous and precise yet flexible, making for a powerful combination.

In my personal opinion, this is one of the greatest features in Python ever. I can’t wait for people to start harnessing the power of it. Sorry for the long answer, but this is what happens when I get excited.


An example of Python code which heavily uses type hinting can be found here.


回答 2

您没有指定类型。该方法仅在尝试访问传入的参数上未定义的属性时(运行时)失败。

所以这个简单的功能:

def no_op(param1, param2):
    pass

……无论传入什么两个参数,都不会失败。

但是,此功能:

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

如果param1并且param2都不具有名为的可调用属性,则会在运行时失败quack

You don’t specify a type. The method will only fail (at runtime) if it tries to access attributes that are not defined on the parameters that are passed in.

So this simple function:

def no_op(param1, param2):
    pass

… will not fail no matter what two args are passed in.

However, this function:

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

… will fail at runtime if param1 and param2 do not both have callable attributes named quack.


回答 3

许多语言都有变量,这些变量属于特定类型并具有值。Python没有变量。它具有对象,您可以使用名称来引用这些对象。

用其他语言,当您说:

a = 1

然后(通常为整数)变量将其内容更改为值1。

在Python中,

a = 1

表示“使用名称a引用对象1 ”。您可以在交互式Python会话中执行以下操作:

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

该函数type用对象调用1; 由于每个对象都知道其类型,因此很容易type找出所述类型并将其返回。

同样,无论何时定义函数

def funcname(param1, param2):

该函数接收两个对象,并为其命名为param1param2,无论它们的类型如何。如果要确保接收到的对象属于特定类型,请对函数进行编码,就好像它们属于所需的类型一样,并捕获不是的异常。引发的异常通常是TypeError(您使用了无效的操作)和AttributeError(您试图访问不存在的成员(方法也是成员))。

Many languages have variables, which are of a specific type and have a value. Python does not have variables; it has objects, and you use names to refer to these objects.

In other languages, when you say:

a = 1

then a (typically integer) variable changes its contents to the value 1.

In Python,

a = 1

means “use the name a to refer to the object 1”. You can do the following in an interactive Python session:

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

The function type is called with the object 1; since every object knows its type, it’s easy for type to find out said type and return it.

Likewise, whenever you define a function

def funcname(param1, param2):

the function receives two objects, and names them param1 and param2, regardless of their types. If you want to make sure the objects received are of a specific type, code your function as if they are of the needed type(s) and catch the exceptions that are thrown if they aren’t. The exceptions thrown are typically TypeError (you used an invalid operation) and AttributeError (you tried to access an inexistent member (methods are members too) ).


回答 4

在静态或编译时类型检查的意义上,Python的类型不是强类型。

大多数Python代码都属于所谓的 “ Duck Typing”鸭子输入) -例如,您read在对象上寻找一种方法-不在乎对象是磁盘上的文件还是套接字上的文件,您只想读N字节。

Python is not strongly typed in the sense of static or compile-time type checking.

Most Python code falls under so-called “Duck Typing” — for example, you look for a method read on an object — you don’t care if the object is a file on disk or a socket, you just want to read N bytes from it.


回答 5

正如Alex Martelli所说

正常的,Python式的首选解决方案几乎总是“鸭式输入”:尝试使用参数,就好像它是某个所需的类型一样,在try / except语句中进行操作,以捕获如果该参数实际上不是所有可能出现的异常该类型(或其他任何可以模仿它的类型;-),然后在except子句中尝试其他操作(使用参数“好像”它是其他类型)。

阅读他的文章的其余部分,以获取有用的信息。

As Alex Martelli explains,

The normal, Pythonic, preferred solution is almost invariably “duck typing”: try using the argument as if it was of a certain desired type, do it in a try/except statement catching all exceptions that could arise if the argument was not in fact of that type (or any other type nicely duck-mimicking it;-), and in the except clause, try something else (using the argument “as if” it was of some other type).

Read the rest of his post for helpful information.


回答 6

Python不在乎您将其传递给什么函数。当您调用时my_func(a,b),param1和param2变量将保存a和b的值。Python不知道您使用正确的类型来调用该函数,因此希望程序员来照顾好它。如果将使用不同类型的参数调用函数,则可以使用try / except块包装代码以访问它们,并以所需的任何方式评估参数。

Python doesn’t care what you pass in to its functions. When you call my_func(a,b), the param1 and param2 variables will then hold the values of a and b. Python doesn’t know that you are calling the function with the proper types, and expects the programmer to take care of that. If your function will be called with different types of parameters, you can wrap code accessing them with try/except blocks and evaluate the parameters in whatever way you want.


回答 7

您从不指定类型;Python具有鸭子类型的概念; 基本上,处理参数的代码将对它们做出某些假设-可能是通过调用期望参数实现的某些方法。如果参数的类型错误,则将引发异常。

通常,由代码决定是否传递正确类型的对象-没有编译器可以提前实施此类型。

You never specify the type; Python has the concept of duck typing; basically the code that processes the parameters will make certain assumptions about them – perhaps by calling certain methods that a parameter is expected to implement. If the parameter is of the wrong type, then an exception will be thrown.

In general it is up to your code to ensure that you are passing around objects of the proper type – there is no compiler to enforce this ahead of time.


回答 8

在这一页上,值得一提的是鸭子输入法,这是一个臭名昭著的exceptions。

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提示我们程序遇到意外类型时应引发哪个异常。

There’s one notorious exception from the duck-typing worth mentioning on this page.

When str function calls __str__ class method it subtly сhecks its type:

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

As if Guido hints us which exception should a program raise if it encounters an unexpected type.


回答 9

在Python中,所有事物都有一个类型。如果参数类型支持它,Python函数将执行被要求执行的任何操作。

示例: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

In Python everything has a type. A Python function will do anything it is asked to do if the type of arguments support it.

Example: foo will add everything that can be __add__ed ;) without worrying much about its type. So that means, to avoid failure, you should provide only those things that support addition.

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

回答 10

我没有在其他答案中看到此内容,因此将其添加到锅中。

正如其他人所说的,Python并不对函数或方法参数强制执行类型。假定您知道自己在做什么,并且如果您确实需要知道传入的内容的类型,则将对其进行检查并决定自己要做什么。

isinstance()函数是执行此操作的主要工具之一。

例如,如果我编写的方法希望获取原始的二进制文本数据,而不是常规的utf-8编码的字符串,则可以检查途中的参数类型并适应我的发现,或者提高exceptions拒绝。

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 ++这样的静态语言来说将是一场噩梦。

I didn’t see this mentioned in other answers, so I’ll add this to the pot.

As others have said, Python doesn’t enforce type on function or method parameters. It is assumed that you know what you’re doing, and that if you really need to know the type of something that was passed in, you will check it and decide what to do for yourself.

One of the main tools for doing this is the isinstance() function.

For example, if I write a method that expects to get raw binary text data, rather than the normal utf-8 encoded strings, I could check the type of the parameters on the way in and either adapt to what I find, or raise an exception to refuse.

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 also provides all kinds of tools to dig into objects. If you’re brave, you can even use importlib to create your own objects of arbitrary classes, on the fly. I did this to recreate objects from JSON data. Such a thing would be a nightmare in a static language like C++.


回答 11

为了有效地使用键入模块(Python 3.5中的新增功能),请包括all(*)。

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,…

To effectively use the typing module (new in Python 3.5) include all (*).

from typing import *

And you will be ready to use:

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.

However, still you can use type names like int, list, dict,…


回答 12

如果有人想指定变量类型,我已经实现了包装器。

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

I have implemented a wrapper if anyone would like to specify variable types.

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

Use it as:

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

EDIT

The code above does not work if any of the arguments’ (or return’s) type is not declared. The following edit can help, on the other hand, it only works for kwargs and does not check 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