python基础之类和对象基础
1.面向对象设计引入
想想我们是如何描述自然界的生物的,就以狗为例,当我们描述一条狗,比如二哈,会怎么描述它的?当然是会说出他的名字,品种,颜色,大小,性别,这些都是描述哈士奇的特征的,除了这些特征之外,哈士奇是不是应该还有一些动作呢?比如咬人,汪汪叫等。下面就用python来描述一条哈士奇。
1 #用字典表示哈士奇的特征: 2 dog_erha={ 3 'name':'某家的二哈', 4 'gender':'male', 5 "type":"二哈", 6 "size":10 7 } 8 #用字典定义了一个人的属性 9 person1={ 10 'name':'张三', 11 'age': 12, 12 'gender':'man' 13 } 14 15 #用两个方法来表示咬人和汪汪叫的动作: 16 def bite(dog_erha): 17 print("%s正在咬人" %dog_erha['name']) 18 19 def jiao(dong_erha): 20 print("%s正在汪汪大叫" %dong_erha['name']) 21 22 #这样就实现了对一条哈士奇的描述,但是现在有个问题就是,jiao这个方法应该是狗才特有的,如果传入的字典是一个表示人的,
#那么不应该有这个动作 23 #但是上面的代码并不能控制只有传入的字典参数是描述一条狗的时候才能调用jiao 24 25 jiao(person1)#这个方法同样可以传入一个描述人的特征的属性,但是人不应该有这个动作 26 27 #为了能限制住这一点,即不让人有jiao这个动作,现在将对哈士奇的描述定义在一个函数里面 28 29 30 def dog(): 31 dog_erha={ 32 'name':'某家的二哈', 33 'gender':'male', 34 "type":"二哈", 35 "size":10 36 } 37 def bite(dog_erha): 38 print("%s正在咬人" %dog_erha['name']) 39 40 def jiao(dong_erha): 41 print("%s正在汪汪大叫" %dong_erha['name']) 42 dog1={ 43 "dog_erha":dog_erha, 44 "bite":bite, 45 "jiao":jiao 46 47 } 48 49 return dog1 50 51 d1=dog() 52 d1['bite'](d1['dog_erha']) 53 d1['jiao'](d1['dog_erha'])
上面定义的函数实现了二哈的属性特征和动作绑定的操作,但是只能用来描述二哈,现在想要描述所有的狗,我们知道,狗都有咬人和叫这两个动作,只是不同的狗的特征属性不一致,因此只需要对上面的函数做一个小小的改动即可:
1 def dog(name,gender,type,size): 2 def bite(dog): 3 print("%s正在咬人" %dog['name']) 4 5 def jiao(dog): 6 print("%s正在汪汪叫"%dog['name']) 7 8 dog1={ 9 'name':name, 10 'gender':gender, 11 'type':type, 12 'size':size, 13 'bite':bite, 14 'jiao':jiao 15 } 16 return dog1 17 18 d1=dog('二哈','feman','哈士奇',10) 19 d2=dog('小差','meal','柴犬',1) 20 21 d1['jiao'](d1) 22 d2['jiao'](d2)
上面的代码已经实现了用一个函数来描述所有的狗。现在我们可以对这个函数再改进一下,上面的函数我们是把狗的特征和动作放在一个字典里面,然后返回字典,
现在我们把返回字典这个操作也放入一个函数里面,这里就可以让dog函数逻辑更加清晰:
1 def dog(name,gender,type,size): 2 def bite(dog): 3 print("%s正在咬人" %dog['name']) 4 5 def jiao(dog): 6 print("%s正在汪汪叫"%dog['name']) 7 def init(name,gender,type,size):#在init函数里面定义狗的属性和动作字典 8 dog1={ 9 'name':name, 10 'gender':gender, 11 'type':type, 12 'size':size, 13 'bite':bite, 14 'jiao':jiao 15 } 16 return dog1 17 18 return init(name,gender,type,size)#返回init函数的运行结果,实际上就是返回字典dog1 19 20 21 d1=dog('二哈','feman','哈士奇',10) 22 d2=dog('小差','meal','柴犬',1) 23 24 d1['jiao'](d1) 25 d2['jiao'](d2)
通过这样改进,虽然没有改变代码的运行结果,但是却使得代码的逻辑更加清晰:在dog函数里面定义了三个函数,两个函数表示狗的动作,一个函数表示狗的所有特征和
属性。
现在来总结我们上面所讨论的内容。在开始,我们提出了用python代码来描述一条二哈,描述的内容包括它的名字,性别,种类等特征,还包括汪汪叫和咬人两个动作。实现了
这一步之后,我们发现,叫和咬人这个动作不但是哈士奇能够调用,一个普通人也可以用这个方法,但是作为一个人,他不应该有着两个动作的。为了实现这一步,我们想到了定义一个函数,将这两个动作和哈士奇的特征全部定义在这个函数里面,这样,一个普通人就不能使用这两个动作了。实现了这一步后我们发现,我们定义的函数只能用来描述二哈,但是我想通过一个函数来描述所有的狗,于是我们又对函数进行了优化。
上面的这一系列优化,实际上就是面向对象的设计思想。面向对象的设计思想其实就是想要通过某种方式,让我们写的代码既能够描述自然界某一类事物(狗),又想让它跟其他类别区分开(人),即我们写的一段特殊的代码应该能实现这一的逻辑:能够用来描述所有的狗,无论是二哈还是柴犬,同时不能用来描述除了狗之前的其他事物,比如人。通过上一段的分析,我们已经知道我们通过不断优化的代码,最后通过一个函数达到了这一目的,所以,上面的函数其实就是一个面向对象的例子。
因为面向对象这种设计思想在实际开发中应用比较广泛,因此在python中,有一套专门的机制来实现面向对象的设计,这一套机制比我们自己用函数来实现面向对象要方便得多。但是应该记住,这套机制只是用来让我们更快的实现面向对象的设计思想的,他本身并不能代表面向对象,也就是说,不是说面向对象设计只能通过这套机制才能来实现(上面的例子已经证明,通过函数也可以实现面向对象)。下面通过这套机制来改写上面的函数:
1 class Dog: 2 ''' 3 1.class表示我们定义了一个类(一个表示狗的类) 4 2注意,Dog后面不需要跟() 5 3我们约定,类名的第一个字母大写 6 ''' 7 def __init__(self,name,gender,type,size): #相当于init函数,但是这里不需要return,这个函数会自动return 8 self.name=name 9 self.gender=gender 10 self.type=type 11 self.size=size #没有bite和jiao,这里只存放特征,不存放动作。特征在类中称之为数据属性,动作称为函数属性 12 def bite(self,name): 13 print("%s正在咬人" %(self.name)) 14 15 def jiao(self,name): 16 print("%s正在汪汪叫" %(self.name)) 17 18 19 20 d1=Dog('二哈','feman','哈士奇',10) #根据__init__函数来进行传入参数,该步骤称为实例化 21 d1.name #通过.调用数据属性和函数属性 22 d1.bite(d1.name)
上面的代码中,我们定义了一个狗的类Dog,用来描述所有的狗,class是python的关键字,他表示我们定义的是一个类,第20行我们定义了一个实例,即一条具体的狗,二哈。这种通过类定义一个实例我们成为实例化,实例也称为对象。因此对象与类的关系是:对象是一个类实例化的结果,一个对象就是一个实例,在本例中,一个对象就是一条具体的狗。
2.类相关知识
1 ''' 2 定义一个类Chiese,这个类包含了一个数据属性:dang,两个函数属性:chaidui,tanqian 3 ''' 4 class Chiese: 5 dang="guomindang" #类的数据属性 6 7 #下面定义类的函数属性 8 def chaidui(): 9 print("这人又在插队") 10 11 def tanqian(self): #self就是一个普通参数,只是约定使用self参数来表示传入的参数是对象自己 12 print("为了钱,命都不要了?") 13 14 #类可以通过类名直接调用自己的数据属性和函数属性 15 print(Chiese.dang) 16 Chiese.chaidui() 17 Chiese.tanqian('bu') 18 19 res=dir(Chiese)#以列表的形式返回类的所有属性,包括内置属性 20 21 #__dict__是一个字典,该字典存放了类或者对象的所有属性,又称为属性字典 22 res=Chiese.__dict__ #以字典的形式返回类的所有属性名和属性值,字典的key值都是字符串形式 23 res=Chiese.__dict__['dang'] #返回guomindang。 24 ''' 通过这个可以看出Chiese.name实际上是在调用Chiese.__dict__['name']''' 25 Chiese.__dict__['tanqian']('niwe') #运行tanqian() 26 '''通过这个例子可以看出,Chiese.tanqian()实际上是在调用Chiese_dict__['tanqian]''' 27 28 res=Chiese.__name__#返回类的名字 29 res=Chiese.__module__#返回类属于哪一个模块,如果是本模块返回__main__ 30 print(res)
上面的例子总结两点:
(1)类可以通过.直接调用其属性,不一定非要通过实例化才行。
(2)类调用属性实际上调用的类字典__dict__里面的属性元素。类的所有属性都是存放在__dict__里面的
3.对象相关知识:
1 class Chiese: 2 '''这是一个中国人的类''' 3 dang='guomindanng' 4 def __init__(self,name,age,gender): 5 self.name=name 6 self.age=age 7 self.gender=gender 8 9 def chadui(self,name): 10 print("%s正在插队" %self.name) 11 12 13 14 p=Chiese('张三',13,'man') #实例化,调用__init__函数,实例化时,会自动把p传给self参数 15 res=p.__dict__ #对象的属性字典,该字典只会显示出对象的所有数据属性,不会显示类的数据属性和函数属性 16 print(res)
实例化类时需要注意三点:
(1)创建一个对象时,会自动调用类的__init__方法
(2)self参数表示吧对象自己作为参数传入,创建一个对象后,调用任何方法都会自动把对象传给self,因此在调用这些方法时不需要管self参数
(3)对象的__dict__,即对象的属性字典只存放对象自己的数据属性,类的数据属性和函数属性不会存放在对象属性中,而是存放在类的字典属性中,因此如果对象要调用类
的数据属性和函数属性,实际上是在调用类的属性字典
4.类属性和对象属性的增删改查
1 #----------------------类属性的增删查改-------------------------------- 2 #查看 3 res=Chiese.country 4 print(res) 5 6 #修改 7 ''' 因为调用类的属性相当于调用类的__dict__属性字典,因此按照字典的操作方法来操作属性就好''' 8 Chiese.country='china' 9 res=Chiese.country 10 print(res) 11 12 #增加 13 Chiese.dang="hu" 14 #print(Chiese.__dict__) 15 16 #删除 17 #del Chiese.dang 18 19 20 #------------------对象的增删改查--------------------- 21 p1=Chiese('张三') 22 print(p1.__dict__) 23 #查看 24 res=p1.name #在自己的字典属性里面查找,如果找不到就去类的字典属性里面去找 25 26 res=p1.country#在自己的字典属性里面去找,找不到就去类的字典属性里面去找 27 28 #增加 29 p1.age=12 #增加到自己的属性字典里面。 30 #p1.country='hahha'#在自己的属性字典里面增加一个名为country的数据属性,不会影响类的数据属性和类的属性字典, 31 res=p1.country#若对象的数据属性和类的数据属性名字一致,会优先找到对象的数据属性,即优先在对象的属性字典里面找 32 33 #删除 34 35 #del p1.country #删除对象属性字典里面的country 36 #del p1.country #对象不能删除类的属性,即对象的删除操作只是针对对象的属性字典 37 print(p1.__dict__) 38 #print(Chiese.country) 39 #print(p1.country) 40 41 #修改 42 p1.age=34 43 print(p1.__dict__) 44 45 print(res)
类和对象属性的增删改查实际上是对类和对象的__dict__字典进行操作,因此按照字典的操作进行就行,但是需要区分什么时候是对类的属性字典进行操作,什么时候是对对象的属性字典进行操作
5.静态属性,静态方法,类方法
(1)静态属性@property(将类的方法封装成数据属性)
我们定义一个类,通过一个类创建一个实例化对象后,如果要调用类的方法,就必须要传入相应的参数,如下:
1 class Room: 2 def __init__(self,name,ower,length,width,height): 3 self.name=name 4 self.ower=ower 5 self.length=length 6 self.width=width 7 self.height=height 8 9 def cal_area(self,length,width): 10 print("%s的房子的面积是%s" %(self.ower,self.length*self.width)) 11 12 return self.length*self.width 13 14 15 r1=Room("海景房",'zhangshan1',10,10,10) 16 17 res=r1.cal_area(10,10)#调用cal_area方法,需要传入参数 18 print(res)
但是,在实际开发中,可能会存在这样的情况,实际需求可能会要求你隐藏掉该方法的参数,即在类外部调用这个方法时不应该让调用者传入参数,只允许他像调用数据属性那样来调用该方法,即以r1.cal_area的形式来调用。遇到这种需求,就需要使用@property这个静态属性的装饰器了,这个装饰器的功能就是将类的方法封装成一个数据属性,这样调用这个类的人就可以把这个方法按照调用数据属性的方式来调用,而不会知道这个方法会有哪些参数。需要注意的是,使用了@property装饰器后,被装饰的函数不应该再有多余的参数:
1 class Room: 2 def __init__(self,name,ower,length,width,height): 3 self.name=name 4 self.ower=ower 5 self.length=length 6 self.width=width 7 self.height=height 8 @property 9 def cal_area(self):#此处不再需要传入参数 10 #print("%s的房子的面积是%s" %(self.ower,self.length*self.width)) 11 12 return self.length*self.width 13 14 15 r1=Room("海景房",'zhangshan1',10,10,10) 16 17 res=r1.cal_area#通过property将其封装成数据属性,直接调用数据属性即可 18 print(res)
(2)类方法@classmethod(只能类使用,对象不能调用该方法):
1 class Room: 2 def __init__(self,name,ower,length,width,height): 3 self.name=name 4 self.ower=ower 5 self.length=length 6 self.width=width 7 self.height=height 8 @property 9 def cal_area(self):#此处不再需要传入参数 10 #print("%s的房子的面积是%s" %(self.ower,self.length*self.width)) 11 12 return self.length*self.width 13 14 @classmethod #将该方法定义为类方法,只有类能使用,对象调用会报错 15 def tell_info(clc,name):#类方法的第一个参数是clc 16 print("这间房子的名字叫%s"%clc.name) 17 18 r1=Room("海景房",'zhangshan1',10,10,10) 19 20 r1.tell_info('海景房')
(3)静态方法:@staticmethod(类和对象都可以用)
1 class Room: 2 def __init__(self,name,ower,length,width,height): 3 self.name=name 4 self.ower=ower 5 self.length=length 6 self.width=width 7 self.height=height 8 @property 9 def cal_area(self):#此处不再需要传入参数 10 #print("%s的房子的面积是%s" %(self.ower,self.length*self.width)) 11 12 return self.length*self.width 13 14 @classmethod #将该方法定义为类方法,只有类能使用,对象调用会报错 15 def tell_info(clc,name):#类方法的第一个参数是clc 16 print("这间房子的名字叫%s"%clc.name) 17 18 #实例方法 19 def test(self,name):#这种方式是不能用类调用的,因为name必须要实例化后才有 20 print("我是一个静态方法") 21 22 @staticmethod #静态方法,类和对象都可以调用 23 def test1(): 24 print(;nihao1) 25 r1=Room("海景房",'zhangshan1',10,10,10)
6.组合
在实际开发中,可能会出现这样的需求:某两个类之间没有相同的数据属性和函数属性,但是两个类之间有某种关联,比如,对于人这个类来说,它跟手,头,这些类是没有相同之处的,但是手,头这两个类却是人这个类的组成部分之一,即三个类之间存在某种关联。python使用组合来描述几个类之间的关联
1 class Head: 2 pass 3 class Hand: 4 pass 5 class Truck: 6 pass 7 class Foot: 8 pass 9 10 class Person: 11 12 def __init__(self,name,age):#在初始化函数中,将其他类的实例作为该类实例的数据属性,建立起关联关系 13 self.name=name 14 self.age=age 15 self.head=Head() 16 self.hand=Hand() 17 self.truck=Truck() 18 self.Foot=Foot() 19 20 21 p1=Person('zhangsan',13) 22 print(p1.__dict__)
1 #组合 2 3 class Head: 4 pass 5 class Hand: 6 pass 7 class Truck: 8 pass 9 class Foot: 10 pass 11 12 class Person: 13 14 def __init__(self,name,age):#在初始化函数中,将其他类的实例作为该类实例的数据属性,建立起关联关系 15 self.name=name 16 self.age=age 17 self.head=Head 18 self.hand=Hand 19 self.truck=Truck 20 self.Foot=Foot 21 22 23 p1=Person('zhangsan',13) 24 print(p1.__dict__)
7.继承
继承即一个类继承另一个类,子类会继承父类的所有数据属性和函数属性,不过在子类中,可以重新父类的方法,并且可以定义子类自己的数据属性
1 #继承 2 class Dad: 3 moeny='这是来自父类的moeny' 4 def __init__(self,name,age): 5 self.name=name 6 self.age=age 7 8 def hit_son(self,name): 9 print("%s正在打儿子" %self.name) 10 11 class Son(Dad): 12 '''' 13 1.若子类没有初始化方法(构造函数),则使用父类的构造函数初始化 14 2.子类可以自己定义初始化方法,这样就不会使用父类的了 15 3.无论是数据属性还是函数属性,寻找的顺序都是先在自己的类中寻找,如果没有找到,就去继承的父类里面找,如果继承 16 的父类也没有,就去父类的父类找(如果父类也存在父类),这种寻找方式跟嵌套函数变量的寻找方式一致,都是优先从本层 17 开始找 18 ''' 19 moeny1="这是子类" 20 #moeny=100 #如果子类存在和父类同名的属性(无论数据属性还是函数属性都一样),会使用自己的属性数据 21 22 def xuexi(self): 23 print("%s正在努力学习" %self.name) 24 25 #print(Dad.__dict__) 26 #print(Son.__dict__)#子类的属性字典中不会包含继承的父类的属性,但是如果需要使用父类的属性,会去父类的属性字典里面寻找 27 28 s=Son('zhangsna',18)#子类没有__init__构造函数,使用父类的构造函数初始化类 29 res=s.moeny #子类没有moeny属性,去父类找该属性,如果子类有同名的该属性,则使用子类自己的 30 res=s.moeny1#子类有moeny1属性,使用子类自己的 31 s.hit_son('zhangsan') #子类没有该方法,使用父类的方法 32 s.xuexi() #子类有该方法,使用子类自己的
8.接口继承
1 import abc #导入接口类模块 2 3 class All_file(metaclass=abc.ABCMeta): 4 ''' 5 将All_file类定义为接口类,该类只是规定继承该类必须实现的方法,不涉及具体的代码逻辑,这样实现统一规范 6 ''' 7 8 @abc.abstractmethod #抽象函数装饰器,表示该函数是一个抽象函数。接口类中的函数都必须是抽象函数 9 def read(self): 10 '''' 11 1定义抽象函数,抽象函数即只声明函数,但是不具体实现的函数 12 2所有继承了接口类的子类,都必须实现接口类中的所有抽象方法 13 ''' 14 pass 15 @abc.abstractmethod 16 def write(self): 17 pass 18 19 class D_mom(All_file): 20 def __init__(self,name): 21 self.name=name 22 23 def read(self):#必须实现接口类中的read方法,否则实例化时会报错 24 print('实现接口类中的read') 25 26 def write(self): 27 print("实现接口类中的write方法") 28 29 d1=D_mom('zhang')
9.多继承时的继承顺序
在python中支持多继承,多继承涉及的问题就是继承顺序,即一个变量的继承顺序是怎么的。在python3中,继承顺序采用的是广度优先的继承顺序(经典类是深度优先)
下面通过一个图直观看一下多继承继承顺序:

