标签归档:python-3.5

如何使用类型提示指定多种返回类型

问题:如何使用类型提示指定多种返回类型

我在python中有一个可以返回a bool或a 的函数list。有没有一种方法可以使用类型提示指定返回类型。

例如,这是正确的方法吗?

def foo(id) -> list or bool:
      ...

I have a function in python that can either return a bool or a list. Is there a way to specify the return types using type hints.

For example, Is this the correct way to do it?

def foo(id) -> list or bool:
      ...

回答 0

文档中

typing.Union

联合类型;Union [X,Y]表示X或Y。

因此,表示多个返回数据类型的正确方法是

from typing import Union


def foo(client_id: str) -> Union[list,bool]

但是请注意,不会强制键入。Python仍然是一种动态类型的语言。注释语法已被开发出来,可在代码发布到生产之前的开发过程中提供帮助。如PEP 484所述,“运行时不进行类型检查。”

>>> def foo(a:str) -> list:
...     return("Works")
... 
>>> foo(1)
'Works'

如您所见,我正在传递一个int值并返回一个str。但是,__annotations__将设置为各自的值。

>>> foo.__annotations__ 
{'return': <class 'list'>, 'a': <class 'str'>}

请通过PEP 483了解有关类型提示的更多信息。另请参见Python 3.5中的类型提示是什么?

请注意,这仅适用于Python的3.5及以上。PEP 484中明确提到了这一点。

From the documentation

class typing.Union

Union type; Union[X, Y] means either X or Y.

Hence the proper way to represent more than one return data type is

from typing import Union


def foo(client_id: str) -> Union[list,bool]

But do note that typing is not enforced. Python continues to remain a dynamically-typed language. The annotation syntax has been developed to help during the development of the code prior to being released into production. As PEP 484 states, “no type checking happens at runtime.”

>>> def foo(a:str) -> list:
...     return("Works")
... 
>>> foo(1)
'Works'

As you can see I am passing a int value and returning a str. However the __annotations__ will be set to the respective values.

>>> foo.__annotations__ 
{'return': <class 'list'>, 'a': <class 'str'>}

Please Go through PEP 483 for more about Type hints. Also see What are Type hints in Python 3.5?

Kindly note that this is available only for Python 3.5 and upwards. This is mentioned clearly in PEP 484.


回答 1

该语句def foo(client_id: str) -> list or bool:在被求值时等效于 def foo(client_id: str) -> list:并且将不会执行您想要的操作。

描述“ A或B”类型提示的本机方法是Union(由于Bhargav Rao):

def foo(client_id: str) -> Union[list, bool]:

我不想成为“您为什么仍要这样做”的家伙,但是也许您不想要2种返回类型:

如果要返回布尔值以指示某种特殊的错误情况,请考虑改用Exceptions。如果您要返回布尔值作为某些特殊值,则空列表可能是一个很好的表示。您还可以指出None可以返回Optional[list]

The statement def foo(client_id: str) -> list or bool: when evaluated is equivalent to def foo(client_id: str) -> list: and will therefore not do what you want.

The native way to describe a “either A or B” type hint is Union (thanks to Bhargav Rao):

def foo(client_id: str) -> Union[list, bool]:

I do not want to be the “Why do you want to do this anyway” guy, but maybe having 2 return types isn’t what you want:

If you want to return a bool to indicate some type of special error-case, consider using Exceptions instead. If you want to return a bool as some special value, maybe an empty list would be a good representation. You can also indicate that None could be returned with Optional[list]


回答 2

如果有人登陆这里搜索“如何指定多个返回值的类型?”,请使用 Tuple[type_value1, ..., type_valueN]

from typing import Tuple

def f() -> Tuple[dict, str]:
    a = {1: 2}
    b = "hello"
    return a, b

更多信息:https//code-examples.net/en/q/2651e60

In case anyone landed here in search of “how to specify types of multiple return values?”, use Tuple[type_value1, ..., type_valueN]

from typing import Tuple

def f() -> Tuple[dict, str]:
    a = {1: 2}
    b = "hello"
    return a, b

More info: https://code-examples.net/en/q/2651e60


如何使用类型提示指定“可空”返回类型

问题:如何使用类型提示指定“可空”返回类型

假设我有一个函数:

def get_some_date(some_argument: int=None) -> %datetime_or_None%:
    if some_argument is not None and some_argument == 1:
        return datetime.utcnow()
    else:
        return None

如何为可以指定的东西指定返回类型None

Suppose I have a function:

def get_some_date(some_argument: int=None) -> %datetime_or_None%:
    if some_argument is not None and some_argument == 1:
        return datetime.utcnow()
    else:
        return None

How do I specify the return type for something that can be None?


回答 0

您正在寻找Optional

由于您的返回类型可以是datetime(从返回datetime.utcnow()),None也应该使用Optional[datetime]

from typing import Optional

def get_some_date(some_argument: int=None) -> Optional[datetime]:
    # as defined

在有关打字的文档中,Optional是以下内容的简写:

Optional[X]等同于Union[X, None]

其中Union[X, Y]表示类型X或的值Y


如果由于担心其他人可能偶然发现Optional而没有意识到它的含义而希望变得明确,则可以始终使用Union

from typing import Union

def get_some_date(some_argument: int=None) -> Union[datetime, None]:

但是我怀疑这是一个好主意,Optional是一个指示性名称,并且确实节省了几次击键。

正如@ Michael0x2a的注释中指出的那样,Union[T, None]已转换为,Union[T, type(None)]因此无需在type此处使用。

在视觉上,这两种方法可能有所不同,但在编程上,结果是完全相同的Union[datetime.datetime, NoneType]将是存储在get_some_date.__annotations__*中的类型:

>>> from typing import get_type_hints
>>> print(get_type_hints(get_some_date))
{'return': typing.Union[datetime.datetime, NoneType],
 'some_argument': typing.Union[int, NoneType]}

*typing.get_type_hints抢的对象的__annotations__直接访问它的属性来代替。

