中企动力 > 头条 > python中类

网站性能检测评分

注:本网站页面html检测工具扫描网站中存在的基本问题,仅供参考。

python中类

看完这篇文章还不懂Python中的闭包,请拍死小编 推广视频课程

img

阿巍

关注

Python作为一门编程语言,被昵称为“胶水语言”,更被热爱它的程序员誉为“最美丽的”编程语言。从云端、客户端,到物联网终端,python应用无处不在。

Python中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西。

闭包的概念

我们尝试从概念上去理解一下闭包。

在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。—— 维基百科

用比较容易懂的人话说,就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包。看例子。

支持将函数当成对象使用的编程语言,一般都支持闭包。比如Python, JavaScript。

如何理解闭包

闭包存在有什么意义呢?为什么需要闭包?

我个人认为,闭包存在的意义就是它夹带了外部变量(私货),如果它不夹带私货,它和普通的函数就没有任何区别。同一个的函数夹带了不同的私货,就实现了不同的功能。其实你也可以这么理解,闭包和面向接口编程的概念很像,可以把闭包理解成轻量级的接口封装。

接口定义了一套对方法签名的约束规则。

在这个例子里,我们想要一个给content加tag的功能,但是具体的tag_name是什么样子的要根据实际需求来定,对外部调用的接口已经确定,就是add_tag(content)。如果按照面向接口方式实现,我们会先把add_tag写成接口,指定其参数和返回类型,然后分别去实现a和b的add_tag。

但是在闭包的概念中,

add_tag

就是一个函数,它需要

tag_name

content

两个参数,只不过

tag_name

这个参数是打包带走的。所以一开始时就可以告诉我怎么打包,然后带走就行。

上面的例子不太生动,其实在我们生活和工作中,闭包的概念也很常见。比如说手机拨号,你只关心电话打给谁,而不会去纠结每个品牌的手机是怎么实现的,用到了哪些模块。再比如去餐馆吃饭,你只要付钱就可以享受到服务,你并不知道那桌饭菜用了多少地沟油。这些都可以看成闭包,返回来的是一些功能或者服务(打电话,用餐),但是这些功能使用了外部变量(天线,地沟油等等)。

你也可以把一个类实例看成闭包,当你在构造这个类时,使用了不同的参数,这些参数就是闭包里的包,这个类对外提供的方法就是闭包的功能。但是类远远大于闭包,因为闭包只是一个可以执行的函数,但是类实例则有可能提供很多方法。

何时使用闭包

其实闭包在Python中很常见,只不过你没特别注意这就是一个闭包。比如Python中的装饰器Decorator,假如你需要写一个带参数的装饰器,那么一般都会生成闭包。

为什么?因为Python的装饰器是一个固定的函数接口。它要求你的装饰器函数(或装饰器类)必须返回这样一种接口,接受一个函数并返回一个函数:

那么如果你的装饰器如果带参数呢?那么你就需要在原来的装饰器上再包一层,用于接收这些参数。这些参数(私货)传递到内层的装饰器里后,闭包就形成了。所以说当你的装饰器需要自定义参数时,一般都会形成闭包。(类装饰器例外)

# 不用@的写法如下# hello = html_tag('b')(hello)# html_tag('b') 是一个闭包,它接受一个函数,并返回一个函数print hello() # Hello Toby!print hello('world') # Hello world!

再深入一点

其实也不必太深入,理解这上面的概念,很多看起来头疼的代码也不过如此。

下面让我们来了解一下闭包的包到底长什么样子。其实闭包函数相对与普通函数会多出一个

__closure__

的属性,里面定义了一个元组用于存放所有的

cell

对象,每个

cell

对象一一保存了这个闭包中所有的外部变量。

原理就是这么简单。

一步一步带你理解 Python 中的浅复制与深复制 互联网视频课程

img

心酸

关注

Python 中的赋值语句不会对对象进行拷贝,仅仅是将变量名指向对象。对于不可修改的对象来说,这种机制不会影响我们日常的使用。但是,对于可修改的对象,你偶尔可能需要对该对象做一个真正的复制。何为真正的复制?就是修改拷贝来的对象不会影响原来的对象。

Python 中内置的可修改的集合类对象,比如列表、字典、集合等,可以直接使用对应的工厂方法进行拷贝。

需要注意的是,对于复合类型的对象,比如列表、字典、集合等,复制有浅复制与深复制两种类型。

浅复制意味着新建一个对象,但是其子元素仍然指向的对应原对象的子对象。也就是说,这只会对原对象进行一层的拷贝,而不会递归的对子对象也进行拷贝。

深复制则会递归的对子对象进行拷贝。

如果上面的看不懂,没关系,我们通过一个个例子来搞清楚。

浅复制

现在下面这个列表,我们使用 list() 方法对它进行复制

这意味着 xs 和 ys 是两个不同的对象。我们可以看看它们的值

为了证明 xs 与 ys 确实不同,我们可以做个小实验。添加一个子列表到 xs 中,然后看会不会影响 ys 。

与预期相同,如果只是修改原对象这一层,确实不对对 ys 造成影响。

但是由于是浅复制,ys 中的子元素并非是 xs 子元素的拷贝,仅仅是 xs 子对象的引用。因此,如果你修改 xs 中的子元素,ys 中的子元素同样会被修改。

看起来我们只是修改了 xs 中的子元素,实则 ys 中的子元素也被修改了,这是由于浅复制的缘故。

现在我们大概清楚了浅复制与深复制的区别,但是还有两个问题待解决:

一是,对于内置的集合类对象,我们怎么进行深复制?

二是,对于任意的类,又该如何进行浅复制与深复制?

答案是标准库中的 copy 模块,其提供了简单的方法对对象进行浅复制或深复制。

深复制

这次我们仍然使用上面的例子,不同的是,我们使用 copy 模块中的 deepcopy() 方法来进行复制。

