python中的类型演绎

Type deduction in Python

本问题已经有最佳答案,请猛点这里访问。

在python中,是否有更好的方法来确定函数参数是单个数字还是数字列表。目前我正在使用通道控制流异常的副作用,但它看起来并不优雅:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def buildPattern( NumberOrList, Framework ):
    FinalPattern = '-->'
    try:
        ListLen = len(NumberOrList)
        #if this is a list, program flow will reach this point
        I = 0
        for Char in Framework:
            if Char == 'x':
                FinalPattern = FinalPattern + ' ' + str(NumberOrList[I])
                I = (I+1) % ListLen
            else:
                FinalPattern = FinalPattern + '  '
    except:
        #if we reach this point, we don't have a list... (or some other problem)
        for Char in Framework:
            if Char == 'x':
                FinalPattern = FinalPattern + ' ' + str(NumberOrList)
            else:
                FinalPattern = FinalPattern + '  '
    return FinalPattern

print buildPattern( 3,'x x x x x x x x ' ) #single number call
print buildPattern( [1,3,5], 'x x x x x x x x ' ) #list call


您可以使用异常处理来完成这项工作,但正如您所说,这并不十分优雅。此外,如果经常引发异常,那么使用显式测试就不那么有效了。

实际上,重新设计代码会更优雅。;)因为python允许您定义一个函数,其中arg可以是一个数字或一个列表,这并不意味着它是一个好主意。正如您所发现的,它往往会使函数的内部逻辑更加复杂,并且常常导致不同执行路径中的重复代码。但是无论如何…

测试arg的干净方法是查看它是否是一个iterable,您可以使用collections.Iterable类来实现这一点。在Python的现代版本中,该类已被移到了collections.abc模块中,但目前在collections中仍然可以使用,以便更容易地编写在python 2&python 3上正确运行的代码。

顺便说一句,使用"裸"except通常不是一个好主意。您应该总是命名您想要捕获的异常,否则您可能最终会捕获到您不期望的东西,并且您的代码将无法正确处理。

此外,最好遵循PEP-0008样式指南。它使其他人更容易阅读您的代码。

这里有一个更紧凑的函数版本,使用PEP-0008样式名编写。它使用itertools.cycle来简化源数据的循环。它还将输出字符串收集到一个列表中,并在一个步骤中将它们连接在一起。这比在循环中进行字符串连接更有效。此代码在python 2和python 3上都正确运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from __future__ import print_function
from collections import Iterable
from itertools import cycle

def build_pattern(source, framework):
    if not isinstance(source, Iterable):
        source = [source]
    source = cycle(map(str, source))
    final_pattern = ['-->']
    for char in framework:
        final_pattern.append(next(source) if char == 'x' else ' ')
    return ' '.join(final_pattern)

print(build_pattern(3, 'x x x x x x x x ')) #single number call
print(build_pattern([1, 3, 5], 'x x x x x x x x ')) #list call
print(build_pattern('abcde', 'x x x x x x x x ')) #string call

输出

1
2
3
--> 3   3   3   3   3   3   3   3  
--> 1   3   5   1   3   5   1   3  
--> a   b   c   d   e   a   b   c

正如VPFB在注释中提到的,字符串是不可更改的,所以如果您将一个字符串传递给my build_pattern,它将直接传递给cycle(map(str, source)),它将不会被包装在列表中。这在这里很好,但在某些情况下,这种行为可能会导致问题,经典的情况是扁平化任意嵌套的列表。这里的答案说明了如何处理这种情况。


你为什么不试试这个

1
2
3
4
5
def buildPattern(NumberOrList, Framework):
    if isinstance(NumberOrList, list):
       # This is list
    else:
       # This is number

这是PEP-8 Python样式指南推荐的,而不是类型(obj)

参考文献:

https://www.python.org/dev/peps/pep-0008/编程建议


只需在函数顶部使用带有IsInstance的if语句:

1
2
3
4
5
6
if isinstance(NumberOrList, int):
    # Its a number
elif isinstance(NumberOrList,list):
    # Its a list
else:
    # Its something you dont want