You’re looking for Optional.

Since your return type can either be datetime (as returned from datetime.utcnow()) or None you should use Optional[datetime]:

from typing import Optional

def get_some_date(some_argument: int=None) -> Optional[datetime]:
    # as defined

From the documentation on typing, Optional is shorthand for:

Optional[X] is equivalent to Union[X, None].

where Union[X, Y] means a value of type X or Y.


If you want to be explicit due to concerns that others might stumble on Optional and not realize it’s meaning, you could always use Union:

from typing import Union

def get_some_date(some_argument: int=None) -> Union[datetime, None]:

But I doubt this is a good idea, Optional is an indicative name and it does save a couple of keystrokes.

As pointed out in the comments by @Michael0x2a Union[T, None] is tranformed to Union[T, type(None)] so no need to use type here.

Visually these might differ but programatically, in both cases, the result is exactly the same; Union[datetime.datetime, NoneType] will be the type stored in get_some_date.__annotations__*:

>>> from typing import get_type_hints
>>> print(get_type_hints(get_some_date))
{'return': typing.Union[datetime.datetime, NoneType],
 'some_argument': typing.Union[int, NoneType]}

*Use typing.get_type_hints to grab the objects’ __annotations__ attribute instead of accessing it directly.


熊猫:如何对单个列使用apply()函数?

问题:熊猫:如何对单个列使用apply()函数?

我有两列的熊猫数据框。我需要在不影响第二列的情况下更改第一列的值,并只更改第一列的值即可获取整个数据帧。我该如何使用大熊猫应用程序?

I have a pandas data frame with two columns. I need to change the values of the first column without affecting the second one and get back the whole data frame with just first column values changed. How can I do that using apply in pandas?


回答 0

给定一个示例数据框df为:

a,b
1,2
2,3
3,4
4,5

您想要的是:

df['a'] = df['a'].apply(lambda x: x + 1)

返回:

   a  b
0  2  2
1  3  3
2  4  4
3  5  5

Given a sample dataframe df as:

a,b
1,2
2,3
3,4
4,5

what you want is:

df['a'] = df['a'].apply(lambda x: x + 1)

that returns:

   a  b
0  2  2
1  3  3
2  4  4
3  5  5

回答 1

对于更好使用的单列map(),像这样:

df = pd.DataFrame([{'a': 15, 'b': 15, 'c': 5}, {'a': 20, 'b': 10, 'c': 7}, {'a': 25, 'b': 30, 'c': 9}])

    a   b  c
0  15  15  5
1  20  10  7
2  25  30  9



df['a'] = df['a'].map(lambda a: a / 2.)

      a   b  c
0   7.5  15  5
1  10.0  10  7
2  12.5  30  9

For a single column better to use map(), like this:

df = pd.DataFrame([{'a': 15, 'b': 15, 'c': 5}, {'a': 20, 'b': 10, 'c': 7}, {'a': 25, 'b': 30, 'c': 9}])

    a   b  c
0  15  15  5
1  20  10  7
2  25  30  9



df['a'] = df['a'].map(lambda a: a / 2.)

      a   b  c
0   7.5  15  5
1  10.0  10  7
2  12.5  30  9

回答 2

您根本不需要功能。您可以直接处理整个列。

示例数据:

>>> df = pd.DataFrame({'a': [100, 1000], 'b': [200, 2000], 'c': [300, 3000]})
>>> df

      a     b     c
0   100   200   300
1  1000  2000  3000

列中所有值的一半a

>>> df.a = df.a / 2
>>> df

     a     b     c
0   50   200   300
1  500  2000  3000

You don’t need a function at all. You can work on a whole column directly.

Example data:

>>> df = pd.DataFrame({'a': [100, 1000], 'b': [200, 2000], 'c': [300, 3000]})
>>> df

      a     b     c
0   100   200   300
1  1000  2000  3000

Half all the values in column a:

>>> df.a = df.a / 2
>>> df

     a     b     c
0   50   200   300
1  500  2000  3000

回答 3

尽管给定的响应是正确的,但是它们修改了初始数据帧,这并不总是令人满意的(并且,如果OP要求示例“使用apply”,那么他们可能想要一个返回新数据帧的版本,就像apply这样)。

可以使用assign:这可能assign对现有列有效,因为文档指出(重点是我的):

将新列分配给DataFrame。

返回一个新对象,该对象具有除新列之外的所有原始列。重新分配的现有列将被覆盖

简而言之:

In [1]: import pandas as pd

In [2]: df = pd.DataFrame([{'a': 15, 'b': 15, 'c': 5}, {'a': 20, 'b': 10, 'c': 7}, {'a': 25, 'b': 30, 'c': 9}])

In [3]: df.assign(a=lambda df: df.a / 2)
Out[3]: 
      a   b  c
0   7.5  15  5
1  10.0  10  7
2  12.5  30  9

In [4]: df
Out[4]: 
    a   b  c
0  15  15  5
1  20  10  7
2  25  30  9

请注意,该函数将传递给整个数据框,而不仅是要修改的列,因此您需要确保在lambda中选择正确的列。

Although the given responses are correct, they modify the initial data frame, which is not always desirable (and, given the OP asked for examples “using apply“, it might be they wanted a version that returns a new data frame, as apply does).

This is possible using assign: it is valid to assign to existing columns, as the documentation states (emphasis is mine):

Assign new columns to a DataFrame.

Returns a new object with all original columns in addition to new ones. Existing columns that are re-assigned will be overwritten.

In short:

In [1]: import pandas as pd

In [2]: df = pd.DataFrame([{'a': 15, 'b': 15, 'c': 5}, {'a': 20, 'b': 10, 'c': 7}, {'a': 25, 'b': 30, 'c': 9}])

In [3]: df.assign(a=lambda df: df.a / 2)
Out[3]: 
      a   b  c
0   7.5  15  5
1  10.0  10  7
2  12.5  30  9

In [4]: df
Out[4]: 
    a   b  c
0  15  15  5
1  20  10  7
2  25  30  9

