关于键入:如何在python中变量类型检查?

How can I type-check variables in Python?

我有一个python函数,它接受一个数字参数,该参数必须是一个整数,以便它正确地工作。在Python中验证这一点的首选方法是什么?

我的第一反应是这样做:

1
2
def isInteger(n):
    return int(n) == n

但我忍不住想,这是1)昂贵2)丑陋和3)受制于机爱普西隆温柔仁慈。

python是否提供了任何类型检查变量的本地方法?或者这被认为是违反了语言的动态类型设计?

编辑:因为有很多人问过这个问题,所以这个应用程序使用了IPv4前缀,从纯文本文件中获取数据。如果将任何输入解析为浮点,则应将该记录视为格式错误并忽略。


1
isinstance(n, int)

如果您需要知道它是否绝对是一个实际的int,而不是int的子类(通常不需要这样做):

1
type(n) is int

这是:

1
return int(n) == n

这不是一个好主意,因为交叉类型比较是正确的,尤其是int(3.0)==3.0


是的,就像埃文说的,不要打字检查。只需尝试使用该值:

1
2
3
def myintfunction(value):
  """ Please pass an integer"""
   return 2 + value

没有排版检查。好多了!让我们看看当我尝试它时会发生什么:

1
2
>>> myintfunction(5)
7

这是有效的,因为它是一个整数。嗯。让我们试试文字。

1
2
3
4
5
>>> myintfunction('text')
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
  File"<stdin>", line 3, in myintfunction
TypeError: unsupported operand type(s) for +: 'int' and 'str'

它显示一个错误,typeerror,这是它应该做的。如果打电话的人想接电话,那是有可能的。

如果你做了排版检查,你会怎么做?显示错误,对吗?所以您不必进行类型检查,因为错误已经自动出现了。

另外,由于您没有进行类型检查,因此您的函数可以与其他类型一起使用:

Floats:

1
2
>>> print myintfunction(2.2)
4.2

复数:

1
2
>>> print myintfunction(5j)
(2+5j)

小数:

1
2
3
>>> import decimal
>>> myintfunction(decimal.Decimal('15'))
Decimal("17")

甚至可以添加数字的完全任意对象!

1
2
3
4
5
6
7
8
9
>>> class MyAdderClass(object):
...     def __radd__(self, value):
...             print 'got some value: ', value
...             return 25
...
>>> m = MyAdderClass()
>>> print myintfunction(m)
got some value:  2
25

所以通过打字你显然什么也得不到。失去很多。

更新:

由于您已经编辑了这个问题,现在很明显您的应用程序调用了一些上游例程,这些例程只对ints有意义。

在这种情况下,我仍然认为应该将接收到的参数传递给上游函数。上游函数将正确地处理它,例如,如果需要,会引发错误。我非常怀疑,如果你将ips传递给它一个float,你处理ips的函数会表现得很奇怪。如果你能告诉我们图书馆的名字,我们可以帮你查一下。

但是…如果上游函数的行为不正确,如果你给它传递一个float(我仍然非常怀疑),就会杀死一些孩子,那么只需在它上面调用int()

1
2
3
def myintfunction(value):
  """ Please pass an integer"""
   return upstreamfunction(int(value))

你仍然没有排字,所以你能从没有排字中得到最大的好处。

即便如此,如果您确实希望进行类型检查,尽管这样做会降低应用程序的可读性和性能,但绝对没有任何好处,请使用assert来进行检查。

1
2
assert isinstance(...)
assert type() is xxxx

这样我们就可以关闭assert,并通过调用它来从程序中删除这个功能

1
python -OO program.py


python现在支持通过输入模块和mypy逐步输入。从python3.5开始,typing模块是stdlib的一部分,如果需要python2或python3以前版本的端口,可以从pypi下载。您可以通过从命令行运行pip install mypy来安装mypy

简而言之,如果您想验证某个函数接受了int、float并返回了一个字符串,您可以这样注释您的函数:

1
2
def foo(param1: int, param2: float) -> str:
    return"testing {0} {1}".format(param1, param2)

如果您的文件名为test.py,则可以在安装mypy后通过从命令行运行mypy test.py来进行类型检查。

如果使用的是不支持函数注释的旧版本的Python,则可以使用类型注释来实现相同的效果:

1
2
3
def foo(param1, param2):
    # type: (int, float) -> str
    return"testing {0} {1}".format(param1, param2)

对于python3文件使用相同的命令mypy test.py,对于python2文件使用相同的命令mypy --py2 test.py

类型注释在运行时被python解释器完全忽略,因此它们施加的开销最小到没有——通常的工作流程是处理代码并定期运行mypy来捕获错误和错误。一些IDE(如pycharm)可以理解类型提示,并可以在直接编辑代码时提醒您代码中的问题和类型不匹配。

如果出于某种原因,您需要在运行时检查类型(也许您需要验证大量输入?)您应该遵循其他答案中列出的建议,例如使用isinstanceissubclass等。还有一些库,比如强制在运行时执行类型检查(考虑到您的类型注释),尽管我不确定它们在编写时的生产就绪程度。

有关更多信息和详细信息,请参阅mypy网站、mypy常见问题解答和PEP 484。


1
if type(n) is int

它检查n是否是python int,并且只检查int。它不接受int的子类。

但是,类型检查不适合"python方式"。最好使用n作为int,如果它抛出异常,则捕获它并对其执行操作。


用python编程并像在其他语言中那样执行类型检查,这就像选择一个螺丝刀来敲入钉子。使用Python的异常处理功能更为优雅。

通过交互式命令行,可以运行如下语句:

1
int('sometext')

这将产生一个错误-IPython告诉我:

1
<type 'exceptions.ValueError'>: invalid literal for int() with base 10: 'sometext'

现在您可以编写如下代码:

1
2
3
4
try:
   int(myvar) + 50
except ValueError:
   print"Not a number"

它可以定制以执行所需的任何操作,并捕获预期的任何错误。它看起来有点复杂,但符合Python的语法和习惯用法,并产生非常可读的代码(一旦习惯了讲Python)。


不要输入检查。鸭子打字的关键是你不应该这么做。例如,如果有人做了这样的事情:

1
2
class MyInt(int):
    # ... extra stuff ...


怎么样:

1
2
3
4
5
6
7
8
def ip(string):
    subs = string.split('.')
    if len(subs) != 4:
        raise ValueError("incorrect input")
    out = tuple(int(v) for v in subs if 0 <= int(v) <= 255)
    if len(out) != 4:
        raise ValueError("incorrect input")
    return out

当然有标准的isInstance(3,int)函数…


我很想去做如下的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def check_and_convert(x):
    x = int(x)
    assert 0 <= x <= 255,"must be between 0 and 255 (inclusive)"
    return x

class IPv4(object):
   """IPv4 CIDR prefixes is A.B.C.D/E where A-D are
       integers in the range 0-255, and E is an int
       in the range 0-32."""


    def __init__(self, a, b, c, d, e=0):
        self.a = check_and_convert(a)
        self.b = check_and_convert(a)
        self.c = check_and_convert(a)
        self.d = check_and_convert(a)
        assert 0 <= x <= 32,"must be between 0 and 32 (inclusive)"
        self.e = int(e)

这样,当您使用它时,任何东西都可以传入,但您只存储一个有效的整数。