Python提高篇
1.模块
1)模块定义
模块就是py文件,可以是你自己写的,也可以是python当中自带的工具,当你在某个py文件下想要引用其他模块的功能,就需要你把你把该py文件导入。
2)导入方法
- 使用import XXXX 直接将模块导入,用这种方法导入时在使用模块里的函数需要用 模块name.函数名/类名
- 另外一种导入模块的方法可以用 from 模块 import 函数/类,这种导入方式仅仅将模块里的函数或者类导入进来,在调用时就可以直接使用该函数,若想要将所有函数导入可以用 * 代替。
3)注意
- 注意在定义py文件名时尽量不要和py本身自带模块名相同。同时尽量不要用*来导入,如果多个模块的函数名相同,后导入的会覆盖先导入的。
4)name变量引入
*调用模块时,会自动将此模块的程序执行一遍,所以在多人协作时,在写完很多方法或类时,一般会在进行测试。所以这些测试不希望在别人导入自己模块时被调用,引入了__name__变量。*
name 变量当你在自己模块下打印时会显示 main ,在别人调用模块执行时会打印该模块名。所以可以在自己测试代码前使用:
if __name__=="__main__":
也就是单独执行该模块时会调用if下面的代码而别人调用时不会。
5)导入不再当前文件夹下的模块
- 先导入sys,修改sys.path.append(),添加一个新的路径即可,重新加载模块用imp下的reload
- import xxx as xxx 导入模块起个别名
6)模块中:__all__的作用
*在模块前面加上__all__(“函数名/类名/变量名”)之后,当你导入你一个模块,只能引用括号里面的,all之外的无法引用*
7)包
*实际开发中,会将很多个相同功能的py文件(模块),存放到一个文件夹里。如果在这个文件夹里构建一个__init__ .py文件,那么这个文件夹就可以称之为包。*
8)包中__init__
- __ init__ 文件的作用就是让python知道,当前文件夹是一个包,可以被导入。并且在导入时会先执行一遍__init__ .py。
- 但如果__init__文件里什么都不写,那么将包导入进去之后,也用不了里面的模块。想要用包里模块,应该在__init__ .py中写一个__all__(),并在括号里写上导入模块的名字,此时就可以用 from 包 import 模块 导入。
- 另外一种方式是在 __init__中写上 from . import 模块 从当前路径下导入模块,就可以用包.模块.类/方法了
2.Py中 '== ' 与 'is'
- ==是判断两个变量内容是否相同,相同返回ture,不相同返回false。
- is 判断两个变量是否指向同一个对象,如果is返回Ture 那么 两个对象id一定相同
3.深拷贝与浅拷贝
- 通过 a=b 进行赋值的过程就是浅拷贝,即只是复制了一份引用,让a指向b指向的对象。
- 而深拷贝需要调用copy模块中的deepcopy(),让通过这种方式拷贝的话是让a重新开辟了一块内存空间,存放着b存放的内容。
- copy模块中除了deepcoy之外还有一个copy方法,使用copy的特点是当拷贝数据类型是嵌套形式的比如列表嵌套列表。
- 使用copy只拷贝当前的最外层的列表,而里面包含的,并不会生成新的空间。同时,对那些不可变类型,copy是不会拷贝的。
4.变量
- xxx:共有的,谁都可以用
- _ xxx:只能在模块里面用,from import *导不进去,也就是别的模块用不了。
- __ xxx:只能在类里面使用,私有熟悉,私有方法,只要出了类之外就用不了。
- __ xxx__:python文件里定义的,具有特殊含义,不要自己定义这样的变量。原因是python将变量名改了_object__变量
- xxx_:为了防止和关键字冲突的定义。
5.property的使用
- 我们在类中定义私有属性时,外部是不能调用的。但有时我们需要在外部去获取、更改这个变量值。
- 所以常规方法是在类中定义两个方法,getter,setter专门为了获取、更改这两个变量,当需要上述操作,直接调用方法实现。
- 但这样以来每一次都去调用方法会很麻烦,所以就引入了property。来实现,用调用对象.XX的操作。
- 具体实现方法有两种:
- 1)变量名 = property(getter,setter),此时你使用变量名.方法时会自动进行获取、更改操作。
- 2)通过property装饰器来实现上述功能,在获取变量方法前+@property
- 3)在设置变量前+变量名.setter
最后,通过property可以实现一个封装效果,即你没法直接随意的去使用私有属性,只能间接的去使用。
6.在函数里面修改全局变量需要先加,global声名
7.Python支持动态添加属性方法。
- 动态特性的好处就在于不用修改整个程序的前提下,可以添加修改部分功能。
- 具体实现方式可以在类的外面添加实例属性、类属性、实例方法、类方法、静态方法。
- 添加实例属性只需要通过 exampleName.的方式添加即可,同理添加类属性通过ClassName.的方式。
- 添加类方法、静态方法时同样的类外面写一个类方法、静态方法,然后按照添加属性的方式一样,将方法传给某个变量,下次可通过改变量调用其方法。
而添加实例方法则需要借助types模块。原因是如果采取和属性同样的方式,在类外定一个函数,里面self。都没问题,但在实际添加的过程中不会去传参数。所以要使用
exampleName.MethodName =types.MethodType (MethodName,exampleName)将所添加的方法与该类绑定即可。
如果想要限定不允许额外添加实例属性,可以用__slots__=("name")来进行控制,这样以来,在类的外面不能添加、使用name以外的属性。
8.当继承多个类时,默认先继承第一个
Python三大器
一、生成器
demo列表生成器:a = [x for x in range(10) ]
1.生成器的引入。
节省当前用不到的内存空间,也就是说你创建一个包含100000个元素的列表,但并不需要马上全部使用,而这100000个元素当前所占用的内存空间就是一种浪费资源。如果将这100000个元素变成一个列表生成器,它在存储的时候就不会占用太多内存,而且能保证每次你在使用的时候迭代输出。
2.生成器的具体实现方法有两种
- 一种是简单的用a = (x for x in range(10)),a保存的就是列表生成器,接下来在调用next(a)或.__next__时,会一直迭代输出。
第二种方法可以将一个函数通过yield改写成一个列表生成器。让一个变量保存该函数,即创建了一个生成器对象。- yield的功能有两点,一是函数的程序执行的yield时会停止向下执行,等待你下一次的next时继续。
二是可以将当前的变量值返回即输出,即yield后面一般都会跟一个正在变化的变量如 yield i
3.next 与 send
- 相同点:在调用以上两种方法时都会进行下一次迭代。
不同点:使用send时可以传一个值,并将这个值附给yield i整体,即可以找一个变量接收。
demo:A.send("哈哈哈") temp = yield i '相当与把temp = 哈哈哈,同时I迭代一次' send(None) = __ next__
4.生成器、多任务、协程
- 使用生成器可以实现多任务,具体实现方法时将两个生成器对象扔到两个while 1里面,然后将两个生成器对象在扔到一个
while 1 里面,这样以来,两个生成器会无限交替往下执行,如果速度足够快,可以看做是同一时间三个while1同时执行,实现多任务。
5.线程、进程、协程
- 进程是资源分配的单位,使用多进程新开辟一段空间来保存,多进程相互之间没有任何练习,一个挂了也不会影响其他。
- 线程是CPU调度的单位,线程执行在进程里面,没有自己的独立空间,Python中的GIL解释器锁,不论CPU有几核,也只能
占用一个核。如果一个子线程挂的时候对共享区造成破坏,那么其余子线程以及主线程(进程)会受到影响. - 协程执行在线程里面,相比线程、进程不占用资源。最大的优势可以避免使用多进程、多线程时导致多任务在CPU的切换成本,
即每一次切换都要保存上一个任务的数据。所以,使用协成可以实现多任务,并且相比来说,效率会更高。
6.io密集型与计算密集型
- 计算密集型--->需要大量CPU资源,用多进程
- io密集型----->需要网络功能,大量的时间都在等待网络数据的到来.
二、迭代器
1.首先区分一个概念,那就是什么是可迭代的(Iterable),什么是迭代器(Iterator)。
- list、dict、str都是Iterable但不是Iterator。可以通过iter()函数将可迭代的变量创建一个Iterator对象。判断可以用isinstance。
2.那什么是可迭代的呢?
凡是可以用for去遍历的都是可迭代的。
3.但是迭代去必须能够通过next()取值,生成器一定是迭代器。
三、装饰器
1.什么是闭包
在一个函数里面在定义一个函数,同时被定义的函数用到了它外层函数的变量,并且外层函数返回的值是内层函数,这样的结构叫做闭包。
def test1(num):
print(---1---)
def test2(num1)
print(num+num1)
print(---2----)
return test2
ret = test1(100)
ret(100)
测试输出:200
ret指向test2,在调用ret时相当于调用test2,里面用到了test1函数的变量会到里面去找。
2.闭包注意的地方
ret指向test1会生成内存空间,并不会被释放,如果接下来用ret1去指向test1(100)会额外开辟一段空间
3.装饰器
基于闭包,让一个函数再执行之前,先去到另外一个函数执行,而并不改变函数本身,执行完之后回来执行自己函数内部。并且通常情况下,会对函数本身产生一定的影响,可以的使输出值的变化,可能是条件判断等。
4.具体实现的方式是找一个变量在装饰器外层函数接收被装饰的函数。
def w1(func): def d1(): print('正在装饰') func() return d1 def d2() print('ha') d2 = w1(d2) d2()
相当于把 d2函数扔到 w1函数里面执行,只不过在执行之前先让其先print('正在装饰')最后把再把执行以后的结果返回给d2,而过程中讲一个函数作为一个参数传入传出是通过闭包的原理实现的。
- 而d2 = w1(d2)这样的过程可以用一种py特殊的方法替代,用@w1,将其放到d2函数的上面,即可达到上面的效果。
而@w1即为装饰器~等价于 d2 = w1(d2)
5.如果同时使用两个或多个装饰器,如:
@w2
@w1
def d2()
print('ha')
- 这种情况即便是先装饰w1,在装饰w2,原因是,装饰器下面有函数的情况下才能被装饰,所以w1挨着d2,所以装饰之后返回函数
在被w2装饰.
6.Python解释器会在调用函数之前就已经进行装饰了,调用是装饰以后的结果
7.装饰器是从近往远装的,但是在调用是从外往里调用的。就是先装里函数最近的装饰器依此往外,调用时是相反的方向。
8.如果装饰的是一个有参数的函数,那么在装饰器里的子函数,以及接受函数的参数也应该设置有参数,并且与被装饰函数的参数一致。否则就会报错,有上述demo,如果d2有两个参数,那么d1,func也需要接受两个参数。
9.但如果函数的参数是不定长的,或者经常需要变换的话,可以将d1,func放不定长的参数,也是我们之前所学的(* args, ** kwargs),用来接受不定长的参数。
10.如果被装饰的函数如果有返回值的话,那么在func处一定得找一个值来接收他的返回值,也就是说用一个变量a = func(),然后return a 将该值返回。
11.那么,一个被装饰的函数可能有返回值,可能有参数,但这些都是不确定的啊,那该如何写一个通用的装饰器呢。
def w1(func):
def d1(*args,**kwargs):
ret = func(*args,**kwargs)
return ret
return d1
这样的一个w1装饰器就能够达到通用的效果,利用args,**kwargs保证传参的不定长,用ret接收返回值如果没有返回值,那么ret接收的就是一个None!*
12.带有参数的装饰器
实现方式:在装饰外面在再来一层,把装饰器包在里面。而外面的函数用来接收装饰器里面的参数,然后返回装饰器函数引用。
def w2(arg): def w1(func): def d1(*args,**kwargs): ret = func(*args,**kwargs) return ret return d1 retunr w1 @w2(haha)
用途:一个装饰器在装饰不同函数的时候有不同的功能,原理就是通过在装饰时传入不同的参数,然后在装饰器里面用if判断。