【问题标题】:Dynamic typing design : is recursivity for dealing with lists a good design?动态类型设计:处理列表的递归是一个好的设计吗?
【发布时间】:2012-04-26 16:56:21
【问题描述】:

缺乏维护动态类型代码的经验,我正在寻找处理这种情况的最佳方法:

(python中的示例,但可以使用任何动态类型语言)

def some_function(object_that_could_be_a_list):
     if isinstance(object_that_could_be_a_list, list):
          for element in object_that_could_be_a_list:
              some_function(element)
     else:
          # Do stuff that expects the object to have certain properties 
          # a list would not have

我对此感到非常不安,因为我认为一个方法应该只做一件事,而且我认为它没有应有的可读性。所以,我很想创建三个函数:第一个函数将获取任何对象并在其他两个函数之间“排序”,一个用于列表,另一个用于“简单”对象。话又说回来,这会增加一些复杂性。

这里最“可持续”的解决方案是什么,并且保证易于维护的解决方案是什么?对于那些我不知道的情况,python 中有没有成语?提前致谢。

【问题讨论】:

  • Python 是动态类型的,而不是弱类型的。
  • 什么是some_function?在许多情况下,它可以像简单对象一样应用于列表。
  • @IgnacioVazquez-Abrams:哎呀!已更正。
  • 确实,这在很大程度上取决于some_function 的作用。
  • 好吧,那么如果你控制双方,要求参数是一个序列是不是不合理?

标签: python list coding-style dynamic-typing


【解决方案1】:

不要键入检查 - 做你想做的事,如果它不起作用,它会抛出一个你可以捕获和管理的异常。

蟒蛇的咒语是'ask for forgiveness, not permission'。类型检查需要额外的时间,大多数时候,它是没有意义的。在鸭子类型的环境中也没有多大意义——如果它有效,谁在乎它为什么是类型?当其他可迭代对象也可以工作时,为什么要将自己限制在列表中?

例如:

def some_function(object_that_could_be_a_list):
    try:
        for element in object_that_could_be_a_list:
            some_function(element)
    except TypeError:
        ...

这更具可读性,适用于更多情况(如果我传入任何其他不是列表的可迭代对象,则有很多)并且通常会更快。

请注意,您混淆了术语。 Python 是 dynamically typed,但不是 weakly typed。弱类型化意味着对象根据需要更改类型。例如,如果您添加一个字符串和一个 int,它会将字符串转换为一个 int 以进行加法。 Python 这样做。动态类型意味着你不为变量声明类型,它可能在某个时候包含一个字符串,然后是一个 int。

Duck typing 是一个术语,用于描述对象的使用而不关心它的类型。如果它像鸭子一样走路,像鸭子一样嘎嘎叫——它可能就是一只鸭子。

现在,这是一般情况,如果您认为您的代码会比“正确”更频繁地获得“错误”类型的对象,那么您可能需要输入检查速度。请注意,这种情况很少见,最好避免过早优化。通过捕获异常来实现,然后进行测试 - 如果发现它是瓶颈,则进行优化。

【讨论】:

  • 另外,使用 isinstance 很好,如果你之前知道你可能会得到一个列表,例如...
  • 这种方法在迭代中的一个常见问题是字符串是可迭代的,因此您可能需要显式类型检查来捕获字符串。
  • 与异常处理相比,类型检查不会花费太多时间。此外,您正在捕获 TypeError,但不兼容的类型也可能引发 AttributeError
  • try/except 的时间不是也很昂贵吗?
  • 这就是问题 - 很多人都在努力使用异常来控制流,因为它被其他语言的人打败了,这是错误的。它实际上很有意义,它可以停止竞争条件,它可以在很多情况下提供更好的性能(奇怪的是),而且它的阅读效果更好。试一试,如果它确实减慢了您的速度,请稍后对其进行优化,但我可以说它可能在 99% 的情况下都不会成为问题。
【解决方案2】:

一种常见的做法是通过对不同类型的输入使用不同的参数来实现多个接口。

def foo(thing=None, thing_seq=None):
    if thing_seq is not None:
        for _thing in thing_seq:
            foo(thing=_thing)
    if thing is not None:
        print "did foo with", thing

【讨论】:

  • 当然,我理解这个想法,但与 isinstance 相比有什么优势?好吧,应该会快一些。不确定它是否更具可读性。虽然我知道这种做法并且不会考虑在那里使用它。这很聪明。
  • 优点是没有猜测,意图很清楚调用者的意思以及被调用者应该如何行动。 import this
【解决方案3】:

我倾向于这样做而不是递归:

def foo(x):
    if not isinstance(x, list):
        x = [x]
    for y in x:
        do_something(y)

【讨论】:

    【解决方案4】:

    在这种情况下,您可以使用装饰器使其更易于维护:

    from mm import multimethod
    
    @multimethod(int, int)
    def foo(a, b):
        ...code for two ints...
    
    @multimethod(float, float):
    def foo(a, b):
        ...code for two floats...
    
    @multimethod(str, str):
    def foo(a, b):
        ...code for two strings...
    

    【讨论】:

    • 还值得注意的是,标准库中没有多方法(IIRC 目前正在考虑一个 PEP,这将使 @overload 做类似的事情,但是)。
    • 我不知道多方法。不过,我想我更喜欢标准库中的东西。
    猜你喜欢
    • 1970-01-01
    • 2012-02-10
    • 1970-01-01
    • 1970-01-01
    • 2010-10-31
    • 2011-01-26
    • 1970-01-01
    • 2011-09-13
    • 1970-01-01
    相关资源
    最近更新 更多