手把手教你学python第十七讲(模块导入的相关知识和爬虫的准备内含深浅拷贝)

图片刷不出来请到https://www.bilibili.com/read/cv334079/和https://www.bilibili.com/read/cv334092?from=articleDetail

str和repr

参考了https://blog.csdn.net/sxingming/article/details/52065242

介绍正式内容之前,先来补充一个repr和str的不同点。以前我们用过__str__和__repr__我直接给一个例子复习一下

现在你们应该是可以看懂错误原因了,我不再多解释了。下面是正确的做法

那么我们来先看一下str和repr在python里的帮助怎么说的,str是一个类对象,repr是python的内置函数。

实际用一下

这里我直接复制了,敬请体谅,因为其实之前我已经写过一遍这篇文章了,但是没有保存,然后。。。所以我已经不想打字了。其实下面就是python官方文档的中文翻译。

什么解释器呢?我们官网下的其实自带cpython的解释器,因为是用c语言编写的,还有其它的比如用java编写的jpython,所谓解释器,其实就是运行.py文件的这个一个东西。关于截图里说的一点浮点数,我尝试了下,发现似乎并没有什么不同的地方。并且元组集合的结果也是一样的。

这里就再顺带提一下前面出现过的evalhttps://www.cnblogs.com/liu-shuai/p/6098246.html。你们就到这个网站直接去看看好吧。我是真的有点火气。

str和repr对于字符串结果的差异也体现出来了

简单来说就像是把最外面的引号去掉的功能。下面简单演示一下eval实现了str和list之间的转化,其它的容器类型类似。直接用list是不行的,用eval是可以的。

模块导入相关的知识

先来补充一个变量的搜索顺序,命名空间和locals(),globals()几个知识点。参考了https://blog.csdn.net/scelong/article/details/6977867和

https://www.cnblogs.com/wanxsb/archive/2013/05/07/3064783.html和

https://www.cnblogs.com/shanys/p/5887023.html

下面说的名字空间就是命名空间

看几个尝试


说明locals()只有在函数里面用有意义,在函数外面直接在模块里面用locals()相当于globals(),globals()在一个模块里的的位置不影响结果。而且globals()会屏蔽掉人为的改动,而globals()则不会,因为其实函数的locals()只是一个拷贝而已,并不会影响函数里变量的值。

闭包也不是例外

如果哪个变量是global,那么它将不会出现在函数的locals()里


这个搜索顺序其实很好理解,我们先来看1,2的先后是怎么体现的。

2和3的顺序怎么体现的呢?

我觉得如果你跟着我一直学到这里,通过对比代码,你是可以理解的。

内置名字空间其实是可以看到的


这还并没有截全。

什么是模块,前面其实也说过。


我们去看个模块,还记得以前有个os模块吗?我们来看一下

这只是截取了一段代码。在安装目录的lib里有很多模块的代码

导入模块有三种方法,这三种前面都已经讲过,这里就简单地演示一下

第一种方法我们适合用模块名比较短的时候,因为每次调用模块的属性或者函数的时候都要打一边模块名字。

我们看一下这种方式对于locals()的影响

看到这里你也应该理解为什么前面必须加上模块名了。这种方法不能也没有必要导入单个函数或者变量

至于报错说的os不是一个pakage是什么意思我们下面会说。还有一点,不知道你注意到没有,前面是有写到变量的。


第二种方法还可以用通配符*来导入所有的函数和变量,我们来看一下对globals()的影响


导入某个具体的函数或者变量,这个函数名或者变量会直接出现在globals()里面,而用通配符*导入会把模块里所有的函数和变量加到globals()里面

不推荐使用,因为很可能两个模块的属性或者函数名有重叠的,就会发生取代现象。比如假如我们有下面两个模块a,b都有t函数

用方法二导入后面的就会取代前面的,这就是globals()里的变量名字重合了,而且第二种方法是不可以用第一种模块的调用格式的。

如果有一些隐私变量,你不想被from a import*导入,你可以在变量或者函数前面加一个_,举个例子


可以看出来这种隐藏方法只能应付from my import*这种格式,其它的方式都还是可以调用到的。看一下globals()就能理解为什么了

第二种方法可能会导致循环导入问题,例子

先后执行a.py,b.py


这是为什么呢?

我们先来看一下from a import x到底是什么一个过程


