新闻资讯  快讯  焦点  财经  政策  社会
互 联 网   电商  金融  数据  计算  技巧
生活百科  科技  职场  健康  法律  汽车
手机百科  知识  软件  修理  测评  微信
软件技术  应用  系统  图像  视频  经验
硬件技术  知识  技术  测评  选购  维修
网络技术  硬件  软件  设置  安全  技术
程序开发  语言  移动  数据  开源  百科
安全防护  资讯  黑客  木马  病毒  移动
站长技术  搜索  SEO  推广  媒体  移动
财经百科  股票  知识  理财  财务  金融
教育考试  育儿  小学  高考  考研  留学
您当前的位置:首页 > IT > 程序开发 > 语言 > Python

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

时间:2019-06-06 13:10:49  来源:  作者:
Python 容器使用的 5 个技巧和 2 个误区

文 | piglei@piglei 公众号

编辑 | EarlGrey

编程派公众号授权发布

“容器”这两个字很少被 Python 技术文章提起。一看到“容器”,大家想到的多是那头蓝色小鲸鱼:Docker,但这篇文章和它没有任何关系。本文里的容器,是 Python 中的一个抽象概念,是对专门用来装其他对象的数据类型的统称。

在 Python 中,有四类最常见的内建容器类型: 列表(list)元组(tuple)字典(dict)集合(set)。通过单独或是组合使用它们,可以高效的完成很多事情。

Python 语言自身的内部实现细节也与这些容器类型息息相关。比如 Python 的类实例属性、全局变量 globals等就都是通过字典类型来存储的。

在这篇文章里,我首先会从容器类型的定义出发,尝试总结出一些日常编码的最佳实践。之后再围绕各个容器类型提供的特殊机能,分享一些编程的小技巧。

 

当我们谈论容器时,我们在谈些什么?

我在前面给了“容器”一个简单的定义:专门用来装其他对象的就是容器。但这个定义太宽泛了,无法对我们的日常编程产生什么指导价值。要真正掌握 Python 里的容器,需要分别从两个层面入手:

  • 底层实现:内置容器类型使用了什么数据结构?某项操作如何工作?

  • 高层抽象:什么决定了某个对象是不是容器?哪些行为定义了容器?

下面,让我们一起站在这两个不同的层面上,重新认识容器。

 

底层看容器

Python 是一门高级编程语言,它所提供的内置容器类型,都是经过高度封装和抽象后的结果。和“链表”、“红黑树”、“哈希表”这些名字相比,所有 Python 内建类型的名字,都只描述了这个类型的功能特点,其他人完全没法只通过这些名字了解它们的哪怕一丁点内部细节。

这是 Python 编程语言的优势之一。相比 C 语言这类更接近计算机底层的编程语言,Python 重新设计并实现了对编程者更友好的内置容器类型,屏蔽掉了内存管理等额外工作。为我们提供了更好的开发体验。

但如果这是 Python 语言的优势的话,为什么我们还要费劲去了解容器类型的实现细节呢?答案是:关注细节可以帮助我们编写出更快的代码。

 

写更快的代码

1. 避免频繁扩充列表/创建新列表

所有的内建容器类型都不限制容量。如果你愿意,你可以把递增的数字不断塞进一个空列表,最终撑爆整台机器的内存。

在 Python 语言的实现细节里,列表的内存是按需分配的[注1],当某个列表当前拥有的内存不够时,便会触发内存扩容逻辑。而分配内存是一项昂贵的操作。虽然大部分情况下,它不会对你的程序性能产生什么严重的影响。但是当你处理的数据量特别大时,很容易因为内存分配拖累整个程序的性能。

还好,Python 早就意识到了这个问题,并提供了官方的问题解决指引,那就是:“变懒”

如何解释“变懒”? range函数的进化是一个非常好的例子。