还是先看看各自的值

这次,因为 zs 对 xs 进行了深复制,即不仅拷贝了 xs 本身,还对它的子对象都进行了递归的拷贝,所以,如果我们再次修改 xs 的子元素时,并不会对 zs 造成影响。我们来试一试

果然,与预期一致。

顺便说下,使用 copy 模块中的 copy() 方法可以对对象进行浅复制。平时开发需要浅复制的时候,你可以使用该方法,但如果碰到内置的集合类对象,比如列表、字典、集合等的时候,使用对应的工厂方法如 list() ,dict() ,set() 等更 Pythonic 一些。

对任意对象进行复制

现在有一个点类 Point ,如下

其中,__repr__ 方法可以让我们更方便的查看对象的信息。需要注意的是,f'...' 这种格式化字符串的方式在 Python 3.6 才支持,如果是 Python 2 或者 3.6 以前的版本可以使用 '...' % (v1[, v2, ...]) 的方式。

现在我们创建一个点对象并进行浅复制

查看两个对象的信息

和预期一致。

需要注意的是,因为类的两个成员 x 和 y 都是简单类型,这里是整型,所以这里浅复制与深复制没有任何差别。后面我会扩展这个例子。

现在我要定义一个矩形类,其类成员将会使用到上面的点类。

首先我们先试一下浅复制

看下各自的信息

记得我们上面修改列表的浅复制与深复制的例子吗?这里,我要类似的实验。我们修改 rect 的成员,不出意外的话,srect 也会改变。

果然。

下面,我会进行深复制,然后再类似的修改

这次,深复制出来的 drect 对象与 srect 才能说是“真正不同”的对象。

copy 模块中还有其它很多用法,比如定义对象的 __copy__() 和 __deepcopy__() 方法可以自定义对象的浅复制与深复制行为等。这不是本文的重心,有兴趣的可以去看相应的文档 https://docs.python.org/3/library/copy.html 。

小结

* 浅复制不会克隆子对象,所以,复制出来的对象和原对象并非完全不想关。

* 深复制会递归的克隆子对象,所以,复制出来的对象和原对象完全不相关,但是深复制比浅复制会慢一些。

* 使用 copy 模块你可以复制任何类,不管是浅复制还是深复制。

巩固

从网上找了组图片(侵删),让大家加深下认识

首先是赋值

然后是浅复制

最后是深复制

分享 Python 3.6 中常用的几个新特性 互联网视频课程

img

安吉拉

关注

Python 3.6 发布已经有段时间了,不少新特性应该被很多开发者用在了日常的开发中,这篇文章主要介绍下自己在开发中常用到的几个。

增加数字可读性

在定义数字的时候,有一个语法特性可以让数字可读性增强,即你在数字之间可以按照自己的喜好添加一个下滑线,就像这样

这样书写方式与之前的并无区别

语义其实是一样的,也就是定义了一个值为 100000 的变量。

当然,对于十六进制与二进制也是支持的

这个小修改也恰好体现了 Python 之禅的 Readability counts (可读性很重要)这一点吧。

详情请看 PEP 515 。

新的格式化字符串的方法

Python 3.6 中添加了另一种格式化字符串的方法,这个方法允许你在字符串中直接嵌入表达式。下面有两个例子,可以看看感受一下

另外,和之前的 str.format() 格式化字符串的方法类似,也可以用特定的格式说明符说明格式化的方式。比如我们想用十六进制的形式格式化一个十进制的数,可以这样

相比于之前的 '...' % (val1[, val2, ...]) 以及 str.format() ,这种方式不管是书写上还是可读性上,感觉都方便了不少,所以如果你在 Python 3.6 中做开发的话,我十分推荐这种方式。

详情请看 PEP 498 。

变量的类型注解

从 Python 3.5 开始,你可以给函数添加类型注解

在 Python 3.6 中,你也可以给独立的变量作类型注解,就像给函数中形式参数作注解差不多

需要注意的是,Python 的类型注解并不会在运行中执行相应的检查

也就是说,如果你定义了一个整形的变量,而你却赋值给它一个字符串,Python 并不会报错,对于函数的参数以及返回值的注解也是这样的。个人觉得这更像是编程时的一个辅助措施。