Note that the function will be passed the whole dataframe, not only the column you want to modify, so you will need to make sure you select the right column in your lambda.


回答 4

如果您真的很关心apply函数的执行速度,并且有庞大的数据集需要处理,则可以使用swifter加快执行速度,以下是在swifter上实现pandas数据框的示例:

import pandas as pd
import swifter

def fnc(m):
    return m*3+4

df = pd.DataFrame({"m": [1,2,3,4,5,6], "c": [1,1,1,1,1,1], "x":[5,3,6,2,6,1]})

# apply a self created function to a single column in pandas
df["y"] = df.m.swifter.apply(fnc)

这将使您所有的CPU内核都能计算结果,因此比正常的应用功能要快得多。尝试让我知道它是否对您有用。

If you are really concerned about the execution speed of your apply function and you have a huge dataset to work on, you could use swifter to make faster execution, here is an example for swifter on pandas dataframe:

import pandas as pd
import swifter

def fnc(m):
    return m*3+4

df = pd.DataFrame({"m": [1,2,3,4,5,6], "c": [1,1,1,1,1,1], "x":[5,3,6,2,6,1]})

# apply a self created function to a single column in pandas
df["y"] = df.m.swifter.apply(fnc)

This will enable your all CPU cores to compute the result hence it will be much faster than normal apply functions. Try and let me know if it become useful for you.


回答 5

让我尝试使用日期时间并考虑空值或空白的复杂计算。我正在减少30年的datetime列,并使用apply方法以及lambda转换datetime格式。Line if x != '' else x将照顾所有空白或相应的空值。

df['Date'] = df['Date'].fillna('')
df['Date'] = df['Date'].apply(lambda x : ((datetime.datetime.strptime(str(x), '%m/%d/%Y') - datetime.timedelta(days=30*365)).strftime('%Y%m%d')) if x != '' else x)

Let me try a complex computation using datetime and considering nulls or empty spaces. I am reducing 30 years on a datetime column and using apply method as well as lambda and converting datetime format. Line if x != '' else x will take care of all empty spaces or nulls accordingly.

df['Date'] = df['Date'].fillna('')
df['Date'] = df['Date'].apply(lambda x : ((datetime.datetime.strptime(str(x), '%m/%d/%Y') - datetime.timedelta(days=30*365)).strftime('%Y%m%d')) if x != '' else x)

Python 3.5中的类型提示是什么?

问题:Python 3.5中的类型提示是什么?

类型提示是Python 3.5中讨论最多的功能之一。

的一个例子类型提示中提到的这篇文章这一个,同时还提负责任地使用类型提示。有人可以解释它们的更多信息,何时使用它们,何时不使用?

One of the most talked about features in Python 3.5 is type hints.

An example of type hints is mentioned in this article and this one while also mentioning to use type hints responsibly. Can someone explain more about them and when they should be used and when not?


回答 0

我建议阅读PEP 483PEP 484和观看演示由Guido的类型提示。

简而言之类型提示从字面上看是什么意思,您可以提示所使用的对象的类型

由于Python 的动态特性,推断或检查所使用对象的类型特别困难。这个事实使开发人员很难理解他们尚未编写的代码中到底发生了什么,而且最重要的是,对于许多IDE所使用的类型检查工具[PyCharm,PyDev想到],由于以下事实而受到限制:他们没有任何指示物是什么类型的指标。结果,他们求助于推断类型(如演示中所述),成功率约为50%。


摘录“类型提示”演示文稿中的两个重要幻灯片:

为什么要输入提示?

  1. 帮助类型检查器通过提示您希望对象成为哪种类型,例如,类型检查器可以轻松检测是否要传递的对象类型不是预期的类型。
  2. 帮助提供文档:查看您的代码的第三方将知道预期的用途,遍历,如何使用它而无需获取它们TypeErrors
  3. 帮助IDE开发更准确和健壮的工具:当知道对象的类型时,开发环境将更适合建议合适的方法。您可能曾经在某个IDE上遇到过这种情况,点击.并弹出未为对象定义的方法/属性。

为什么要使用静态类型检查器?

  • 尽早发现错误:我相信,这是不言而喻的。
  • 项目越大,就越需要:同样,这很有意义。静态语言提供了动态语言所缺乏的鲁棒性和控制力。您的应用程序越大,越复杂,就需要越多的控制和可预测性(从行为方面)。
  • 大型团队已经在进行静态分析:我猜这可以验证前两点。

作为这个小介绍的结束语:这是可选的功能,据我了解,已引入该功能是为了获得静态类型化的某些好处。

通常您无需担心它,并且绝对不需要使用它(尤其是在将Python用作辅助脚本语言的情况下)。在开发大型项目时,它应该会很有帮助,因为它提供了非常需要的鲁棒性,控制和其他调试功能


用mypy输入提示

为了使这个答案更完整,我认为稍作示范是合适的。我将使用mypy,它启发了PEP中介绍的Type Hints的库。这主要是为任何遇到此问题并想从哪里开始的人编写的。

在此之前,我要重申以下内容:PEP 484不执行任何操作;它只是为功能注释设置方向,并就如何 /应该执行类型检查。您可以注释功能,并根据需要提示任意多的内容。无论注释是否存在,您的脚本仍将运行,因为Python本身不使用它们。

无论如何,如PEP中所述,提示类型通常应采用三种形式:

此外,您将要结合使用类型提示和中typing引入的新模块Py3.5。其中定义了许多(附加)ABC(抽象基类)以及用于静态检查的辅助功能和装饰器。包含大多数内容ABCscollections.abcGeneric以允许订阅的形式(通过定义__getitem__()方法)。

对于那些对这些内容有更深入说明的人来说,mypy documentation它的编写非常好,并且有许多代码示例演示/描述了其检查器的功能。绝对值得一读。

功能注释和特殊注释:

首先,观察使用特殊注释时可以得到的某些行为很有趣。特别# type: type如果不能直接推断出对象的类型,则可以在变量分配期间添加注释以指示对象的类型。通常可以轻松推断出简单的任务,但是其他任务,例如列表(就其内容而言)则不能。

