Python 容器使用的 5 个技巧和 2 个误区( 三 )


  • return comments


  •  
  • comments = [
  • "Implementation note",
  • "Changed",
  • "ABC for generator",
  • ]
  • print(" ".join(add_ellipsis(comments)))
  • # OUTPUT:
  • # Implementati...
  • # Changed
  • # ABC for gene...
  • 上面的代码里, add_ellipsis函数接收一个列表作为参数,然后遍历它,替换掉需要修改的成员 。这一切看上去很合理,因为我们接到的最原始需求就是:“有一个列表,里面...” 。但如果有一天,我们拿到的评论不再是被继续装在列表里,而是在不可变的元组里呢?
    那样的话,现有的函数设计就会逼迫我们写出 add_ellipsis(list(comments))这种即慢又难看的代码了 。
     
    面向容器接口编程我们需要改进函数来避免这个问题 。因为 add_ellipsis函数强依赖了列表类型,所以当参数类型变为元组时,现在的函数就不再适用了(原因:给comments[index]赋值的地方会抛出TypeError异常) 。如何改善这部分的设计?秘诀就是:让函数依赖“可迭代对象”这个抽象概念,而非实体列表类型 。
    使用生成器特性,函数可以被改成这样:
    【Python 容器使用的 5 个技巧和 2 个误区】 
    1. def add_ellipsis_gen(comments: typing.Iterable[str], max_length: int = 12):
    2. """如果可迭代评论里的内容超过 max_length,剩下的字符用省略号代替
    3. """
    4. for comment in comments:
    5. comment = comment.strip
    6. if len(comment) > max_length:
    7. yield comment[:max_length] + '...'
    8. else:
    9. yield comment


    10.  
    11. print(" ".join(add_ellipsis_gen(comments)))
    在新函数里,我们将依赖的参数类型从列表改成了可迭代的抽象类 。这样做有很多好处,一个最明显的就是:无论评论是来自列表、元组或是某个文件,新函数都可以轻松满足:
     
    1. # 处理放在元组里的评论
    2. comments = ("Implementation note", "Changed", "ABC for generator")
    3. print(" ".join(add_ellipsis_gen(comments)))

    4. # 处理放在文件里的评论
    5. with open("comments") as fp:
    6. for comment in add_ellipsis_gen(fp):
    7. print(comment)
    将依赖由某个具体的容器类型改为抽象接口后,函数的适用面变得更广了 。除此之外,新函数在执行效率等方面也都更有优势 。现在让我们再回到之前的问题 。从高层来看,什么定义了容器?
    答案是:各个容器类型实现的接口协议定义了容器 。不同的容器类型在我们的眼里,应该是是否可以迭代是否可以修改有没有长度等各种特性的组合 。我们需要在编写相关代码时,更多的关注容器的抽象属性,而非容器类型本身,这样可以帮助我们写出更优雅、扩展性更好的代码 。
    Hint:在 itertools 内置模块里可以找到更多关于处理可迭代对象的宝藏 。
     
    常用技巧1. 使用元组改善分支代码有时,我们的代码里会出现超过三个分支的 if/else 。就像下面这样:
     
    1. import time


    2.  
    3. def from_now(ts):
    4. """接收一个过去的时间戳,返回距离当前时间的相对时间文字描述
    5. """
    6. now = time.time
    7. seconds_delta = int(now - ts)
    8. if seconds_delta < 1:
    9. return "less than 1 second ago"
    10. elif seconds_delta < 60:
    11. return "{} seconds ago".format(seconds_delta)
    12. elif seconds_delta < 3600:
    13. return "{} minutes ago".format(seconds_delta // 60)


      推荐阅读