python基础
1. 闭包
1. 概念
有时候需要在函数外部得到函数内的局部变量,于是在函数的内部,再定义一个函数,把f2作为返回值,result就相当于f2。
def f1():
n=999
def f2():
print(n)
return f2
result = f1()
result()
上一部分代码中的f2函数,就是闭包。
nonlocal语句
使用nonlocal语句可以让嵌套函数的内层函数去修改外层函数的变量,但是不影响全局变量
1)修改嵌套函数内部的global变量
2)修改嵌套函数内部的nonlocal变量
2. 类和对象
对象=属性+方法
所谓属性就是类里面的变量,所谓方法就是类里面的函数
(1)类的创建
先创建一个类:相当于模具,再根据模具进行批量生产
t1 = Turtle()
#t1就是turtle类的对象,也叫实例对象,它就拥有了这个类的属性和方法
t1.legs = 3#修改属性
t1.mouth = 1#添加属性
(2)面向对象的三个基本特性:封装、继承、多态
1)封装
在创建对象之前,通过创建类,将相关的变量和属性打包到一起。
类中的每一个方法默认的第一个参数都是self(传递给方法的是实例对象自身),因为一个类有无数个方法,需要确定是哪个对象在调用
2)继承
python的类可以继承,它可以使用现有类的所有功能,并在无需重新编写代码的情况下,对这些功能进行扩展。通过继承创建的新类叫子类。
class A:
x = 520
def hello(self):
print("你好")
#B继承A的写法
class B(A):
pass#表示占位的空语句
b = B()#b可以访问到类A中的属性和方法
#类B中重新定义变量和方法,可以实现覆盖,B的对象结果更新
class B(A):
x = 111
def hello(self):
print("why")
#判断一个对象是否属于某个类,可以使用isinstance函数
isinstance(b,B)
isinstance(b,A)
#b属于子类B,也同时属于父类A
issubclass(B,A)#检测B是否为A的子类
多重继承
一个子类可以同时继承多个父类
class A:
x = 520
def hello(self):
print("A")
class B:
x = 111
y = 222
def hello(self):
print("B")
class C(A,B):
pass
c = C()#520
c.hello()#A
#访问顺序从左到右
组合
class Dog:
def say(self):
print("汪汪汪")
class Cat:
def say(self):
print("喵喵喵")
class Zoo:
d = Dog()
c = Cat()
def say(self):
self.d.say()#方法是大家的,属性可以是自己的,所以用self来绑定对象z和类Zoo
self.c.say()#没有self就表示一个局部变量而不是z的属性
z = Zoo()
z.say()
#汪汪汪
#喵喵喵
想知道对象拥有哪些属性,可以用__dict__进行内省
d = Dog()
d.x = 5
d.__dict__#{'x': 5}
通过空类生成的实例来模拟字典
#定义一个最小的类
class C:
pass
#可以作为字典使用
C.x = 250;
C.y = "abc"
C.z = [1,2,3]
print(C.x)
print(C.y)
print(C.z)
#对比使用字典的方法
d = {}
d['x'] = 250
d['y'] = "abc"
d['z'] = [1,2,3]
print(d['x'])
print(d['y'])
print(d['z'])
#通过空类生成的实例来模拟字典
class C:
pass
c = C()
c.x = 250;
c.y = "abc"
c.z = [1,2,3]
构造函数__init__
在实例化对象的同时实现个性化定制,可以传递参数
class C:
def __init__(self,x,y):
self.x = x
self.y = y
def add(self):
return self.x + self.y
def mul(self):
return self.x * self.y
c = C(2,3)
c.add()#5
c.mul()#6
重写
class D(C):
def __init__(self,x,y,z):
C.__init__(self,x,y)#直接通过类名访问类里面的方法称为调用未绑定的父类方法
self.z = z
def add(self):
return C.add(self) + self.z
def mul(self):
return C.mul(self) * self.z
d = D(2,3,4)
d.add()#9
d.mul()#24
修改钻石继承问题
class A():
def __init__(self):
print("A")
class B1(A):
def __init__(self):
A.__init__(self)#调用未绑定的父类A的init方法
print("B1")
class B2(A):
def __init__(self):
A.__init__(self)
print("B2")
class C(B1,B2):
def __init__(self):
B1.__init__(self)
B2.__init__(self)
print("C")
c = C()
#A
#B1
#A
#B2
#C
#修改后
class B1(A):
def __init__(self):
super().__init__()#使用super函数去查找父类(不一定是父类,而是MRO列表中下一个类)的方法,括号里不再需要self
print("B1")
class B2(A):
def __init__(self):
super().__init__()
print("B2")
class C(B1,B2):
def __init__(self):
super().__init__()
print("C")
#A
#B2
#B1
#C
MRO顺序:类的方法解析顺序
C.mro()
#[__main__.C, __main__.B1, __main__.B2, __main__.A, object]
C.__mro__
#[__main__.C, __main__.B1, __main__.B2, __main__.A, object]
多继承中的Mixin
class Animal:
def __init__(self,name,age):
self.name = name
self.age = age
def say(self):
print(f"我叫{self.name},今年{self.age}岁。")
class FlyMixin:
def fly(self):
print("I can fly.")
class Pig(Animal):
def special(self):
print("pig")
#FlyMixin()类怎么插入到Pig类里面呢?
#通过多继承的方式写到Pig的父类里面
class Pig(FlyMixin,Animal):
def special(self):
print("pig")
#然后就可以调用FlyMixin()里面的方法p.fly()了
3)多态
同一个运算符、函数或对象在不同的场景下具有不同的作用效果
#Square,Circle,Triangle都继承自同一个类,但是重写了area的计算方法
class Shape:
def __init__(self,name):
self.name = name
def area(self):
pass
class Square(Shape):
def __init__(self,length):
super().__init__("正方形")
self.length = length
def area(self):#重写计算面积的方法
return self.length * self.length
class Circle(Shape):
def __init__(self,r):
super().__init__("圆形")
self.r = r
def area(self):#重写计算面积的方法
return 3.14 * self.r * self.r
class Triangle(Shape):
def __init__(self,a,h):
super().__init__("三角形")
self.a = a
self.h = h
def area(self):#重写计算面积的方法
return 0.5 * self.a * self.h
s = Square(5)
c = Circle(6)
t = Triangle(3,4)
s.area()#25
c.area()#113.03999999999999
t.area()#6.0
自定义函数如何实现多态接口
class Cat:
def __init__(self,name,age):
self.name = name
self.age = age
def intro(self):
print(f"我叫{self.name},今年{self.age}岁。")
def say(self):
print("mua")
class Dog:
def __init__(self,name,age):
self.name = name
self.age = age
def intro(self):
print(f"我叫{self.name},今年{self.age}岁。")
def say(self):
print("汪汪")
class Pig:
def __init__(self,name,age):
self.name = name
self.age = age
def intro(self):
print(f"我叫{self.name},今年{self.age}岁。")
def say(self):
print("oink")
c = Cat("web",4)
d = Dog("bubu",7)
p = Pig("大肠",5)
def animal(x):#animal函数就具有多态性,接收不同的对象作为参数,并且不检查类型的情况下执行它的方法
x.intro()
x.say()
animal(c)#我叫web,今年4岁。mua
animal(d)#我叫bubu,今年7岁。汪汪
animal(p)#我叫大肠,今年5岁。oink
鸭子类型
animal函数不需要关注x是什么,只要x里面有intro和say方法,就可以被调用
(3)“私有变量”
通过某种手段,使得对象中的属性或方法无法被外部所访问。name mangling(名字改编)
class C:
def __init__(self,x):
self.__x = x#__x使得变量__x私有,c.__x无法访问到x的值
def set_x(self,x):
self.__x = x
def get_x(self):
print(self.__x)
c = C(250)
#可以使用函数访问
c.set_x(100)
c.get_x()
#或者使用_C__x来访问,不建议那么做,因为使用__就是为了不让外界访问到
c.__dict__#{'_C__x': 100}
c._C__x#100
方法的隐藏
class D:
def __fun(self):
print("hello")
对象创建之后再使用__x将不能进行名字改编,如c.__y=250,__y依然可以被外部访问。名字改编是发生在类实例化对象的时候。
_单个下横线开头的变量通常是仅供内部使用的变量
单个下横线结尾的变量通常是要用关键字做变量名,如class
(4)效率提升之道
__slot__类属性,避免了利用字典来存放造成空间上的浪费,以牺牲Python语言的动态灵活性为前提
如果一个类的对象只需要几个固定的属性,也没有动态添加属性的需求,可以用__slot__类属性
class C:
__slots__=["x","y"]
def __init__(self,x):
self.__x = x
c = C(250)
#c只能访问x,y,c.z=666报错
#构造函数中也不能有xy以外的属性,__init__(self,x,y,z)报错
继承自父类的slots属性不会在子类中生效,C的子类对象e可以添加z属性
(5)对象的魔法方法
哪一个魔法方法创建了实例化对象/实例化对象中调用的第一个魔法方法?
对象被创建时调用的魔法方法 new(cls[,…])
需要重写__new__()的情况极少,两种情况:1、在元类中去定制类 2、在继承不可变数据类型的时候,想要改变数据类型
class CapStr(str):
def __new__(cls,string):#第一个参数式cls,第二个参数是实例化传递进去的参数
string = string.upper()#之所以能对不可变对象进行修改,是因为在实例对象被创建之前就进行了拦截
return super().__new__(cls,string)#然后才调用super()去创建真正的实例
cs = CapStr("abc")
cs#ABC小写的输入变成了大写
#CapStr类继承自str字符串类,字符串的方法也一并继承了下来
cs.lower()#变成小写abc
cs.capitalize()#首字母大写Abc
对象即将被销毁时也会调用一个魔法方法def del(self):
#对象即将被销毁时也会调用一个魔法方法__del__(self)
class C:
def __init__(self):
print("我来了")
def __del__(self):
print("我走了")
c = C()#我来了#实例化对象自动调用init方法
del c#我走了#del c之后调用__del__方法
#但是调用del C不一定会触发魔法方法,只有对象被销毁时才能触发,当检测到一个对象没有任何引用时会被销毁
d = c#当d还在引用c时
del c#不会调用__del__方法,没有输出
del d#此时再del 引用c的d,输出我走了
对象的重生
#利用全局变量
class D:
def __init__(self,name):
self.name = name
def __del__(self):
global x#通过创建全局变量把self给送出去
x = self
d = D("xiao")
d.name#xiao
del d#删除d之后d访问不到了,但是x可以访问到,x就是原来的d
#对象被销毁前创建一个函数,通过参数传递将self传递出去
class E:
def __init__(self,name,func):#实例化时先把参数给传进去
self.name = name
self.func = func
def __del__(self):
self.func(self)#在对象被删除之前将self传递给一个函数
#通过闭包将外层函数的局部变量保存下来
def outter():
x = 0#定义闭包,让self保存在外部函数的变量中
def inner(y=None):
nonlocal x#内部函数窃取self对象
if y:
x = y
else:
return x
return inner
f = outter()
e = E("a",f)
del e#调用del,将self存储起来
g = f()#g就是刚才e这个对象
(5)与运算相关的魔法方法
重新定义add方法,变为字符统计
radd方法,s1的add方法要返回NotImplemented
调用radd方法时,两者要基于不同的类
增强赋值运算,会改编s1的数据类型,对s1进行赋值
(6)与属性访问相关的魔法方法
hasatter(c,“name”)#检测某对象是否有某属性
getattr(c,“name”)#获取对象中的某个属性
setattr(c,“name”,“123”)#设置对象中指定属性的值
delattr(c,“name”)#删除对象中的某个属性
class C:
def __init__(self,name,age):
self.name = name
self.__age = age
def __getattribute__(self,attrname):
print("拿来吧你")
return super().__getattribute__(attrname)
c = C("xiao",18)
getattr(c,"name")
#拿来吧你
#'xiao
class C:
def __init__(self,name,age):
self.name = name
self.__age = age
def __getattribute__(self,attrname):
print("拿来吧你")
return super().__getattribute__(attrname)
def __getattr__(self,attrname):#只有在访问不存在的属性时才会拦截
if attrname == "fish":
print("I love fish.")
else:
raise AttributeError(attrname)
c = C("xiao",18)
c.fish#拿来吧你 I love fish.
c.x#拿来吧你
class D:
def __setattr__(self,name,value):
self.__dict__[name] = value#注意要使用字典访问
d = D()
d.name = "fish"
d.name#fish
class D:
def __setattr__(self,name,value):
self.__dict__[name] = value
def __delattr__(self,name):
del self.__dict__[name]
d = D()
d.name = "fish"
d.__dict__#{'name': 'fish'}
del d.name
d.__dict__#{}
(7)索引、切片、迭代协议
当对象被索引时,python会调用 getitem(self,index)方法,既能响应单个下标的索引操作,又能支持代表范围的切片索引。
为索引或切片赋值时,会被__setitem__拦截
class D:
def __init__(self,data):
self.data = data
def __getitem__(self,index):
return self.data[index]
def __setitem__(self,index,value):
self.data[index] = value
d = D([1,2,3,4,5])
d[1]#2
d[2:4]#[3, 4]
d[2:4]=[2,3]
d[:]#[1, 2, 2, 3, 5]
class D:
def __init__(self,data):
self.data = data
def __getitem__(self,index):
return self.data[index] * 2
d = D([1,2,3,4,5])
for i in d:
print(i,end = ' ')#2 4 6 8 10
针对可迭代对象的魔法方法__iter__(self),next(self)
#针对可迭代对象的魔法方法__iter__(self),__next__(self)
#根据python的迭代协议,如果一个对象定义了__iter__(self)方法,那它就是可迭代对象,如果一个对象定义了__next__(next)方法,那它就是迭代器
#创造一个迭代器对象
class Double:
def __init__(self,start,stop):
self.value = start - 1
self.stop = stop
def __iter__(self):
return self
def __next__(self):
if self.value == self.stop:
raise StopIteration
self.value += 1
return self.value * 2
d = Double(1,5)
for i in d:
print(i,end = ' ')#2 4 6 8 10
(8)代偿
用于实现成员关系的检测 contains(self,item),对应运算符in/not in
class C:
def __init__(self,data):
self.data = data
def __contains__(self,item):
print("hi")
return item in self.data
c = C([1,2,3,4,5])
3 in c#hi True
#如果没有使用__contains__(self,item),但是又用了in/not in进行成员关系判断,Python就会尝试去查找__iter__(self),__next__(self)方法
class C:
def __init__(self,data):
self.data = data
def __iter__(self):
print("Iter",end = "->")
self.i = 0
return self
def __next__(self):
print("Next",end = "->")
if self.i == len(self.data):
raise StopIteration
item = self.data[self.i]
self.i += 1
return item
c = C([1,2,3,4,5])
3 in c#Iter->Next->Next->Next->True
6 in c#Iter->Next->Next->Next->Next->Next->Next->False
#如果__iter__(self),__next__(self)方法都没有,则会查找__getitem__()方法
class C:
def __init__(self,data):
self.data = data
def __getitem__(self,index):
print("Getitem",end = "->")
return self.data[index]
c = C([1,2,3,4,5])
3 in c#Getitem->Getitem->Getitem->True
6 in c#Getitem->Getitem->Getitem->Getitem->Getitem->Getitem->False
布尔测试
#遇到bool()函数,python会去寻找__bool__()方法
class D:
def __bool__(self):
print("Bool")
return True
d = D()
bool(d)#Bool True
#如果没有定义__bool__()方法,Python就会去寻找__len__(),返回非0,表示true,返回0,表示False
class D:
def __init__(self,data):
self.data = data
def __len__(self):
print("Len")
return len(self.data)
d = D("abc")
bool(d)#Len True
跟运算相关的魔法方法
#定义比较运算符判断字符串长度
class S(str):
def __lt__(self,other):
return len(self) < len(other)
def __gt__(self,other):
return len(self) > len(other)
def __eq__(self,other):
return len(self) == len(other)
#若不想让某个方法生效,只需在后面加
#__lt__ = None
#__gt__ = None
#__eq__ = None
s1 = S("abc")
s2 = S("edc")
s1 < s2#False
s1 == s2#True
(9)像调用一个函数一样去调用对象
#像调用一个函数一样去调用对象,需要对象的类去定义__call__()方法
class C:
def __call__(self):
print("hi")
c = C()
c()#hi
#call支持位置参数、关键字参数
class C:
def __call__(self,*args,**kwargs):#*代表位置参数,**代表关键字参数
print(f"位置参数->{args}\n关键字参数->{kwargs}")
c = C()
c(1,2,3,x=250,y=520)
#位置参数->(1, 2, 3)
#关键字参数->{'x': 250, 'y': 520}
class Power:
def __init__(self,exp):
self.exp = exp
def __call__(self,base):
return base ** self.exp
square = Power(2)
square(2)#4
square(5)#25
cube = Power(3)
cube(2)#8
cube(5)#125
跟字符串相关的魔法方法
#跟字符串相关的魔法方法__str__(self)参数转化为字符串,__repr__(self)将对象转化为程序可执行的字符串
#repr可以作为str的代偿,反过来的话不行
repr可以打印整个列表,str不可以
通过同时定义两个方法,可以让对象在不同场景下有不同的显示
(10)property函数
#class property(fget=None,fset=None,fdel=None,doc=None)#本质上是一个内置的类
class C:
def __init__(self):
self._x=250
def getx(self):
return self._x
def setx(self,value):
self._x=value
def delx(self):
del self._x
x = property(getx,setx,delx)
c = C()
c.x#250
c.x = 300
c.__dict__#{'_x': 300}
del c.x
c.__dict__#{}
#将property函数作为装饰器使用,创建只读属性
class E:
def __init__(self):
self._x=250
@property#将内置函数作为装饰器来使用x=property(x)
def x(self):
return self._x
e = E()
e.x#250
e.x = 520#e.x是只读的,所以会报错
#多个参数传入
class E:
def __init__(self):
self._x=250
@property#将内置函数作为装饰器来使用x=property(x)
def x(self):
return self._x
@x.setter
def x(self,value):
self._x=value
@x.deleter
def x(self):
del self._x
e = E()
e.x#250
e.x = 300
e.__dict__#{'_x': 300}
del e.x
e.__dict__#{}
(11)类方法和静态方法
类方法
专门用来绑定类的方法@classmethod
class C:
def funA(self):
print(self)
@classmethod
def funB(cls):#类方法
print(cls)
c = C()
c.funA()#普通方法,绑定的是一个对象object
#<__main__.C object at 0x000002024E383A20>
c.funB()#绑定的类<class '__main__.C'>
#通过创建列表来维护一个类对应的所有对象
class C:
count = 0
def __init__(self):
C.count += 1
@classmethod
def get_count(cls):
print(f"该类一共实例化了{cls.count}个对象")
c1 = C()
c2 = C()
c3 = C()
c3.get_count()#该类一共实例化了3个对象
静态方法
放在类里面的函数,不需要和对象绑定
class C:
@staticmethod
def funC():
print("123")
c = C()
c.funC()#既可通过对象访问,又可通过类访问
C.funC()
#用静态方法统计对象数量
class C:
count = 0
def __init__(self):
C.count += 1
@staticmethod
def get_count():
print(f"该类一共实例化了{C.count}个对象")
c1 = C()
c2 = C()
c3 = C()
c3.get_count()#该类一共实例化了3个对象
class C:
count = 0
def __init__(self):
C.count += 1
@classmethod#添加另一个类方法
def add(cls):
cls.count += 1#对应类属性的count
def __init__(self):
self.add()#让构造函数去调用add类方法,哪个类去调就会传入哪个类
@classmethod
def get_count(cls):
print(f"该类一共实例化了{cls.count}个对象")
class D(C):
count = 0
class E(C):
count = 0
c1 = C()
d1,d2 = D(),D()
e1,e2,e3 = E(),E(),E()
c1.get_count()#该类一共实例化了1个对象
d1.get_count()#该类一共实例化了2个对象
e1.get_count()#该类一共实例化了3个对象
(12)描述符
描述符协议:实现了get,set,delete三个中任何一个或多个方法的类
self参数对应描述符类D的实例对象
instance对应被描述符拦截的属性所在的类的对象,类C的对象c
owner对应被描述符拦截的属性所在的类
#将property引用的案例修改为描述符形式