虽然仅仅是导入了x,但是还是运行了整个a.py然后我们来分析一下,运行b.py的时候,第一行是from a import x,然后就跳到a.py,又遇到第一行from b import y,又跳到b.py又遇到第一行from a import x,就是一个无限循环。但是python开发人员应该是有自己的一套处理这种问题的办法,有内置的异常处理,从而这种无限循环停在和起始相同的地方。开始是from a import x,结束也是在from a import x。

为了这里稍微理解深入一点,我做了以下改动来看看效果。

改动1

b.py没有改变。想一想运行a.py和b.py分别有什么结果呢?

运行a.py为什么会这样呢?前三行是加载了x的,从a.py模块的locals()也能看出来(这里的locals()和globals()没什么区别),第四行from b import y,CPU跳到了b.py,而第二行就是from a import x,我们又跳到了a.py。这里是不是会有小问题?前面不是已经加载了x吗?但是要注意我们执行的脚本是a.py,在a.py的globals()里有,但是在要导入的模块b.py的globals()是没有这个东西的,所以这里仍然会跳到a.py,然后就出现了重复性导入,在黑圈里的语句一样的嘛。

我们稍微来验证一下,修改a.py和b.py如下

这里有一个小细节不知道你们注意到没有,为什么b.py前后是*号?这代表你做了修改但是没有保存,你保存一下就可以了,Ctrl+S或者F5运行一下,运行自动保存嘛,就没有了。

运行一下a,py,结果很长哈,我们用Edit里的Find来找'x',最后结果只找到下面一个红箭头指的地方。



只是为了让你信服b.py的globals()里确实没有'x'这个东西的。下面为了节省空间都把print(globals())去掉了。


那么b.py是个怎么样的过程呢?第二行from a  import x跳到a.py,前三行已经把x导入到了b.py的globals()里了,到第四行from b import y,跳到b.py这时候就不用在第二行跳了,因为已经有了x了,于是就打印了离开b,而我们只从a的第四行跳过来的,又回去打印离开a,a又是从b.py的第二行跳过来的,跳回去执行结束打印离开b。过程就下面这样,红色序号是打印的顺序。

下面这个小改动看着上面这个图应该好分析。


前面也说了这种不建议使用,下面的改动就看看结果,就不展开分析了

改动二


分别运行a.py和b.py


看上去a好像是没有问题的,但是还是有问题,所以不建议使用第二种方式


那么第一种方法表现如何?我认为这里python也是有机制的,如果a.py里出现了import  b,那么后来再遇到import b应该就不会再跳转了,我们先来试验一下这个特性对不对。


果然如此啊,因为进入b和离开b都只打印了一次。



循环嵌套导入的问题是解决了,但是,还是有问题的,



所以还是别随便循环嵌套导入,可能会出现各种问题。当然这些问题不是不能解决的,下面会有解决的办法。

第三种就是用as 后面的新名字代替前面的模块名而已,模块名很长的时候可以考虑,但是新名字别乱取,可能会和别的模块重名的

除了要说三种导入方法之外呢,还有以下三点内容

下面参考了https://www.zhihu.com/question/49136398。和http://blog.konghy.cn/2017/04/24/python-entry-program/。

下面这段话我们看一看就好

如果你学过c语言,应该会理解得更快,因为mian(){}就是一个程序入口。下面这段我们要理解一下

我们先来看一下__name__,我们新启动一次IDLE之后,没有运行任何脚本也就是.py文件时

我们运行下面这样一个脚本文件的时候。

并且是不会显示隐藏的变量_b和_m的。虽然调用是正常的,只是不显示,如果不显示别人也不知道有这些隐藏的东西,就比较隐私嘛


如果不是直接运行a.py文件而是把它作为一个模块导入的时候

打印的就是my了,也就是文件的名字(不包含扩展名)。也就是说

我们来稍微看一下前面的循环嵌套导入会是怎样的一个情况。

运行a.py

用一张简单的图来说就是,红色是前向的顺序,黄色是回去的顺序。可以发现在a.py里运行到import b的时候,就跳到了b.py,然后又跳到a.py里运行,虽然我们运行的是a.py,但是在这个时候,a.py是在b.py里导入的,所以print(__name__)就是a,然后回到b.py里,打印的__name__是b,这很好理解,然后回到a.py里,这是我们运行的那个a.py。所以打印的是__main__。

这有什么用呢?至少我们上面出现的错误,我在a里加这么一句。

就不会报错了,如果你看懂了上面的轨迹,你应该知道为什么不会报错了而且也知道为什么上面会报错。在b.py里加上一个if __name__='__main__'也有一样的效果,原理一样的嘛,就不展示了。__name__='__mian__'通常的用处是拿来做测试,比如说


