Python核心编程 (全)
一、浅拷贝和深拷贝
1.浅拷贝
是对于一个对象的顶层拷贝,通俗的理解是:拷贝了引用,并没有拷贝内容。相当于把变量里面指向的一个地址给了另一个变量就是浅拷贝,而没有创建一个新的对象,如a=b。
2.深拷贝
首先要import copy,然后c = copy.deepcopy(a),就表示把a的内容深拷贝到c中,如果发现了a中也存在引用的内容,则递归拷贝,也就是把当前的这个引用的对象继续深拷贝。
3. copy和deepcopy的区别
①copy:浅拷贝,里面如果有可变类型,修改这个可变类型(如list),被拷贝的对象也会相应改变,仅仅拷第一层,如果是不可变类型,就一层都不拷,如果是可变类型就拷一层。
②deepcopy:深拷贝,里面不管是可变类型和不可变类型,被拷贝的对象都不会受到影响,递归拷贝。
4.copy和deepcopy拷贝元组的特点
使用copy模块的copy功能的时候,它会根据当前拷贝的数据类型是可变类型还是不可变类型有不同的处理方式,如元组是不可变类型,拷贝多份没有用,对copy来说,如果是可变类型就拷一层,如果是不可变类型,就一层都不拷。
二、属性property
1.属性property-1
①私有属性添加getter和setter方法
②使用property升级getter和setter方法
num = property(getNum,setNum)
注意:
Num到底是调用getNum()还是setNum(),要根据实际的场景来判断,值得注意的是一定要先填getNum后setNum。
如果是给t.num赋值,那么一定调用setNum()。
如果是获取t.num的值,那么就一定调用getNum()。
property的作用:相当于把方法进行了封装,开发者在对属性设置数据的时候更方便。
2.属性property-2
class Money(object): @property #修饰器 def num(self): print("------getter-----") return self.__num @num.setter #修饰器 def num(self,new_num): print("------setter------") self.__num = new_num t.num = 20 print(t.num)
三、迭代器
1.迭代器
迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束,迭代器只能往前不会后退。
2.可迭代对象(for 循环遍历的数据类型)
①一类是集合数据类型,如 list 、 tuple 、 dict 、 set 、 str 等。
②一类是 generator(列表生成式,生成器) ,包括生成器和带 yield 的generator function。
③这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
from collections import Iterable # 如果可以迭代就返回True isinstance([ ], Iterable)
3.判断是否可以迭代
可以使用isinstance()判断一个对象是否是Iterable对象:
from collections import Iterable # 如果可以迭代就返回True isinstance([ ], Iterable)
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
4.迭代器
①可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
②可以使用isinstance()判断一个对象是否是Iterator对象。
③生成器(i for i in range(10))一定是迭代器,但迭代器不一定是生成器。
④迭代器一定是可迭代对象,但可迭代对象不一定是迭代器。
from collections import Iterator isinstance((x for x in range(10)), Iterator) # 如果是的话就返回True
5.iter( )函数
①生成器都是Iterator(迭代器)对象,但 list、dict、str虽然是Iterable(可迭代),却不是Iterator(迭代器)。
②把list、dict、str 等 Iterable(可迭代)变成 Iterator(迭代器)可以使用iter()函数,就好比人可以游泳,但不是天生就会,可迭代对象就好比人,迭代器就好比会游泳的人,需要经过iter()训练一样。
isinstance(iter([ ]), Iterator) True
四、闭包
1.函数的引用
test1() # 调用函数 ret = test1 # 引用函数 ret() # 通过引用调用函数
2.什么是闭包
def test(number): print("-----1-----") def test_in(number2): print("----2-----") print(number+number2) print("------3------") # 把函数的引用返回了 return test_in # 用来接收test(100),指向了一个函数体,这个100传给了number ret = test(100) # 这个1传给了number2 ret(1) # 这个返回101 ret(100) # 这个返回200 ret(200) # 这个返回300
3.闭包再理解
内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包
闭包的实际例子:
def line_conf(a, b): def line(x): return a*x + b return line line1 = line_conf(1, 1) line2 = line_conf(4, 5) print(line1(5)) print(line2(5))
这个例子中,函数line与变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用
五、装饰器
1.装饰器
在有两个重名的函数中,Python解释器会调用最后定义的那重名函数,因为在Python里,第一个函数指向的是一片内存,然后又让这个函数指向另一片内存,就会利用第二片内存来执行,所有函数名应尽量避免相同
写代码要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
①封闭:已实现的功能代码块
②开放:对扩展开发
③实例:
def w1(func): def inner(): # 验证1 # 验证2 # 验证3 func() return inner @w1 # 装饰器 def f1(): print(‘f1‘) @w1 # 装饰器 def f2(): print(‘f2‘) ........
对于上述代码,也是仅仅对基础平台的代码进行修改,就可以实现在其他人调用函数 f1、 f2 、f3 、f4 之前都进行【验证】操作,并且其他业务部门无需做任何操作
2.装饰器的功能
①引入日志
②函数执行时间统计
③执行函数前预备处理
④执行函数后清理功能
⑤权限校验等场景
⑤缓存
⑥如果是有多个装饰器的情况,一般是先装饰最下面的一个,然后依次往上,@w1类比于f1 = w1(f1)
3.装饰有参数的函数
在传递参数的时候,需要在闭包里面定义一个形参,闭包里面的调用的函数也要定义一个形参,否则会导致两部分函数调用失败
4.装饰不定长的参数的函数
在传递参数的时候,需要在闭包里面定义一个*args和**kwargs,闭包里面的调用的函数也要定义一个*args和**kwargs,这样就可以在调用的时候传递任意长度的参数,增加代码的可复用性
5.装饰带返回值的函数
需要在闭包里面进行一个接收,也就是ret = test(),然后再把接收到的ret return出去,这样在装饰的test才能返回出当前需要返回的东西,否则只会返回None
6.通用的装饰
def w1(func): print("-----正在装饰-----") def inner(*args,**kwargs): print("---正在验证权限---") print("----记录日志----") ret = func(*args,**kwargs) return ret return inner
带有参数的装饰器:也就是在原来包含一个闭包的函数外面再给他套一个函数,用来传递装饰器的参数
def func_arg(arg): def w1(func): print("---记录日志---") def inner(*args,**kwargs): func(*args,**kwargs) return inner return w1 @func_arg("heihei") def f1(): print("----f1----") # 1.先执行func_arg("heihei")函数,这个函数return的结果是 # # 3.使用@w1对f1进行装饰 # 作用:带有参数的装饰器,能够起到在运行时,有不同的功能
六、python是动态语言
1.Python是动态语言
动态编程语言是高级程序设计语言的一个类别,在计算机科学领域已被广泛应用。它是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。这种动态语言的应用就好比是在没有更新app的情况下,它的界面在后台也可以被开发者更改,因为它是动态的,可以把新增的动态程序放置在文本,只要加载一遍即可
2.运行的过程中给对象绑定(添加)属性
也就是说给对象绑定一个实例属性(这个属性是初始化之外的额外属性),只有这个创建对象的属性如laozhao.addr = "北京"
3.运行的过程中给类绑定(添加)属性
如果需要所有的一个类的实例加上一个属性怎么办呢? 答案就是直接给这个类绑定属性,如Person.sex = "male"
4.运行的过程中给类绑定(添加)方法:
如果是对这个类绑定一个实例方法,那么就要先import types,然后如对象.方法名 = types.MethodType(函数名, 对象),把run这个方法绑定到P对象上。如果是静态方法和类方法,就直接用类名.方法名=函数名
5.运行的过程中删除属性、方法
①del 对象.属性名
②delattr(对象, "属性名")
__slots__的作用
1.动态语言
可以在运行的过程中,修改代码
2.静态语言
编译时已经确定好代码,运行过程中不能修改
3.__slots__的作用
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性,如__slots__ = ("name","age"),就可以达到限制name和age的属性,如果发现有添加其他属性的程序就会发生异常
4.使用__slots__注意
__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
生成器
1.什么是生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了
①while的列表推导
list.append(i)
②for的列表推导,range与切片很类似
for i in range(10,78):
③第一个i是元素的值,后面的for是循环的次数,如果第一个i=11,那么所有的元素都是11
a=[i for i in range(1,18)]
④for控制循环的次数,for和if的嵌套
c = [i for i in range(10) if i%2==0]
⑤每执行第一个for循环都要执行第二个for循环的所有次数
d = [i for i in range(3) for j in range(2)]
⑥每执行第一个for循环都要执行第二个for循环的所有次数
d = [(i,j) for i in range(3) for j in range(2)]
例题:找出100以内能被3整除的正整数
aiquot = [] for n in range(1,100) if n%3 ==0: aiquot.append(n)range(3,100,3) # 很简洁
2.创建生成器方法1
要创建一个生成器,有很多种方法。第一种方法很简单,只要把一个列表生成式的[ ]改成( )
如L = [ x*2 for x in range(5)]和G = ( x*2 for x in range(5)),L是一个列表,而G是一个生成器,可以通过next(G)函数获得生成器的下一个返回值,不断调用 next()实在是太变态了,正确的方法是使用for循环,因为生成器也是可迭代对象
3.创建生成器方法2
fib函数变成generator,只需要把print(b)改为yield b就可以了,循环过程中不断调用yield ,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来,当循环到没有元素的时候,将会生成异常,这时候就要用try和exception来检测异常,#print自动检测异常并停止,但是next()就要用try ,在创建生成器的时候需要接收函数的返回值
#1.next(返回函数名)
#2.返回函数名.__next__()是一样的方法来获取下一个返回值
总结:生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次或第 n 次调用跳转至该函数中间,而上次调用的所有局部变量都保持不变,生成器不仅记住了它数据状态;生成器还记住了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置
4.生成器的特点
①节约内存
②迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的
5.send用法
①如果在在程序中有个变量等于yield,不是说把yield的值给了这个变量,而是接下来在下一次调用执行一次的时候可以传一个值,t.send("haha")和t.__next__()都可以让生成器继续执行,不同的是send可以传递一个值,但是不能在程序刚开始执行就用send传值,有两种方法,要么先用__next__调用一次,再send一个值,或者t.send(None)
②生成器:完成多任务,控制多个任务执行的情况
元类
1.元类
①类也是对象
②在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段,类同样也是一种对象
2.动态的创建类
因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样
def choose_class(name): if name == ‘foo‘: class Foo(object): pass return Foo # 返回的是类,不是类的实例 else: class Bar(object): pass return BarMyClass = choose_class(‘foo‘) # 当你使用class关键字时,Python解释器自动创建这个对象
2.使用type创建类
①type还有一种完全不同的功能,动态的创建类,type可以像这样工作:
②type(类名, 由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
Test2 = type("Test2",(),{}) #定了一个Test2类
3.使用type创建带有属性的类
Foo = type(‘Foo‘, (), {‘bar‘:True})
4.使用type创建带有方法的类
FooChild = type(‘FooChild‘, (Foo,), {‘echo_bar‘: echo_bar}) # 这是添加实例方法echo_bar Foochild = type(‘Foochild‘, (Foo,), {"echo_bar":echo_bar, "testStatic": testStatic}) # 添加静态方法Foochild = type(‘Foochild‘,(Foo,),{"echo_bar":echo_bar, "testStatic":testStatic, "testClass":testClass}) # 添加类方法
5.到底什么是元类
元类就是用来创建类的东西,元类就是用来创建这些类 (对象) 的,元类就是类的类,元类又由元类创建,Python中所有的东西都是对象。这包括整数、字符串、函数以及类
6.__metaclass__属性
class Foo(object): __metaclass__ = something…
如果这么做了,Python就会用元类来创建类Foo,这里面有些技巧。首先写下class Foo(object),但是类Foo还没有在内存中创建。Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的type来创建这个类
GC垃圾回收
1.GC垃圾回收
①小整数对象池:Python为了优化速度,使用了小整数对象池, 避免为整数频繁申请和销毁内存空间。Python 对小整数的定义是[-5, 257) 这些整数对象是提前建立好的,不会被垃圾回收
②大整数对象池:每一个大整数,均创建一个新的对象
③intern机制:假如要创建n个对象的是一样的字符串,那么python只会创建一个内存空间来存储,其他对象都是引用,但如果字符串中出现空格或其他符号就表示为不同的对象
2.GC(Garbage collection)垃圾回收
Python里也同Java一样采用了垃圾收集机制,不过不一样的是: Python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略
3.引用计数机制的优点
①简单
②实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时
4. 引用计数机制的缺点
①维护引用计数消耗资源
②循环引用
5.GC系统所承担的工作远比"垃圾回收"多得多。实际上,它们负责三个重要任务
①为新生成的对象分配内存
③从垃圾对象那回收内存
6.垃圾回收机制
Python中的垃圾回收是以引用计数为主,分代收集为辅
①导致引用计数+1的情况:
对象被创建,例如a=23
对象被引用,例如b=a
对象被作为参数,传入到一个函数中,例如func(a)
对象作为一个元素,存储在容器中,例如list1=[a,a]
②导致引用计数-1的情况:
对象的别名被显式销毁,例如del a
对象的别名被赋予新的对象,例如a=24
一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
对象所在的容器被销毁,或从容器中删除对象
7.查看一个对象的引用计数
import sysa = "hello world"sys.getrefcount(a)
①可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1
②有三种情况会触发垃圾回收
调用gc.collect()
当gc模块的计数器达到阀值的时候
程序退出的时候
8.gc模块的自动垃圾回收机制
①必须要import gc模块,并且is_enable()=True才会启动自动垃圾回收。
②这个机制的主要作用就是发现并处理不可达的垃圾对象。
③垃圾回收 = 垃圾检查 + 垃圾回收
④在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾检查中,该对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中
⑤gc模块里面会有一个长度为3的列表计数器,可以通过gc.get_count()获取,gc.set_threshold(threshold0[, threshold1[, threshold2]) 设置自动执行垃圾回收的频率,例如(700,10,10)每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,700表示阈值,10表示没清理10次零代就清理一次二代,第二个10表示每清理10次一代链表就清理二代一次
注意点:
gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__方法
内建属性
常用专有属性 说明 触发方式
__init__ 构造初始化函数 创建实例后,赋值时使用,在__new__后
__new__ 生成实例所需属性 创建实例时
__class__ 实例所在的类 实例.__class__
__str__ 实例字符串表示,可读性 print(类实例),如没实现,使用repr结果
__repr__ 实例字符串表示,准确性 类实例 回车 或者 print(repr(类实例))
__del__ 析构 del删除实例
__dict__ 实例自定义属性 vars(实例.__dict__)
__doc__ 类文档,子类不继承 help(类或实例)
__getattribute__ 属性访问拦截器 访问实例属性时
__bases__ 类的所有父类构成元素 类名.__bases__
def __getattribute__(self,obj): if obj == ‘subject1‘: print(‘log subject1‘) return ‘redirect python‘ else: # 测试时注释掉这2行,将找不到subject2 return object.__getattribute__(self,obj)
__getattribute__的作用可以用来打印Log日志
__getattribute__的坑:
class Person(object): def __getattribute__(self,obj): print("---test---") if obj.startswith("a"): return "hahha" else: return self.test def test(self): print("heihei")t.Person()t.a #返回hahhat.b #会让程序死掉# 原因是:当t.b执行时,会调用Person类中定义的__getattribute__方法,但是在这个方法的执行过程中if条件不满足,所以 程序执行else里面的代码,即return self.test 问题就在这,因为return 需要把self.test的值返回,那么首先要获self.test的值,因为self此时就是t这个对象,所以self.test就是t.test 此时要获取t这个对象的test属性,那么就会跳转到__getattribute__方法去执行,即此时产生了递归调用,由于这个递归过程中 没有判断什么时候推出,所以这个程序会永无休止的运行下去,又因为每次调用函数,就需要保存一些数据,那么随着调用的次数越来越多,最终内存吃光,所以程序崩溃# 注意:以后不要在__getattribute__方法中调用self.xxxx
调试
1.调试:pdb是基于命令行的调试工具,非常类似gnu的gdb(调试c/c++)
执行时调试
程序启动,停止在第一行等待单步调试
python -m pdb xxx.py
2.调试方法
①n(next)执行下一步
②l(list)显示当前执行进度
③c(continue)继续执行代码
④b(break)添加断点
⑤q(quit)中止并退出
⑥clear num删除指定断点
⑦p(print)打印变量的值
⑧a(args)打印所有的形参数据
⑨s(step)进入到一个函数
r执行代码直到从当前函数返回
num = property(getNum,setNum)