注意:如果要使用的任何派生形式Containers并需要为该容器指定内容,则必须使用模块中的泛型类型typing这些支持索引。

# generic List, supports indexing.
from typing import List

# In this case, the type is easily inferred as type: int.
i = 0

# Even though the type can be inferred as of type list
# there is no way to know the contents of this list.
# By using type: List[str] we indicate we want to use a list of strings.
a = []  # type: List[str]

# Appending an int to our list
# is statically not correct.
a.append(i)

# Appending a string is fine.
a.append("i")

print(a)  # [0, 'i']

如果我们将这些命令添加到文件中并使用解释器执行它们,则一切工作正常,print(a)仅打印list的内容a。这些# type注释已被丢弃,被视为无附加语义含义的普通注释

mypy另一方面,通过运行,我们得到以下响应:

(Python3)jimmi@jim: mypy typeHintsCode.py
typesInline.py:14: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "str"

表示str对象列表不能包含int,从静态上讲是声音。可以通过遵循a和仅添加str对象的类型来解决此问题,也可以通过更改内容的类型a来表明任何值都是可接受的(直观地用List[Any]after Any导入from来执行typing)来解决此问题。

param_name : type在函数签名中的每个参数之后,以形式添加函数注释,并在-> type结束函数冒号之前使用表示法指定返回类型;所有注释都__annotations__以方便的字典形式存储在该函数的属性中。使用一个简单的示例(不需要typing模块中的其他类型):

def annotated(x: int, y: str) -> bool:
    return x < y

annotated.__annotations__属性现在具有以下值:

{'y': <class 'str'>, 'return': <class 'bool'>, 'x': <class 'int'>}

如果我们是一个完整的noobie,或者我们熟悉Py2.7概念并且因此不知道TypeError在比较中存在潜伏性annotated,那么我们可以执行另一项静态检查,捕获错误并为我们节省一些麻烦:

(Python3)jimmi@jim: mypy typeHintsCode.py
typeFunction.py: note: In function "annotated":
typeFunction.py:2: error: Unsupported operand types for > ("str" and "int")

除其他外,使用无效参数调用该函数也将被捕获:

annotated(20, 20)

# mypy complains:
typeHintsCode.py:4: error: Argument 2 to "annotated" has incompatible type "int"; expected "str"

这些基本可以扩展到任何用例,并且捕获的错误比基本调用和操作要扩展的更多。您可以检查的类型非常灵活,我只是对其潜能进行了简要介绍。通过查看typing模块,PEP或mypy文档,您将对所提供的功能有更全面的了解。

存根文件:

存根文件可以在两种不同的非互斥情况下使用:

  • 您需要键入检查您不想直接更改功能签名的模块
  • 您想编写模块并进行类型检查,但又希望将注释与内容分开。

存根文件(扩展名为.pyi)是您正在/将要使用的模块的带注释的接口。它们包含要键入的功能的签名,并丢弃了这些功能的主体。为了了解这一点,给定名为模块的三个随机函数randfunc.py

def message(s):
    print(s)

def alterContents(myIterable):
    return [i for i in myIterable if i % 2 == 0]

def combine(messageFunc, itFunc):
    messageFunc("Printing the Iterable")
    a = alterContents(range(1, 20))
    return set(a)

我们可以创建一个存根文件randfunc.pyi,如果需要的话,可以在其中放置一些限制。不利的一面是,在不了解存根的情况下查看源代码的人在试图理解应该传递到何处时将不会真正获得注释帮助。

无论如何,存根文件的结构都非常简单:添加带有空主体(pass填充)的所有函数定义,并根据您的要求提供注释。在这里,假设我们只想使用int容器的类型。

# Stub for randfucn.py
from typing import Iterable, List, Set, Callable

def message(s: str) -> None: pass

def alterContents(myIterable: Iterable[int])-> List[int]: pass

def combine(
    messageFunc: Callable[[str], Any],
    itFunc: Callable[[Iterable[int]], List[int]]
)-> Set[int]: pass

combine函数提供了一个指示,说明为什么您可能要在其他文件中使用批注,它们有时会使代码混乱,并降低可读性(对于Python来说,很大的缺点)。您当然可以使用类型别名,但有时会比其有用之处更加混乱(因此请明智地使用它们)。


这应该使您熟悉Python中的类型提示的基本概念。即使使用了类型检查器, mypy您也应该逐渐开始看到它们的更多弹出窗口,其中一些内部是IDE(PyCharm),其他是标准python模块。当我发现它们(或建议)时,我将尝试在以下列表中添加其他检查器/相关程序包。

我知道的跳棋

  • Mypy:如此处所述。
  • PyType:由Google使用,与我收集到的符号不同,可能值得一看。

相关软件包/项目

  • 类型:官方Python回购,其中包含标准库的各种存根文件。

typeshed实际上,该项目是您可以查看的最佳场所之一,以了解如何在自己的项目中使用类型提示。让我们以相应文件中该类__init__笨拙Counter为例.pyi

class Counter(Dict[_T, int], Generic[_T]):
        @overload
        def __init__(self) -> None: ...
        @overload
        def __init__(self, Mapping: Mapping[_T, int]) -> None: ...
        @overload
        def __init__(self, iterable: Iterable[_T]) -> None: ...

在哪里_T = TypeVar('_T')用来定义泛型类。对于Counter该类,我们可以看到它可以在其初始值设定项中不接受任何参数,可以将Mapping任何类型的单个值获取为an,int 可以采用Iterable任何类型的。


注意:我忘记提及的一件事是该typing模块是临时引入的。从PEP 411

临时包可以在“升级”为“稳定”状态之前对其API进行修改。一方面,这种状态为软件包提供了正式加入Python发行版的好处。另一方面,核心开发团队明确声明,对于包API的稳定性没有任何保证,在下一发行版中可能会更改。虽然这被认为是不太可能的结果,但是如果对它们的API或维护的担心被证明是有根据的,那么甚至可以在不弃用期限的情况下从标准库中删除此类软件包。

