【问题标题】:How to ensure solid contracts without strong typing?如何在没有强类型的情况下确保可靠的合同?
【发布时间】:2015-09-24 07:56:26
【问题描述】:

get_min_length() 接受一个必须匹配get_pkt_type() 的可能返回值的参数:

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)

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

【问题讨论】:

  • 在 Python 中你不能。但是,如果您有一个完整的有效数据包列表,您可以添加一个单元测试,以确保您的所有函数都接受有效的数据包类型。

标签: python python-2.7 types duck-typing dynamic-typing


【解决方案1】:

一般而言,在 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

【讨论】:

  • 非常感谢您富有洞察力的回答。我想知道您为什么建议使用 Enum 而不是集合或元组。我假设Enum 将保存所有数据包类型“CMD”、“RES”等。但是 Enum 对每个项目都有一个关联的值(但在我的上下文中不存在这样的值)。那么为什么 Enum 适合这个呢?
  • Enum 将为packet_types 生成真正的单例,将为您提供较少字符串类型的界面,并允许您检查唯一性等内容并获取有效值列表。如果您不想使用关联值,则无需关心它们。
【解决方案2】:

即使可以,也不应该这样做。 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 的美妙之处,您添加了一个新数据包,而您根本不需要更改代码。

回答您的问题 - 编写测试

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

【讨论】:

  • FWIW,你的SpecialPacket 可能也应该实现__hash__
  • @Veedrac 这只是一个例子来说明这一点。这不是详尽的实现,兄弟
【解决方案3】:

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

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)

所有这些选项都具有直接在对象上定义参数的优点,即它们实际属于的位置。这意味着只有一个点需要定义新参数(在新实例上),而不是您的设置中的几个。它在使用鸭子打字时也表现得更好,因为任何带有namelength 的类,无论扩展如何,都可以使用。

【讨论】:

    【解决方案4】:

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

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

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

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

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

    【讨论】:

    • 这里应该raise ValueError
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-01
    • 2022-10-13
    • 1970-01-01
    • 1970-01-01
    • 2016-11-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多