如何在没有强类型的情况下确保可靠的合约?

How to ensure solid contracts without strong typing?

get_min_length() 采用的参数必须匹配 get_pkt_type() 的可能 return 值:

def get_pkt_type(some_val):
    """Determine the type of an XCP packet.

    :return:
        'CMD' if "Command" packet,
        'RES' if "Command Response" packet,
        'ERR' if "Error" packet,
        'CMD/RES' if uncertain whether a CONNECT CMD or a
            RES packet, as their frame bytes can look identical.
    :rtype: str
    """
    if something:
        return 'CMD'
    elif something_else2:
        return 'RES'
    elif something_else3:
        return 'ERR'
    elif something_else4:
        return 'CMD/RES'

def get_min_length(packet_type):
    if packet_type in ['CMD', 'RES']:
        return 4
    elif packet_type in ['ERR', 'CMD/RES']:
        return 6

packet_type = get_pkt_type(some_val)
length = get_min_length(packet_type)

我如何确保,如果程序员将新的数据包类型 return 值添加到 get_pkt_type(),他也不会忘记将值添加到 get_min_length()。在强类型语言中,packet_type 将是一个已定义的类型,它被 return 编辑并传递,所以我会这样安全。

没有办法确保程序员不会忘记任何事情。程序员也是人,而人往往会忘记事情。

但是您可以向 get_min_length(packet_type) 添加一些代码,从而提醒程序员向该函数添加新值。

else:
    print "Unknown packet type. Please add new value to the list."
    return some_int_value

或者您甚至可以引发异常,这样程序员会立即看到错误。

PS。在 Python 你必须在调用它们之前定义你的函数。

一般来说,在 Python 中,如果您有一组可扩展的值,那么创建一个浅继承层次结构是有意义的。这不太容易健忘。枚举更适合固定值集。

也就是说,您应该做的第一件事是尾随

raise ValueError("Unexpected enum value")

你的职能。

您可能会考虑的另一件事是使用字典来表示此类映射:

pkt_lengths = {
    'CMD': 4,
    'RES': 4,
    'ERR': 6,
    'CMD/RES': 6,
}

get_min_length = pkt_lengths.__getitem__

然后您可以添加一个简单的测试

packet_types = {'CMD', 'RES', 'ERR', 'CMD/RES'}
assert not packet_types.symmetric_difference(pkt_lengths)

如果您经常这样做,请构建一个函数:

def enum_mapper(enum, mapper):
    assert not enum.symmetric_difference(mapper)
    return mapper.__getitem__

这让你可以做到

get_min_length = enum_mapper(packet_types, pkt_lengths)

并在启动时进行检查。

此外,请考虑使用适当的 enum.Enum

你不应该这样做,即使你可以。 Python 是一种 Duck-Typed 语言,在 duck-typed 语言中,我们不会根据变量的类型来限制变量。考虑我定义自定义 packet_type 的情况,如下所示:-

class SpecialPacket(object):

    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        return self.value == other

special_packet = SpecialPacket('CMD')

print(get_min_length(special_packet))
# 6

这就是 duck-typing 的美妙之处,您添加了一个新数据包并且根本不需要更改代码。

回答你的问题 - 编写测试

这就是为什么测试在动态和弱类型语言中被认为非常重要的原因。我们不依赖于强类型并最终编写了比我们需要的更多的代码,而是我们在所有阶段都对我们的代码进行了大量测试。添加新数据包的人有责任确保他的代码正常工作,并且他最终不会破坏你的代码。

您实际上可以在 python 中创建自己的类型 - 类型只是 classes。

class packet_type(object):
  def __init__(self, name, length):
    self.name = name
    self.length = length

CMD = packet_type("CMD", 4)

有时,这似乎有些沉重。一个更简单的替代方法是将属性转储到原始数据结构中,如 Veedrac 的回答。 如果你只想要一个类似结构的容器,一个非常好的中间立场是 namedtuples.

from collections import namedtuple
packet_type = namedtuple('packet_type', ['name', 'length']
CMD = packet_type("CMD", 4)

所有这些选项的优点是参数是直接在对象上定义的,它们实际上属于这些对象。这意味着只有一个点需要定义新参数(在新实例上),而不是像您的设置中那样定义多个。它在 duck-typing 上的表现也更好,因为任何带有 namelength 的 class,无论扩展名如何,都可以使用。