如果你打算严格执行注解的要求,你可以使用 python/mypy 工具进行检查。(使用方法请看 https://github/python/mypy )

详情请看 PEP 526 。

其它值得注意的变化

新增加的模块中有一个叫做 secrets ,可以较为方便的提供密码学安全的随机串,我之前的一篇文章《玩转 Python 中的随机数》的后面部分对这个模块进行了一些简单的说明,有兴趣的朋友可以去看一下。

Python 3.6 中不仅仅是新特性的添加,也有性能的提升,比如字典类型相比之前更快,并且节省了 20% 到 25% 的空间。

当然,这个版本带来的变化肯定不止上面这么一些,我例举出来的都是一些常用的点。如果想看全面的说明,可以去官网( https://docs.python.org/3.6/whatsnew/3.6.html )看看。

上面的 PEP 515 、PEP 498 、PEP 526 的具体信息都可以在上面的地址中看到。

Python代码风格:PEP8规则 笔记 推广视频课程

img

幼蓉

关注

Python程序设计的代码风格应该遵循PEP8规则:

一、代码布局

1、缩进:

每级缩进4个空格(不用Tab,更不空格Tab混用)

1、续行应该与其包裹元素对齐,要么使用圆括号、方括号和花括号内的隐式行连接来垂直对齐,要么使用悬挂式缩进对齐。当使用悬挂缩进时,应该考虑到第一行不应该有参数,以及使用缩进以区分自己是续行。

2、缩进4个空格的规则对于续行是可选的。

3、当 if 语句的条件部分长到需要换行写的时候,注意可以在两个字符关键字的连接处(比如 if ),增加一个空格,再增加一个左括号来创造一个4空格缩进的多行条件。这会与 if 语句内同样使用4空格缩进的代码产生视觉冲突。PEP没有明确指明要如何区分i发的条件代码和内嵌代码。可使用的选项包括但不限于下面几种情况:

4、(可以参考下面关于是否在二进制运算符之前或之后截断的讨论)

在多行结构中的大括号/中括号/小括号的右括号可以与内容对齐单独起一行作为最后一行的第一个字符,如:

或者也可以与多行结构的第一行第一个字符对齐,如:

2、Tab还是空格?

空格是被首先推荐的缩进方式。

Tab应该只在现有代码已经使用tab进行缩进的情况下使用,以便和现有代码保持一致。

Python 3不允许再同一个代码块中Tab和空格混合使用。

混合使用制表符和空格缩进的Python2代码应该统一转成空格。

使用命令行运行Python 2时,使用-t选项,会出现非法混用tab和空格的警告。当使用-tt选项时,这些警告会变成错误。强烈推荐使用这些选项!

3、最大行长

每行最大长度79个字符。

对于连续大段的文字(比如文档字符串(docstring)或注释),每行应该被限制在72个字符长度内。

Python标准库比较传统,将行长限制在79个字符以内(文档字符串/注释为72个字符)。

一种推荐的换行方式是利用Python圆括号、方括号和花括号中的隐式续行。长行可以通过在括号内换行来分成多行。应该最好加上反斜杠来区别续行。

有时续行只能使用反斜杠才。例如,较长的多个 with 语句不能采用隐式续行,只能接受反斜杠表示换行:

另一个这样的例子是assert语句。要确保续行的缩进适当。

在二元运算符之前应该换行吗?

遵循数学的传统能产出更多可读性高的代码:

4、空行

顶层函数和类的定义,前后用两个空行隔开。

类里的方法定义用一个空行隔开。

相关的功能组可以用额外的空行(尽量少地)隔开。一堆相关的单行代码之间的空白行可以省略(例如,一组虚拟实现 dummy implementations)。

在函数中使用空行来区分逻辑段(尽量少地)。

Python接受control-L(即^L)换页符作为空格;许多工具把这些字符当作页面分隔符,所以你可以在文件中使用它们来分隔相关段落。请注意,一些编辑器和基于Web的代码阅读器可能无法识别control-L为换页,将在其位置显示另一个字形。

5、源文件编码

Python核心发布版本中的代码总是以UTF-8格式编码(或者在Python2中用ASCII编码)。

使用ASCII(Python 2)或者UTF-8(Python 3)的文件不应该添加编码声明。

在标准库中,只有用作测试目的,或者注释或文档字符串需要提及作者名字而不得不使用非ASCII字符时,才能使用非默认的编码。否则,在字符串文字中包括非ASCII数据时,推荐使用\x, \u, U或N等转义符。

对于Python 3.0及其以后的版本中,标准库遵循以下原则(参见PEP 3131):Python标准库中的所有标识符都必须只采用ASCII编码的标识符,在可行的条件下也应当使用英文词(很多情况下,使用的缩写和技术术语词都不是英文)。此外,字符串文字和注释应该只包括ASCII编码。只有两种例外:

(a) 测试情况下为了测试非ASCII编码的特性

(b) 作者名字。作者名字不是由拉丁字母组成的也必须提供一个拉丁音译名。

鼓励面向全球的开源项目都采用类似的原则。

6、导入

1、imports应该分行写,而不是都写在一行,例如:

这样写也是可以的:

导入(import)始终在文件的顶部,在模块注释和文档字符串之后,在模块全局变量和常量之前。

导入顺序如下:

imports应该按照下面的顺序分组来写:

1、标准库imports

2、相关第三方imports

3、本地应用/库的特定imports

不同组的imports之前用空格隔开。

将任何相关的 __all__ 说明(specification)放在imports之后。

推荐使用绝对(absolute)imports,因为这样通常更易读,在import系统没有正确配置的情况下,也会有更好的表现(或者至少会给出错误信息):

在绝对路径比较长的情况下,也可以使用相对导入:

Python 3中已经禁止隐式的相对导入。

导入类的方法,通常可以这样写:

如果和本地命名的拼写产生了冲突,应当使用绝对导入:

禁止使用通配符导入。

通配符导入(from import *)应该避免,因为它不清楚命名空间有哪些名称存,混淆读者和许多自动化的工具。唯一的例外是重新发布对外的API时可以考虑使用。

7、模块中前后具有双下划线的变量名

像__all__ , __author__ , __version__ 等这样的模块中的变量名(也就是名字里有两个前缀下划线和两个后缀下划线),应该放在文档字符串的后面,以及除from __future__ 之外的import表达式前面。Python要求将来在模块中的导入,必须出现在除文档字符串之外的其他代码之前。

比如:

二、字符串引号

Python中单引号字符串和双引号字符串都是相同的。注意尽量避免在字符串中的反斜杠以提高可读性。

根据PEP 257, 三个引号都使用双引号。

三、表达式和语句中的空格

在下列情况下,避免使用无关的空格:

1、紧跟在小括号,中括号或者大括号后。

2、紧贴在逗号、分号或者冒号之前。

3、然而,冒号在切片中就像二元运算符,在两边应该有相同数量的空格(把它当做优先级最低的操作符)。在扩展的切片操作中,所有的冒号必须有相同的间距。例外情况:当一个切片参数被省略时,空格就被省略了。

4、紧贴在函数参数的左括号之前。

5、紧贴索引或者切片的左括号之前。

6、为了和另一个赋值语句对齐,在赋值运算符附件加多个空格。

其他建议

1、避免在尾部添加空格。因为尾部的空格通常都看不见,会产生混乱:比如,一个反斜杠后面跟一个空格的换行符,不算续行标记。有些编辑器不会保留尾空格,并且很多项目(像CPython)在pre-commit的挂钩调用中会过滤掉尾空格。

总是在二元运算符两边加一个空格:赋值(=),增量赋值(+=,-=),比较(==,,!=,,=,in,not,in,is,is not),布尔(and, or, not)。

如果使用具有不同优先级的运算符,请考虑在具有最低优先级的运算符周围添加空格。有时需要通过自己来判断;但是,不要使用一个以上的空格,并且在二元运算符的两边使用相同数量的空格。

2、在指定函数 关键字参数 或者 默认参数 值的时候,不要在=附近加上空格。

3、功能型注释应该使用冒号的一般性规则,并且在使用 ->的时候要在两边加空格。(参考下面的功能注释得到能够多信息)

4、当给有类型备注的参数赋值的时候,在=两边添加空格(仅针对那种有类型备注和默认值的参数)。

5、复合语句(同一行中的多个语句)通常是不允许的。

6、虽然有时候将小的代码块和 if/for/while 放在同一行没什么问题,多行语句块的情况不要这样用,同样也要避免代码行太长!

四、注释

与代码自相矛盾的注释比没注释更差。修改代码时要优先更新注释!

注释是完整的句子。如果注释是断句,首字母应该大写,除非它是小写字母开头的标识符(永远不要修改标识符的大小写)。

如果注释很短,可以省略末尾的句号。注释块通常由一个或多个段落组成。段落由完整的句子构成且每个句子应该以点号(后面要有两个空格)结束,并注意断词和空格。

非英语国家的程序员请用英语书写你的注释,除非你120%确信代码永远不会被不懂你的语言的人阅读。

1、注释块

注释块通常应用在代码前,并和这些代码有同样的缩进。每行以 '# '(除非它是注释内的缩进文本,注意#后面有空格)。

注释块内的段落用仅包含单个 '#' 的行分割。

2、行内注释

慎用行内注释(Inline Comments) 节俭使用行内注释。 行内注释是和语句在同一行,至少用两个空格和语句分开。行内注释不是必需的,重复罗嗦会使人分心。

不推荐:

但是有时,很有必要:

加了以后对理解代码很有帮助的情况下,关键处才加。

3、文档字符串

文档字符串的标准参见:PEP 257。

为所有公共模块、函数、类和方法书写文档字符串。非公开方法不一定有文档字符串,建议有注释(出现在 def 行之后)来描述这个方法做什么。

更多参考:PEP 257 文档字符串约定。注意结尾的 """ 应该单独成行,例如:

单行的文档字符串,结尾的 """ 在同一行。

4、版本标签

如果你必须在源文件中包含git、Subversion、CVS或RCS crud信息,放置在模块的文档字符串之后,任何其他代码之前,上下各用一个空行:

五、命名规范

Python库的命名约定有点混乱,不可能完全一致。但依然有些普遍推荐的命名规范的。新的模块和包 (包括第三方的框架) 应该遵循这些标准。对不同风格的已有的库,建议保持内部的一致性。

1、最重要的原则

用户可见的API命名应遵循使用约定而不是实现。

2、命名风格

以下是常见的命名方式:

b(单个小写字母)

B(单个大写字母)

lowercase (小写字母)

lower_case_with_underscores (使用下划线分隔的小写字母)

UPPERCASE( 大写字母)

UPPER_CASE_WITH_UNDERSCORES (使用下划线分隔的大写字母)

CapitalizedWords(首字母大写的单词串或驼峰缩写)

注意: 使用大写缩写时,缩写使用大写字母更好。故 HTTPServerError 比 HttpServerError 更好。

mixedCase(不同于首字母大写,第一个单词的首字母小写)

Capitalized_Words_With_Underscores(带下划线,首字母大写,巨丑无比)

还有一种风格使用短前缀分组名字。这在Python中不常用, 但出于完整性提一下。例如,os.stat()返回的元组有st_mode, st_size, st_mtime等等这样的名字(与POSIX系统调用结构体一致)。

X11库的所有公开函数以X开头, Python中通常认为是不必要的,因为属性和方法名有对象作前缀,而函数名有模块名为前缀。

下面讲述首尾有下划线的情况:

_single_leading_underscore:(单前置下划线): 弱内部使用标志。 例如"from M import " 不会导入以下划线开头的对象。

single_trailing_underscore_(单后置下划线): 用于避免与 Python关键词的冲突。 例如:

__double_leading_underscore(双前置下划线): 当用于命名类属性,会触发名字重整。 (在类FooBar中,__boo变成 _FooBar__boo)。

__double_leading_and_trailing_underscore__(双前后下划线):用户名字空间的魔法对象或属性。例如:__init__ , __import__ or __file__,不要自己发明这样的名字。

3、命名约定规范

避免采用的名字:

决不要用字符'l'(小写字母el),'O'(大写字母oh),或 'I'(大写字母eye) 作为单个字符的变量名。一些字体中,这些字符不能与数字1和0区别。用'L' 代替'l'时。

包和模块名:

模块名要简短,全部用小写字母,可使用下划线以提高可读性。包名和模块名类似,但不推荐使用下划线。

模块名对应到文件名,有些文件系统不区分大小写且截短长名字,在 Unix上不是问题,但当把代码迁移到 Mac、Windows 或 DOS 上时,就可能是个问题。当然随着系统的演进,这个问题已经不是经常出现。

另外有些模块底层用C或C++ 书写,并有对应的高层Python模块,C/C++模块名有一个前置下划线 (如:_socket)。

类名:

遵循CapWord。

接口需要文档化并且可以调用时,可能使用函数的命名规则。

注意大部分内置的名字是单个单词(或两个),CapWord只适用于异常名称和内置的常量。

异常名:

如果确实是错误,需要在类名添加后缀 "Error"。

全局变量名:

变量尽量只用于模块内部,约定类似函数。

对设计为通过 "from M import " 来使用的模块,应采用 __all__ 机制来防止导入全局变量;或者为全局变量加一个前置下划线。

函数名:

函数名应该为小写,必要时可用下划线分隔单词以增加可读性。 mixedCase(混合大小写)仅被允许用于兼容性考虑(如: threading.py)。

函数和方法的参数:

实例方法第一个参数是 'self'。

类方法第一个参数是 'cls'。

如果函数的参数名与保留关键字冲突,通常在参数名后加一个下划线。

方法名和实例变量:

同函数命名规则。

非公开方法和实例变量增加一个前置下划线。

为避免与子类命名冲突,采用两个前置下划线来触发重整。类Foo属性名为__a, 不能以 Foo.__a访问。(执著的用户还是可以通过Foo._Foo__a。) 通常双前置下划线仅被用来避免与基类的属性发生命名冲突。

常量:

<...

Python中不可变类型和可变类型详解 行业视频课程

img

潘乐巧

关注

Python中不可变类型和可变类型详解

1. 什么是不可变类型

变量对应的值中的数据是不能被修改,如果修改就会生成一个新的值从而分配新的内存空间。

不可变类型:

数字(int,long,float)

布尔(bool)

字符串(string)

元组(tuple)

案例一:

结果:

两个不同的数字

案例二:

结果:

两个不同的数字

2. 什么是不可变类型

变量对应的值中的数据可以被修改,但内存地址保持不变。

不可变类型:

列表(list)

字典(dict)

案例一:

结果:

两个相同的数字

['刘备', '关羽', '张飞', '赵云']

案例二:

结果:

两个相同的数字

{'name': '刘备', 'age': 20}

3. 为什么生成以上两种情况

因为python中的值是通过引用(地址值)传递的,不可变类型的值一旦被修改后会创建一个内存空间并且生成新的地址值,可变类型的值会在原内存空间中被修改。

4. 不可变类型执行过程和内存分析

5. 不可变类型执行过程和内存分析

感谢源码时代教学讲师提供此文章!

本文为原创文章,转载请注明出处!

Python程序员最常犯的10个错误,你中招了吗? 营销视频课程

img

Phoebe

关注

大数据文摘作品

编译:什锦甜、Gao Ning、小鱼

Python简介

Python是一种具有动态语义的、面向对象的解释型高级编程语言。因其内置了高级数据结构,并支持动态类型和动态绑定,使用Python进行快速应用程序开发十分便利。同时作为一门脚本语言,它兼容部分现有的组件和服务。Python还支持模块和各种库的扩展,有助于实现模块化编程和提高代码复用率。

关于本文

刚接触这门语言的新手可能会对Python简洁灵活的语法有些不适应,或是低估了Python强大的性能。鉴于此,本文列出了Python开发人员常犯的10个小错误,资深程序猿也难免会中招哦。

本文供Python高级开发人员参考,Python小白可以参考下面这篇文章:

http://onlamp/pub/a/python/2004/02/05/learn_python.html

常见错误1:滥用表达式作为函数参数的默认值

Python允许开发者指定函数参数的默认值,这也是Python的一大特色,但当默认值可变时,可能会给开发者带来一些困扰。例如下面定义的函数:

>>> def foo(bar=[]): # bar is optional and defaults to [] if not specified... bar.append("baz") # but this line could be problematic, as we'll see...... return bar

看出bug了吗?那就是在每次调用函数前没有对可变参数进行赋值,而认为该参数就是默认值。比如上面的代码,有人可能期望在反复调用foo()时返回'baz',以为每次调用foo()时,bar的值都为[],即一个空列表。

但是,让我们来看看代码运行结果:

>>> foo()["baz"]>>> foo()["baz", "baz"]>>> foo()["baz", "baz", "baz"]

嗯?为什么每次调用foo()后会不断把"baz"添加到已有的列表,而不是新建一个新列表呢?答案就是,函数参数的默认值仅在定义函数时执行一次。因此,仅在第一次定义foo()时,bar初始化为默认值(即空列表),此后,每次调用foo()函数时,参数bar都是第一次初始化时生成的列表。

常见的解决方案:

>>> def foo(bar=None):... if bar is None: # or if not bar:... bar = []... bar.append("baz")... return bar...>>> foo()["baz"]>>> foo()["baz"]>>>foo()["baz"]

常见错误2:错误地使用类变量

代码示例:

>>> class A(object):... x = 1...>>> class B(A):... pass...>>> class C(A):... pass...>>> print A.x, B.x, C.x1 1 1

运行结果没问题。

>>> B.x = 2>>> print A.x, B.x, C.x1 2 1

结果也正确。

>>> A.x = 3>>> print A.x, B.x, C.x3 2 3

什么鬼?我们只改变了A.x.,为什么C.x 也变了?

在Python中,类变量是以字典形式进行内部处理,遵循方法解析顺序(Method Resolution Order ,MRO)。因此,在上述代码中,因为在类C中没有找到属性x,它就会从父类中查找x的值(尽管Python支持多重继承,但上述代码只存在一个父类A)。换句话说,C没有独立于类A的属于自己的x。因此,C.x实际上指的是A.x。除非处理得当,否则就会导致Python出现错误。

如果想更深入了解Python的类特性,请戳:

https://toptal/python/python-class-attributes-an-overly-thorough-guide

常见错误3:错误指定异常代码块的参数

假设你有如下代码:

>>> try:... l = ["a", "b"]... int(l[2])... except ValueError, IndexError: # To catch both exceptions, right?... pass...Traceback (most recent call last):File "", line 3, in IndexError: list index out of range

这里的问题是except语句不接受以这种方式指定的异常列表。在Python2.x中,except Exception语句中变量e可用来把异常信息绑定到第二个可选参数上,以便进一步查看异常的情况。因此,在上述代码中,except语句并没有捕捉到IndexError异常;而是将出现的异常绑定到了参数IndexError中。

想在一个except语句同时捕捉到多个异常的正确方式是,将第一个参数指定为元组,并将要捕捉的异常类型都写入该元组中。为了方便起见,可以使用as关键字,Python 2 和Python 3都支持这种语法格式:

>>> try:... l = ["a", "b"]... int(l[2])... except (ValueError, IndexError) as e: ... pass...>>>

常见错误4:错误理解Python中变量的作用域

Python变量作用域遵循LEGB规则,LEGB是Local,Enclosing,Global,Builtin的缩写,分别代表本地作用域、封闭作用域、全局作用域和内置作用域,这个规则看起来一目了然。事实上,Python的这种工作方式较为独特,会导致一些编程错误,例如:

>>> x = 10>>> def foo():... x += 1... print x...>>> foo()Traceback (most recent call last):File "", line 1, in File "", line 2, in fooUnboundLocalError: local variable 'x' referenced before assignment

问题出在哪?

上面的错误是因为在作用域内对变量赋值时,Python自动将该变量视为该作用域的本地变量,并对外部定义的同名变量进行了屏蔽。因此,原本正确的代码,在某个函数内部添加了一个赋值语句后,却意外收到了UnboundLocalError的报错信息。

关于UnboundLocalError更多内容请戳:

https://docs.python.org/2/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value

在使用列表时,Python程序员更容易掉入此类陷阱,例如:

>>> lst = [1, 2, 3]>>> def foo1():... lst.append(5) # This works ok......>>> foo1()>>> lst[1, 2, 3, 5]>>> lst = [1, 2, 3]>>> def foo2():... lst += [5] # ... but this bombs!...>>> foo2()Traceback (most recent call last):File "", line 1, in File "", line 2, in fooUnboundLocalError: local variable 'lst' referenced before assignment

奇怪,为什么foo1正常运行,而foo2崩溃了呢?

原因和上一个案例中出现的问题相似,但这里的错误更加细微。函数foo1没有对变量lst进行赋值操作,而函数foo2有赋值操作。

首先, lst += [5]是lst = lst + [5]的缩写形式,在函数foo2中试图对变量lst进行赋值操作(Python将变量lst默认为本地作用域的变量)。但是,lst += [5]语句是对lst变量自身进行的赋值操作(此时变量lst的作用域是函数foo2),但是在函数foo2中还未声明该变量,所以就报错啦!

常见错误5:在遍历列表时修改列表

下面代码中的错误很明显:

>>> odd = lambda x : bool(x % 2)>>> numbers = [n for n in range(10)]>>> for i in range(len(numbers)):... if odd(numbers[i]):... del numbers[i] # BAD: Deleting item from a list while iterating over it...Traceback (most recent call last):File "", line 2, in IndexError: list index out of range

有经验的程序员都知道,在Python中遍历列表或数组时不应该删除该列表(数组)中的元素。虽然上面代码的错误很明显,但是在编写复杂代码时,资深程序员也难免会犯此类错误。

幸好Python集成了大量经典的编程范式,如果运用得当,可以大大简化代码并提高编程效率。简单的代码会降低出现上述bug的几率。列表解析式(list comprehensions)就是利器之一,它将完美避开上述bug,解决方案如下:

>>> odd = lambda x : bool(x % 2)>>> numbers = [n for n in range(10)]>>> numbers[:] = [n for n in numbers if not odd(n)] # ahh, the beauty of it all>>> numbers[0, 2, 4, 6, 8]

更多有关列表解析式的详细内容,请戳:https://docs.python.org/2/tutorial/datastructures.html#tut-listcomps

常见错误6:不理解Python闭包中的变量绑定

代码示例:

>>> def create_multipliers():... return [lambda x : i * x for i in range(5)]>>> for multiplier in create_multipliers():... print multiplier(2)...

你以为运行结果会是:

02468

但实际输出结果是:8

8888

惊不惊喜!

这种情况是由于Python延迟绑定(late binding)机制造成的,也就是说只有在内部函数被调用时才会搜索闭包中变量的值。所以在上述代码中,每次调用create_multipliers()函数中的return函数时,会在附近作用域中查询变量i的值。(此时,return中循环已结束,所以i值为4)。

常见解决方案:

>>> def create_multipliers():... return [lambda x, i=i : i * x for i in range(5)]...>>> for multiplier in create_multipliers():... print multiplier(2)...02468

没错!我们利用了匿名函数lambda的默认参数来生成结果序列。有人觉得这种用法很简洁,有人会说它很巧妙,还有人会觉得晦涩难懂。如果你是Python开发人员,那么深刻理解上述语法对你而言非常重要。

常见错误7:模块之间出现循环依赖

假设你有两个文件,分别是a.py和b.py,两者相互导入,如下所示:

a.py模块中的代码:

import bdef f():return b.xprint f()

b.py模块中的代码:

import ax = 1def g():print a.f()

首先,我们尝试导入a.py:

>>> import a1

运行结果正确!这似乎有点出人意料,因为我们在这里进行循环导入,应该会报错呀!

答案是,在Python中如果仅存在一个循环导入,程序不会报错。如果一个模块已经被导入,Python会自动识别而不会再次导入。但是如果每个模块试图访问其他模块不同位置的函数或变量时,那么Error又双叒叕出现了。

回到上面的示例中,当导入a.py模块时,程序可以正常导入b.py模块,因为此时b.py模块未访问a.py中定义任何的变量或函数。b.py模块仅引用了a.py模中的a.f()函数。调用的a.f()函数隶属于g()函数,而a.py或b.py模块中并没有调用g()函数。所以程序没有报错。

但是,如果我们在未导入a.py模块之前先导入b.py模块,结果会怎样?

>>> import bTraceback (most recent call last):File "", line 1, in File "b.py", line 1, in import a File "a.py", line 6, in print f() File "a.py", line 4, in f return b.xAttributeError: 'module' object has no attribute 'x'

报错了!问题在于,在导入b.py的过程中,它试图导入a.py模块,而a.py模块会调用f()函数,f()函数又试图访问b.x变量。但此时,还未对变量b.x进行定义,所以出现了AttributeError异常。

稍微修改下b.py,即在g()函数内部导入a.py就可以解决上述问题。

修改后的b.py:

x = 1def g():

import a # This will be evaluated only when g() is calledprint a.f()

现在我们再导入b.py模块,就不会报错啦!

>>> import b>>> b.g()1 # Printed a first time since module 'a' calls 'print f()' at the end1 # Printed a second time, this one is our call to 'g'

常见错误8:文件命名与Python标准库模块的名称冲突

Python的优势之一就是其集成了丰富的标准库。正因为如此,稍不留神就会在为自己的文件命名时与Python自带标准库模块重名。例如,如果你的代码中有一个名为email.py的模块,恰好就和Python标准库中email.py模块重名了。)

上述问题比较复杂。举个例子,在导入模块A的时候,假如该模块A试图导入Python标准库中的模块B,但你已经定义了一个同名模块B,模块A会错误导入你自定义的模块B,而不是Python标准库中的模块B。这种错误很糟糕,因为程序员很难察觉到是因为命名冲突而导致的。

因此,Python程序员要注意避免与Python标准库模块的命名冲突。毕竟,修改自己模块的名称比修改标准库的名称要容易的多!当然你也可以写一份Python改善建议书(Python Enhancement Proposal,PEP)提议修改标准库的名称。

常见错误9:不熟悉Python2和Python3之间的差异

先来看看foo.py文件中的代码:

import sysdef bar(i):if i == 1: raise KeyError(1) if i == 2: raise ValueError(2)def bad(): e = None try: bar(int(sys.argv[1])) except KeyError as e: print('key error') except ValueError as e: print('value error') print(e)bad()

在Python 2中,上述代码运行正常

$ python foo.py 1key error1$ python foo.py 2value error2

但是在Python 3中运行时:

$ python3 foo.py 1key errorTraceback (most recent call last):File "foo.py", line 19, in bad() File "foo.py", line 17, in bad print(e)UnboundLocalError: local variable 'e' referenced before assignment

什么情况?原来,在Python 3中,在except代码块作用域外无法访问异常对象。(原因是,Python 3会将内存堆栈中的循环引用进行保留,直到垃圾回收...

Python中集合(set)类型的详细解释及操作 营销视频课程

img

薛雅香

关注

一、集合(set)类型的含义:

Set是一个无序不重复元素集,与列表和元组不同,集合是无序的,无法通过数字进行索引。

注意:下面所举例子在python3.6,IDE为pycharm2016.1中通过。

创建集合:用set()函数,或直接赋值。

例子:

x=set('Nike MM')

y=set(['w','a','m','a'])

print(x)

print(y)

输出:

{'M', 'N', 'e', 'k', ' ', 'i'}

{'w', 'm', 'a'}

可以看到,在输出中,是用一对{}号包住,里面重复的元素被去除。

再看一个例子:

s={'11','22','33'}

print(s)

print(type(s))

s={}

{'33', '11', '22'}

在定义不,不能用s={},这关创建的实际上是一个字典类型。

二、有关集合的操作:

1.增加操作

a=set('python')

a.add('why')

print(a)

b=set('python')

b.update('why')

print(b)

{'n', 'p', 'y', 'h', 'o', 't', 'why'}

{'n', 'p', 'y', 'h', 'o', 'w', 't'}

可能看到:add是单个元素的添加,并没有把元素再分拆为单个字符。Update是批量的增加,增加的元素如果是一个字符串(实际上,在Python中字符串也是一个系列),是作为一个系列增加的。在输出结果中,两个函数都是无序的,并且无重复,也非添加到尾部。

2.删除操作(remove,discard,pop)

例子1:

a=set('abcdefghijk')

a.remove('a')

a.remove('w')

输出 :

Traceback (most recent call last):

{'h', 'k', 'e', 'd', 'g', 'c', 'f', 'i', 'b', 'j'}

File "D:/python/temp3.py", line 4, in

KeyError: 'w'

例子2:

a.discard('a')

a.discard('w')

{'f', 'h', 'd', 'e', 'b', 'k', 'i', 'j', 'c', 'g'}

例子3:

b=a.pop()

print(b,type(b))

{'k', 'd', 'h', 'c', 'b', 'j', 'g', 'i', 'e', 'f'}

a

从以上例子可以看到,remove方法删除指定无素,如果要删除的元素的不在集合中,则报错;discard方法删除指定元素,如果要删除物元素不在集合中,则不报错,pop方法删除任意元素,并可将这个元素赋值给一个变量,但集合并没有把这个元素移除。

3.清空(clear)

例子:

a.clear()

set()

4.交集&,并集|,差集-,对称差集^,子集(被包含)<=,父集(包含)>=

a=set(['a','b','c','d','e','f'])

b=set(('d','e','f','g','h','i'))

d=set('def')

print('交集:',a.intersection(b))

print('交集:',a & b)

print('并集:',a.union(b))

print('并集:',a | b)

print('差集:',a.difference(b))

print('差集:',a-b)

#对称差集:

#把两个集合中相同的元素去掉,然后

#两个集合中剩下的元素组成一个新的集合

print('对称差集:',a.symmetric_difference(b) )

print('对称差集:',a ^ b )

print('子集:',a.issubset(d) )

print('子集:',a<=d )

print('父集:',a.issuperset(d) )

print('父集:',a>=d )

交集: {'f', 'e', 'd'}

并集: {'c', 'e', 'd', 'b', 'f', 'a', 'g', 'i', 'h'}

差集: {'a', 'c', 'b'}

对称差集: {'a', 'c', 'g', 'b', 'i', 'h'}

子集: False

父集: True

5.集合的其它一些操作

#如果a和d没有交集,返回True,有则返回False

print(a.isdisjoint(d) ) 输出:False

print(a

print(a>d) 输出:True

print(a!=b) 输出:True

print(a.copy()) 输出:{'f', 'e', 'b', 'a', 'd', 'c'} #复制一个集合

print('a' in a) 输出:True #测试元素是否在集合中

print('a' not in a) 输出:False #测试元素是否不在集合中

print(len(a)) 输出:6 #返回集合的长度

6.集合计算:

(1)

#从a中减去a和b的交集,即从a集合中删除和b集合中相同的元素

a.difference_update(b) 即等于:a=a-b 或a-=b

print(a) 输出:{'a', 'b', 'c'}

(2)

#修改a集合,仅仅保持a与b的交集,如果没有交集,则a变为空集合set()

a.intersection_update(b) 即等于:a=a&b 或a&=b

print(a )

输出:{'e', 'd', 'f'}

(3)

#a集合中增加‘在b集合中除去a和b交集剩下的元素’

a.symmetric_difference_update(b) 即等于:a=a^b 或 a^=b

print(a) 输出:{'i', 'g', 'a', 'c', 'b', 'h'}

一步一步带你理解 Python 中的浅复制与深复制 企业视频课程

img

Tiffany

关注

Python 中的赋值语句不会对对象进行拷贝,仅仅是将变量名指向对象。对于不可修改的对象来说,这种机制不会影响我们日常的使用。但是,对于可修改的对象,你偶尔可能需要对该对象做一个真正的复制。何为真正的复制?就是修改拷贝来的对象不会影响原来的对象。

Python 中内置的可修改的集合类对象,比如列表、字典、集合等,可以直接使用对应的工厂方法进行拷贝。

需要注意的是,对于复合类型的对象,比如列表、字典、集合等,复制有浅复制与深复制两种类型。

浅复制意味着新建一个对象,但是其子元素仍然指向的对应原对象的子对象。也就是说,这只会对原对象进行一层的拷贝,而不会递归的对子对象也进行拷贝。

深复制则会递归的对子对象进行拷贝。

如果上面的看不懂,没关系,我们通过一个个例子来搞清楚。

浅复制

现在下面这个列表,我们使用 list() 方法对它进行复制

这意味着 xs 和 ys 是两个不同的对象。我们可以看看它们的值

为了证明 xs 与 ys 确实不同,我们可以做个小实验。添加一个子列表到 xs 中,然后看会不会影响 ys 。

与预期相同,如果只是修改原对象这一层,确实不对对 ys 造成影响。

但是由于是浅复制,ys 中的子元素并非是 xs 子元素的拷贝,仅仅是 xs 子对象的引用。因此,如果你修改 xs 中的子元素,ys 中的子元素同样会被修改。

看起来我们只是修改了 xs 中的子元素,实则 ys 中的子元素也被修改了,这是由于浅复制的缘故。

现在我们大概清楚了浅复制与深复制的区别,但是还有两个问题待解决:

一是,对于内置的集合类对象,我们怎么进行深复制?

二是,对于任意的类,又该如何进行浅复制与深复制?

答案是标准库中的 copy 模块,其提供了简单的方法对对象进行浅复制或深复制。

深复制

这次我们仍然使用上面的例子,不同的是,我们使用 copy 模块中的 deepcopy() 方法来进行复制。

还是先看看各自的值

这次,因为 zs 对 xs 进行了深复制,即不仅拷贝了 xs 本身,还对它的子对象都进行了递归的拷贝,所以,如果我们再次修改 xs 的子元素时,并不会对 zs 造成影响。我们来试一试

果然,与预期一致。

顺便说下,使用 copy 模块中的 copy() 方法可以对对象进行浅复制。平时开发需要浅复制的时候,你可以使用该方法,但如果碰到内置的集合类对象,比如列表、字典、集合等的时候,使用对应的工厂方法如 list() ,dict() ,set() 等更 Pythonic 一些。

对任意对象进行复制

现在有一个点类 Point ,如下

其中,__repr__ 方法可以让我们更方便的查看对象的信息。需要注意的是,f'...' 这种格式化字符串的方式在 Python 3.6 才支持,如果是 Python 2 或者 3.6 以前的版本可以使用 '...' % (v1[, v2, ...]) 的方式。

现在我们创建一个点对象并进行浅复制

查看两个对象的信息

和预期一致。

需要注意的是,因为类的两个成员 x 和 y 都是简单类型,这里是整型,所以这里浅复制与深复制没有任何差别。后面我会扩展这个例子。

现在我要定义一个矩形类,其类成员将会使用到上面的点类。

首先我们先试一下浅复制

看下各自的信息

记得我们上面修改列表的浅复制与深复制的例子吗?这里,我要类似的实验。我们修改 rect 的成员,不出意外的话,srect 也会改变。

果然。

下面,我会进行深复制,然后再类似的修改

这次,深复制出来的 drect 对象与 srect 才能说是“真正不同”的对象。

copy 模块中还有其它很多用法,比如定义对象的 __copy__() 和 __deepcopy__() 方法可以自定义对象的浅复制与深复制行为等。这不是本文的重心,有兴趣的可以去看相应的文档 https://docs.python.org/3/library/copy.html 。

小结

* 浅复制不会克隆子对象,所以,复制出来的对象和原对象并非完全不想关。

* 深复制会递归的克隆子对象,所以,复制出来的对象和原对象完全不相关,但是深复制比浅复制会慢一些。

* 使用 copy 模块你可以复制任何类,不管是浅复制还是深复制。

巩固

从网上找了组图片(侵删),让大家加深下认识

首先是赋值

然后是浅复制

最后是深复制

img

在线咨询

建站在线咨询

img

微信咨询

扫一扫添加
动力姐姐微信

img
img

TOP