在 Python 2 中,如果你调用 range(100000000),需要等待好几秒才能拿到结果,因为它需要返回一个巨大的列表,花费了非常多的时间在内存分配与计算上。但在 Python 3 中,同样的调用马上就能拿到结果。因为函数返回的不再是列表,而是一个类型为range的懒惰对象,只有在你迭代它、或是对它进行切片时,它才会返回真正的数字给你。

所以说,为了提高性能,内建函数 range“变懒”了。而为了避免过于频繁的内存分配,在日常编码中,我们的函数同样也需要变懒,这包括:

  • 更多的使用 yield关键字,返回生成器对象

  • 尽量使用生成器表达式替代列表推导表达式

    • 生成器表达式: (iforinrange(100))

    • 列表推导表达式: [iforinrange(100)]

  • 尽量使用模块提供的懒惰对象:

    • 使用 re.finditer替代re.findall

    • 直接使用可迭代的文件对象: forlineinfp,而不是forlineinfp.readlines

 

2. 在列表头部操作多的场景使用 deque 模块

列表是基于数组结构(Array)实现的,当你在列表的头部插入新成员( list.insert(0,item))时,它后面的所有其他成员都需要被移动,操作的时间复杂度是O(n)。这导致在列表的头部插入成员远比在尾部追加(list.append(item)时间复杂度为O(1))要慢。

如果你的代码需要执行很多次这类操作,请考虑使用 collections.deque 类型来替代列表。因为 deque 是基于双端队列实现的,无论是在头部还是尾部追加元素,时间复杂度都是 O(1)

 

3. 使用集合/字典来判断成员是否存在

当你需要判断成员是否存在于某个容器时,用集合比列表更合适。因为 itemin[...]操作的时间复杂度是O(n),而itemin{...}的时间复杂度是O(1)。这是因为字典与集合都是基于哈希表(Hash Table)数据结构实现的。


 
  1. # 这个例子不是特别恰当,因为当目标集合特别小时,使用集合还是列表对效率的影响微乎其微

  2. # 但这不是重点 :)

  3. VALID_NAMES = ["piglei", "raymond", "bojack", "caroline"]


  4. # 转换为集合类型专门用于成员判断

  5. VALID_NAMES_SET = set(VALID_NAMES)




  6.  
  7. def validate_name(name):

  8. if name not in VALID_NAMES_SET:

  9. # 此处使用了 Python 3.6 添加的 f-strings 特性

  10. raise ValueError(f"{name} is not a valid name!")

Hint: 强烈建议阅读 TimeComplexity - Python Wiki,了解更多关于常见容器类型的时间复杂度相关内容。

如果你对字典的实现细节感兴趣,也强烈建议观看 Raymond Hettinger 的演讲 Modern Dictionaries(YouTube)

 

高层看容器

Python 是一门“鸭子类型”语言:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”所以,当我们说某个对象是什么类型时,在根本上其实指的是:这个对象满足了该类型的特定接口规范,可以被当成这个类型来使用。而对于所有内置容器类型来说,同样如此。

打开位于 collections 模块下的 abc(“抽象类 Abstract Base Classes”的首字母缩写)子模块,可以找到所有与容器相关的接口(抽象类)[注2]定义。让我们分别看看那些内建容器类型都满足了什么接口:

  • 列表(list):满足IterableSequenceMutableSequence等接口

  • 元组(tuple):满足IterableSequence

  • 字典(dict):满足IterableMappingMutableMapping[注3]

  • 集合(set):满足IterableSetMutableSet[注4]

每个内置容器类型,其实就是满足了多个接口定义的组合实体。比如所有的容器类型都满足 “可被迭代的”(Iterable) 这个接口,这意味着它们都是“可被迭代”的。但是反过来,不是所有“可被迭代”的对象都是容器。就像字符串虽然可以被迭代,但我们通常不会把它当做“容器”来看待。

了解这个事实后,我们将在 Python 里重新认识面向对象编程中最重要的原则之一:面向接口而非具体实现来编程。

让我们通过一个例子,看看如何理解 Python 里的“面向接口编程”。

 

写扩展性更好的代码