在运行my.py的时候我们希望这个测试成功打印出来。但是当它作为模块被导入的时候不希望测试的结果打印出来。那么我们就可以用__name__=='__main__'。


当然这个__name__其实是可以改的。可以改为已经有的脚本名字,但是改这个没有太大意义。


你可以这么皮一下,貌似会有用处。

下一个问题搜索路径。什么是搜索路径呢?就类似于上面说过的变量和函数的搜索顺序,如果在路径里,当然路径是有顺序的,都没有找到的话,就会报错。

比如我现在在桌面有一个my.py


我能成功导入吗?

出错了。因为不在sys.path里面,我们可以添加进去

然后我们来看一下搜索顺序是怎么样的。我新创建一个同名的文件,位置如标题栏显示的

注意这里要先Restart一下,因为你新建了一个文件,如果没有运行过它,IDLE

还没有添加它


可以看到列表搜索的顺序应该是从前往后搜索,我就来皮一下

这个sys.path可以改变,你就可以自己改变搜索顺序了,你可以对sys,path为所欲为

。只是需要RESTART一下哦。但是注意一点,你对sys.path的任何改动都不会影响内置的开发人员编写的内部模块,下面并不影响os的导入,但是影响到了我们自己添加的模块my的导入。

并且呢

下面就是包(package)的相关内容,包只是为了管理脚本文件方便一点,分级好一点


下面来演示一下,__init_.py是python规定,而且你的包必须在sys.path里面,不然是没有办法找到的


导入的格式是文件夹的名字.脚本的名字。你可以直接import 文件夹,也可以from 文件夹 import 脚本名,上面的格式对于包来说都是适用的。并且这个文件夹名字其实是指向文件夹里的__init__.py的。也就是说我们可以这么干

但是不可以这么干 ,因为m.a这个.前面首先必须是一个包,后面必须是一个.py文件

导入包的变量和函数其实就是导入__init__.py里的变量和函数。导入成功以后,就会自动生成一个缓冲的文件夹。

我们删除掉__init__.py可想而知后果是什么。因为这个包的名字就指向__init__.py,没了它,当然是会报错的。但是其实不是这样的,你把__init__删掉它不会报错,但是m就只是个namesapce,并且没办法导入文件夹里面的模块,就和没用处一样。导入不存在的文件夹是会直接报错。如果你不想每次都RESTART都导入一边搜索路径,最好还是放在原来的python默认路径里面,比如上面的site-packages。

__init__.py文件就是告诉python你这个东西是个包而不是普通的文件夹。

下面是一些琐碎知识

学习模块的方法的一些琐碎知识

我们有三种方法查看python的帮助。

第一种

调出,右边这几个蓝字是什么意思,我不解释了,程序员基本必须会英语,计算机软件专业还要学日语呢

你可以在索引里面搜

第二种是查看__doc__也就是文档。用print()出来是因为好看一些

第三种就是help,我就不演示了。

下面这个是收集第三方模块的一个网站,你也可以自己写一个模块发上去

你可能经常听到


说白了就是python社区开发的一些规范。还有API会经常听到

最后要说的是__all__和__file__,不一定所有模块都有__all__,有的话__all__里显示的就是开发者希望你调用的变量或者函数,虽然不在__all__里也可以调用,但是其实开发者希望你调用的就是在__all__里,如果你写一个模块,提供调用的接口最好都写在__all__里,__file__比较简单了,就是返回源代码文件的位置。

我们就去源文件里看看__all__和__file__究竟是什么

并没有找到__file__在哪里,这估计涉及python自带的机制了。

最后,还是一些练习,基础很重要的,基础不会,下一讲的爬虫还是很难的。

练习(爬虫之前先简单复习一下,还有就是提醒一些易错的地方)

0.定义一个常量模块,给我们调用。什么是常量呢?最直接的就是π啊,e啊,还有欧拉常数C啊,等这些不需要改变的数学常量。为了铺垫,我们先来介绍一个sys.modules


后面还有很多,这里面记录的是很多内置的模块是自动导入的。下面你们先自己试着写写,要求是你自己一旦传入一个参数,这个类就不可以被改变了。大家发挥自己想象力,创造属于自己的常量吧。

python无处不对象,模块也是一个对象,我们上面sys.modules[__name__]=constant()。

下面的const.pi就相当于constant().pi了。