所以在这里放些盐。我怀疑它将以重大方式被删除或更改,但是人们永远不会知道。


**另一个主题,但在类型提示的范围内完全有效PEP 526::变量注释的语法# type通过引入新的语法来替换注释的工作,该语法允许用户在简单的varname: type语句中注释变量的类型。

请参阅什么是Python 3.6中的变量注释?,如前所述,有关这些内容的小介绍。

I would suggest reading PEP 483 and PEP 484 and watching this presentation by Guido on Type Hinting.

In a nutshell: Type hinting is literally what the words mean, you hint the type of the object(s) you’re using.

Due to the dynamic nature of Python, inferring or checking the type of an object being used is especially hard. This fact makes it hard for developers to understand what exactly is going on in code they haven’t written and, most importantly, for type checking tools found in many IDEs [PyCharm, PyDev come to mind] that are limited due to the fact that they don’t have any indicator of what type the objects are. As a result they resort to trying to infer the type with (as mentioned in the presentation) around 50% success rate.


To take two important slides from the Type Hinting presentation:

Why Type Hints?

  1. Helps Type Checkers: By hinting at what type you want the object to be the type checker can easily detect if, for instance, you’re passing an object with a type that isn’t expected.
  2. Helps with documentation: A third person viewing your code will know what is expected where, ergo, how to use it without getting them TypeErrors.
  3. Helps IDEs develop more accurate and robust tools: Development Environments will be better suited at suggesting appropriate methods when know what type your object is. You have probably experienced this with some IDE at some point, hitting the . and having methods/attributes pop up which aren’t defined for an object.

Why use Static Type Checkers?

  • Find bugs sooner: This is self evident, I believe.
  • The larger your project the more you need it: Again, makes sense. Static languages offer a robustness and control that dynamic languages lack. The bigger and more complex your application becomes the more control and predictability (from a behavioral aspect) you require.
  • Large teams are already running static analysis: I’m guessing this verifies the first two points.

As a closing note for this small introduction: This is an optional feature and, from what I understand, it has been introduced in order to reap some of the benefits of static typing.

You generally do not need to worry about it and definitely don’t need to use it (especially in cases where you use Python as an auxiliary scripting language). It should be helpful when developing large projects as it offers much needed robustness, control and additional debugging capabilities.


Type Hinting with mypy:

In order to make this answer more complete, I think a little demonstration would be suitable. I’ll be using mypy, the library which inspired Type Hints as they are presented in the PEP. This is mainly written for anybody bumping into this question and wondering where to begin.

Before I do that let me reiterate the following: PEP 484 doesn’t enforce anything; it is simply setting a direction for function annotations and proposing guidelines for how type checking can/should be performed. You can annotate your functions and hint as many things as you want; your scripts will still run regardless of the presence of annotations because Python itself doesn’t use them.

Anyways, as noted in the PEP, hinting types should generally take three forms:

Additionally, you’ll want to use type hints in conjunction with the new typing module introduced in Py3.5. In it, many (additional) ABCs (Abstract Base Classes) are defined along with helper functions and decorators for use in static checking. Most ABCs in collections.abc are included but in a Generic form in order to allow subscription (by defining a __getitem__() method).

For anyone interested in a more in-depth explanation of these, the mypy documentation is written very nicely and has a lot of code samples demonstrating/describing the functionality of their checker; it is definitely worth a read.

Function annotations and special comments:

First, it’s interesting to observe some of the behavior we can get when using special comments. Special # type: type comments can be added during variable assignments to indicate the type of an object if one cannot be directly inferred. Simple assignments are generally easily inferred but others, like lists (with regard to their contents), cannot.

Note: If we want to use any derivative of Containers and need to specify the contents for that container we must use the generic types from the typing module. These support indexing.

# generic List, supports indexing.
from typing import List

# In this case, the type is easily inferred as type: int.
i = 0

# Even though the type can be inferred as of type list
# there is no way to know the contents of this list.
# By using type: List[str] we indicate we want to use a list of strings.
a = []  # type: List[str]

# Appending an int to our list
# is statically not correct.
a.append(i)

# Appending a string is fine.
a.append("i")

print(a)  # [0, 'i']

If we add these commands to a file and execute them with our interpreter, everything works just fine and print(a) just prints the contents of list a. The # type comments have been discarded, treated as plain comments which have no additional semantic meaning.

By running this with mypy, on the other hand, we get the following responce:

(Python3)jimmi@jim: mypy typeHintsCode.py
typesInline.py:14: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "str"

Indicating that a list of str objects cannot contain an int, which, statically speaking, is sound. This can be fixed by either abiding to the type of a and only appending str objects or by changing the type of the contents of a to indicate that any value is acceptable (Intuitively performed with List[Any] after Any has been imported from typing).

Function annotations are added in the form param_name : type after each parameter in your function signature and a return type is specified using the -> type notation before the ending function colon; all annotations are stored in the __annotations__ attribute for that function in a handy dictionary form. Using a trivial example (which doesn’t require extra types from the typing module):

def annotated(x: int, y: str) -> bool:
    return x < y

The annotated.__annotations__ attribute now has the following values:

{'y': <class 'str'>, 'return': <class 'bool'>, 'x': <class 'int'>}

If we’re a complete noobie, or we are familiar with Py2.7 concepts and are consequently unaware of the TypeError lurking in the comparison of annotated, we can perform another static check, catch the error and save us some trouble:

(Python3)jimmi@jim: mypy typeHintsCode.py
typeFunction.py: note: In function "annotated":
typeFunction.py:2: error: Unsupported operand types for > ("str" and "int")

Among other things, calling the function with invalid arguments will also get caught:

annotated(20, 20)

# mypy complains:
typeHintsCode.py:4: error: Argument 2 to "annotated" has incompatible type "int"; expected "str"

