问题:Python函数如何处理您传入的参数类型?
除非我没有记错,否则在Python中创建函数的工作方式如下:
def my_func(param1, param2):
# stuff
但是,您实际上并未提供这些参数的类型。另外,如果我记得,Python是一种强类型语言,因此,似乎Python不应让您传递与函数创建者所期望的类型不同的参数。但是,Python如何知道函数的用户正在传递正确的类型?假定函数实际上使用了参数,那么如果类型错误,程序会死掉吗?您必须指定类型吗?
回答 0
Python是强类型的,因为每个对象都有一个类型,每个对象都知道其类型,不可能无意或故意使用类型“好像”它是不同类型的对象,并且对该对象的所有基本操作都是委托给它的类型。
这与名称无关。Python中的名称没有“具有类型”:如果且在定义名称时,该名称指向一个对象,并且该对象确实具有一个类型(但实际上并不会强制该名称使用类型:名称是一个名称)。
Python中的名称可以很好地在不同时间引用不同的对象(就像在大多数编程语言中一样,尽管不是全部)-并且名称不受任何限制,因此,如果它曾经引用过X类型的对象,这样一来,便永远只能引用其他类型为X的对象。名称的约束不属于“强类型”概念的一部分,尽管一些静态类型的爱好者(名称确实受到约束,并且在静态AKA中会编译-时间,时尚也是如此)请勿以这种方式滥用该术语。
回答 1
其他答案在解释鸭子的类型和tzot的简单答案方面做得很好:
Python没有变量,就像其他语言一样,变量具有类型和值。它具有指向对象的名称,这些对象知道其类型。
但是,自2010年(首次提出该问题)以来,发生了一件有趣的事情,即PEP 3107的实现(在Python 3中实现)。现在,您实际上可以像这样指定参数的类型和函数的返回类型的类型:
def pick(l: list, index: int) -> int:
return l[index]
我们在这里可以看到pick
有两个参数,一个列表l
和一个整数index
。它还应该返回一个整数。
因此,这里暗示的l
是一个整数列表,我们可以很轻松地看到它,但是对于更复杂的函数,该列表应包含的内容可能会有些混乱。我们还希望默认值为index
0。要解决此问题,您可以选择这样写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引入了用于类型提示的标准模块。
这些类型提示来自类型检查器mypy(GitHub),它现在符合PEP 484。
键入模块随附了非常全面的类型提示集合,包括:
List
,Tuple
,Set
,Map
-为list
,tuple
,set
和map
分别。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代码示例。
回答 2
您没有指定类型。该方法仅在尝试访问传入的参数上未定义的属性时(运行时)失败。
所以这个简单的功能:
def no_op(param1, param2):
pass
……无论传入什么两个参数,都不会失败。
但是,此功能:
def call_quack(param1, param2):
param1.quack()
param2.quack()
如果param1
并且param2
都不具有名为的可调用属性,则会在运行时失败quack
。
回答 3
许多语言都有变量,这些变量属于特定类型并具有值。Python没有变量。它具有对象,您可以使用名称来引用这些对象。
用其他语言,当您说:
a = 1
然后(通常为整数)变量将其内容更改为值1。
在Python中,
a = 1
表示“使用名称a引用对象1 ”。您可以在交互式Python会话中执行以下操作:
>>> type(1)
<type 'int'>
该函数type
用对象调用1
; 由于每个对象都知道其类型,因此很容易type
找出所述类型并将其返回。
同样,无论何时定义函数
def funcname(param1, param2):
该函数接收两个对象,并为其命名为param1
和param2
,无论它们的类型如何。如果要确保接收到的对象属于特定类型,请对函数进行编码,就好像它们属于所需的类型一样,并捕获不是的异常。引发的异常通常是TypeError
(您使用了无效的操作)和AttributeError
(您试图访问不存在的成员(方法也是成员))。
回答 4
在静态或编译时类型检查的意义上,Python的类型不是强类型。
大多数Python代码都属于所谓的 “ Duck Typing”(鸭子输入) -例如,您read
在对象上寻找一种方法-不在乎对象是磁盘上的文件还是套接字上的文件,您只想读N字节。
回答 5
正常的,Python式的首选解决方案几乎总是“鸭式输入”:尝试使用参数,就好像它是某个所需的类型一样,在try / except语句中进行操作,以捕获如果该参数实际上不是所有可能出现的异常该类型(或其他任何可以模仿它的类型;-),然后在except子句中尝试其他操作(使用参数“好像”它是其他类型)。
阅读他的文章的其余部分,以获取有用的信息。
回答 6
Python不在乎您将其传递给什么函数。当您调用时my_func(a,b)
,param1和param2变量将保存a和b的值。Python不知道您使用正确的类型来调用该函数,因此希望程序员来照顾好它。如果将使用不同类型的参数调用函数,则可以使用try / except块包装代码以访问它们,并以所需的任何方式评估参数。
回答 7
您从不指定类型;Python具有鸭子类型的概念; 基本上,处理参数的代码将对它们做出某些假设-可能是通过调用期望参数实现的某些方法。如果参数的类型错误,则将引发异常。
通常,由代码决定是否传递正确类型的对象-没有编译器可以提前实施此类型。
回答 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提示我们程序遇到意外类型时应引发哪个异常。
回答 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
回答 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 ++这样的静态语言来说将是一场噩梦。
回答 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.
但是,你仍然可以使用类的名称,如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