除非我弄错了,否则在Python中创建一个函数就像这样:
def my_func(param1, param2):
# stuff
但是,实际上并没有给出这些参数的类型。另外,如果我记得,Python是一种强类型语言,因此,似乎Python不应该让你传递一个与函数创建者所期望的不同类型的参数。但是,Python如何知道函数的用户是否传递了正确的类型?假设函数实际使用参数,程序是否会因错误类型而死亡?你必须指定类型吗?
答案 0 :(得分:535)
其他答案在解释鸭子打字和the simple answer by 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
的类型放入,这在语法上是允许的,但它不适合以编程方式进行解析(我们将在稍后回溯)。
重要的是要注意,如果将float传递给TypeError
,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不仅提高了代码的可读性,还提供了几个适合的用例,您可以阅读here。
类型注释在Python 3.5中得到了更多的关注,引入了PEP 484,它引入了类型提示的标准模块。
这些类型提示来自类型检查器mypy(GitHub),现在符合PEP 484。
打字模块附带了一个非常全面的类型提示集合,包括:
List
,Tuple
,Set
,Map
- 适用于list
,tuple
,set
和{{1}分别。map
- 对发电机很有用。Iterable
- 什么时候可以。Any
- 当它可以是指定的一组类型中的任何内容时,而不是Union
。Any
- 当可能为无时。 Optional
的简写。Union[T, None]
- 与泛型一起使用。TypeVar
- 主要用于函数,但可用于其他callables。这些是最常见的类型提示。完整列表可在documentation for the typing module。
中找到以下是使用输入模块中引入的注释方法的旧示例:
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
时,上述示例可能会变得更精确,但这已经留给读者,因为我相信我已经填补了我的答案关于通过类型提示启用的精彩新功能的大量信息。
以前当一个记录的Python代码例如Sphinx时,可以通过编写如下格式的文档字符串来获得上述某些功能:
Any
正如您所看到的,这需要额外的一些行(确切的数字取决于您希望的显式方式以及格式化文档字符串的方式)。但现在应该清楚PEP 3107如何提供一种替代方案,这种方式在很多(所有?)方面都很优越。结合PEP 484尤其如此,正如我们所见,它提供了一个标准模块,它定义了这些类型提示/注释的语法,可以使用它的方式明确,精确而灵活,为了一个强大的组合。
在我个人看来,这是Python史上最伟大的功能之一。我等不及人们开始利用它的力量了。对不起,答案很长,但这就是我兴奋时会发生的事情。
可以找到大量使用类型提示的Python代码示例here。
答案 1 :(得分:139)
Python是强类型的,因为每个对象都有类型,每个对象知道它的类型,不可能意外或故意使用类型的对象“好像”它是不同类型的对象,对象上的所有基本操作都被委托给它的类型。
这与名称无关。 Python中的名称不具有“类型”:如果名称定义,名称引用对象,对象确实有一个类型(但实际上并不强制名称上的类型:名称是名称)。
Python中的名称可以很好地在不同时间引用不同的对象(如在大多数编程语言中,但不是全部) - 并且对名称没有约束,如果它曾经引用过一个对象类型X,它永远受限于仅引用类型X的其他对象。名称的约束不是“强类型”概念的一部分,尽管有一些静态的爱好者>输入(名称做受约束,并且在静态的,AKA编译时,时尚)也会以这种方式滥用这个术语。
答案 2 :(得分:13)
您没有指定类型。如果该方法尝试访问未在传入的参数上定义的属性,则该方法将失败(在运行时)。
所以这个简单的功能:
def no_op(param1, param2):
pass
......无论传递两个参数,都不会失败。
然而,这个功能:
def call_quack(param1, param2):
param1.quack()
param2.quack()
如果param1
和param2
都没有名为quack
的可调用属性,...将在运行时失败。
答案 3 :(得分:8)
许多语言都有变量,这些变量属于特定类型且具有值。 Python没有变量;它有对象,你使用名称来引用这些对象。
在其他语言中,当你说:
a = 1
然后一个(通常是整数)变量将其内容更改为值。
在Python中,
a = 1
表示“使用名称 a 来引用对象 1 ”。您可以在交互式Python会话中执行以下操作:
>>> type(1)
<type 'int'>
使用对象type
调用函数1
;由于每个对象都知道它的类型,type
很容易找到所述类型并将其返回。
同样,无论何时定义函数
def funcname(param1, param2):
该函数接收两个对象,并将它们命名为param1
和param2
,无论其类型如何。如果要确保收到的对象是特定类型,请将您的函数编码为所需类型,并捕获所引发的异常(如果不是)。抛出的异常通常为TypeError
(您使用了无效操作)和AttributeError
(您尝试访问不存在的成员(方法也是成员))。
答案 4 :(得分:6)
从静态或编译时类型检查的意义上来说,Python不是强类型的。
大多数Python代码属于所谓的"Duck Typing" - 例如,您在对象上查找方法read
- 您不关心对象是磁盘上的文件还是一个套接字,你只想从中读取N个字节。
答案 5 :(得分:5)
正常的,Pythonic,首选解决方案几乎总是“鸭子打字”:尝试使用该参数,就好像它是某种所需的类型一样,在try / except语句中执行它,捕获可能出现的所有异常,如果参数是实际上并不是那种类型(或任何其他类型很好地模仿它;-),并且在except子句中,尝试别的东西(使用参数“好像”它是某种其他类型)。
阅读其余帖子以获取有用的信息。
答案 6 :(得分:3)
Python并不关心你传递给它的函数。当你调用my_func(a,b)
时,param1和param2变量将保存a和b的值。 Python不知道你用正确的类型调用函数,并期望程序员处理它。如果使用不同类型的参数调用函数,则可以使用try / except块包装访问它们的代码,并以任何方式评估参数。
答案 7 :(得分:2)
你永远不会指明类型; Python有duck typing的概念;基本上处理参数的代码将对它们做出某些假设 - 可能通过调用参数预期实现的某些方法。如果参数类型错误,则抛出异常。
通常,您的代码可以确保传递正确类型的对象 - 没有编译器可以提前强制执行此操作。
答案 8 :(得分:2)
有一个臭名昭着的例外,在这个页面上值得一提的鸭子打字。
当dense_defaultindex = pd.DataFrame(np.zeros(len(SecondBins)),columns=['Binary'])
sparse_defaultindex = pd.DataFrame(np.zeros(len(SecondBins)),columns=['Binary']).to_sparse(fill_value=0)
pickle.dump(dense_defaultindex,open('dense_defaultindex.p','wb'))
pickle.dump(sparse_defaultindex,open('sparse_defaultindex.p','wb'))
函数调用str
类方法时,它会巧妙地解析它的类型:
__str__
好像Guido暗示我们在程序遇到意外类型时应该引发哪个例外。
答案 9 :(得分:1)
在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
答案 10 :(得分:1)
我在其他答案中没有看到这一点,所以我将其添加到底池中。
正如其他人所说,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 ++这样的静态语言的噩梦。
答案 11 :(得分:0)
要有效使用键入模块(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.
但是,您仍然可以使用int
,list
,dict
,...等类型名称。
答案 12 :(得分:0)
如果有人想指定变量类型,我已经实现了包装器。
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