These can be extended to basically any use-case and the errors caught extend further than basic calls and operations. The types you can check for are really flexible and I have merely given a small sneak peak of its potential. A look in the typing module, the PEPs or the mypy docs will give you a more comprehensive idea of the capabilities offered.

Stub Files:

Stub files can be used in two different non mutually exclusive cases:

  • You need to type check a module for which you do not want to directly alter the function signatures
  • You want to write modules and have type-checking but additionally want to separate annotations from content.

What stub files (with an extension of .pyi) are is an annotated interface of the module you are making/want to use. They contain the signatures of the functions you want to type-check with the body of the functions discarded. To get a feel of this, given a set of three random functions in a module named randfunc.py:

def message(s):
    print(s)

def alterContents(myIterable):
    return [i for i in myIterable if i % 2 == 0]

def combine(messageFunc, itFunc):
    messageFunc("Printing the Iterable")
    a = alterContents(range(1, 20))
    return set(a)

We can create a stub file randfunc.pyi, in which we can place some restrictions if we wish to do so. The downside is that somebody viewing the source without the stub won’t really get that annotation assistance when trying to understand what is supposed to be passed where.

Anyway, the structure of a stub file is pretty simplistic: Add all function definitions with empty bodies (pass filled) and supply the annotations based on your requirements. Here, let’s assume we only want to work with int types for our Containers.

# Stub for randfucn.py
from typing import Iterable, List, Set, Callable

def message(s: str) -> None: pass

def alterContents(myIterable: Iterable[int])-> List[int]: pass

def combine(
    messageFunc: Callable[[str], Any],
    itFunc: Callable[[Iterable[int]], List[int]]
)-> Set[int]: pass

The combine function gives an indication of why you might want to use annotations in a different file, they some times clutter up the code and reduce readability (big no-no for Python). You could of course use type aliases but that sometime confuses more than it helps (so use them wisely).


This should get you familiarized with the basic concepts of Type Hints in Python. Even though the type checker used has been mypy you should gradually start to see more of them pop-up, some internally in IDEs (PyCharm,) and others as standard python modules. I’ll try and add additional checkers/related packages in the following list when and if I find them (or if suggested).

Checkers I know of:

  • Mypy: as described here.
  • PyType: By Google, uses different notation from what I gather, probably worth a look.

Related Packages/Projects:

  • typeshed: Official Python repo housing an assortment of stub files for the standard library.

The typeshed project is actually one of the best places you can look to see how type hinting might be used in a project of your own. Let’s take as an example the __init__ dunders of the Counter class in the corresponding .pyi file:

class Counter(Dict[_T, int], Generic[_T]):
        @overload
        def __init__(self) -> None: ...
        @overload
        def __init__(self, Mapping: Mapping[_T, int]) -> None: ...
        @overload
        def __init__(self, iterable: Iterable[_T]) -> None: ...

Where _T = TypeVar('_T') is used to define generic classes. For the Counter class we can see that it can either take no arguments in its initializer, get a single Mapping from any type to an int or take an Iterable of any type.


Notice: One thing I forgot to mention was that the typing module has been introduced on a provisional basis. From PEP 411:

A provisional package may have its API modified prior to “graduating” into a “stable” state. On one hand, this state provides the package with the benefits of being formally part of the Python distribution. On the other hand, the core development team explicitly states that no promises are made with regards to the the stability of the package’s API, which may change for the next release. While it is considered an unlikely outcome, such packages may even be removed from the standard library without a deprecation period if the concerns regarding their API or maintenance prove well-founded.

So take things here with a pinch of salt; I’m doubtfull it will be removed or altered in significant ways but one can never know.


** Another topic altogether but valid in the scope of type-hints: PEP 526: Syntax for Variable Annotations is an effort to replace # type comments by introducing new syntax which allows users to annotate the type of variables in simple varname: type statements.

See What are variable annotations in Python 3.6?, as previously mentioned, for a small intro on these.


回答 1

添加到吉姆精心设计的答案中:

检查typing模块 -该模块支持PEP 484指定的类型提示。

例如,下面的函数采用并返回type的值,str并且其注释如下:

def greeting(name: str) -> str:
    return 'Hello ' + name

typing模块还支持:

  1. 输入别名
  2. 回调函数的类型提示。
  3. 泛型 -抽象基类已扩展为支持预订,以表示容器元素的预期类型。
  4. 用户定义的通用类型 -用户定义的类可以定义为通用类。
  5. 任何类型 -每个类型都是Any的子类型。

Adding to Jim’s elaborate answer:

Check the typing module — this module supports type hints as specified by PEP 484.

For example, the function below takes and returns values of type str and is annotated as follows:

def greeting(name: str) -> str:
    return 'Hello ' + name

The typing module also supports:

  1. Type aliasing.
  2. Type hinting for callback functions.
  3. Generics – Abstract base classes have been extended to support subscription to denote expected types for container elements.
  4. User-defined generic types – A user-defined class can be defined as a generic class.
  5. Any type – Every type is a subtype of Any.

回答 2

新发布的PyCharm 5支持类型提示。在有关它的博客文章中(请参阅PyCharm 5中的Python 3.5类型提示),他们提供了关于什么是类型提示和不存在类型提示的很好的解释,并提供了一些示例和插图说明如何在代码中使用它们。

此外,Python 2.7支持它,如以下注释中所述

PyCharm支持来自PyPI的用于Python 2.7,Python 3.2-3.4的键入模块。对于2.7,您必须在* .pyi存根文件中放入类型提示,因为函数注释是在Python 3.0中添加的

The newly released PyCharm 5 supports type hinting. In their blog post about it (see Python 3.5 type hinting in PyCharm 5) they offer a great explanation of what type hints are and aren’t along with several examples and illustrations for how to use them in your code.

Additionally, it is supported in Python 2.7, as explained in this comment:

PyCharm supports the typing module from PyPI for Python 2.7, Python 3.2-3.4. For 2.7 you have to put type hints in *.pyi stub files since function annotations were added in Python 3.0.


回答 3