我们当然可以不这么做。上面就是强行用一个sys.modules,233。


这里的代码是有根据的哦,原来的时候很多人都建议把6.18作为圆周率,这样圆的周长就是π*r了,欧拉也是有的时候π=6.28,有时候π=3.14。后来欧拉有一本书里写了π=3.14,然后某本课本上用了这个,后来就广泛流传了。

第0道应该没什么难度,

第一道涉及到以前没有讲过的一个复数的类。

python里用了工程里常用的虚数单位j或者J,而不是数学里的i。


第二题带我们复习了一下匿名函数

第三题很简单。


第四题就是继承的一个函数的覆盖问题而已,也不难。


五实际上就是很久以前说过的一个关于标签的问题。看了下面你应该能回忆起来

a,b是两个标签,第一行是把a这个标签贴到了1上,第二行把b也贴在了1上,第三行把b从1上撕下来贴到3上,和a没有什么关系。

这里就必须要和下面再区别一次了,因为太重要了

首先要明白列表是容器类型,然后b=a是把a,b指向了同一个地址空间。b[0]=0是改变地址空间的内容,而没有改变b标签指向的地址空间,这里就可以说是将其中某一个电容放电了,于是1就变成了0,由于a,b指向同一地址空间,所以a也跟着变。并且是可以改变内容的,可以看到改变内容前后的id都是不变的,还是在!7768536这个地址空间。

这种b=a[:]或者用b=a.copy()本身a和b就指向不同的地址空间。所以b改变和a是没有关系的。

这道题就是复习一下filter还有集合而已,本身并没有很难的地方。

学到这里我觉得你应该可以看懂下面呢英文了

就是把iterable作为参数传进functio里,只返回结果是True的嘛。其实这道题并没有用到集合的唯一性。


就是复习一下


就是把后面作为参数,一个一个传进去函数作用而已。这里再提醒一点

lambda后面是不能有return的。

这道没什么说的



只要你前面都看了这里不会错。


并没有什么,只是注意{}是空字典不是空集合。





只是考了一个收集参数是元组而已。


也许你注意到了题目里其实是python2里print的写法。




这道题我觉得蛮容易错的。因为你看到dict2=dict1.copy()(其实这是一种浅拷贝,下面的题会说)里对吧,但是却忽视了集合的元素是列表这个容器类型啊。dict['1'][0]=5就是把list的第一个元素改为5,你要注意dict1和dict2里key='1'对应的value都是list1,那结果自然是10咯。



这道题涉及到一个copy模块的深拷贝。我以前也说过,但是应该是有错误的,以这里的为准

,你不记得更好嘛,就看这里就行了。


翻译一下就是说浅拷贝和深拷贝的区别只有在作用在复合对象(包含其它对象的对象,比如列表和类的实例化对象)才有实质性差别。一个浅拷贝建立了一个新的复合对象(在可能的范围被内)把原始对象的内容,比如说列表的元素,插入到这个新的复合对象里。深拷贝建立了一个新的复合对象然后递归地把原来对象内容的拷贝插入到新的复合对象里。

参考了https://blog.csdn.net/u014745194/article/details/70271868。话说的再明白一点,

浅拷贝就是把最外围的对象复制了一个,换了一个id,里面的内容我只是新建了一个地址指针指向它们而已。而深拷贝是不只是最外围的对象新建一个id,里面的内容我都新建了id,可见复制的程度更深,所以叫深拷贝。

下面举例子来区别=赋值号,分片,copy()和deepcopy()

赋值号


我觉得赋值既不是深拷贝也不是浅拷贝,因为我们看到最外层的对象的id都一样,说明并没有开辟内存空间,只是把c标签贴在b上了,我称之为比浅拷贝还浅。对象可不可变不影响结果的。

分片和内置方法copy()


分片才是真正的浅拷贝。下面我就不展示不可变对象了,我自己实验过结果是一样的。

内置的copy()也是一种货真价实的浅拷贝。只是不是所有对象都有内置的copy()方法。

可变对象列表,字典,集合都是有这个内置方法的,不可变对象元组,字符串,frozenset没有。但是我们还有copy.copy()

copy.copy和copy.deepcopy()

对于深拷贝,对象可变不可变是有很大影响的。先看元组

再看列表

这里我说一下id返回的值是一个通识符,不是地址,但是它在每次运行IDLE的时候是唯一的,和地址对应关系是唯一的,所以我们看id来看地址是否一样是可以的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值