1.1三种编程方法论
1.面向过程:把复杂的任务一步一步分解成简单的任务。
2.函数式编程:思想上接近于数学运算,根据某种方式,根据方式得出的结果。
3.面向对象编程:一种编程方式,需要使用“类”和“对象”来实现,其实就是对“类”和“对象的”使用
1.1.1 面向对象设计和面向对象编程
1.面向对象设计(Object-oriented design):将一类的具体的事物的属性和方法整合到一起,即面向对象设计。
注:面向对象(OO),是一种程序设计思想,不一定只有class定义类才是面向对象,def定义函数的就不是面向对象了,这是一种错误的判断,面向对象是一种思想。
1 def person(name,age,gender): #理解一个人种类 2 def play(name): #人的方法 3 print("[%s],正在玩python" %(name[‘name‘])) 4 def eat(name): #人的方法 5 print("[%s]正在吃饭"%(name["name"])) 6 def init(name,age,gender): #初始化人的属性,以及方法。 7 person1 = { 8 "name":name, 9 "age":age, 10 "gender":gender, 11 "play":play, 12 "eat":eat 13 } 14 return person1 15 return init(name,age,gender) 16 aa = person("xixi",18,"male") #可以简单理解为生成了一个人的实例 17 aa["play"](aa) #调用人的方法 18 aa["eat"](aa) 19 bb = person("xiaoxi",19,"female") #有创建了一个人的实例 20 bb["play"](bb) 21 22 #这就是一个简单的面向对象的设计
2.面向对象编程(Object-oriented programming):用定义类+实例/对象的方式去实现面向对象的设计。
类(class):用来描述具有相同的属性和方法的集合,它定义了该集合中每个对象所共有的属性和方法。对象是类的实例
对象(object):通过类定义(或创建)的数据结构实例。实例就是对象
实例化:由类产生对象的过程叫实例化
一.初始类
定义类的格式
1 class ClassName: #注:定义关键字(类名)单词首字母要大写,这是一种规范 2 #关于类的的注释文档 3 类体 4 5 #ClassName类名
1 #创建一个类Data 2 class Data: 3 "这是一个测试的数据" 4 pass 5 6 #实例化类 7 d1 = Data() # Data()类实例化的过程 ,利用Data是实例化一个对象d1
python2中的类和python3中的类
1 前提: 2 1.在python2中分新式类和经典类,python3中统一都是新式类。 3 2.新式类和经典类声明的最大不同在于,所有新式类必须继承至少一个父类。 4 3.所有类不管是否显示声明父类,都有一个默认继承object父类(类的继承,下面会写) 5 6 经典类: 7 class 类名: 8 pass 9 10 新式类: 11 class 类名(父类): 12 pass 13 14 注:在python3中上面这两种方式都是新式类
1.2属性
类是用来描述某一个类的事物,类的对象是这一类事物中一个个体
是事物就要有属性,(类)属性分为:
1.数据属性:就是变量
2.函数属性:就是函数,在面向对象中通常称为方法。
注意:类和对象都用点(.)来访问自已的属性
class.属性
object.属性
1.2.1类相关认识
一.类的数据属性
1 #定义了一个中国的类,在类中定义了一个属性person 2 #类属性又称为静态变量,或者静态数据,这些属性是类对象绑定在一起的,不依赖实例。 3 (不同的类,都有不同的属性,只有自已的类,才能访问自已的属性) 4 5 6 class China: 7 person = "Chinese" 8 print(China.person) #查看类的属性
二.类的方法
1 class China: 2 person = "Chinese" 3 def create(self): #这个self 是把类实例本身传进去 4 print("%s 是个发展中国家" %self) 5 6 country = "china" 7 China.create(country) #调用类的方法create
三.查看类属性和方法
有两种方式查看:
1.dir(类名):查出的是类的属性名的列表
2.类名.__dict__:查出的是一个字母,key为属性名,value为属性值
1 print(dir(China)) 2 # [‘__class__‘, ‘__delattr__‘, ‘__dict__‘, ‘__dir__‘, ‘__doc__‘, ‘__eq__‘, ‘__format__‘, ‘__ge__‘, ‘__getattribute__‘, ‘__gt__‘, ‘__hash__‘, ‘__init__‘, ‘__le__‘, ‘__lt__‘, ‘__module__‘, ‘__ne__‘, ‘__new__‘, ‘__reduce__‘, ‘__reduce_ex__‘, ‘__repr__‘, ‘__setattr__‘, ‘__sizeof__‘, ‘__str__‘, ‘__subclasshook__‘, ‘__weakref__‘, ‘create‘, ‘person‘] 3 4 5 print(China.__dict__) 6 # {‘__dict__‘: <attribute ‘__dict__‘ of ‘China‘ objects>, ‘__module__‘: ‘__main__‘, ‘create‘: <function China.create at 0x000000F0DBB8E2F0>, ‘__doc__‘: None, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘China‘ objects>, ‘person‘: ‘Chinese‘}
查看特定属性的内容
1 class China: 2 person = "Chinese" 3 def create(self): #现在在这里只是一个参数的形式 4 print("%s 是个发展中国家" %self) 5 6 # 可以根据字典取key的方式,查看该属性的内容 7 print(China.__dict__["person"]) #Chinese 8 print(China.__dict__["create"]) #<function China.create at 0x000000569A2CE2F0>
1.2.2对象相关认识
对象是有类实例化而来的,类实例的结果就是一个对象。
由于类可以起到模块的作用,因此创建实例的时候,我们可以把共有的数据属性,可以给强制填写进去,通过定义一个特殊的__init__的方法,把共有的属性绑定到上去。
1 class China: 2 "我们是中国人" 3 person = "Chinese" 4 def create(self): #现在在这里只是一个参数的形式 5 print("%s 是个发展中国家" %self) 6 # country = "china" 7 # China.create(country) 8 9 # 可以根据字典取key的方式,查看该属性的内容 10 # print(China.__dict__["person"]) #Chinese 11 # print(China.__dict__["create"]) #<function China.create at 0x000000569A2CE2F0> 12 13 14 class Student: 15 def __init__(self,name,age,gender):#这里的self 就相当于得到把对象本身自已 16 self.name = name 17 self.age = age 18 self.gender = gender 19 def score(self): 20 print("%s 的成绩是99 "%self.name) 21 def play(self): 22 print("%s 的喜欢玩球,年龄是 %s" %(self.name,self.age)) 23 24 25 one = Student("xiaoxi",12,"male") #通过实例化的得到了一个对象one 26 print(one.name) #该对象的共有的数据属性 ,得到该对象的名字 27 one.score() #该对象的方法score 28 one.play() #该对象的方法play 29 two = Student("xixi",11,"male")#又创建了个对象 30 print(two.name)
注意上面的代码:__init__方法的第一个参数永远是self,表示创建的实例本身,因此在__init__方法内部,就可以把各种属性绑定到self上,因为self就指向了创建的实例本身。
有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,python解释器会把自已实例变量传进去。
和普通函数相比,在类中定义的函数只有一点不同,就是第一参数永远是实际变量,并且,调用时不用传递该参数。除此之外的类的方法和普通函数没什么区别。我们任然可以用函数的其他参数。
实例属性:
实例化的过程就是执行__init__的过程,这个函数内部只是为实例本身即self设定了一些变量,所有实例只有数据属性。
1 class Teacher: 2 def __init__(self,name,age,gender): 3 self.name = name 4 self.age = age 5 self.gender = gender 6 def teach(self): 7 print("%s 老师正在教书,他现在已近 %s 岁了 ,是一位 %s 老师" %(self.name,self.age,self.gender)) 8 9 one_teacher = Teacher("小林",48,"male") 10 print(one_teacher.__dict__) #查看实例属性 11 print(one_teacher.name,one_teacher.age,one_teacher.gender) #访问实例的数据属性
现在访问实例的函数属性(方法,其实就是类的函数属性)
1 class Teacher: 2 def __init__(self,name,age,gender): 3 self.name = name 4 self.age = age 5 self.gender = gender 6 def teach(self): 7 print("%s 老师正在教书,他现在已近 %s 岁了 ,是一位 %s 老师" %(self.name,self.age,self.gender)) 8 9 one_teacher = Teacher("小林",48,"male") 10 print(one_teacher.__dict__) #查看实例属性 11 print(one_teacher.name,one_teacher.age,one_teacher.gender) #访问实例的数据属性 12 #新加的下面3行内容 13 print(Teacher.__dict__) #函数属性只存在于类中 14 Teacher.teach(one_teacher) #我们只能通过类去调用类的函数属性,然后把实例当做变量传递给self 15 one_teacher.teach() #注其实就是Teacher.teach(one_teacher)
查看实例的属性同样是用dir和内置__dict__两种方式。
警告:类和对象虽然调用__dict__返回时一个字典结构,但是千万不要直接修改该字典,会导致你原来oop不稳定。
1.3类属性与对象(实例)属性
类属性的增删改查
1 class Teacher: 2 country = "China" 3 def __init__(self,name,age,gender): 4 self.name = name 5 self.age = age 6 self.gender = gender 7 def teach(self): 8 print("%s 老师正在教书,他现在已近 %s 岁了 ,是一位 %s 老师" %(self.name,self.age,self.gender)) 9 10 #查看类的属性 11 print(Teacher.country) 12 #修改类的属性 13 Teacher.country = "中国" 14 print("修改后类的属性》》",Teacher.country) 15 #删除类属性 16 del Teacher.country 17 print(Teacher.__dict__) 18 # print(Teacher.country) #在打印会报错,提示没有country 属性 19 Teacher.location = "Beijing" 20 print("添加类属性>>",Teacher.location) 21 print(Teacher.__dict__) 22 23 24 25 #给类在添加一个函数属性(方法), 26 def do(self,homework): 27 print("%s 正在修改 %s" %(self.name,homework)) 28 29 Teacher.do = do #把do函数,添加到类里面去 30 print(Teacher.__dict__) 31 one = Teacher("xiaoli",30,"female") 32 Teacher.do(one,"homework") #把one实例传进去
实例属性的增删改查
1 class Teacher: 2 country = "China" 3 def __init__(self,name,age,gender): 4 self.name = name 5 self.age = age 6 self.gender = gender 7 def teach(self): 8 print("%s 老师正在教书,他现在已近 %s 岁了 ,是一位 %s 老师" %(self.name,self.age,self.gender)) 9 10 11 12 two = Teacher("dav",32,"male") 13 #查看实例属性 14 print(two.name) 15 print(two.__dict__) 16 print(two.country) #这里country不存在two的属性字典中, 17 18 #删除实例属性 19 # del two.country #报错 20 del two.name 21 print(two.__dict__) 22 # 增加实例属性 23 two.name = "xixi" #又重新添加一个name属性 24 two.test = "data test" 25 print(two.__dict__) 26 #修改实例属性 27 two.age = 35 28 print(two.__dict__)
注:上面虽然这样写,但是一般最好不要修改原来oop。
总结:
1.类是创建实例的模块,而实例则是一个一个具体的对象,各个实例拥有的数据都相互独立,互不影响。
2.方法就是实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据。
3.通过实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部实现的的细节。
2.1类的静态属性,类方法,类的静态方法
2.1.1静态属性(@property)
在类中:
python内置的@property装饰器就是负责把一个方法(函数)变成属性来调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class Student: def __init__( self ,name,age,score): self .name = name self .age = age self .score = score @property def get_score( self ): return self .score def learn( self ): return self .name s1 = Student( "小明" , 12 , 88 ) print (s1.get_score) #使用的@property 属性调用,简短了函数代码 print (s1.learn()) #原来方法的调用 # s1.get_score = 12 #不能修改该属性用了@property # s1.learn = 12 #可以修改该函数属性 没用@property #注@property广泛应用在类的定义中,让调用者写出简短的代码。 |
可以封装函数的逻辑,让用户调用的时候,让函数的方法看起来像普通属性。
2.1.2类方法(@classmethod)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class Kls: money = 10 def __init__( self ,money): self .init = money + 10 @classmethod def Get_no_of_instance( cls ): #cls应该是传入的一个类名 # print(cls.money) 相当于是#print(Kls.money) return cls .money s1 = Kls( 12 ) #类实例化一个对象 print (s1.init) #查看实例属性的变量init的值 print (s1.money) #查看类属性的变量 Kls.Get_no_of_instance() s1.Get_no_of_instance() #这里的调用,会把实例所属的类给传进去 #注:这里是类在调用自已的方法,跟实例没有任何关系 |
@classmethod类方法,只是给类使用(不管是否存在实例),只能访问实例变量
2.1.3静态方法(@staticmethod)
经常有一些跟类有关系的功能但在运行时,又不需要实例和类参与的情况下需要用到静态方法,比如更改其他类的属性等能用到的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class Kls: money = 10 def __init__( self ,money): self .init = money + 10 @classmethod def Get_no_of_instance( cls ): #cls应该是传入的一个类名 # print(cls.money) 相当于是#print(Kls.money) return cls .money @staticmethod def test(x,y): print (x,y) #这跟类的变量,和实例变量完全没关系 return x,y def do( self ): print ( "这里会把自已实例(self)给传进去" ) s2 = Kls( 55 ) s2.test( 1 , 2 ) Kls.test( 11 , 22 ) |
@staticmethod只是名义上归属类的管理,不能使用类的变量和实例的变量,只是类的工具包,很少用。
2.2类组合
例如:定义一个人的类,人有头,躯干,手,脚等数据属性,这几个属性可以是通过一个类实例化的对象,这就是组合。
组合用途:
1:做关联
2:小的组成大的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class Hand: pass class Foot: pass class Trunk: pass class Head: pass class Person: def __init__( self ,id_num,name,hand,foot,trunk,head): self .id_num = id_num self .name = name self .hand = Hand() self .foot = Foot() self .trunk = Trunk() self .head = Head() |
例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
class School: def __init__( self ,name,addr): self .name = name self .addr = addr def tell_info( self ): print ( "shcool %s %s" % ( self .name, self .addr)) class Course: def __init__( self ,name,price,period,school): self .name = name self .price = price self .period = period self .schoool = school class Tearcher: def __init__( self ,name,hobby,school): self .name = name self .hobby = hobby self .school = school s1 = School( "北大" , "北京" ) s2 = School( "上大" , "上海" ) s3 = School( "重大" , "重庆" ) info = """ 1 北大 北京校区 2 上大 上海校区 3 重大 深圳校区 """ while True : print (info) menu = { "1" :s1, "2" :s2, "3" :s3, } choice = input ( "请选择学校>>:" ) school_obj = menu[choice] name = input ( "课程名>>:" ) price = input ( "课程价格>>:" ) period = input ( "课程周期>>:" ) Tname = input ( "老师名字>>" ) Thobby = input ( "老师爱好>>" ) teacher_obj = Tearcher(Tname,Thobby,school_obj) new_course = Course(name,price,period,school_obj) print ( "%s【老师】【爱好】%s【课程】%s【价格】%s【周期】%s【校区】%s 【地址】%s" \ % (teacher_obj.name,teacher_obj.hobby,\ new_course.name,new_course.price,new_course.period,\ school_obj.name,school_obj.addr)) |
2.3面向对象编程三大特性
1.继承
2.封装
3.多态
2.3.1继承
类的继承:就跟生活中父,子,继承的关系类似,父类又称为基类。
python中的类的继承分为:单继承和多继承
1
2
3
4
5
6
7
8
9
10
11
|
class ParentClass1: #基类 pass class ParentClass2: #基类 pass class SubClass(ParentClass1): #单继承 子类 pass class SubClass(ParentClass1,ParentClass2): #多继承 子类(subclass) pass |
2.3.2子继承继承了父类什么属性
继承的本质是父类把自已的属性引用传递给了子类,子类可以调用父类的属性,其实父类的属性是不属于儿子的
因此。在子类中定义的任何数据属性和函数属性都存在于子类的属性中,调用时优先从自已的属性字典里面查。
1
2
3
4
5
6
7
8
9
10
11
|
class Father: money = 100 #父类自已有100 def __init__( self ): pass def Teach( self ): print ( "教儿子" ) class Son(Father): money = 9999999 #儿子通过发展的9999999 s1 = Son() print (s1.money) #子类自已的属性 s1.Teach() #子类调用父类的方法 |
2.3.3什么时候用继承
1.当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
例如:描述一个机器人类,机器人这个大类是由很多互不相关的小类组成,如机械胳膊类、腿类、身体类、电池类
2.当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好
例如:
猫可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
class 猫: def 喵喵叫( self ): print ‘喵喵叫‘ def 吃( self ): # do something def 喝( self ): # do something def 拉( self ): # do something def 撒( self ): # do something class 狗: def 汪汪叫( self ): print ‘喵喵叫‘ def 吃( self ): # do something def 喝( self ): # do something def 拉( self ): # do something def 撒( self ): # do something 伪代码 |
上述代码不难看出,吃,喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现:
动物:吃、喝、拉、撒
猫:喵喵叫(猫继承动物的功能)
狗:汪汪叫(狗继承动物的功能)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
class 动物: def 吃( self ): # do something def 喝( self ): # do something def 拉( self ): # do something def 撒( self ): # do something # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 class 猫(动物): def 喵喵叫( self ): print ( ‘喵喵叫‘ ) # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 class 狗(动物): def 汪汪叫( self ): print ( "汪汪汪" ) 伪代码 |
继承的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
#!/usr/bin/env python #-*- coding:utf-8 -*- class Animal: def eat( self ): print ( "%s 吃 " % self .name) def drink( self ): print ( "%s 喝 " % self .name) def shit( self ): print ( "%s 拉 " % self .name) def pee( self ): print ( "%s 撒 " % self .name) class Cat(Animal): def __init__( self , name): self .name = name self .breed = ‘猫‘ def cry( self ): print ( ‘喵喵叫‘ ) class Dog(Animal): def __init__( self , name): self .name = name self .breed = ‘狗‘ def cry( self ): print ( ‘汪汪叫‘ ) # ######### 执行 ######### c1 = Cat( ‘黑猫‘ ) c1.eat() c2 = Cat( ‘白猫‘ ) c2.drink() d1 = Dog( ‘二哈‘ ) d1.eat() d1.cry() |
继承同时具有的两种含义:
1.继承基类的方法,并且做出自已的改变或者扩展(代码重用)
2.声明某个子类兼容与某积累,定义一个接口类,子类继承接口类,并且实现接口中定义的方法。
但是在实践中,继承的第一种含义并不很大,甚至常常是有害的,因为它使得子类与积累出现强耦合。
继承的第二种函数非常重要,又叫“接口继承”
接口继承实质上是要求作出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体的细节。可一视同仁的处理时限了特定接口的所有对象。这种程序设计上,叫归一化。
2.3.4类的继承顺序
上面的是python2中类的继承顺序
python3中类的继承顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#!/usr/bin/env python #-*- coding:utf-8 -*- class A: def test( self ): print ( "A" ) class B(A): # def test(self): # print("B") pass class C(A): def test( self ): print ( "C" ) class D(B): # def test(self): # print("D") pass class E(C): def test( self ): print ( "E" ) class F(D,E): # def test(self): # print("F") pass f = F() f.test() # print(F.mro()) print (F.__mro__) #python3中类继承顺序:F->D->B->E->C->A |
#python2经典类继承顺序:F->D->B->A->E->C python2经典类, 需要切换到python2的解释器
python对类的继承顺序。是根据mro的解析来继承的。
这个mro列表就是一个简单的所有积累的线形顺序的列表。
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
2.3.5子类中调用父类的方法
子类继承了父类的方法,然后想进行修改。注:是基于原有的基础上修改,那么就需要在子类中调用父类的方法。
方法一:父类名.父类方法()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class Vehicle: # 定义交通工具类 父类 Country = ‘China‘ def __init__( self , name, speed, load, power): self .name = name self .speed = speed self .load = load self .power = power def run( self ): print ( ‘开动啦...‘ ) class Subway(Vehicle): # 地铁 子类 def __init__( self , name, speed, load, power, line): Vehicle.__init__( self , name, speed, load, power) #父类的属性 self .line = line def run( self ): print ( ‘地铁%s号线欢迎您‘ % self .line) Vehicle.run( self ) #子类中调用父类的方法 line13 = Subway( ‘中国地铁‘ , ‘180m/s‘ , ‘1000人/箱‘ , ‘电‘ , 13 ) line13.run() |
方法二:super()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class Vehicle: #定义交通工具类 Country = ‘China‘ def __init__( self ,name,speed,load,power): #注 self .name = name self .speed = speed self .load = load self .power = power def run( self ): print ( ‘开动啦...‘ ) class Subway(Vehicle): #地铁 def __init__( self ,name,speed,load,power,line): #super(Subway,self) 就相当于实例本身 super (Subway, self ).__init__(name,speed,load,power) #注:调用父类的属性 self .line = line def run( self ): print ( ‘地铁%s号线欢迎您‘ % self .line) super (Subway, self ).run() #子类调用父类的方法supper line13 = Subway( ‘中国地铁‘ , ‘180m/s‘ , ‘1000人/箱‘ , ‘电‘ , 13 ) line13.run() |
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次 。
2.4多态
什么是多态
类的继承有两层含义:改变 扩展
多态就是类的这两层意义的一个具体的实现机制。即调用不同的类实例化得到对象下的相同方法。实现的过程不一样而已了
python中的标准类型就是多态概念的一个很好的示范如(str.__len__(),list.__len__(),tuple.__len__可以len(str),len(list),len(tuple))
多态实际上是依附于继承的两种含义的:“改变”和“扩展”本身就意味着必须有机制去自动选用你改变/扩展过的版本,故无多态,则两种含义就不可能实现。
所以,多态实质上是继承的实现细节;那么让多态与封装、继承这两个概念并列,显然是不符合逻辑的
2.5封装
什么是封装:在实例生活中,装就好比一个麻袋,用来存放物品;封,就是这个麻袋口子给封上。
在面向对象中这个麻袋就是类或者对象,内部装了数据属性和函数属性,那么对于类和对象来说,封装的慨念就表示隐藏。
例:
就是类中定义私有的属性和方法,只在类的内部使用,外部无法访问。
python不依赖语言特性去实现第二层面的封装,而是通过遵循一定的数据属性和函数属性的命名约定来达到封的效果
约定一:任何一单下划线开头的名字都应该是内部的,私有的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class People: _star = ‘earth‘ def __init__( self , id ,name,age,salary): self . id = id self .name = name self ._age = age self ._salary = salary def _get_id( self ): print ( ‘我是私有方法啊,我找到的id是[%s]‘ % self . id ) #我们明明约定好了的,只要属性前加一个单下划线,那他就属于内部的属性,不能被外部调用了啊,为何还能调用??? print (People._star) p1 = People( ‘3706861900121221212‘ , ‘yj‘ , 18 , 10 ) print (p1._age,p1._salary) p1._get_id() |
上面私有_属性:还能访问。
python并不会真的阻止你访问私有属性,这只是以一种约定。
约定二:双下划线开头的名字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class People: __star = ‘earth‘ #私有 star = "moon" #共有 def __init__( self , id ,name,age,salary): self . id = id self .name = name self .__age = age #私有 self ._salary = salary #私有 def _get_id( self ): print ( ‘我是私有方法啊,我找到的id是[%s]‘ % self . id ) p1 = People( ‘333333‘ , ‘xixi‘ , 18 , 10 ) print (People.star) #能访问 print (p1._salary) #能访问 print (People.__star) #不能访问 |
私有属性怎么可以访问:
例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class Site: def __init__( self , name, url): self .name = name # public self .__url = url # private def who( self ): print ( ‘name : ‘ , self .name) print ( ‘url : ‘ , self .__url) def __foo( self ): # 私有方法 print ( ‘这是私有方法‘ ) def foo( self ): # 公共方法 print ( ‘这是公共方法‘ ) self .__foo() # print(Site.__dict__) x = Site( ‘baidu‘ , ‘www.baidu.com‘ ) print (x.__dict__) #查看对象的属性字典 print (x._Site__url) #通过属性字典调用私有方法 # print(Site.__dict__)#查看类的属性字典 x._Site__foo() |
为什么可以访问私有属性了?
python没有从根本上限制你的访问。python之所以这么设计,原因就是:python做成非严格意义的封装,避免我们滥用封装。
封装在于明确区分内外,使得类实现则可以修改封装内的东西,而不影响外部调用者;而外部调用者也可以知道自己不可以碰哪里。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
3.1isinstance和issubclass的方法
isinstance(obj,cls)检查对象(obj)是否是类(类的对象)
1
2
3
4
5
6
7
|
class Foo( object ): pass obj = Foo() print ( isinstance (obj, Foo)) #True print ( isinstance ( str ,Foo)) #false |
issubclass(sub, super)检查字(sub)类是否是父( super) 类的派生类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class Foo( object ): #父类 pass class Bar(Foo): #父类的派生类 pass print ( issubclass (Bar, Foo)) #True #所有的父类都是object派生 print ( issubclass ( str , object )) #True print ( issubclass (Foo, object )) #True print ( issubclass ( str ,Foo)) #False |
3.2反射
什么是反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。
python中面向对象中的反射:通过字符串的形式操作对象的相关属性。python中的一切事物都是对象(都可以使用反射)
可以实现自省的函数
hasattr,getattr,setattr,delattr
下列方法使用与类和对象(类本身也是一个对象)
1.hasattr(object,name) 判断object中有没有一个name字符串对应的方法或者属性
1
2
|
print ( hasattr ( str , "count" )) #True str对象中有count的方法 print ( hasattr ( str , "sb" )) #False |
2.getattr(object, name, default=None)
1
2
3
4
5
6
7
8
9
10
11
12
|
def getattr ( object , name, default = None ): # known special case of getattr """ getattr(object, name[, default]) -> value Get a named attribute from an object; getattr(x, ‘y‘) is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn‘t exist; without it, an exception is raised in that case. """ pass #得到该name属性在object中的值,不存在则报错 |
1
2
|
print ( getattr ( list , "index" )) print ( getattr ( list , "big sb" )) #报错 |
3setattr(x,y,z)
1
2
3
4
5
6
7
8
9
|
def setattr (x, y, v): # real signature unknown; restored from __doc__ """ Sets the named attribute on the given object to the specified value. setattr(x, ‘y‘, v) is equivalent to ``x.y = v‘‘ """ pass setattr (x, y, v) |
1
2
|
print ( setattr ( dict , "get" , "super sb" )) #报错 #注:这是内置的对象,该属性的是只读的(可以自定义类来修改) #设置对象中的 "get"属性改为 "super sb" |
4delattr(x,y)
1
2
3
4
5
6
7
8
9
|
def delattr (x, y): # real signature unknown; restored from __doc__ """ Deletes the named attribute from the given object. delattr(x, ‘y‘) is equivalent to ``del x.y‘‘ """ pass #删除对象中的属性 |
上面函数的代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
class BlackMedium: feature = ‘Ugly‘ def __init__( self ,name,addr): self .name = name self .addr = addr def sell_house( self ): print ( ‘%s 黑中介卖房子啦,傻逼才买呢,但是谁能证明自己不傻逼‘ % self .name) def rent_house( self ): print ( ‘%s 黑中介租房子啦,傻逼才租呢‘ % self .name) b1 = BlackMedium( ‘万成置地‘ , ‘回龙观天露园‘ ) # 检测是否含有某属性 print ( hasattr (b1, ‘name‘ )) print ( hasattr (b1, ‘sell_house‘ )) # 获取属性 n = getattr (b1, ‘name‘ ) print (n) func = getattr (b1, ‘rent_house‘ ) func() getattr (b1, ‘aaaaaaaa‘ ) #报错 #获取该属性的值 print ( getattr (b1, ‘aaaaaaaa‘ , ‘不存在啊‘ )) #没有该属性,打印默认值 # 设置属性 setattr (b1, ‘sb‘ , True ) setattr (b1, ‘show_name‘ , lambda self : self .name + ‘sb‘ ) print (b1.__dict__) print (b1.show_name(b1)) # 删除属性 delattr (b1, ‘addr‘ ) delattr (b1, ‘name‘ ) delattr (b1, ‘show_name111‘ ) #不存在,则报错 # print(b1.__dict__) # 四个方法的使用演示 #注:用完一个属性,注释一个 |
为什么用反射之反射的好处
可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能。
3.3__setattr__,__delattr__,__getattr__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
class Foo: x = 1 def __init__( self ,y): self .y = y def __getattr__( self , item): print ( ‘----> from getattr:你找的属性不存在‘ ) def __setattr__( self , key, value): print ( ‘----> from setattr‘ ) # self.key=value #这就无限递归了,你好好想想 self .__dict__[key] = value #应该使用它 def __delattr__( self , item): print ( ‘----> from delattr‘ ) # del self.item #无限递归了 self .__dict__.pop(item) #__setattr__添加/修改属性会触发它的执行 f1 = Foo( 10 ) print (f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值 f1.z = 3 #添加 print (f1.__dict__) #__delattr__删除属性的时候会触发 f1.__dict__[ ‘a‘ ] = 3 #我们可以直接修改属性字典,来完成添加/修改属性的操作 del f1.a print (f1.__dict__) #__getattr__只有在使用点调用属性且属性不存在的时候才会触发 f1.xxxxxx # 三者的用法演示 |
3.4__getattribute__
__getattr__和__getattribute__
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class Foo: # def __setattr__(self, key, value): # print("setaaaa") # self.__dict__[key] = value def __getattr__( self , item): #可以把__getattr__和__getattribute__分别注释看看 print ( "执行getattr" ) def __getattribute__( self , item): print ( "执行attribute" ) #无论有没有该属性,都执行 raise AttributeError( "这是错误异常" ) #遇到异常 ,去执行__getattr__ #raise 错误属性 ("抛出异常的错误内容") aa = Foo() aa.ssssss #不存在该属性会执行__getattribute__xxxxxxxxxx |
#当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError
3.5描述符(__get__,__set__,__delete__)
1.描述符是什么:描述符本质就是一个 新式类,至少出现了__get__(),__set__(),__delete__() 中的一个,这也就称为描述符协议
这也被称为描述符协议:
__get__()调用一个属性时,触发
__set__()为一个属性赋值是,触发
__delete__()采用del删除属性时,触发
1
2
3
4
5
6
7
|
class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符 def __get__( self , instance, owner): pass def __set__( self , instance, value): pass def __delete__( self , instance): pass |
2描述符是做什么的:描述符的作用就是来代理另外一个类的属性的(必须把描述符定义成这个类属性),不得定义到构造函数(__init__(self))中。
何时何地会触发描述符:请看下面代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class Foo: #这是一个描述符 def __get__( self , instance, owner): print ( "执行get" ) #只是定义一个打印 def __set__( self , instance, value): print ( "执行set方法" ) def __delete__( self , instance): print ( "执行delete方法" ) #包含这三个方法的新式类称为描述符, 由这个类产生的实例进行属性的调用 / 赋值 / 删除, 并不会触发这三个方法 aa = Foo() aa.__dict__[ "bb" ] = 123 # print(aa.__dict__) class Bar: x = Foo() #x被Foo类代理 def __init__( self ,n): self .x = n #交给代理去另一类属性才会执行描述符的方法 b1 = Bar( 10 ) #触发set b1.name = "yi" #不会触发 这是在自已对象新增了一个属性 # print(b1.__dict__) b1.x #触发get del b1.x #触发delete |
3描述符分两种:
一:数据描述符:至少实现了__get__()和__set__()
1
2
3
4
5
|
class Foo: def __set__( self , instance, value): pass def __get__( self , instance, owner): pass |
二.非数据描述符,没有实现__set__()
1
2
3
|
class Foo: def __get__( self , instance, owner): print ( ‘get‘ ) |
4.注意事项
一描述符本身应该定义成新式类,被代理的类也也该是新式类。
二必须把描述符定义成这个类的类属性,不能定义到构造函数中。
三要严格遵循该优先级,优先级由高到低分别是。
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性会触发__getattr__()
1.类属性高于数据描述符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#描述符Str class Str : def __get__( self , instance, owner): print ( ‘Str调用‘ ) def __set__( self , instance, value): print ( ‘Str设置...‘ ) def __delete__( self , instance): print ( ‘Str删除...‘ ) class People: name = Str () def __init__( self ,name,age): #name被Str类代理 self .name = name self .age = age #注在一个类中定义描述符它就是一个类属性,存在于类的属性字典中,而不是实例的属性字典 People.name #调用类属性name,本质就是在调用描述符Str,触发了__get__() # print(People.__dict__) People.name = "yj" #这相当于重新修改了People.name的属性,name 就没给Str描述符代理,就不会触发set # print(People.__dict__) del People.name #这相当于重新修改了People.name的属性,跟修改一样 注: 1. :描述符在使用时被定义成另外一个类的类属性,因而类属性比二次加工的描述符伪装而来的类属性有更高的优先级 People.name #恩,调用类属性name,找不到就去找描述符伪装的类属性name,触发了__get__() eople.name = “yj ” #那赋值,直接赋值了一个类属性,它拥有更高的优先级,相当于覆盖了描述符,肯定不会触发描述符的__set__() 总结:类属性高于数据描述符 |
2数据描述符高于实例属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#描述符Str class Str : def __get__( self , instance, owner): print ( ‘Str调用‘ ) def __set__( self , instance, value): print ( ‘Str设置...‘ ) def __delete__( self , instance): print ( ‘Str删除...‘ ) class People: name = Str () def __init__( self ,name,age): #name被Str类代理 self .name = name self .age = age p1 = People( "yj" , 18 ) #生成一个实例 #name会触发Str #如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,于p1本身无关了,相当于覆盖了实例的属性 p1.name = "19" print (p1.__dict__) #实例的属性字典中没有name,因为name是一个数据描述符,优先级高于实例属性,查看/赋值/删除都是跟描述符有关,与实例无关了 数据描述符高于实例属性 |
3.实例属性高于非数据描述符(没有set)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class Foo: def __get__( self , instance, owner): print ( ‘get‘ ) class Room: name = Foo() def __init__( self ,name,width,length): self .name = name self .width = width self .length = length #name是一个非数据描述符,因为name=Foo()而Foo没有实现set方法,因而比实例属性有更低的优先级 #对实例的属性操作,触发的都是实例自己的 r1 = Room( ‘大厅‘ , 10 , 10 ) r1.name r1.name = ‘厨房‘ #实例属性高于非数据描述符 |
描述符的使用
从所周值,python是弱类型语言,即参数的复制没有类型的限制,下面我们通过描述符机制来实现类型限制功能,给传入参数加上类型限制的功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
class Foo: def __init__( self ,name): self .name = name def __get__( self , instance, owner): print ( "get》》》" ,instance,owner) return instance.__dict__[ self .name] def __delete__( self , instance): print ( "delte >>>" ,instance) instance.__dict__.pop( self .name) #保存修改实例属性的值 def __set__( self , instance, value): print ( "set>>>" ,instance,value) instance.__dict__[ self .name] = value #保存修改实例属性的值 class People: name = Foo( "name" ) def __init__( self ,name,age,salary): self .name = name self .age = age self .salary = salary p1 = People( "yj" , 18 , 55 ) #查看 ————get p1.name # print(p1.__dict__) #赋值 ———set p1.name = "aa" # print(p1.__dict__) #删除————delete del p1.name # print(p1.__dict__) #为了把相应的p1.name 的保存到自已的实例属性的字典里面,通过描述符来实现 |
现在给name参数做类型,限制,只允许传字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
class Foo: def __init__( self ,key): self .key = key def __get__( self , instance, owner): print ( "get》》》" ,instance,owner) return instance.__dict__[ self .key] def __delete__( self , instance): print ( "delte >>>" ,instance) instance.__dict__.pop( self .key) #保存修改实例属性的值 def __set__( self , instance, value): print ( "set>>>" ,instance,value) if not isinstance (value, str ): #如果该值的类型不是字符串则抛出异常 raise TypeError( "你传入的类型不是字符串" ) instance.__dict__[ self .key] = value #保存修改实例属性的值 class People: name = Foo( "name" ) def __init__( self ,name,age,salary): self .name = name self .age = age self .salary = salary p = People( "aa" , 12 , 12 ) print (p.__dict__) # p2 = People(12,2124,45) #报错 |
上面只是给name做了类型限制,但是现在要age和salary都要做类型限制,就不能像上面这样写了,(上面写死了,必须传入相应的类型,age是int类型,salary是float型)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
class Foo: def __init__( self ,key,expected_type): self .key = key self .expected_type = expected_type def __get__( self , instance, owner): print ( "get》》》" ,instance,owner) if instance is None : #这是类调用name,age 防止报错 return instance return instance.__dict__[ self .key] def __delete__( self , instance): print ( "delte >>>" ,instance) instance.__dict__.pop( self .key) #保存修改实例属性的值 def __set__( self , instance, value): print ( "set>>>" ,instance,value) if not isinstance (value, self .expected_type): #如果该值的类型不是字符串则抛出异常 raise TypeError( "%s传入的类型不是%s" % (value, self .expected_type)) instance.__dict__[ self .key] = value #保存修改实例属性的值 class People: name = Foo( "name" , str ) #name属性字符串 age = Foo( "age" , int ) #age属性整型 salary = Foo( "salary" , float ) #salary属性浮点型 def __init__( self ,name,age,salary): self .name = name self .age = age self .salary = salary p = People( "aa" , 12 , 12.2 ) print (p.__dict__) # p1 = People(12,19,12.2) # 报错 name 不是字符串 # p2 = People("qw","sd",12.2) # 报错age 不是整型 # p3 = People("qw",19,"aaa") # 报错salary 不是浮点型 |
上面虽然实现了给传入参数,加了类型限制,也实现了相应的功能。
但是问题是,如果我们的类有很多属性,任然采用在定义一堆类属性的方式去实现,实现是太low,这时候,我们可以用到装饰器,给类做装饰
我现在自定义一个类,来给这个类,新增属性。
1
2
3
4
5
6
7
8
|
def test(obj): obj.x = 1 obj.y = 12 return obj @test #原理是:Bar=test(Bar) test()运行函数 class Bar: pass print (Bar.__dict__) |
ok现在给参数做类型限制,用装饰器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
class Typed: def __init__( self ,name,expected_type): self .name = name self .expected_type = expected_type def __get__( self , instance, owner): print ( "get>>>" ) if instance is None : return self return instance.__dict__[ self .name] def __set__( self , instance, value): print ( "set>>>" ) if not isinstance (value, self .expected_type): raise TypeError ( "%s该类型不是%s" % (value, self .expected_type)) instance.__dict__[ self .name] = value def __delete__( self , instance): print ( "delete>>>" ) instance.__dict__.pop( self .name) def typeset( * * kwargs): def decor(obj): for key,val in kwargs.items(): setattr (obj,key,Typed(key,val)) return obj return decor @typeset (name = str ,age = int ,salary = float ) #1运行typeset(name=str,age=int,salary=float) # 返回结果是decor一个函数对象 #2 People = decor(People) class People: def __init__( self ,name,age,salary): self .name = name self .age = age self .salary = salary print (People.__dict__) p1 = People( "xiaoxi" , 12 , 45.4 ) print (p1.__dict__) |
描述符总结:
描述符是可以实现大部门python类特性中的底层魔@classmethod,@staticmethd,@property甚至是__slots__属性
描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件
现在来做一个自定制@property。
利用描述符原理完成一个自定制@property实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名key,value为描述符类产生的对象)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class Selfproperty: def __init__( self ,func): self .func = func def __get__( self , instance, owner): print ( ‘这是我们自己定制的静态属性,r1.area实际是要执行r1.area()‘ ) if instance is None : return self # print(self) return self .func(instance) #func(instance)就是area的返回值 class Room: def __init__( self ,name,width,length): self .name = name self .width = width self .length = length # @property @Selfproperty #area=Selfproperty(area) def area( self ): return self .width * self .length r1 = Room( "hourse" , 10 , 20 ) print (r1.area) print (r1.area) print (r1.area) |
现在来实现延迟计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
class Selfproperty: def __init__( self ,func): self .func = func def __get__( self , instance, owner): print ( ‘这是我们自己定制的静态属性,r1.area实际是要执行r1.area()‘ ) if instance is None : return self else : value = self .func(instance) setattr (instance, self .func.__name__,value) #计算一次就缓存到实例的属性字典中 return value class Room: def __init__( self ,name,width,length): self .name = name self .width = width self .length = length # @property @Selfproperty #area=Selfproperty(area) def area( self ): return self .width * self .length r1 = Room( "hourse" , 10 , 20 ) print (r1.area) #先从自己的属性字典找,没有再去类的中找,然后出发了area的__get__方法 print (r1.area) #先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算 # 这是我们自己定制的静态属性,r1.area实际是要执行r1.area() # 200 # 200 #ok 现在就实现了延迟计算, |
3.6__setitem__,__getitem,__delitem__
这只是通过字典方式,查看修改,类或者对象的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class Foo: def __init__( self ,name): self .name = name def __getitem__( self , item): print ( "执行getitem" ) return self .__dict__[item] def __setitem__( self , key, value): print ( "执行setitem" ) #为了让效果明显所以用print self .__dict__[key] = value def __delitem__( self , key): print ( "del obj[key],执行" ) self .__dict__.pop(key) def __delattr__( self , item): print ( "del obj.key,执行" ) self .__dict__.pop(item) n = Foo( "xiaoxi" ) # print(n.__dict__) # del n.name #执行delattr # del n["name"] #执行delitem n[ "age" ] = 18 #执行setitem print (n[ "age" ]) #执行getitem |
总结:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
#自省 hasattr (obj, ‘属性‘ ) #obj.属性 是否存在 getattr (obj, ‘属性‘ ) #获取obj.属性 不存在则报错 getattr (obj, ‘属性‘ , ‘默认值‘ ) #获取obj.属性 不存在不会报错,返回那个默认值 setattr (obj, ‘属性‘ , ‘属性的值‘ ) #obj.属性=属性的值 delattr (obj, ‘属性‘ ) #del obj.属性 #__getattr__,__setattr__,__delattr__ obj点的方式去操作属性时触发的方法 __getattr__:obj.属性 不存在时触发 __setattr__:obj.属性 = 属性的值 时触发 __delattr__: del obj.属性 时触发 #__getitem__,__setitem_,__delitem__ obj[‘属性’]的方式去操作属性时触发的方法 __getitem__:obj[ ‘属性‘ ] 时触发 __setitem__:obj[ ‘属性‘ ] = 属性的值 时触发 __delitem__: del obj[ ‘属性‘ ] 时触发 #__get__,__set__,__delete__ 描述就是一个新式类,这个类至少要实现上述三个方法的一个 class 描述符: def __get__(): pass def __set__(): pass def __delete__(): pass class 类: name = 描述符() obj = 类() obj.name #get obj.name = ‘egon‘ #set del obj.name #delete #__del__:析构方法 垃圾回收时触发 |
3.7__str__,__repr__,__format__
改变对象的字符串显示__str__,__repr__
自定义格式化字符串
str和repr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class Bar: def __init__( self ,name,age): self .name = name self .age = age def __str__( self ): print ( "this str" ) return "名字是%s age%s" % ( self .name, self .age) def __repr__( self ): #转换字符串,在解释器中执行 print ( "thsi repr" ) return "%s" % self .name test = Bar( "xi" , 10 ) print (test) print (test.__repr__()) #print(repr(test)) 执行的是__repr__ |
str函数或者print函数--->obj.__str__() repr或者交互式解释器--->obj.__repr__() 如果__str__没有被定义,那么就会使用__repr__来代替输出 注意:这俩方法的返回值必须是字符串,否则抛出异常
自定义__format__练习
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#和生成实例化对象的内容相对应 menu = { "ymd" : "{0.year}{0.month}{0.day}" , "y-m-d" : "{0.year}-{0.month}-{0.day}" } class Foo: def __init__( self ,year,month,day): self .year = year self .month = month self .day = day def __format__( self , format_spec): #format_spec指定格式化的类型 # print(format_spec,self) if not format_spec or format_spec not in menu: #如果没有传入格式,或则在menu中没有这个格式 format_spec = "ymd" #则格式化的默认格式是ymd ,在menu中有 fm = menu[format_spec] #通过menu字典格式化成相应格式的类型 # print(fm) return fm. format ( self ) #把对象传入,格式化的内容 ss = Foo( 2016 , 12 , 26 ) # print(format(ss,"ymd")) print ( format (ss, "y-m-d" )) print ( format (ss, "4565456" )) |
3.8__slots__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
1.__slots__ 是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性) 2. 引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的) 3. 为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__ 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个 字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给 实例添加新的属性了,只能使用在__slots__中定义的那些属性名。 4. 注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该 只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。 class Bar: __slots__ = [ ‘x‘ , ‘y‘ ] n = Bar() n.x,n.y = 1 , 2 n.z = 3 #报错 print (n.__slots__) #n不再有__dict__ |
3.9__next__和__iter__实现迭代器协议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
class Foo: def __init__( self ,n): self .n = n def __iter__( self ): return self def __next__( self ): if self .n = = 15 : raise StopIteration( "已经迭代完了" ) #抛出异常的提示信息 StopIteration: 已经迭代完了 self .n + = 1 return self .n aa = Foo( 10 ) print (aa.n) print ( next (aa)) print ( next (aa)) print ( next (aa)) print ( next (aa)) print ( next (aa)) print ( next (aa)) # raise StopIteration("已经迭代完了") #抛出异常的提示信息 StopIteration: 已经迭代完了 # StopIteration: 已经迭代完了 #一般用for循环,遇到异常就结束for循环 # for i in aa: # print(i) |
4.0__doc__
这只是类的描述信息,描述这个的作用及功能,无法继承该属性(__doc__)
1
2
3
4
5
6
7
8
|
class Foo: "这是测试" pass print (Foo.__doc__) #查看Foo类的注释 class Bar(Foo): pass print (Bar.__doc__) #没有注释信息,打印的是None |
4.1__module__和__class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
1
2
3
4
5
6
7
|
class Foo: "这是测试" pass test = Foo() print (test.__module__) print (test.__class__) |
4.2__del__
这是析构方法,当对象在内存中被释放时,自动触发执行
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
4.3__call__
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
1
2
3
4
5
6
7
8
9
10
|
class Foo: def __init__( self ): pass def __call__( self , * args, * * kwargs): print ( ‘__call__‘ ) # Foo()() 执行__call__ aa = Foo() #执行__init__ # aa() 执行__call__ |
4.4__enter__和__exit__
在操作文件对象的时候可以这么写
with open ( "文件名" ) as f: 代码 |
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
上下文管理协议
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class Foo: def __init__( self ,name): self .name = name def __enter__( self ): print ( ‘出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量‘ ) return self def __exit__( self , exc_type, exc_val, exc_tb): print ( "执行完毕exit" ) with Foo( "a.txt" ) as f: print (f) print (f.name) print ( "我是分割线" .center( 50 , "*" )) |
__exit__()中的三个参数分别代表异常类型(exc_type),异常值(exc_val)和追溯信息(exc_tb),即with语句中代码块出现异常,则with后的代码都无法执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Foo: def __init__( self ,name): self .name = name def __enter__( self ): print ( ‘出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量‘ ) return self def __exit__( self , exc_type, exc_val, exc_tb): print ( "执行exit" ) print (exc_type) #<class ‘NameError‘> print (exc_val) #name ‘aaaaaaaaa‘ is not defined print (exc_tb) #Traceback with Foo( "a.txt" ) as f: print (aaaaaaaaa) #错误的代码 print ( "我是分割线" .center( 50 , "*" )) #不会执行这个语句 |
如果__exit__返回值为True,那么异常会被清空,就好像啥都没发生一样,with后语句都正常执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class Foo: def __init__( self ,name): self .name = name def __enter__( self ): print ( ‘出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量‘ ) return self def __exit__( self , exc_type, exc_val, exc_tb): print ( "执行exit" ) # print(exc_type) #<class ‘NameError‘> # print(exc_val) #name ‘aaaaaaaaa‘ is not defined # print(exc_tb) #Traceback return True with Foo( "a.txt" ) as f: print (aaaaaaaaa) print ( "这是测试异常" ) #没执行 #with后的语句 print ( "我是分割线" .center( 50 , "*" )) #会执行 |
总结
1
2
3
4
5
6
7
8
9
10
11
|
with obj as f: "代码块" 1.with obj - - - - - 》触发obj.__center()__,拿到返回值 2. with obj as f 等同于 f = obj.__center__() ,返回值赋值f 3. 执行代码块 一:没有异常的情况下,整个代码运行完毕后去触发__exit__,它的三个参数都为 None 二:有异常的情况下,从异常出现的位置直接触发__exit__, a.如果__exit__返回值为 True ,代表吞掉异常 ,执行with下面的语句 b.如果__exit__返回值为 True ,代表突出异常 (也就是正常报错),不执行下面的with语句 c.__exit__的运行完毕就代表了整个with语句的执行完毕 |
上下文管理的好处:
1.使用with语句的目的就是把代码块放入with中执行,
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制。
4.5元类metaclass
python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例)
例
1
2
3
|
class Foo: pass f1 = Foo() #f1是同过Foo类实例化的对象 |
上述:可以看f1是由Foo这个类产生的对象,而Foo本身也是对象,那他又是由那个类产生的呢?
1
2
3
4
5
|
class Foo: pass f1 = Foo() #f1是同过Foo类实例化的对象 print ( type (f1)) # #查看这个对象是有那个类产生的 print ( type (Foo)) #查看这个类是由那个类产生的 ,所有的类都是由type产生的 |
2、什么是元类?
元类是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样
元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象。