1 ###多继承的继承顺序 2 class A: 3 pass 4 5 class B(A): 6 pass 7 8 class C(A): 9 pass 10 11 class D(B): 12 pass 13 14 class F(C): 15 pass 16 17 class E(D,F): 18 pass 19 20 print(E.__mro__) #通过__mro__属性可以快速查看类的继承顺序
10.在子类中调用父类属性:
在实际开发中,为了提高开发效率,有时需要再子类中调用父类同名的属性数据,此时可以通过父类.属性来完成:
1 class Subway(Vicheal): 2 def __init__(self,name,speed,load,power,line):#子类比父类多了一个power属性 3 4 ''' 5 传统的定义方法会造成大量的重复代码 6 self.name=name 7 self.speed=speed 8 self.load=load 9 self.line=line 10 self.power=power 11 ''' 12 13 Vicheal.__init__(self,name,speed,load,power)#调用父类的构造方法,就可以减少很多重复代码 14 self.line=line 15 16 def run(self):#子类中的函数需要嵌套父类的同名方法,即调用父类同名方法 17 Vicheal.run(self) 18 print("%s号线开始运作啦"%self.line)
但是,这样一个问题是,如果后面父类的类名改变了,那么所有在子类用到这种方式的地方都要修改,这样很容易出错,为了避免出现这样的问题,python提供了supper关键字来替代用父类.属性的方式:
1 class Subway(Vicheal): 2 def __init__(self,name,speed,load,power,line):#子类比父类多了一个power属性 3 4 ''' 5 传统的定义方法会造成大量的重复代码 6 self.name=name 7 self.speed=speed 8 self.load=load 9 self.line=line 10 self.power=power 11 ''' 12 13 #Vicheal.__init__(self,name,speed,load,power)#调用父类的构造方法,就可以减少很多重复代码 14 super().__init__(name,speed,load,power)#注意,supper()调用时不需要self参数, 15 # supper().__init__相当于supper(__class__,self).__init__() 16 self.line=line 17 18 def run(self):#子类中的函数需要嵌套父类的同名方法,即调用父类同名方法 19 #Vicheal.run(self) 20 super().run() 21 print("%s号线开始运作啦"%self.line)
11.多态
多态指的是,通过一个类实例化几个不同的对象后,不同的对象调用同一个类方法,得到的运行结果不一致:
1 class H2O: 2 def __init__(self,name,temper): 3 self.name=name 4 self.temper=temper 5 6 def trun_ice(self): 7 if self.temper<0: 8 print('温度太低,%s已经结成冰了' %self.name) 9 10 elif self.temper >100: 11 print("温度太高,%s已经变成蒸汽了"%self.name) 12 13 else: 14 print("温度正常,%s就是液体" %self.name) 15 16 17 h1=H2O('water',-20) 18 h2=H2O('water',190) 19 h3=H2O('water',78) 20 21 h1.trun_ice() 22 h2.trun_ice() 23 h3.trun_ice()
12.封装
封装指的是,我们在定义一个类的时候,某些数据属性我们可能不想让调用者知道,但是调用者又需要使用这些数据,因此我们将这些数据设为类私有,然后提供一个方法让调用者能使用这些数据,但是无法得知原始数据,即将某些需要使用这些数据的功能封装在类中,然后提供一个方法,供外部调用
需要注意的是,在python中,并不存在绝对的私有数据,我们只是通过一种约定来告诉类的使用者,这些数据不应该在外部使用:
1 class Room: 2 def __init__(self,name,ower,length,width,height): 3 self.name=name 4 self.ower=ower 5 '''' 6 通过__将length,width,height三个属性设置为类的私有属性 7 在类的外部同样可以通过对象._Room_length的方式调用到该属性,但是不建议这样做 8 ''' 9 self.__length=length #将length设置为私有属性,告诉类的使用者,这个数据不应该在外部调用 10 self.__width=width #__代表这个属性是类的私有属性 11 self.__height=height 12 13 def get_area(self):#将需要使用到私有属性的功能封装称为函数,供外部调用,调用者不会知道私有属性的具体值 14 return self.__length*self.__width

浙公网安备 33010602011771号