某日,我们接到一个需求:有一个列表,里面装着很多用户评论,为了在页面正常展示,需要将所有超过一定长度的评论用省略号替代。

这个需求很好做,很快我们就写出了第一个版本的代码:


 
  1. # 注:为了加强示例代码的说明性,本文中的部分代码片段使用了Python 3.5

  2. # 版本添加的 Type Hinting 特性


  3. def add_ellipsis(comments: typing.List[str], max_length: int = 12):

  4. """如果评论列表里的内容超过 max_length,剩下的字符用省略号代替

  5. """

  6. index = 0

  7. for comment in comments:

  8. comment = comment.strip

  9. if len(comment) > max_length:

  10. comments[index] = comment[:max_length] + '...'

  11. index += 1

  12. return comments




  13.  
  14. comments = [

  15. "Implementation note",

  16. "Changed",

  17. "ABC for generator",

  18. ]

  19. print(" ".join(add_ellipsis(comments)))

  20. # OUTPUT:

  21. # Implementati...

  22. # Changed

  23. # ABC for gene...

上面的代码里, add_ellipsis函数接收一个列表作为参数,然后遍历它,替换掉需要修改的成员。这一切看上去很合理,因为我们接到的最原始需求就是:“有一个列表,里面...”。但如果有一天,我们拿到的评论不再是被继续装在列表里,而是在不可变的元组里呢?

那样的话,现有的函数设计就会逼迫我们写出 add_ellipsis(list(comments))这种即慢又难看的代码了。

 

面向容器接口编程

我们需要改进函数来避免这个问题。因为 add_ellipsis函数强依赖了列表类型,所以当参数类型变为元组时,现在的函数就不再适用了(原因:给comments[index]赋值的地方会抛出TypeError异常)。如何改善这部分的设计?秘诀就是:让函数依赖“可迭代对象”这个抽象概念,而非实体列表类型。

