Python面向对象
1. 类和对象
1.1 面向对象概述
1)面向对象的优点
- 面向对象编程,将一类相似功能函数的集合在一起,可以让代码更清晰,更合理化
- 站在上帝视角看问题,类其实就是某种事物的公共的模板,对象就是从这个具体模板实例化出来的
2)类和对象
- 类:具有相同属性和功能的一类事物
- 对象:就是类的具体表现形式
3)面向对象&实例化
- 面向对象设计:将一类具体事物的数据和动作整合到一起,即面向对象设计
- 面向对象编程:用定义 类+实例/对象 的方式去实现面向对象的设计
- 实例化:由类生产对象的过程叫实例化,类实例化的结果就是一个对象,或者叫做一个实例(实例=对象)
4)用函数模拟面向对象
思路:函数嵌套函数,外部函数的结尾调用内部函数。而内部函数会返回一个字典,这个字典包含了一切内容。
# 分析:调用dog函数时,会直接执行init函数,init函数的返回值是dog1字典的地址 def dog(name, gender, type): # 这里dog函数返回的就是dog1的字典 def bark(dog): # 这里必须将返回的字典作为参数传给这个函数,以拿到字典中的内容 print("A dog [%s], is barking......" % dog['name']) def eat(dog): print("A dog [%s] , is eating......" % dog['type']) def init(name, gender, type): # 利用了函数的作用域和字典的数据类型,将对象函数和功能函数之间建立起联系 dog1 = { # 这个字典中包含了一切的内容 'name': name, 'gender': gender, 'type': type, 'bark': bark, 'eat': eat, } return dog1 # 返回字典的地址 # 这一步才开始执行第一个函数----> init(),这个函数进行初始化 return init(name, gender, type) # 将这个dog1字典作为整个大的dog函数的返回值 d1 = dog("xiaoming", "male", "black_dog") # 将初始化之后的dog保存为一个d1对象 # 其实就是将一个个函数或变量的地址保存为字典类型中的键值对,以建立起相应的联系 d2 = dog("alex", "female", "fool_dog") # 这里的d1和d2对象其实就是一个保存了函数和变量对应地址的键值对 d1['bark'](d1) # 获取d1字典中的bark键对应的bark函数的地址,然后将d1作为bark函数的参数,执行bark函数 d2['eat'](d2)
1.2 类的结构与使用
1)类的本质
- 类:数据属性 + 函数属性
- 类的本质可以近似于一个字典,这个字典中包含了所有的数据属性和函数属性
2)类的结构在大的方向上分为两部分
- 静态变量
- 动态方法
class Human: # 类名使用驼峰式命名风格,首字母大写,私有类可以用一个下划线开头 """ qfd """
mind = '那叫一个牛X' # 第一部分:静态属性 属性 静态变量 静态字段 def work(self): # 第二部分:方法 函数 动态属性 print('shi里山路bu换jian')
3)类的定义和调用
class Chinese(object): color = "yellow" # 这里定义一个数据属性 def make_money(): # 定义函数属性 print("Can make money...") def impolite(self): print("Do something is impolite") print(Chinese.color) # 这种加上 . 的调用形式,本质上就是在属性字典中调用 Chinese.make_money() print(dir(Chinese)) # 查看所有的变量(列表的形式) print(Chinese.__dict__) # 查看属性字典(包含数据属性和函数属性) print(Chinese.__dict__["color"]) Chinese.__dict__["make_money"]() # 这样就相当于直接去调用make_money函数 Chinese.__dict__["impolite"](Chinese) # 这样调用impolite函数,因为这个函数中接受一个self参数,所以要将Chinese传入 print(Chinese.__name__) # 显示类名 print(Chinese.__doc__) # 类中的文档 print(Chinese.__module__) # 显示类所在的模块
1.3 对象和实例化
1)对象
- 对象是从类中出来的,只要是类名加上(),这就是实例化的过程,就会实例化一个对象。
2)实例化的过程
- 在内存中开辟一个对象空间
- 自动执行类中的__init__方法,并将这个对象空间(内存地址)传给__init__() 方法的第一个位置参数self
- 在__init__方法中通过 self 给对象空间添加属性
3)关于self
- 类中的方法一般都是通过对象执行的(除去类方法,静态方法外),并且对象执行这些方法都会主动将对象空间传给方法中的第一个参数self
- self就是自身,self指的就是类实例化的那个对象。谁将类实例化了,self指的就是谁(self就是实例本身)
1.4 数据属性和函数属性的调用
类就犹如一个函数,里面的方法拥有自己的局部作用域,当调用的属性在init中没有找到时,就会到init方法的上一层继续找
类和函数不同的地方是,如果在类中没有找到,就会报错,而函数则继续往外在全局作用域中继续找
- 实例的字典中并不包含方法,只包含属性
- 实例只有数据属性(在init的作用域中),它的函数属性是进入上一个作用域,在类中寻找的
class Chinese(object): color = "yellow" # 这里定义一个数据属性 def __init__(self, name, age, gender): # 类在调用的时候,会自动先触发__init__函数的运行 # 这里的self和p1指向的是同一个内存地址同一个空间,以下就是通过self给这个对象空间封装3个属性 self.mingzi = name # 在类中用一个变量来接受传给init方法的值 self.nianji = age self.xingbie = gender def make_money(): # 定义函数属性 print("Can make lots of money...") def impolite(self): print("Do something is impolite") p1 = Chinese('hgzero', 21, 'male') # 这里将自身p1传递给self,然后后面的参数依次传递给init方法 # 创建了一个Chinese的实例p1 , 这个过程叫做类的实例化 print(p1.__dict__) # 其实init方法中的变量和值将会以字典的形式作为整个类的返回值,这里用__dict__显示它的字典 print(Chinese.__dict__) # 类的字典中包含所有方法、属性 Chinese.make_money() # 可以不实例对象而直接调用类中不包含self的方法 Chinese.impolite(p1) # 当调用含有self的方法时,必须要将实例后的对象作为参数传递进去 print(p1.__dict__['xingbie']) print(p1.mingzi) # 其实这种调用方式就是从dict去找 print(p1.color) # 调用时先从init的字典中去找,若没找到再到类的字典中去找,这是若再没找到就会报错 p1.make_money() # 当实例去调用方法时,会自动将自身(p1)作为第一个参数传递给被调用的方法(就是传递给self) # 这里因为类中的make_money这个方法没有self,而python在处理的时候却会传递给它一个p1,所以会报错
1.5 类属性的增删改查
- 实例的字典中保存数据属性,不保存函数属性
- 实例之间公用函数属性
class Chinese(object): color = "yellow" # 这是一个类属性 def __init__(self, name, age, gender): self.mingzi = name self.nianji = age self.xingbie = gender def make_money(self): print("Can make lots of money...") def impolite(self): print("Do something is impolite") def eat_food(self, food): print("The %s is eating %s" % (self.mingzi, food)) p1 = Chinese("hgzero", 21, "male") # 因为实例的字典中只保存数据属性,而不保存函数属性,所以,实例之间公用函数属性 p1.eat_food("Apple") p2 = Chinese("hg", 23, "male") p2.eat_food("Banana") # 查看类属性 print(Chinese.color) # 增加 Chinese.language = "HanYu" print(Chinese.language) # 修改类属性 Chinese.color = "black" print(Chinese.color) # 删除类属性 del Chinese.color # 给类增加一个函数属性 def eating(self, food): print("Now, %s is eating %s" %(self.mingzi, food)) # 在类中添加一个eat的方法,其地址指向eating函数的地址 Chinese.eat = eating p1.eat("baozi")
1.6 实例属性的增删改查
class Chinese(object): color = "yellow" # 这是一个类属性 def __init__(self, name, gender): self.mingzi = name self.xingbie = gender def make_money(self): print("Can make lots of money...") def impolite(self): print("Do something is impolite") def eat_food(self, food): print("The %s is eating %s" % (self.mingzi, food)) p1 = Chinese("hgzero", "male") # 创建一个实例 print(p1.__dict__) # 查看实例的字典 # 查看 print(p1.mingzi) # 增加 p1.age = 21 # 这里的age是被加入到了实例的字典中 print(p1.__dict__) print(p1.age) #-----------------------------不建议去直接修改属性字典结构------------------------------- # 这些属性的添加和修改删除无非就是通过操作底部的字典实现的,但是不建议直接去修改属性字典结构 p1.__dict__['sex'] = 'male' print(p1.__dict__) print(p1.sex) # ---------------------------------------------------------------------------------- # 修改 p1.age = 23 print(p1.__dict__) print(p1.age) # 删除 del p1.age print(p1.__dict__)
- 为实例添加一个函数属性(不常用)
# 为实例属性添加一个函数属性 def test(self): print("A test") p1.test = test # p1.test() # 若这样写会报错,因为class只有在实例调用类的方法时才会自动将自身作为第一个参数传递进去 # 而这里创建的test方法是属于实例自己的,所以class不会将自身传递进去 p1.test(p1)
1.7 对于属性的查找
- 查找规则
- 对象查找属性的顺序:对象空间栈 ——> 类空间栈 ——> 父类空间栈
- 类名查找属性的顺序:本类空间栈 ——> 父类空间栈 ——> ...
- 特别注意:
- 当不用用点 . 的方法调用变量时,会直接跳出类,到全局作用域查找这个变量
- 使用点 . 的形式调用变量时,才会在类中一层一层的寻找
country = "America" class Chinese: country = "China" def __init__(self, name): self.name = name print("--->", country) # ---这里需要注意,这里的country会直接调用类外面的country # 因为它没有用 . 的形式调用(没有使用self的形式),所以既不是类属性,也不是实例属性 def play_ball(self, ball): print('%s now is play %s' %(self.name, ball)) p1 = Chinese('hgzerowzh') print(p1.country) # 这里在实例中没有找到country,所以到类中找到了country p1.country = "Japan" # 这里只是在实例中增加了country的属性 print(Chinese.country) # 这里访问的是类中的country print(p1.country) # 注意:当不是用 . 的方法调用变量时,会跳出类,到全局作用域查找这个变量 # 使用 . 的形式调用变量时,才会在类中一层层的寻找
2. 类与类之间的关系
类与类之间存在以下关系:
- 依赖关系
- 关联关系
- 组合关系
- 聚合关系
- 实现关系
- 继承关系
2.1 依赖关系
- 依赖关系就是将一个类的对象或者类名传到另一个类的方法使用。
class Elphant: def __init__(self, name): self.name = name def open(self,obj1): '''开门''' print('大象要开门了,开') obj1.open_door() def close(self): '''关门''' print('大象要关门了,关') class Refrigerator: def open_door(self): print("冰箱门被打开了") def close_door(self): print("冰箱门被关上了") # 先实例化一个大象类和一个冰箱类 elphant1 = Elphant('大象') haier = Refrigerator() # 将冰箱对象作为参数传给大象对象的open方法 elphant1.open(haier)
2.2 关联关系
- 两种事物必须是互相关联的,但是在某些特殊情况下是可以更改和更换的。
- 当在逻辑上出现了,我需要你,你还得属于我,这种逻辑就是关联关系。
老师和学校的相互依赖:
class School: def __init__(self,name,address): self.name = name self.address = address self.teacher_list = [] def append_teacher(self,teacher): self.teacher_list.append(teacher)
class Teacher: def __init__(self,name,school): self.name = name self.school = school
s1 = School('日本','早稻田') s2 = School('美国','哈佛') s3 = School('英国','牛津')
t1 = Teacher('张三',s1) t2 = Teacher('李四',s2) t3 = Teacher('日天',s3)
s1.append_teacher(t1) s1.append_teacher(t2) s1.append_teacher(t3) # print(s1.teacher_list) # for teacher in s1.teacher_list: # print(teacher.name)
2.3 聚合关系
- 属于关联关系的一种特例,侧重点是XXX和XXX聚合成XXX,各自有各自的声明周期。
- 组合关系和聚合关系,代码上差别不大。
2.4 组合关系
- 组合关系是关联关系中的一种特例,组合关系比聚合还要紧密。
- 将一个类的对象封装到另一个类的对象的属性中就叫组合。
class Gamerole: def __init__(self,name,ad,hp): self.name = name self.ad = ad self.hp = hp def attack(self,p1): p1.hp -= self.ad print('%s攻击%s,%s掉了%s血,还剩%s血'%(self.name,p1.name,p1.name,self.ad,p1.hp)) def equip_weapon(self,wea): self.wea = wea # 组合:给一个对象封装一个属性改属性是另一个类的对象 class Weapon: def __init__(self,name,ad): self.name = name self.ad = ad def weapon_attack(self,p1,p2): p2.hp = p2.hp - self.ad - p1.ad print('%s 利用 %s 攻击了%s,%s还剩%s血' %(p1.name,self.name,p2.name,p2.name,p2.hp)) # 实例化三个人物对象: barry = Gamerole('太白',10,200) panky = Gamerole('金莲',20,50) pillow = Weapon('绣花枕头',2) # 给人物装备武器对象。 barry.equip_weapon(pillow) # 开始攻击 barry.wea.weapon_attack(barry,panky)
3. 类的继承
3.1 面向对象的继承
1)继承
继承(inheritance)是面向对象软件技术中的一个概念。
继承可以使得子类具有父类中的各种属性和方法,而不需要再次编写相同的代码。
在子类继承父类的同时,可以重新定义某些属性,并重写某些方法。即覆盖父类中的原有属性和方法,使其获得与父类不同的功能。
为子类追加新的属性和方法也是常见的做法。
一般静态的面向对象编程语言,继承属于静态的,即子类的行为在编译期就已经决定,无法在执行期间扩充。
2)继承的优点
- 增加了类的耦合性(耦合性不宜多,宜精)
- 减少了重复代码
- 使得代码更加规范化,合理化
3.2 继承的分类
1)继承的分类
- 单继承
- 多继承
2)py2和py3的异同
python2版本中有两种类:
- 经典类:经典类在基类的根什么都不写
- 新式类:新式类的特点是基类的根是object类
python3中使用的都是新式类,如果基类什么都不继承,则默认继承object
3.3 继承的执行顺序
1)单继承浅显的原则
- 实例化对象必须执行__init__方法,类中如果没有,则从父类找,父类没有,从object类中找
- 实例化对象执行方法时,先在自己的类中找,自己的类中没有才能执行父类中的方法
class Aniaml(object): type_name = '动物类' def __init__(self,name,sex,age): self.name = name self.age = age self.sex = sex def eat(self): print(self) print('吃东西') class Person(Aniaml): def eat(self): print('%s 吃饭'%self.name) class Cat(Aniaml): pass class Dog(Aniaml): pass p1 = Person('barry','男',18) # 实例化对象时必须执行__init__方法,类中没有,从父类找,父类没有,从object类中找。 p1.eat() # 先要执行自己类中的eat方法,自己类没有才能执行父类中的方法。
2)深层理解继承的顺序
浅显理解:
- 深度优先(在python2中类后面加上object就是新式类,不加就是经典类)
- 广度优先
继承顺序的原理:(MRO列表)
- MRO(Method Resolution Order)方法解析顺序
- 对于定义的每一个类,python都会计算出一个方法解析序列(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表
- 为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类位置
所谓的继承的顺序就是合并所有父类的MRO列表并遵循以下三条法则:
- 子类先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,则选择第一个父类(而不再继续选择第二个父类了)
3)MRO的计算法则
MRO是一个有序列表,在类被创建时就计算出来。
merge操作是C3算法的核心:C3算法,多继承查找规则
mro(B) = mro( B(A1, A2, A3 …) ) = [B] + merge( mro(A1), mro(A2), mro(A3) ..., [A1, A2, A3] )
merge的操作法则:
如计算merge( [E,O], [C,E,F,O], [C] ) 有三个列表 : ① ② ③
1 merge不为空,取出第一个列表列表①的表头E,进行判断 各个列表的表尾分别是[O], [E,F,O],E在这些表尾的集合中,因而跳过当前当前列表
2 取出列表②的表头C,进行判断 C不在各个列表的集合中,因而将C拿出到merge外,并从所有表头删除 merge( [E,O], [C,E,F,O], [C]) = [C] + merge( [E,O], [E,F,O] )
3 进行下一次新的merge操作 ......
merge计算示例:
mro(A) = mro( A(B,C) ) 原式= [A] + merge( mro(B),mro(C),[B,C] ) mro(B) = mro( B(D,E) ) = [B] + merge( mro(D), mro(E), [D,E] ) # 多继承 = [B] + merge( [D,O] , [E,O] , [D,E] ) # 单继承mro(D(O))=[D,O] = [B,D] + merge( [O] , [E,O] , [E] ) # 拿出并删除D,然后对第二个列表的开头E进行判断 = [B,D,E] + merge([O] , [O]) # 拿出并在所有列表开头删除E = [B,D,E,O] mro(C) = mro( C(E,F) ) = [C] + merge( mro(E), mro(F), [E,F] ) = [C] + merge( [E,O] , [F,O] , [E,F] ) = [C,E] + merge( [O] , [F,O] , [F] ) # 跳过O,拿出并删除 = [C,E,F] + merge([O] , [O]) = [C,E,F,O] 原式= [A] + merge( [B,D,E,O], [C,E,F,O], [B,C]) = [A,B] + merge( [D,E,O], [C,E,F,O], [C]) = [A,B,D] + merge( [E,O], [C,E,F,O], [C]) # 跳过E,但是不删除E = [A,B,D,C] + merge([E,O], [E,F,O]) = [A,B,D,C,E] + merge([O], [F,O]) # 跳过O = [A,B,D,C,E,F] + merge([O], [O]) = [A,B,D,C,E,F,O] ---------------------
计算图示:
3.4 在子类中执行父类的方法
1)方法一:如果想要在子类中执行父类的方法,需要在子类的方法中写上:
- 父类.func(对象, 其他参数)
class Aniaml(object): type_name = '动物类' def __init__(self,name,sex,age): self.name = name self.age = age self.sex = sex def eat(self): print('吃东西') class Person(Aniaml): def __init__(self,name,sex,age,mind): Aniaml.__init__(self,name,sex,age) # 方法一 self.mind = mind def eat(self): super().eat() print('%s 吃饭'%self.name) class Cat(Aniaml): pass class Dog(Aniaml): pass p1 = Person('春哥','laddboy',18,'有思想') print(p1.__dict__)
2)方法二:利用super
- super().func(参数)
class Aniaml(object): type_name = '动物类' def __init__(self,name,sex,age): self.name = name self.age = age self.sex = sex def eat(self): print('吃东西') class Person(Aniaml): def __init__(self,name,sex,age,mind): # super(Person,self).__init__(name,sex,age) # 方法二 # super(__class__,self).__init__(name,sex,age) # 方法二,__class__变量就是当前类 super().__init__(name,sex,age) # 方法二 self.mind = mind def eat(self): super().eat() print('%s 吃饭'%self.name) class Cat(Aniaml): pass class Dog(Aniaml): pass
p1 = Person('春哥','laddboy',18,'有思想') print(p1.__dict__)
3)特别注意
super中的self指的是子类中self,也就是子类的对象,这个self被传入了父类中,并作为父类的self:如果父类的方法调用实例属性,那么就是在调用子类中的self字典中的属性,所以这个子类self字典必须要包含所有父类init方法中的属性;如果父类中的方法想通过self调用实例方法,因为这里的self是子类的对象,所以只能调用子类中的方法。
子类在继承的时候,子类中的方法最好不要和父类中的方法重名,因为万一父类中的方法调用了父类本身的方法,如果子类中的方法和父类想调用的这个方法重名,那么父类通过self调用的方法就会变成了子类中和父类重名的方法(因为继承的时候传给父类的self是子类的对象)。
3.5 super的本质
super方法的本质:
- super方法其实并不是继承,它只是一个查找MRO列表的函数(直接调用类而已);
- 将当前类传入super函数,super函数就会找到MRO列表中的当前函数的下一个类,然后将这个类作为返回值返回;
注意:
- 如果super不加参数,则它指的就是本类,如super.func(),这里的super其实就是本类;
- 然后因为有了继承的关系,在子类中没有找到func方法就到父类的作用域中找,造成了super代指父类的假象;
class A(): def go(self): print("go A") class B(A): def go(self): super(B,self).go() print("go B") class C(A): def go(self): super(C,self).go() print("go C") class D(B,C): def go(self): super(D,self).go() print("go D") d = D() d.go() # 这里的运行结果顺序是 A-C-B-D # 原因: # super函数的类似实现: # super接收两个参数, # 第一个是类名,第二个是一个实例对象(MRO列表就是通过这个实例对象计算出来的) # 这里实例要是最初的那个类的实例,以得到完整的MRO列表 def super(cls, inst): mro = inst.__class__.mro() # 这里通过传入的那个实例来找到它的类,然后再拿到类的mro列表 return mro[mro.index(cls)+1] # 这里返回的是super参数中的那个类在MRO列表中的下一个类
3.6 接口继承
1)接口继承
接口继承就是定义一个接口(父类),它不实现它自己的方法,但所继承它的子类必须实现父类中的方法(实现了归一化设计)
但是python中没有对接口继承进行限定,需要导入一个abc模块作为辅助
2)接口继承的实质
接口继承的实质就是做一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象,这在程序设计上,叫做 归一化。
import abc class All_file(metaclass=abc.ABCMeta): # 定义了一个接口类(不实现自己的方法,但继承它的子类必须实现它的全部接口方法) @abc.abstractmethod # 定义一个接口方法 def read(self): pass @abc.abstractmethod def write(self): pass class Disk(All_file): def read(self): print('Disk read') def write(self): print('Disk write') class Cdrom(All_file): def read(self): print("Cdrom read") def write(self): pritn("Cdrom write") class Mem(All_file): def read(self): print("Mem read") def write(self): print("Mem write") m1 = Mem() m1.read() m1.write()
4. 封装&继承&多态
4.1 封装
1)封装概述
封装,就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
综上所述,面向对象的封装,其实就是使用构造方法将内容封装到对象中,然后通过对象直接或者self间接的获取被封装的内容。
2)三个层面的封装
- 第一个层面的封装:类就是麻袋,这本身就是一种封装
- 第二个层面的封装:类中定义私有的、只有在类的内部使用,外部无法访问的 __变量
- 第三个层面的封装:明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用(这才是真正的封装)
class People: _start_1 = "this is one" # 单下划线开始的成员变量叫做【保护变量】,意思是只有类对象和子类对象能访问这些变量, # 但python对保护变量只是约定,没有限定外部一定不能访问 __start_2 = "this is two" # 双下划线开头的是【私有成员】,只有类对象自己能访问,连子类对象也不能访问到这个数据 def __init__(self, id, name, age, salary): self.id = id self.name = name self.age = age self.salary = salary def get_id(self): print("I am privaty method, I find method is [%s]" % self.id) # 访问函数,通过这个函数去访问内部私有成员(这就是外部访问内部私有成员的接口) def get_start(self): print(self.__start_2) # 类的内部可以访问到私有成员 p1 = People('1778', "hgzero", '21', 1000000) # print(People.__dict__) print(p1._start_1) # print(p1.__start_2) # 以__开头的变量之所以不能访问,是因为python在内部进行了重命名, 以一个下划线+类名__变量名 命名 print(p1._People__start_2) # python对__start_2重命名后的变量
4.2 继承
子类可以拥有父类中除了私有属性外的其他所有内容。
从代码层面上来看,两个类具有相同的功能或者特征的时候,可以采用继承的形式,提取一个父类,这个父类中编写两个类相同的部分,然后两个类分别继承这个类就可以了。
4.3 多态
多态的概念指出了对象如何通过它们共同的属性和动作来操作及访问,而不需要考虑它们具体的类。
简单来说,多态就是不同的对象可以调用相同的方法,而产生不同的结果。
class H2O: def __init__(self, name, temperature): self.name = name self.temperature = temperature def turn_ice(self): if self.temperature < 0: print('[%s] 温度太低结冰了' % self.name) elif self.temperature > 0 and self.temperature < 100: print('[%s] 液化成水' % self.name) elif self.temperature > 100: print('[%s] 温度太高变成了水蒸气' % self.name) class Water(H2O): pass class Ice(H2O): pass class Steam(H2O): pass w1 = Water('水', 25) i1 = Ice('冰', -20) s1 = Steam("蒸汽", 3000) def func(obj): obj.turn_ice() func(w1) # 统一的接口,不同的实现 func(i1) func(s1) # w2 = Water('水', 101) # func(w2)
5. 类的组成成员
5.1 类的私有成员
- xx:公有变量
- _x:单前置下划线,私有化属性或方法,from somemodule import *禁止导入,类对象和子类可以访问
- __xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(名字重整所以访问不到)
- __xx__:双前后下划线,用户名字空间的魔法对象或属性。例如:
__init__
, __ 不要自己发明这样的名字 - xx_:单后置下划线,用于避免与Python关键词的冲突
1)每一个类的成员都有两种形式
- 共有成员:在任何地方都能访问
- 私有成员:只有在类的内部才能访问
2)普通字段(对象属性)
- 共有普通字段:对象可以访问,类内部可以访问,派生类中可以访问
- 私有普通字段:仅类内部可以访问
3)静态字段(静态属性)
- 公有静态字段:类可以访问,类内部可以访问,派生类中可以访问
- 私有静态字段:仅类内部可以访问
4)方法
- 共有方法:对象可以访问,类内部可以访问,派生类中可以访问
- 私有方法:仅类内部可以访问
class A: company_name = '日天company' # 静态变量(静态字段) __iphone = '1353333xxxx' # 私有静态变量(私有静态字段)
def __init__(self,name,age): # 特殊方法 self.name = name # 对象属性(普通字段) self.__age = age # 私有对象属性(私有普通字段)
def func1(self): # 普通方法 pass
def __func(self): # 私有方法 print(666)
@classmethod # 类方法 def class_func(cls): """ 定义类方法,至少有一个cls参数 """ print('类方法')
@staticmethod # 静态方法 def static_func(): """ 定义静态方法 ,无默认参数""" print('静态方法')
@property # 属性 def prop(self): pass
- 以__开头的成员之所以不能访问,是因为python在内部进行了重命名, 以一个 xx._类名__成员变量名 命名
5.2 静态属性
1)什么是静态属性
静态属性可以使得函数就像属性一样被调用,使得这个函数成为一个属性(调用时不要加括号)
将类的函数定义成属性以后,对象再去使用时根本无法觉察出自己是执行了一个函数然后计算出来的
这种特性的使用方式遵循了统一访问原则
2)三种访问方式
由于新式类中具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
class Room: def __init__(self, name, owner, width, length, height): self.name = name self.owner = owner self.width = width self.length = length self.height = height # 静态属性 @property # 使得函数就像属性一样被调用,使得这个函数成为一个属性(调用时不要加括号) def cal_area(self): print('%s 住的 %s 总面积是 %s' %(self.owner, self.name, self.width*self.length)) return self.width*self.length @cal_area.setter # 对它所修饰的静态属性进行赋值操作时触发 def cal_area(self, val): # self就是调用它的那个实例,val就是传给他的那个值 print('set的时候运行', val) @cal_area.deleter # 对它所修饰的静态属性进行删除操作时触发 def cal_area(self): print('del的时候运行') # 注意:同一个静态方法的函数名必须相同 r1 = Room("厕所", "alex", 100, 100, 10000) r2 = Room('公共厕所', "linghaifeng", 1, 1, 1) print(r1.cal_area) print(r2.cal_area) r1.cal_area = 'hgzero' # 赋值操作 del r1.cal_area # 删除操作 print(r1.name) print(r2.name) # ================================================================================== # =================用property的内置属性来实现对静态属性的操作========================== class Room: def get_cal_area(self): print('get的时候运行') def set_cal_area(self, val): print('set的时候运行', val) def del_cal_area(self): print('del的时候运行') cal_area = property(get_cal_area, set_cal_area, del_cal_area) # 这种写法一定要按照get、set、del的顺序来写 f1 = Room() f1.cal_area f1.cal_area = 'wzh' del f1.cal_area
5.3 类的其他成员
- 方法包括:普通方法、静态方法、类方法;这三种方法在内存中都归属于类;
1)实例方法
- 定义:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法)
- 调用:只能由实例对象调用
2)类方法
- 定义:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法)
- 调用:实例对象和类对象都可以调用
class Room: tag = 1 # 类属性,直接定义在类的作用域之下了 def __init__(self, name, owner, width, length, height): self.name = name self.owner = owner self.width = width self.length = length self.height = height @property def cal_area(self): print('%s 住的 %s 总面积是 %s' %(self.owner, self.name, self.width*self.length)) return self.width*self.length def test(self): print('from test', self.name) # def tell_info(self): # 这样每当调用这个方法时都要传入一个实例,但有时的需求是不传实例直接调用类中的变量 # print('----->', self.tag) @classmethod # 类方法,被调用时会自动将类名传入作为第一个参数cls def tell_info(cls, x): print(cls) print('----->', cls.tag, x) print(Room.tag) Room.tell_info(10) # 类方法在调用时会自动将类本身作为第一个参数传入给cls # ----------------------实例能调用类方法,但是没人这么用-------------------------- r1 = Room("厕所", "alex", 100, 100, 10000) r2 = Room('公共厕所', "linghaifeng", 1, 1, 1) r1.tell_info(20) # 实例也能调用类方法 # -----------------------------------------------------------------------------
3)静态方法
- 静态方法主要用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系;
- 在静态方法中,不会涉及到类中的属性和方法的操作;
- 静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护;
class Room: tag = 1 def __init__(self, name, owner, width, length, height): self.name = name self.owner = owner self.width = width self.length = length self.height = height @property def cal_area(self): print('%s 住的 %s 总面积是 %s' %(self.owner, self.name, self.width*self.length)) return self.width*self.length def test(self): print('from test', self.name) @classmethod # 类方法,被调用时会自动将类名传入作为第一个参数cls def tell_info(cls, x): print(cls) print('----->', cls.tag, x) @staticmethod # 静态方法(类的工具包),只是名义上归类管理,不能使用类变量和实例变量 def wash_body(a, b, c): print('%s %s %s正在washing' %(a, b, c)) Room.wash_body("alex", "linghaifeng", "wupeiqi")
5.4 isinstance与issubclass
1)isinstance(a, b)
- 判断a是否是b类(或者b类的派生类)实例化的对象
class A: pass class B(A): pass class C(B): pass print(issubclass(B,A)) print(issubclass(C,A))
2)issubclass
- 判断a类是否是b类(或者b的派生类)的派生类
from collections import Iterable print(isinstance([1,2,3], list)) # True print(isinstance([1,2,3], Iterable)) # True print(issubclass(list,Iterable)) # True
# 由上面的例子可得,这些可迭代的数据类型,list str tuple dict等 都是 Iterable的子类
5.6 getattribute
##### isinstance和issubclass ##### class Foo: pass f1 = Foo() print(isinstance(f1, Foo)) # 判断f1(对象)是否是由Foo(类)实例化而来的, True或者False class Bar(Foo): pass print(issubclass(Bar, Foo)) # 判断Bar是否是Foo的子类 ##### getattribute #####class Func: def __init__(self, x): self.x = x # 在两者同时出现时: def __getattr__(self, item): # 当在__getattribute__中触发了一个AttrubuteError的异常的时候才会触发getattr的运行 print('执行的是getattr') def __getattribute__(self, item): # 无论属性是否找到都会触发__getattribute__的运行 # 注意,如果在这个方法中不对实例的字典做操作的话,那么实力属性便不会被赋值,就会是None print('执行的是getattribute') raise AttributeError('抛出异常了') # 只有当getattribute函数抛出了这个异常才会执行__getattr__函数 f1 = Func(10) f1.x # 执行一个找不到的属性 f1.xxxxx
6. 反射
- 反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
6.1 hasattr&getattr&setattr&delattr
class BlackMedium: feture = "Ugly" def __init__(self, name, addr): self.name = name self.addr = addr def sell_hourse(self): print("[%s] 在卖房子" % self.name) def rent_hourse(self): print("[%s] 在租房子" % self.name) b1 = BlackMedium("Place", "北京") # hasattr hasattr(b1, 'name') # 检测b1能不能调用name这个属性 hasattr(b1, 'sell_hourse') # 检测b1能不能调用sell_hourse这个方法 # getattr print(getattr(b1, 'name')) func = getattr(b1, 'rent_hourse') # 通过getattr拿到这个属性或者方法,如果没有则报错 func() getattr(b1, 'hire_house', "hgzero_house") # 可以指定第三个参数,若找不到就返回第三个参数的值 # setattr setattr(b1, 'sb', True) # 将sb=True添加到b1中去,也可以改变已有的变量 setattr(b1, 'func' , lambda x:x+1) # 添加一个匿名函数 setattr(b1, 'func2', lambda self:self.name+' sb') print(b1.func(10)) print(b1.func2(b1)) # delattr delattr(b1, 'sb') # 将b1中的sb变量删除
6.2 双下划线开头的getattr&setattr&delattr
class BlackMedium: feture = "Ugly" def __init__(self, name, addr): self.name = name self.addr = addr def sell_hourse(self): print("[%s] 在卖房子" % self.name) def rent_hourse(self): print("[%s] 在租房子" % self.name) # 以下3个方法类的实例使用时才会触发 def __getattr__(self, item): # 当调用一个不存在的属性或者方法时,就会触发__getattr__方法 print("执行__getattr__") def __setattr__(self, key, value): # 实例化时一设置属性就会触发这个方法运行,注意这里传入的是一个键值对 print('__setattr__执行') # self.key = value # 这样设置的话,也是在设置属性,又会调用这个方法,陷入无限递归 self.__dict__[key] = value # 为了不陷入无限递归,可以直接在__dict__中设置属性 def __delattr__(self, item): self.__dict__.pop(item) # 这样直接操作底层字典,也是为了避免陷入无限递归 b1 = BlackMedium("Place", "北京") # 双下划线开头的__getattr__() print(b1.hgzero) # 当调用一个不存在的属性或者方法时,就会触发__getattr__方法 # 双下划线开头的__delattr__() del b1.feture # 删除某一属性或者方法时,就会触发__delattr__方法 # 双下划线开头的__setattr__() b2 = BlackMedium("Place", "东京") # 初始化时,只要一设置属性,就会触发__setattr__方法的执行 print(dir(BlackMedium)) # dir()方法可以打印出最全的类中的属性和方法
- 使用示例
class Foo: def __init__(self, name): self.name = name def __getattr__(self, item): print('要找的属性【%s】不存在' % item) def __setattr__(self, k, v): # 这里的k和f是f1实例通过 . 的方式添加属性的操作传过来的键值对 print('执行setattr', k, v) if type(v) is str: # 设置一个验证的过程,验证设置的属性值为字符串类型 print('开始设置') self.__dict__[k] = v else: print('必须是字符串类型') def __delattr__(self, item): print("执行delattr", item) # del self.item # 这样的删除操作又会触发delattr函数,造成无限递归 self.__dict__.pop(item) f1 = Foo('alex') # 这个实例化的过程在init方法中设置了name属性的值为alex,所以也会触发setattr方法 f1.age = 18 f1.gender = 'male' print(f1.__dict__) # 这里的字典如果为空,是因为实例化的过程触及了属性设置,而这个类中我们自己定义的setattr没有将属性添加到属性字典中 # 系统自带的__setattr__方法会将设置的属性添加到属性字典中 del f1.name
6.3 动态导入模块
# 当import*时,不会导入模块中的私有方法(前面加上_的方法),但是可以通过直接import __变量name 调用 module_t = __import__('m.t') # 这里动态的导入模块,可以执行m中的t模块,但是返回值拿到的却是最顶层的m module_t.t.test1() # 由于上面返回的是最顶层的m,所以后面要导入时还要重新写 import importlib importlib.import_module('m.t') # 这种动态导入模块的方式可以直接拿到最底层,现在已经定位到t了
7. 包装&授权
7.1 继承的方式完成包装
可以通过包装已知的类型来达到自定制的效果,这种做法可以新建、修改或删除原有产品的功能,其他则保持原样。
# 包装: 可以通过包装这种已知的类型来达到自定制的效果,这种做法可以新建、修改或删除原有产品的功能,其他则保持原样 class List(list): def append(self, p_object): # 重构list中的append方法 if type(p_object) is str: # self.append(p_object) # 这样会不断陷入递归之中 # list.append(self, p_object) super(List, self).append(p_object) # 再调用父类中的append方法 else: print('只能添加字符串类型') def show_midle(self): mdi_index = int(len(self)/2) return self[mdi_index] l1 = List('helloworld') print(l1.show_midle()) l1.append("SB") print(l1)
7.2 组合的方式完成授权
# 授权:授权是包装的一个特征,授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性 import time class FileHandle: def __init__(self, filename, mode='r', encoding="utf-8"): # self.filename = filename self.file = open(filename, mode, encoding=encoding) # 先将文件操作的所有方法和属性全部保存到self.file中 self.mode = mode self.encoding = encoding def write(self, line): # 这种授权机制不让用户访问原生的方法,而访问自己定制的方法 print('------>', line) t = time.strftime('%Y-%m-%d %X', time.localtime()) self.file.write('%s %s' %(t,line)) def __getattr__(self, item): # 当调用一个FileHandle类中不存在的方法或属性时,就会触发__getattr__方法,这里的self就是f1实例 print(item, type(item)) return getattr(self.file, item) # 返回值就是保存在self.file中的原生的文件操作里面的方法或属性 f1 = FileHandle('a.txt', 'r+') print(f1.file) f1.write("hello, what's your name?\n") f1.write("welcome you\n") f1.write("nice to meet you\n") f1.seek(0) print(f1.read()) # 触发__getattr__
8. 面向对象进阶
8.1 item系列
- __getitem__()
- __setitem__()
- __delitem__()
class Foo: # 当进行对底层字典的操作时,才会触发下面这三个方法 def __getitem__(self, item): # 当以字典的形式调用不存在的属性时,就会触发gettiem方法 print('__getitem__') def __setitem__(self, key, value): # 当以字典的形式设置属性时,就会触发setitem方法 print('__setitem__') self.__dict__[key] = value def __delitem__(self, key): # 当以字典的形式删除一个属性时,就会触发delitem方法 print('__delitem__') self.__dict__.pop(key) f1 = Foo() print(f1.__dict__) f1['name'] = 'hgzero' # 相当于在给字典添加键值对,触发了setitem方法,然后该方法在内部将键值对添加到底层字典中 f1['age'] = 21 f1['gender'] = 'male' # 注意:只要是对字典操作的动作都会触发这些方法,然后真正对字典的操作都是由这些方法在内部完成的 print(f1.__dict__) del f1.name # 这种以点的形式的操作不会触发delitem方法 print(f1.__dict__) # 特别注意: 像这种 f1.__dict__['name'] = 'hgzero' 的调用方式不会触发这些方法 print(f1.age) del f1['gender'] # 以字典的形式删除属性,会触发delitem方法 f1['wzh'] # 以字典的形式操作一个不存在的属性,会触发getitem方法
8.2 __len__ 、__hash__、__eq__
1)__len__
class B: def __len__(self): print(666) b = B() len(b) # len 一个对象就会触发 __len__方法。 class A: def __init__(self): self.a = 1 self.b = 2 def __len__(self): return len(self.__dict__) a = A() print(len(a))
2)__hash__
class A: def __init__(self): self.a = 1 self.b = 2 def __hash__(self): return hash(str(self.a)+str(self.b)) a = A() print(hash(a))
3)__eq__
class A: def __init__(self): self.a = 1 self.b = 2 def __eq__(self,obj): if self.a == obj.a and self.b == obj.b: return True a = A() b = A() print(a == b)
8.3 __str__、__repr__、__doc__、__format__
1)__str__
class Foo: def __init__(self, name, age): self.name = name self.age = age def __str__(self): # print打印这个Foo的实例的时候,触发的其实就是__str__方法 return 'str---> 名字是【%s】 , 年龄是【%s】' %(self.name, self.age) # 输出的结果都必须是字符串 f1 = Foo('hgzero', 21) print(f1) # print打印这个实例就会触发其__str__方法,或者是str()函数也会触发 x = str(f1) print(x)
2)__repr__
class Foo2: def __init__(self, name, age): self.name = name self.age = age def __repr__(self): # repr方法在解释器中会被触发 return 'repr---> 名字是【%s】 , 年龄是【%s】' %(self.name, self.age) f2 = Foo2('wzh', 23) print(f2) # ******************************************************************************* # 注意:当str和repr共存时,print会先去找str,若找不到str则会去找repr,若再找不到repr,就使用默认的 # repr方法对解释器更友好 # repr()函数得到的字符串通常可以用来重新获得该对象,repr()的输入对python比较友好 # 通常情况下obj==eval(repr(obj))这个等式是成立的, 而str()就不行 obj==eval(str(obj)) 这样是不成立的
3)__doc__
class Foo: '描述信息' # 这就是doc属性的内容 pass class Bar(Foo): pass print(Foo. __doc__) # __doc__属性不会被继承,原因是每个类中都会自动定义一个doc,如果不写它的内容,就会自动定义为None print(Bar.__doc__) print(Foo.__dict__) print(Bar.__dict__) form lib.aa import C c1 = C() print(c1.__module__) # 查看c1来自于哪个模块 print(c1.__class__) # 查看c1是由哪个类产生的
4)自定制format
format_dic = { # 定义一个字典,以确定格式之间的对应关系 'ymd': '{0.year}{0.mon}{0.day}', 'm-d-y': '{0.mon}-{0.day}-{0.year}', 'y:m:d': '{0.year}:{0.mon}:{0.day}' } class Date: def __init__(self, year, mon, day): self.year = year self.mon = mon self.day = day def __format__(self, format_spec): print('我执行了--->', format_spec) if not format_spec or format_spec not in format_dic: format_spec = 'ymd' fm = format_dic[format_spec] return fm.format(self) # 这里的self其实就是Date的实例对象d1,是一个个实例的键值对 d1 = Date(2018, 8, 29) # format(d1) # 这样就是触发了d1下面的__format__方法 print(format(d1)) print(format(d1, 'ymd')) print(format(d1, 'm-d-y')) # 问题:这样的format()函数的方式和str.format()的方式有何异同,后者会不会也触发__format__方法?
8.4 slots属性
使用点来访问属性本质就是在访问类或者对象的 __dict__ 属性字典(类的字典是共享的,而每个实例的则是独立的)
字典会占用大量内存,如果有一个属性很少的类,但是有很多实例,为了节省内存可以使用 __slots__ 取代实例的 __dict__
__slots__ 是一个类变量,变量值可以是列表、元组,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
# 注意: slots是定义在类中的类变量,一旦出现了slots,则所以的实例都没有了__dict__字典了 # 出于节省内存的目的才会使用slots class Foo: __slots__ = ['name', 'age'] # 等价于 {'name': None, 'age': None} # __slots__ = 'name' # 一旦slots确定了属性,后面的实例都只能修改slots提供的那些属性,不能越界 f1 = Foo() print(Foo.__slots__) print(f1.__slots__) f1.name = "hgzero" f1.age = 21 print(f1.name) print(f1.age)
8.5 __del__析构方法
- 析构方法,当对象在内存中被释放时,自动触发执行
- 此方法一般无须定义,Python在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的
# 析构方法: 当对象在内存中被释放时,自动触发执行 # 注意:此方法一般无需定义,析构函数的调用是由解释器在进行垃圾回收是自动触发执行的 class Foo: def __init__(self, name): self.name = name def __del__(self): # (析构方法)在实例被删除的时候触发 print('执行了。。。') f1 = Foo('hgzero') print('-------------------执行结束--------------------')
8.6 __call__方法
- 对象后面加括号,触发执行;
- __call__方法的执行是由对象后加括号触发的: 对象() 或者 类()()
class Foo: def __init__(self, *args, **kwargs): print('这里执行了init方法') def __call__(self, *args, **kwargs): print('这里执行了call方法') f1 = Foo() # 执行__init__方法 f1() # 执行__call__方法 Foo() # 产生它的那个类下面的__call__方法 # 对象 + 括号() ======> 就是在执行这个对象所在的类下面的__call__方法
8.7 __iter__() 和__next__()
1)__iter__() 和 __next__() 实现迭代器协议
class Foo: def __init__(self, n): self.n = n def __iter__(self): return self def __next__(self): if self.n == 13: raise StopIteration("终止了") self.n += 1 return self.n f1 = Foo(10) # print(f1.__next__()) # print(f1.__next__()) # print(f1.__next__()) for i in f1: # 先调用对象下面的iter方法将其转变成可迭代对象,然后再调用可迭代对象下面的next方法直至遇到StopIteration异常 print(i)
2)迭代器协议实现斐波那契数列
class Fib: def __init__(self): self._a = 1 self._b = 1 def __iter__(self): return self def __next__(self): if self._a > 1000: # 但数字大于1000时,主动抛出StopIteration异常 raise StopIteration("终止了") self._a, self._b = self._b, self._a + self._b return self._a f1 = Fib() print(next(f1)) print(next(f1)) print(next(f1)) print(next(f1)) print(next(f1)) for i in f1: print(i)
8.8 上下文管理协议
class Open: def __init__(self, name): self.name = name def __enter__(self): # 在with语句执行时触发 print('执行enter') return self def __exit__(self, exc_type, exc_val, exc_tb): # 在with语句代码块执行完毕的时候触发 print('执行exit') print(exc_type) # 异常所在的类名 print(exc_val) # 异常的描述信息(异常的值) print(exc_tb) # 异常的追踪信息 # return True # 这里返回True会将异常吞没 with Open('a.txt') as f: # with会触发enter, 然后enter返回的值会传递给f print(f.name) print("=======>") print("=======>") print("=======>") print(hgzero) # 一:没有异常的情况下,整个代码块运行完毕后去触发__exit__,它的三个参数都是None # 二:有异常的情况下,从异常出现的位置直接触发__exit__ # 1.如果__exit__的返回值为True, 代表吞掉了异常 # 2.如果__exit__的返回值不为True,代表吐出了异常 # 3.__exit__的运行完毕就代表了整个with语句的执行完毕 print("=======>") print("=======>") print('hello')
8.9 描述符
1)描述符
- 数据描述符:至少实现了 __get__() 和 __set__()
- 非数据描述符:没有实现 __set__()
- 注意:必须把描述符定义成这个类的类属性,不能定义到构造函数中
# 描述符(描述符协议):本质上就是一个新式类,在这个新式类中,至少实现了__get__(), __set__(), __delete__()中的一个 # __get__() : 调用一个属性时触发 # __set__() : 为一个属性赋值时触发 # __delete__() : 采用del删除属性时触发 # 描述符的作用:描述符是用来代替另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中) # 描述符分两种; # 1.数据描述符:至少实现了__get__()和__set__() # 2.非数据描述符:没有实现__set__() # 描述符Foo class Foo: def __get__(self, instance, owner): # instance是触发这个方法的实例,owner是产生这个实例的类 print('===>get方法') def __set__(self, instance, value): # 这里的instance就是b1(触发它的实例),而value就是传过来的值10 print('===>set方法',instance,value) # instance.__dict__['x']=value # b1.__dict__ def __delete__(self, instance): print('===>delete方法') # 描述符Bar class Bar: x=Foo() # 将Foo()定义成Bar的类属性(x被Foo类代理) def __init__(self,n): self.x=n # b1.x=10 b1=Bar(10) # 触发了set方法 print(b1.__dict__) # 这里为空是因为所有与x有关的内容都被Foo类代理了,都交由Foo处理,这里就不添加到属性字典中去了 b1.x=12345 # 为x赋值,触发了set方法 print(b1.__dict__) b1.y=11111 # 这里只是仅仅是为b1的字典中添加了y=11111 print(b1.__dict__) print(Bar.__dict__) # del b1.x # 触发delete方法
2)描述符的优先级
### 要严格遵循该优先级,优先级由高到底分别是: # 1.类属性 # 2.数据描述符 # 3.实例属性 # 4.非数据描述符 # 5.找不到的属性触发__getattr__() class Foo: def __get__(self, instance, owner): print('===>get方法') def __set__(self, instance, value): print('===>set方法',instance,value) def __delete__(self, instance): print('===>delete方法') class Bar: x=Foo() def __init__(self, n): self.x=n # print(Bar.x) # 这里因为涉及到取值操作,所以触发了get方法 # Bar.x = 1 # 这里因为添加类属性的操作覆盖了原有的描述符,所以不会触发set方法(它已经不是描述符了) # print(Bar.__dict__) b1 = Bar(10) b1.x = 1 # 实例进行修改操作时因为没有覆盖描述符,所以触发了set方法 del b1.x # 触发了delete方法
3)描述符的应用
# 目的:对传入类中的参数进行类型筛选 class Typed: def __init__(self, key, expected_type): self.key = key self.expected_type = expected_type def __get__(self, instance, owner): # 这里的instance是实例,owner是实例所属的类 print('get方法') print('instance参数【%s】' % instance) print('owner参数【%s】 ' % owner) def __set__(self, instance, value): print('set方法') print('instance参数【%s】' % instance) print('value参数【%s】' % value) if not isinstance(value, self.expected_type): print('【%s】传入的类型不是【%s】,Error!!!' % (self.key,self.expected_type)) return instance.__dict__[self.key] = value def __delete__(slef, instance): print('delete方法') print('instance参数【%s】' % instance) instance.__dict__.pop(self.key) class People: name = Typed('name', str) # 这里先设定了传入参数的类型 age = Typed('age ', int) def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary p1 = People('hgzero', 21, 35000) # 这里触发了两个描述符设置了一个name和一个age,所以这里触发了两个set方法 p1.name p1.name = 'wzh' print(p1.__dict__)
8.10 类的装饰器
1)类的装饰器
- 为类设置属性
def Typed(**kwargs): def deco(obj): for key, val in kwargs.items(): setattr(obj, key, val) # 为类设置属性,obj.key = val return obj return deco @Typed(x=1, y=2, z=3) # 等价于Foo=deco(Foo) class Foo: pass print(Foo.__dict__) @Typed(name='hgzero') class Bar: pass print(Bar.name)
2)类的装饰器 + 描述符 的应用
# 目的;对传入类中的数据进行类型筛选 # 更进一步:巧妙的解决传入过多参数的筛选问题 class Typed: def __init__(self, key, expected_type): self.key = key self.expected_type = expected_type def __get__(self, instance, owner): print('get方法') print('instance参数【%s】' % instance) print('owner参数【%s】 ' % owner) def __set__(self, instance, value): print('set方法') print('instance参数【%s】' % instance) print('value参数【%s】' % value) if not isinstance(value, self.expected_type): print('【%s】传入的类型不是【%s】,Error!!!' % (self.key,self.expected_type)) return instance.__dict__[self.key] = value def __delete__(slef, instance): print('delete方法') print('instance参数【%s】' % instance) instance.__dict__.pop(self.key) def deco(**kwargs): def wrapper(obj): # 这里的obj就是People类 for key, val in kwargs.items(): print("===>", key, val) setattr(obj, key, Typed(key, val)) # 利用for循环依次进行描述符的设定 return obj return wrapper @deco(name=str, age=int) # 将这两个参数以字典的形式传递给deco函数的**kwargs参数 class People: # name = Typed('name', str) # age = Typed('age', int) def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary p1 = People('hgzero', 21, 35000)
8.11 利用描述符自定制property
1)自定制property
# 用描述符来模拟property class Lazyproperty: def __init__(self, func): print('===>', func) self.func = func def __get__(self, instance, owner): # instance指的是触发它的实例,owner指的是将实例产生的类 print('get') # print(instance) # print(owner) if instance is None: # 如果instance为空(用类来触发了get方法),这返回Lazyroperty类本身 return self res = self.func(instance) # 这里的instance就是r1(外部触发这个描述符的对象) setattr(instance, self.func.__name__, res) # 将得到的值保存到一个实例属性中 return res # def __set__(self, instance, value): # 【注意】 如果定义了这个方法,则查找数据优先级就会发生改变 # pass class Room: def __init__(self, name, width, length): self.name = name self.width = width self.length = length @Lazyproperty # area = Lazyproperty(area) 这一步会【执行】 这一步相当于Lazyproperty被area代理了 # 这一步将Lazyproperty这个类实例化成了area实例 def area(self): return self.width * self.length r1 = Room('home', 1, 1) print(r1.area) # r1去调用area其实是去调用了area实例(Lazyproperty实例化后的对象),而area实例是一个描述符,所以会触发get方法 # (area实例去执行下面的get方法,所以Lazyproperty中的self指的就是area实例本身) print(Room.area) # 注意:如果用类调用去触发描述符的方法,则instance会默认为None
2)自定制classmethod
class Myclassmethod: def __init__(self, func): self.func = func def __get__(self, instance, owner): def back(): # 可以在这里添加额外的功能 self.func(owner) return back class People: address = "China" def __init__(self, name, age): self.name = name self.age = age @Myclassmethod def eat(cls): print("this is classmethod!") # p1 = People("hgzero", 18) People.eat()
8.12 __new__方法
__new__可以实现单例模式。
9. 元类
9.1 元类概述
- 元类是类的类,是类的模板;元类是用来控制如何控制类的,正如类是创建对象的模板一样高
- 元类的实例为类,正如类的实例为对象(f1 对象是Foo类的一个实例,Foo类是type类的一个实例)
- type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象
- 一个类如果没有声明自己的元类,默认它的元类就是type,除了使用元类type,也可以通过继承type来自定义元类
# 元类: # 元类是类的类,是类的模板 # 元类是用来控制如何创建类的,正如类是创建对象的模板一样 # 元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是type类的一个实例) # type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象 # 一个类没有声明自己的元类,默认它的元类就是type,除了使用元类type,也可以通过继承type来自定义元类 def __init__(self, name, age): self.name = name self.age = age def test(self): print('===>') FFo = type('Foo', (object,), {'x':1, '__init__':__init__, 'test':test}) # 参数依次为:('类名',(父类),{类的字典}) print(FFo) print(FFo.__dict__)
9.2 自定制元类
- 类的创建过程
class MyType(type): def __init__(self, a, b, c): # 这里的self就是Foo print('元类的构造函数执行') def __call__(self, *args, **kwargs): print('=====>') print(self) print(args, kwargs) obj = object.__new__(self) # 利用object类创建一个对象 object.__new__(Foo), 这就是Foo产生的对象,就是f1 self.__init__(obj, *args, **kwargs) # 这一步相当于Foo.__init__(obj,*args,**kwargs) , 执行了Foo下面的init方法 return obj # 将Foo产生的对象返回 class Foo(metaclass=MyType): # Foo = MyType(Foo,'Foo',(object,),{}) 传递了4个参数,这时Foo是MyType的实例 ----> 触发MyType的init方法 def __init__(self, name): # 这里的self就是MyType类中所创建的Foo的对象obj(相当于f1) self.name = name f1 = Foo('hgzero') # 当Foo加上括号就是触发了MyType下面的__call__方法