类型提示是对动态语言的最新补充,数十年来人们一直宣誓像匈牙利语一样简单的命名约定(对象标签的首字母为b =布尔语,c =字符,d =字典,i =整数,l =列表,n =数字,s =字符串,t =元组),太麻烦了,但是现在决定了,哦,等等……使用语言(type())来识别对象和我们花哨的IDE实在是太麻烦了需要帮助来完成任何复杂的事情,并且动态分配的对象值使它们无论如何都变得毫无用处,而对于任何开发人员而言,一个简单的命名约定就可以解决所有问题。

Type hint are a recent addition to a dynamic language where for decades folks swore naming conventions as simple as Hungarian (object label with first letter b = boolian, c = character, d = dictionary, i = integer, l = list, n = numeric, s = string, t= tuple) were not needed, too cumbersome, but now have decided that, oh wait … it is way too much trouble to use the language (type()) to recognize objects, and our fancy IDEs need help doing anything that complicated, and that dynamically assigned object values make them completely useless anyhow, whereas a simple naming convention could have resolved all of it, for any developer, at a mere glance.


回答 4

类型提示是为了可维护性,不会被Python解释。在下面的代码中,该行def add(self, ic:int)直到下一return...行才导致错误:

class C1:
    def __init__(self):
        self.idn = 1
    def add(self, ic: int):
        return self.idn + ic
    
c1 = C1()
c1.add(2)

c1.add(c1)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 5, in add
TypeError: unsupported operand type(s) for +: 'int' and 'C1'
 

Type-hints are for maintainability and don’t get interpreted by Python. In the code below, the line def add(self, ic:int) doesn’t result in an error until the next return... line:

class C1:
    def __init__(self):
        self.idn = 1
    def add(self, ic: int):
        return self.idn + ic
    
c1 = C1()
c1.add(2)

c1.add(c1)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 5, in add
TypeError: unsupported operand type(s) for +: 'int' and 'C1'
 

如何指定方法的返回类型与类本身相同?

问题:如何指定方法的返回类型与类本身相同?

我在python 3中有以下代码:

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: Position) -> Position:
        return Position(self.x + other.x, self.y + other.y)

但是我的编辑器(PyCharm)说,参考位置无法解析(在__add__方法中)。我应该如何指定期望返回类型为type Position

编辑:我认为这实际上是一个PyCharm问题。它实际上在警告和代码完成中使用该信息

但如果我错了,请纠正我,并需要使用其他语法。

I have the following code in python 3:

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: Position) -> Position:
        return Position(self.x + other.x, self.y + other.y)

But my editor (PyCharm) says that the reference Position can not be resolved (in the __add__ method). How should I specify that I expect the return type to be of type Position?

Edit: I think this is actually a PyCharm issue. It actually uses the information in its warnings, and code completion

But correct me if I’m wrong, and need to use some other syntax.


回答 0

TL; DR:如果您使用的是Python 4.0,它将正常工作。从今天(2019年)开始,在3.7+中,您必须使用将来的语句(from __future__ import annotations)启用此功能-对于Python 3.6或更低版本,请使用字符串。

我猜你有这个exceptions:

NameError: name 'Position' is not defined

这是因为Position必须先定义,然后才能在批注中使用它,除非您正在使用Python 4。

Python 3.7+: from __future__ import annotations

Python 3.7引入了PEP 563:推迟对注释的评估。使用future语句的模块from __future__ import annotations将自动将注释存储为字符串:

from __future__ import annotations

class Position:
    def __add__(self, other: Position) -> Position:
        ...

按计划,它将成为Python 4.0中的默认设置。由于Python仍然是一种动态类型化的语言,因此在运行时不进行类型检查,因此键入注释应该不会对性能产生影响,对吗?错误!在python 3.7之前,键入模块曾经是内核中最慢的python模块之一,因此,如果升级到3.7,import typing您将看到性能提高多达7倍

Python <3.7:使用字符串

根据PEP 484,您应该使用字符串而不是类本身:

class Position:
    ...
    def __add__(self, other: 'Position') -> 'Position':
       ...

如果您使用Django框架,可能会很熟悉,因为Django模型还将字符串用于正向引用(外键模型已self声明或尚未声明的外键定义)。这应该与Pycharm和其他工具一起使用。

资料来源

PEP 484PEP 563的相关部分,为您节省行程:

转发参考

当类型提示包含尚未定义的名称时,该定义可以表示为字符串文字,稍后再解析。

通常会发生这种情况的情况是容器类的定义,其中定义的类出现在某些方法的签名中。例如,以下代码(简单的二叉树实现的开始)不起作用:

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

为了解决这个问题,我们写:

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

字符串文字应包含有效的Python表达式(即,compile(lit,”,’eval’)应为有效的代码对象),并且在模块完全加载后,其值应无错误。在其中评估本地和全局命名空间的命名空间应与在其中评估同一函数的默认参数的命名空间相同。

和PEP 563:

在Python 4.0中,将不再在定义时评估函数和变量注释。而是将字符串形式保留在相应的__annotations__字典中。静态类型检查器在行为上不会有任何区别,而在运行时使用批注的工具将必须执行推迟的评估。

可以使用以下特殊导入从Python 3.7开始启用上述功能:

from __future__ import annotations

您可能会想做的事情

A.定义一个假人 Position

在类定义之前,放置一个虚拟定义:

class Position(object):
    pass


class Position(object):
    ...

这样可以摆脱NameError甚至看起来还可以:

>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}

但是吗?

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: False
other is Position: False

B. Monkey-patch为了添加注释:

您可能想尝试一些Python元编程魔术,并编写装饰器以Monkey修补类定义,以便添加注释:

class Position:
    ...
    def __add__(self, other):
        return self.__class__(self.x + other.x, self.y + other.y)

装饰者应对此负责:

Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position

至少看起来是正确的:

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: True
other is Position: True

可能麻烦太多了。

结论

如果您使用的是3.6或更低版本,请使用包含类名的字符串文字,在3.7中使用from __future__ import annotations它就可以了。