使用生成器特性,函数可以被改成这样:


 
  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)

  14. elif seconds_delta < 3600 * 24:

  15. return "{} hours ago".format(seconds_delta // 3600)

  16. else:

  17. return "{} days ago".format(seconds_delta // (3600 * 24))




  18.  
  19. now = time.time

  20. print(from_now(now))

  21. print(from_now(now - 24))

  22. print(from_now(now - 600))

  23. print(from_now(now - 7500))

  24. print(from_now(now - 87500))

  25. # OUTPUT:

  26. # less than 1 second ago

  27. # 24 seconds ago

  28. # 10 minutes ago

  29. # 2 hours ago

  30. # 1 days ago

上面这个函数挑不出太多毛病,很多很多人都会写出类似的代码。但是,如果你仔细观察它,可以在分支代码部分找到一些明显的“边界”。比如,当函数判断某个时间是否应该用“秒数”展示时,用到了60。而判断是否应该用分钟时,用到了3600

从边界提炼规律是优化这段代码的关键。如果我们将所有的这些边界放在一个有序元组中,然后配合二分查找模块 bisect。整个函数的控制流就能被大大简化:


 
  1. import bisect




  2.  
  3. # BREAKPOINTS 必须是已经排好序的,不然无法进行二分查找

  4. BREAKPOINTS = (1, 60, 3600, 3600 * 24)

  5. TMPLS = (

  6. # unit, template

  7. (1, "less than 1 second ago"),

  8. (1, "{units} seconds ago"),

  9. (60, "{units} minutes ago"),

  10. (3600, "{units} hours ago"),

  11. (3600 * 24, "{units} days ago"),

  12. )




  13.  
  14. def from_now(ts):

  15. """接收一个过去的时间戳,返回距离当前时间的相对时间文字描述

  16. """

  17. seconds_delta = int(time.time - ts)

  18. unit, tmpl = TMPLS[bisect.bisect(BREAKPOINTS, seconds_delta)]

  19. return tmpl.format(units=seconds_delta // unit)

除了用元组可以优化过多的 if/else分支外,有些情况下字典也能被用来做同样的事情。关键在于从现有代码找到重复的逻辑与规律,并多多尝试。

 

2. 在更多地方使用动态解包

动态解包操作是指使用 ***运算符将可迭代对象“解开”的行为,在 Python 2 时代,这个操作只能被用在函数参数部分,并且对出现顺序和数量都有非常严格的要求,使用场景非常单一。


 
  1. def calc(a, b, multiplier=1):

  2. return (a + b) * multiplier




  3.  
  4. # Python2 中只支持在函数参数部分进行动态解包

  5. print calc(*[1, 2], **{"multiplier": 10})

  6. # OUTPUT: 30

不过,Python 3 尤其是 3.5 版本后, ***的使用场景被大大扩充了。举个例子,在 Python 2 中,如果我们需要合并两个字典,需要这么做:


 
  1. def merge_dict(d1, d2):

  2. # 因为字典是可被修改的对象,为了避免修改原对象,此处需要复制一个 d1 的浅拷贝

  3. result = d1.copy

  4. result.update(d2)

  5. return result


  6. user = merge_dict({"name": "piglei"}, {"movies": ["Fight Club"]})

但是在 Python 3.5 以后的版本,你可以直接用 **运算符来快速完成字典的合并操作:


 
  1. user = {**{"name": "piglei"}, **{"movies": ["Fight Club"]}}

除此之外,你还可以在普通赋值语句中使用 *运算符来动态的解包可迭代对象。如果你想详细了解相关内容,可以阅读下面推荐的 PEP。

Hint:推进动态解包场景扩充的两个 PEP:

  • PEP 3132 -- Extended Iterable Unpacking | Python.org

  • PEP 448 -- Additional Unpacking Generalizations | Python.org

 

3. 最好不用“获取许可”,也无需“要求原谅”

这个小标题可能会稍微让人有点懵,让我来简短的解释一下:“获取许可”与“要求原谅”是两种不同的编程风格。如果用一个经典的需求:“计算列表内各个元素出现的次数” 来作为例子,两种不同风格的代码会是这样:


 
  1. # AF: Ask for Forgiveness

  2. # 要做就做,如果抛出异常了,再处理异常

  3. def counter_af(l):

  4. result = {}

  5. for key in l:

  6. try:

  7. result[key] += 1

  8. except KeyError:

  9. result[key] = 1

  10. return result




  11.  
  12. # AP: Ask for Permission

  13. # 做之前,先问问能不能做,可以做再做

  14. def counter_ap(l):

  15. result = {}

  16. for key in l:

  17. if key in result:

  18. result[key] += 1

  19. else:

  20. result[key] = 1

  21. return result

整个 Python 社区对第一种 Ask for Forgiveness 的异常捕获式编程风格有着明显的偏爱。这其中有很多原因,首先,在 Python 中抛出异常是一个很轻量的操作。其次,第一种做法在性能上也要优于第二种,因为它不用在每次循环的时候都做一次额外的成员检查。

不过,示例里的两段代码在现实世界中都非常少见。为什么?因为如果你想统计次数的话,直接用 collections.defaultdict就可以了:


 
  1. from collections import defaultdict




  2.  
  3. def counter_by_collections(l):

  4. result = defaultdict(int)

  5. for key in l:

  6. result[key] += 1

  7. return result

这样的代码既不用“获取许可”,也无需“请求原谅”。整个代码的控制流变得更清晰自然了。所以,如果可能的话,请尽量想办法省略掉那些非核心的异常捕获逻辑。一些小提示:

  • 操作字典成员时:使用 collections.defaultdict类型

    • 或者使用 dict[key]=dict.setdefault(key,0)+1内建函数

  • 如果移除字典成员,不关心是否存在:

    • 调用 pop 函数时设置默认值,比如 dict.pop(key,None)

  • 在字典获取成员时指定默认值: dict.get(key,default_value)

  • 对列表进行不存在的切片访问不会抛出 IndexError异常:["foo"][100:200]

 

4. 使用 next 函数

next是一个非常实用的内建函数,它接收一个迭代器作为参数,然后返回该迭代器的下一个元素。使用它配合生成器表达式,可以高效的实现“从列表中查找第一个满足条件的成员”之类的需求。


 
  1. numbers = [3, 7, 8, 2, 21]

  2. # 获取并 **立即返回** 列表里的第一个偶数

  3. print(next(i for i in numbers if i % 2 == 0))

  4. # OUTPUT: 8

 

5. 使用有序字典来去重

字典和集合的结构特点保证了它们的成员不会重复,所以它们经常被用来去重。但是,使用它们俩去重后的结果会丢失原有列表的顺序。这是由底层数据结构“哈希表(Hash Table)”的特点决定的。


 
  1. >>> l = [10, 2, 3, 21, 10, 3]

  2. # 去重但是丢失了顺序

  3. >>> set(l)

  4. {3, 10, 2, 21}

如果既需要去重又必须保留顺序怎么办?我们可以使用 collections.OrderedDict模块:


 
  1. >>> from collections import OrderedDict

  2. >>> list(OrderedDict.fromkeys(l).keys)

  3. [10, 2, 3, 21]

Hint: 在 Python 3.6 中,默认的字典类型修改了实现方式,已经变成有序的了。并且在 Python 3.7 中,该功能已经从 语言的实现细节变成了为可依赖的正式语言特性

但是我觉得让整个 Python 社区习惯这一点还需要一些时间,毕竟目前“字典是无序的”还是被印在无数本 Python 书上。所以,我仍然建议在一切需要有序字典的地方使用 OrderedDict。

 

常见误区

1. 当心那些已经枯竭的迭代器

在文章前面,我们提到了使用“懒惰”生成器的种种好处。但是,所有事物都有它的两面性。生成器的最大的缺点之一就是:它会枯竭。当你完整遍历过它们后,之后的重复遍历就不能拿到任何新内容了。


 
  1. numbers = [1, 2, 3]

  2. numbers = (i * 2 for i in numbers)


  3. # 第一次循环会输出 2, 4, 6

  4. for number in numbers:

  5. print(number)


  6. # 这次循环什么都不会输出,因为迭代器已经枯竭了

  7. for number in numbers:

  8. print(number)

而且不光是生成器表达式,Python 3 里的 map、filter 内建函数也都有一样的特点。忽视这个特点很容易导致代码中出现一些难以察觉的 Bug。

Instagram 就在项目从 Python 2 到 Python 3 的迁移过程中碰到了这个问题。它们在 PyCon 2017 上分享了对付这个问题的故事。访问文章 Instagram 在 PyCon 2017 的演讲摘要,搜索“迭代器”可以查看详细内容。

 

2. 别在循环体内修改被迭代对象

这是一个很多 Python 初学者会犯的错误。比如,我们需要一个函数来删掉列表里的所有偶数:


 
  1. def remove_even(numbers):

  2. """去掉列表里所有的偶数

  3. """

  4. for i, number in enumerate(numbers):

  5. if number % 2 == 0:

  6. # 有问题的代码

  7. del numbers[i]




  8.  
  9. numbers = [1, 2, 7, 4, 8, 11]

  10. remove_even(numbers)

  11. print(numbers)

  12. # OUTPUT: [1, 7, 8, 11]

注意到结果里那个多出来的 “8” 了吗?当你在遍历一个列表的同时修改它,就会出现这样的事情。因为被迭代的对象 numbers在循环过程中被修改了。遍历的下标在不断增长,而列表本身的长度同时又在不断缩减。这样就会导致列表里的一些成员其实根本就没有被遍历到。

所以对于这类操作,请使用一个新的空列表保存结果,或者利用 yield返回一个生成器。而不是修改被迭代的列表或是字典对象本身。

 

总结

在这篇文章中,我们首先从“容器类型”的定义出发,在底层和高层两个层面探讨了容器类型。之后遵循系列文章传统,提供了一些编写容器相关代码时的技巧。

让我们最后再总结一下要点:

  • 了解容器类型的底层实现,可以帮助你写出性能更好的代码

  • 提炼需求里的抽象概念,面向接口而非实现编程

  • 多使用“懒惰”的对象,少生成“迫切”的列表

  • 使用元组和字典可以简化分支代码结构

  • 使用 next函数配合迭代器可以高效完成很多事情,但是也需要注意“枯竭”问题

  • collections、itertools 模块里有非常多有用的工具,快去看看吧!

看完文章的你,有没有什么想吐槽的?请留言或者在 项目 Github Issues 告诉我吧。

 

注解

  1. Python 这门语言除了 CPython 外,还有许许多多的其他版本实现。如无特别说明,本文以及 “Python 工匠” 系列里出现的所有 Python 都特指 Python 的 C 语言实现 CPython

  2. Python 里没有类似其他编程语言里的“Interface 接口”类型,只有类似的“抽象类”概念。为了表达方便,后面的内容均统一使用“接口”来替代“抽象类”。

  3. 有没有只实现了 Mapping 但又不是 MutableMapping 的类型?试试 MappingProxyType({})

  4. 有没有只实现了 Set 但又不是 MutableSet 的类型?试试 frozenset
     



Tags:Python   点击:()  评论:()
声明:本站部分内容来自互联网,如有任何版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▌相关评论
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
▌相关推荐
1、MySQLdbMySQLdb又叫MySQL-python ,是 Python 连接 MySQL 最流行的一个驱动,很多框架都也是基于此库进行开发,遗憾的是它只支持 Python2.x,而且安装的时候有很多前置条件,因为...【详细内容】
2019-06-14 Python  点击:(1)  评论:(0)  加入收藏
回顾在Python进阶记录之基础篇(十六)中,我们介绍了Python面向对象中对属性的访问限制,需要重点掌握私有变量和公有变量的区别和用法,牢记面向对象的编程规则。今天我们讲一下Pyth...【详细内容】
2019-06-14 Python  点击:(1)  评论:(0)  加入收藏
如果您曾经使用setup.py脚本发布过一个python包,那么您可能会发现编写脚本来发布包比编写包本身要困难。Python 开发人员认识到了这一点,并且有一些工具采用了一种更现代的方...【详细内容】
2019-06-14 Python  点击:(1)  评论:(0)  加入收藏
今天呢,给大家带来一个Python的暴力破解加密的压缩文件的方法~(是不是压缩文件不重要)。另外你会不会Python也不重要 首先,我们得说一下,如何打开一个压缩文件,如下: 打开一个...【详细内容】
2019-06-14 密码  点击:(1)  评论:(0)  加入收藏
文 | piglei@piglei 公众号编辑 | EarlGrey编程派公众号授权发布“容器”这两个字很少被 Python 技术文章提起。一看到“容器”,大家想到的多是那头蓝色小鲸鱼:Docker,但这篇文...【详细内容】
2019-06-06 Python  点击:(2)  评论:(0)  加入收藏
关于cookie和session估计很多程序员面试的时候都会被问到,这两个概念在写web以及爬虫中都会涉及,并且两者可能很多人直接回答也不好说的特别清楚,所以整理这样一篇文章,也帮助自...【详细内容】
2019-06-06 Python  点击:(6)  评论:(0)  加入收藏
回顾在Python进阶记录之基础篇(十一)中,我们介绍了Python中迭代器与生成器的相关知识点,需要重点掌握迭代器与生成器的概念与基本用法。今天我们来讲一下Python中的几个高阶函数...【详细内容】
2019-06-06 Python  点击:(3)  评论:(0)  加入收藏
本套教程学习时间15天1-3天内容:为Linux基础命令4-13天内容:为Python基础教程14-15 天内容:为飞机大战项目演练第一阶段(1-3天):该阶段首先通过介绍不同领域的三种操作系统,操作系...【详细内容】
2019-06-05 Python,教程  点击:(10)  评论:(0)  加入收藏
导读:在本文中,你会学到如何处理数字、定义与使用变量和常量,以及编写使用这些数据类型执行实际任务的简单程序。作者:凯&middot;霍斯特曼(Cay Horstmann),兰斯&middot;尼塞斯(Rance...【详细内容】
2019-06-05 Python  点击:(10)  评论:(0)  加入收藏
目录 Python中的基本main()函数 Python中的执行模式 基于命令行执行 导入模块或解释器 Main函数的最佳实践 将大部分代码放入函数或类中 使用__name__控制代码的执行 创建名...【详细内容】
2019-06-05 Python  点击:(13)  评论:(0)  加入收藏
作者 | Jeff Hale译者 | 风车云马;责编 | Jane,Rachel出品 | Python大本营(ID:pythonnews)【导读】在编写一些Python程序的时候,我们常常需要与文件系统进行交互。在本文中,营长...【详细内容】
2019-06-05 Python  点击:(6)  评论:(0)  加入收藏
导读:数据分析将作为一门通用技能,进入越来越多的不同工作中。毕竟“技多不压身”,掌握数据分析,一方面可以提升自己相应的业务能力,另一方面也可以让自己建立一种从数据出发的视...【详细内容】
2019-05-17 Python  点击:(13)  评论:(0)  加入收藏
在我们覆盖 7 个 PyPI 库的系列文章中了解解决 Python 问题的更多信息。-- Moshe Zadka(作者)Python 是当今使用最多的 流行编程语言 之一,因为:它是开源的,它有广泛的用途(例如...【详细内容】
2019-05-17 Python  点击:(9)  评论:(0)  加入收藏
Python是一种计算机程序设计语言,它是一种动态的、面向对象的脚本语言。它是一种跨平台的,可以运行在 Windows,Mac和 Linux/Unix系统上。在日常使用中需要对大量数据进行数据分...【详细内容】
2019-05-16 Python,数据库  点击:(15)  评论:(0)  加入收藏
有着20年编程经验的资深程序员,以自己多年来的经历,总结出程序员的编程语言切换规律,做了一个主流编程语言的进阶流程图,展示不同编程语言之间的承接关系、程序员选择编程语言...【详细内容】
2019-05-16 编程  点击:(11)  评论:(0)  加入收藏
CDA数据分析研究院出品介绍你是否曾经处理过具有一千多个特征的数据集?5万多个特征呢?我曾经有过,让我告诉你这是一项非常具有挑战性的任务,特别是如果你不知道从哪里开始的时候...【详细内容】
2019-05-15 Python  点击:(22)  评论:(0)  加入收藏
要完全理解透生成器,需要我们先掌握三个概念: 可迭代对象(Iterable) 迭代器(Iterator) 迭代(Iteration)放一张图来理解,来自这里 额外提到了容器(container),说的是我们的集合类...【详细内容】
2019-05-15 Python  点击:(10)  评论:(0)  加入收藏
进程和线程什么是进程?进程就是正在运行的程序, 一个任务就是一个进程, 进程的主要工作是管理资源, 而不是实现功能什么是线程?线程的主要工作是去实现功能, 比如执行计算....【详细内容】
2019-05-14 python  点击:(11)  评论:(0)  加入收藏
一、选取节点常用的路劲表达式:<img src="https://pic4.zhimg.com/v2-0ea5d1dba9a1cf0c04695edbcfbc248b_b.jpg" data-caption="" data-size="normal" data-rawwidth="681"...【详细内容】
2019-05-09 Python,Xpath  点击:(14)  评论:(0)  加入收藏
加密你的数据并使其免受攻击者的攻击。-- Moshe Zadka(作者)密码学俱乐部的第一条规则是:永远不要自己发明密码系统。密码学俱乐部的第二条规则是:永远不要自己实现密码系统:在现...【详细内容】
2019-05-09 Python  点击:(11)  评论:(0)  加入收藏
推荐资讯
相关文章
栏目更新
栏目热门
'); })();