TL;DR: if you are using Python 4.0 it just works. As of today (2019) in 3.7+ you must turn this feature on using a future statement (from __future__ import annotations) – for Python 3.6 or below use a string.

I guess you got this exception:

NameError: name 'Position' is not defined

This is because Position must be defined before you can use it in an annotation unless you are using Python 4.

Python 3.7+: from __future__ import annotations

Python 3.7 introduces PEP 563: postponed evaluation of annotations. A module that uses the future statement from __future__ import annotations will store annotations as strings automatically:

from __future__ import annotations

class Position:
    def __add__(self, other: Position) -> Position:
        ...

This is scheduled to become the default in Python 4.0. Since Python still is a dynamically typed language so no type checking is done at runtime, typing annotations should have no performance impact, right? Wrong! Before python 3.7 the typing module used to be one of the slowest python modules in core so if you import typing you will see up to 7 times increase in performance when you upgrade to 3.7.

Python <3.7: use a string

According to PEP 484, you should use a string instead of the class itself:

class Position:
    ...
    def __add__(self, other: 'Position') -> 'Position':
       ...

If you use the Django framework this may be familiar as Django models also use strings for forward references (foreign key definitions where the foreign model is self or is not declared yet). This should work with Pycharm and other tools.

Sources

The relevant parts of PEP 484 and PEP 563, to spare you the trip:

Forward references

When a type hint contains names that have not been defined yet, that definition may be expressed as a string literal, to be resolved later.

A situation where this occurs commonly is the definition of a container class, where the class being defined occurs in the signature of some of the methods. For example, the following code (the start of a simple binary tree implementation) does not work:

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

To address this, we write:

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

The string literal should contain a valid Python expression (i.e., compile(lit, ”, ‘eval’) should be a valid code object) and it should evaluate without errors once the module has been fully loaded. The local and global namespace in which it is evaluated should be the same namespaces in which default arguments to the same function would be evaluated.

and PEP 563:

In Python 4.0, function and variable annotations will no longer be evaluated at definition time. Instead, a string form will be preserved in the respective __annotations__ dictionary. Static type checkers will see no difference in behavior, whereas tools using annotations at runtime will have to perform postponed evaluation.

The functionality described above can be enabled starting from Python 3.7 using the following special import:

from __future__ import annotations

Things that you may be tempted to do instead

A. Define a dummy Position

Before the class definition, place a dummy definition:

class Position(object):
    pass


class Position(object):
    ...

This will get rid of the NameError and may even look OK:

>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}

But is it?

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: False
other is Position: False

B. Monkey-patch in order to add the annotations:

You may want to try some Python meta programming magic and write a decorator to monkey-patch the class definition in order to add annotations:

class Position:
    ...
    def __add__(self, other):
        return self.__class__(self.x + other.x, self.y + other.y)

The decorator should be responsible for the equivalent of this:

Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position

At least it seems right:

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: True
other is Position: True

Probably too much trouble.

Conclusion

If you are using 3.6 or below use a string literal containing the class name, in 3.7 use from __future__ import annotations and it will just work.


回答 1

将类型指定为字符串是可以的,但总是让我有些讨厌,因为我们基本上是在绕过解析器。因此,您最好不要拼写以下任何文字字符串:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)

有一个细微的变化是使用绑定的typevar,至少在声明typevar时,您只需编写一次字符串即可:

from typing import TypeVar

T = TypeVar('T', bound='Position')

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: T) -> T:
        return Position(self.x + other.x, self.y + other.y)

Specifying the type as string is fine, but always grates me a bit that we are basically circumventing the parser. So you better not misspell any one of these literal strings:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)

A slight variation is to use a bound typevar, at least then you have to write the string only once when declaring the typevar:

from typing import TypeVar

T = TypeVar('T', bound='Position')

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: T) -> T:
        return Position(self.x + other.x, self.y + other.y)

回答 2

在解析类主体本身时,名称“ Position”不可用。我不知道您如何使用类型声明,但是Python的PEP 484-如果使用这些键入提示表示您可以在此时将名称简单地作为字符串,这是大多数模式应使用的方式:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)

检查https://www.python.org/dev/peps/pep-0484/#forward-references-符合该要求的工具将知道从那里解包并使用类名。(请记住,Python语言本身不执行任何这些注释-它们通常用于静态代码分析,或者可以具有一个库/框架以在运行时进行类型检查-但您必须明确地进行设置。

更新此外,从Python 3.8开始,请检查pep-563-从Python 3.8开始,可以编写from __future__ import annotations以推迟对批注的求值-前向引用类应简单易用。

The name ‘Position’ is not avalilable at the time the class body itself is parsed. I don’t know how you are using the type declarations, but Python’s PEP 484 – which is what most mode should use if using these typing hints say that you can simply put the name as a string at this point:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)

Check https://www.python.org/dev/peps/pep-0484/#forward-references – tools conforming to that will know to unwrap the class name from there and make use of it.(It is always important to have in mind that the Python language itself does nothing of these annotations – they are usually meant for static-code analysis, or one could have a library/framework for type checking in run-time – but you have to explicitly set that).

update Also, as of Python 3.8, check pep-563 – as of Python 3.8 it is possible to write from __future__ import annotations to defer the evaluation of annotations – forward referencing classes should work straightforward.


回答 3

当基于字符串的类型提示可接受时,__qualname__也可以使用该项目。它包含类的名称,并且在类定义的主体中可用。

class MyClass:
    @classmethod
    def make_new(cls) -> __qualname__:
        return cls()

这样,重命名类并不意味着修改类型提示。但是我个人并不希望智能代码编辑器能够很好地处理这种形式。

When a string-based type hint is acceptable, the __qualname__ item can also be used. It holds the name of the class, and it is available in the body of the class definition.

class MyClass:
    @classmethod
    def make_new(cls) -> __qualname__:
        return cls()

By doing this, renaming the class does not imply modifying the type hints. But I personally would not expect smart code editors to handle this form well.