17.python面向对象
面向对象的编程(object oriented programming),简称oop:是一种编程的思想。oop把对象当成一个程序的基本单元,一个对象包含了数据和操作数据的函数。面向对象的出现极大的提高了编程的效率,使其编程的重用性增高。
模拟场景理解面向对象和面向过程:
1 ''' 2 使用面向过程的思想解决吃饭的问题? 3 步骤: 4 1).思考今天吃什么? 5 2).去买菜(货比三家) 6 3).摘菜 7 4).洗菜 8 5).切菜 9 6).炒菜 10 7).焖饭 11 8).吃饭 12 9).洗刷 13 使用面向对象的思想解决吃饭的问题? 14 步骤: 15 1).思考今天吃什么? 16 2).去饭店 17 ①.调用服务员的点菜功能 18 ②.将菜品告知后台大厨 19 ③.大厨调用服务员的上菜功能 20 3).开始吃饭 21 4).结账走人(多种支付方式) 22 '''
python是解释性语言,但是它是面向对象的,能够进行对象编程。面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用;类 是一个模板,模板中包装了多个“函数”供使用(可以讲多函数中公用的变量封装到对象中);对象,根据模板创建的实例(即:对象),实例用于调用被包装在类中的函数。大白话理解类和对象的区别:i类:具有一些列相同特征、行为的"事物",它的表现是不具体的、不清晰的、模糊的概念;对象:从类中实例化得到,一个实实在在的个体,在内存中有体现;它的表现是具体的、清晰的、看得见摸的着的。
遇到面向对象的问题,通常可以考虑如下三个环节:
1). 设计类,定义属性、函数、...(可能需要花费大量的时间) ;
2). 创建(实例化)对象(简单,一行代码搞定,但是内存比较复杂);
3). 对象调用属性或者函数完成需求。
1 # 1.设计类 2 class car(object): 3 4 # 属性 5 color = "红色" 6 brand = "bmw" 7 number = "沪a88888" 8 9 # 函数/方法 10 def run(self): 11 print('%s的%s,车牌为%s,正在飞速的行驶...' %(self.color,self.brand,self.number)) 12 13 def stop(self): 14 print('车停了...') 15 16 # 2.创建对象/实例化对象 17 c1 = car() 18 print(c1,type(c1)) # 得到<__main__.car object at 0x000000000220d710> <class '__main__.car'> 19 20 # 3.对象调用属性 21 print(c1.color,c1.brand,c1.number) # 红色 bmw 沪a88888 22 23 # 4.对象调用函数 24 c1.run() # 红色的bmw,车牌为沪a88888,正在飞速的行驶... 25 c1.stop() # 车停了... 26 27 # 创建第二个对象 28 c2 = car() 29 print(c2,type(c2)) # 得到<__main__.car object at 0x000000000272d7b8> <class '__main__.car'> 30 31 print(c1 == c2) # 得到false ,比较的是地址,c1和c2的地址不一样 32 33 c2.color = "白色" 34 c2.brand = "byd" 35 c2.number = "京a66666" 36 print(c2.color,c2.brand,c2.number) # 白色 byd 京a66666 37 print(c1.color,c1.brand,c1.number) # 红色 bmw 沪a88888 38 '''在一个模块中可以创建多个对象,它们彼此之间是相互独立存在(堆空间有体现),切互不干扰...''' 39 40 c3 = c1 # c1和c3的地址是一样的,共用,c1不调用了,但c3还指在堆上,c1记录的地址又给到c3一份 41 c1 = none 42 '''此时堆中有1个空间,不存在垃圾空间;因为c1虽然被赋值为none了,但是c3仍然记录了堆中对象空间的地址(维护这层关系)'''
类的成员:
1). 字段:普通字段、静态字段
普通字段需要通过对象来访问,而静态字段通过类访问,在使用上可以看出普通字段和静态字段的归属是不同的;他们的本质的区别是内存中保存的位置不同,普通字段属于对象,而静态字段属于类;静态字段在内存中只保存一份,普通字段在每个对象中都要保存一份;通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态字段。
1 class province: 2 3 # 静态字段/类属性 4 country = '中国' 5 6 def __init__(self, name): 7 # 普通字段/对象属性 8 self.name = name 9 10 # 实例对象obj1 11 obj1 = province('河北省') 12 print(obj.name) # 直接访问普通字段 13 14 # 实例对象obj2 15 obj2 = province('河南省') 16 print(obj.name) # 直接访问普通字段 17 18 # 直接访问静态字段 19 province.country
2). 方法:普通方法、类方法、静态方法、属性方法
前三种方法在内存中都归属于类,区别在于调用方式不同。普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;类方法:由类调用;至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;静态方法:由类调用;无默认参数。
相同点:对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份;不同点:方法调用者不同、调用方法时自动传入的参数不同。
静态方法意思是把 @staticmethod 下面的函数和所属的类截断了,这个函数就不属于这个类了,没有类的属性了,只不是还是要通过类名的方式调用 ,把静态方法当作一个独立的函数给他传参就行了。
类方法只能访问类变量,不能访问实例变量。
属性方法把一个方法变成一个静态属性,属性就不用加小括号那样的去调用了
1 class foo: 2 3 def __init__(self, name): 4 self.name = name 5 6 def ord_func(self): 7 """ 定义普通方法,至少有一个self参数 """ 8 print('普通方法') 9 10 @classmethod 11 def class_func(cls): 12 """ 定义类方法,至少有一个cls参数 """ 13 print('类方法') 14 15 @staticmethod 16 def static_func(): 17 """ 定义静态方法 ,无默认参数""" 18 print('静态方法') 19 20 # 调用普通方法 21 f = foo() 22 f.ord_func() 23 24 # 调用类方法 25 foo.class_func() 26 27 # 调用静态方法 28 foo.static_func()
属性方法:其实是方法里面普通方法的变种。属性方法的定义和调用要注意:定义时,在普通方法的基础上添加 @property 装饰器;定义时,属性仅有一个self参数调用时,无需括号。补充:属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象,属性由方法变种而来,如果python中没有属性,方法完全可以代替其功能。python的属性的功能是:属性内部进行一系列的逻辑计算,最终将计算结果返回。
1 class foo: 2 3 # 定义普通方法 4 def func(self): 5 pass 6 7 # 定义属性 8 @property 9 def prop(self): 10 pass 11 12 # 实例化对象 13 foo_obj = foo() 14 15 foo_obj.func() # 调用普通方法 16 foo_obj.prop # 调用属性方法
属性方法的两种定义方式:装饰器 即:在方法上应用装饰器;静态字段 即:在类中定义值为property对象的静态字段。
1 # 装饰器方式:在类的普通方法上应用@property装饰器 2 class goods: 3 4 @property 5 def price(self): 6 return "wupeiqi" 7 8 obj = goods() 9 10 obj.price # 自动执行 @property 修饰的 price 方法,并获取方法的返回值 11 12 13 # 静态字段方式,创建值为property对象的静态字段 14 class foo: 15 16 def get_bar(self): 17 return 'wupeiqi' 18 19 bar = property(get_bar) 20 21 obj = foo() 22 23 obj.bar # 自动调用get_bar方法,并获取方法的返回值
类特殊成员:
魔术函数:__开头并且__结尾的函数,我们称为魔术函数;特点:调用执行都不需要程序员关注,系统自行决定;例如:__init__、__del__、__str__ 、...... 构造函数,析构函数, 重写函数,...... 。
构造函数(constructor):又称构造方法/构造器,在生成对象时调用,一个对象只会被执行一次,可以用来进行一些初始化操作,不需要显示去调用,系统会默认去执行。
格式:__init__(self):
执行时机:在创建对象时被执行
1 class person(object): 2 3 def __init__(self,name,age): 4 self.name = name 5 self.age = age 6 self.address = '中国'
6 7 def details(self): 8 print('姓名为:%s,年龄为:%d,籍贯是:%s' %(self.name,self.age,self.address)) 9 10 # 创建对象 11 p1 = person("多多",18) # 构造方法,通过类创建对象时,自动触发执行 12 # 创建第二个对象,与p1互不干扰,共同存在与堆中 13 p2 = person("老王",28) 14 p2.details()
析构函数:当对象在内存中被释放时,自动触发执行,此方法一般无须定义,因为python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。这个方法默认是不需要写的,不写的时候,默认是不做任何操作的。因为不知道对象是在什么时候被垃圾回收掉,所以,除非确实要在这里面做某些操作,不然不要自定义这个方法。
格式:__del__(self):
执行时机:在程序结束前,将对象回收,清出内存
1 class dog: 2 3 def __init__(self,name,age,color): 4 print('我是构造函数...') 5 self.name = name 6 self.age = age 7 self.color = color 8 9 def __del__(self): 10 print('我是析构函数...') 11 12 def func(self): 13 print('我是func函数...')
__str__(self)函数:如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值
对象实例化之后将数据给到对象名,此时如果打印对象名,在控制台上我们看到的是整个对象的类型以及在内存中的地址(十六进制),但是我们在开发过程中,对于类型和地址并不关注;我们更希望看到的是对象的各个属性内容,此时我们可以自己重新定义__str__(self)函数的函数体(就是函数重写),此函数有返回值,return后面的内容必须是str类型
执行时机:在打印对象名/引用名时被触发
1 # 没有定义__str__方法时,打印创建的对象 2 class person(object): 3 4 def __init__(self,name,age,address): 5 self.name = name 6 self.age = age 7 self.address = address 8 9 p = person('韩梅梅',20,'上海') 10 print(p) # <__main__.person object at 0x0000000004c3d978> 11 12 13 # 有定义__str__方法时,打印创建的对象 14 class person(object): 15 16 def __init__(self,name,age,address): 17 self.name = name 18 self.age = age 19 self.address = address 20 21 def __str__(self): 22 return '姓名为:%s,年龄为:%d,籍贯是:%s人' %(self.name,self.age,self.address) # return后面必须是字符串数据 23 24 p = person('韩梅梅',20,'上海') 25 print(p) # 姓名为:韩梅梅,年龄为:20,籍贯是:上海人
__dict__方法:这个方法是以字典的形式列出类或对象中的所有成员,在类里面有,在对象里面也有。
1 class abc: 2 def __init__(self,age): 3 self.age=age 4 def __add__(self,obj): 5 return self.age+obj.age 6 7 a1=abc(18) 8 9 print(abc.__dict__) # 类里面的所有成员{'__add__': <function abc.__add__ at 0x0000020666c9e2f0>, '__module__': '__main__', '__weakref__': 10 # <attribute '__weakref__' of 'abc' objects>, '__init__': <function abc.__init__ at 0x0000020666c9e268>, '__doc__': none, 11 # '__dict__': <attribute '__dict__' of 'abc' objects>} 12 13 print(a1.__dict__) # 对象里的成员{'age': 18}
面向对象的三大特性:封装性、继承性、多态性
1). 封装(encapsulation):将内容封装到某个地方,以后再去调用被封装在某处的内容。 对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。
计算机层面:①.模块、类、函数...②.属性数据的封装与隐藏 (数据私有化);封装的好处:安全性提高了。
场景演示:
1 class person: 2 def __init__(self,name,age,money): 3 self.name = name 4 self.age = age 5 self.money = money 6 def __str__(self): 7 return "name:%s,age:%s,money:%s"%(self.name,self.age,self.money) 8 9 # 实例化person对象 10 p = person('tom',30,10000) 11 print(p) # name:tom,age:30,money:10000 12 13 # age可以设置值,但是不符逻辑了 14 p.age=-40 15 print(p) # name:tom,age:-40,money:10000
以上情况不会出现编译和运行异常,但是出现了数据不符合逻辑的情况;关系到对象直接在外部去操作数据(属性),导致"脏数据"的出现;要解决此问题,需要将上面的年龄age私有化,一旦私有化age之后,那么age使用的方位只有在class中,出了class外界无法使用他,使用__属性名的方式。
1).首先第一步是在外界不允许对象直接操作/访问属性(将此权利没收) --> 将属性私有化:__属性名
2).需要在类的内部提供给外界额外的访问方式(函数:getter/setter)
1 class person: 2 def __init__(self,name,age,money): 3 self.name = name 4 self.__age = age 5 self.money = money 6 def __str__(self): 7 return "name:%s,age:%s,money:%s"%(self.name,self.__age,self.money) 8 9 # 实例化person对象 10 p = person('tom',30,10000) 11 print(p) # name:tom,age:30,money:10000 12 13 p.age=-40 # 现在此行代码相当于动态为对象p添加一个属性age 14 print(p) # name:tom,age:30,money:10000,年龄任然是30 15 16 # 查看对象p中所有的成员变量(属性) 17 print(p.__dict__) # 得到{'name': 'tom', '_person__age': 30, 'money': 10000}
18 '''所以age私有化之后,在计算机底层真正的名字已经变成了_person__age,一个属性一旦被私有化,在底层真正的名字是:_类名__属性名 19 其实python的私有化我们可以理解为伪私有(只是换了个名)''' 20 21 # 以下的操作仅仅是为对象p动态添加一个属性为__age 22 p.__age=-50 23 print(p) # 仍然得到name:tom,age:30,money:10000 24 print(p.__dict__) # 再看属性{'name': 'tom', '_person__age': 30, 'money': 10000, '__age': -50} ,只是多了一个名为__age的参数 25 26 '''但是动态数据还是可以改的(但是不要去改,这样私有化就没意义了)''' 27 p._person__age = -100 28 print(p) # 得到name:tom,age:-100,money:10000
私有化之后可以不会出现逻辑不符的现象,但是对于age,需要在类的内部提供给外界额外的访问方式(函数:getter/setter);
格式:get属性名(self)-->有返回值;set属性名(self,变量参数)-->有返回值;
以上两个函数的属性名都满足首字母大写其余字母小写的规范。
1 class person: 2 def __init__(self,name,age,money): 3 self.name = name 4 self.__age = age 5 self.money = money 6 7 # 设置__age 8 def setage(self,age): 9 # 对age值进行合法性的校验 10 if age < 0 or age > 130: 11 raise exception('年龄不合法...') 12 else: 13 self.__age = age 14 15 # 获取__age 16 def getage(self): 17 return self.__age
18 def __str__(self): 19 return "name:%s,age:%s,money:%s"%(self.name,self.__age,self.money) 20 21 p = person('tom',30,10000) 22 print(p) # name:tom,age:30,money:10000 23 24 # 调用函数完成设置和获取属性值的操作 25 print(p.getage()) # 30 26 p.setage(40) 27 print(p) # name:tom,age:40,money:10000 28 29 p.setage(-40) 30 print(p) # exception: 年龄不合法...
总结:python的类中只有私有成员和公有成员两种,不像c++中的类有公有成员(public),私有成员(private)和保护成员(protected).并且python中没有关键字去修饰成员,默认python中所有的成员都是公有成员,但是私有成员是以两个下划线开头的名字标示私有成员,私有成员不允许直接访问,只能通过内部方法去访问,私有成员也不允许被继承。在类的内部提供外界额外的访问方式(定义setter和getter方法),并且在需要的时候,可以在函数的内部加入数据合法性的校验;模板:对于setter函数,命名:set属性名(首字母大写);对于getter函数,命名:get属性名(首字母大写)
2). 继承性(inheritance):面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。计算机层面:两部分组成,一部分我们称为父类(基类、超类、superclass);另一部分我们称为子类(派生类、subclass);子类可以使用父类中的成员(使用权)。
继承性的好处:1).代码复用性变强;2).代码扩展性变强;3).代码维护性变好;4).代码阅读性变好;继承性弊端:类和类之间是一种强耦合关系,继承的好处要远远多于弊端,所以我们还是要经常使用继承的(合理),但不能为了继承而继承。
继承体系可以很庞大(呈现树状结构图),越往上层的类,感觉越模糊,越不清晰越往下层的类,感觉越清晰,越具体,所以得出结论,开发过程中创建父类的可能性变低,子类实例化的可能性极高。注意事项:1).由于继承的特点,子类对象被实例化,但是可能需要为父类属性赋值,那么可以在子类的构造函数中显示的调用父类构造来实现;2) .记住:虽然父类构造被执行,但是它仅仅做的就是赋值这件事,内存中的对象只有子类对象一个。
分类:1).单继承(单一继承);2).多重继承;3).多继承(很多语言是不合法的)
1). 单继承的使用:
1 # 父类 2 class person: 3 def __init__(self,name,age): 4 print('我是person类的构造函数。。。') 5 self.name = name 6 self.age = age 7 # 吃 8 def eat(self): 9 print('吃一个...') 10 # 睡 11 def sleep(self): 12 print('睡一会...') 13 14 # 子类 15 class teacher(person): 16 def __init__(self,name,age,salary): 17 print('我是teacher类的构造函数。。。') 18 self.salary = salary 19 # 在子类构造函数中显示的调用其父类构造;调用父类构造函数的目的:父类的属性由父类自己赋值 20 # 方法1:super(teacher,self).__init__(name,age) 21 # 方法2:super().__init__(name,age) 22 # 方法3:调用父类,这种方式最好 23 person.__init__(self,name,age) 24 25 # 教学 26 def teach(self): 27 print('教书育人...') 28 29 # 实例化子类对象 30 t = teacher('老郭',30,6000.0) 31 # 调用属性 32 print(t.name,t.age,t.salary) 33 # 调用函数 34 t.eat() 35 t.sleep() 36 t.teach()
2). 多重继承的使用:
1 # 定义生物类: 2 class creature: 3 def __init__(self,age): 4 self.age = age 5 6 def breath(self): 7 print('呼吸...') 8 9 # 定义动物类: 10 class animal(creature): 11 def __init__(self,age,name): 12 self.name = name 13 super().__init__(age) 14 15 def eat(self): 16 print('吃饭...') 17 18 # 定义狗类 19 class dog(animal): 20 def __init__(self,age,name,color): 21 self.color = color 22 animal.__init__(self,age,name) 23 24 def wangwang(self): 25 print('犬吠...') 26 27 def __str__(self): 28 return "name:%s,age:%s,color:%s" %(self.name,self.age,self.color) 29 30 d = dog(3,'旺财','black') 31 print(d) # 因为定义了__str__,即重写,所以可以直接看到属性,不然 print(d)得到的是打印d的地址而已,只有print(t.name,t.age,t.color),才能看到结果 33 d.wangwang() 34 d.eat() 35 d.breath()
3). 多继承的使用:大白话就是一个子类可以调用多个父类
1 # 定义father类 2 class father: 3 def __init__(self,money): 4 self.money = money 5 6 def drinking(self): 7 print('喝喝喝...') 8 9 # 定义mother类 10 class mother: 11 def __init__(self,facevalue): 12 self.facevalue = facevalue 13 def shopping(self): 14 print('买买买...') 15 16 # 定义child类,同时继承father和mother类 17 class child(father,mother): 18 def __init__(self,money,facevalue,work): 19 self.work = work 20 father.__init__(self,money) 21 mother.__init__(self,facevalue) 22 23 def playing(self): 24 print('玩玩玩...') 25 26 # 实例化子类对象 27 child = child(1000000,true,"语数外") 28 29 # 调用函数 30 child.playing() # 玩玩玩... 31 child.drinking() # 喝喝喝... 32 child.shopping() # 买买买...
函数重写(复写,覆盖,override) :
前提:必须有继承性;原因:父类中的功能(函数),子类需要用,但是父类中函数的函数体内容和我现在要执行的逻辑还不相符,那么可以将函数名保留(功能还是此功能),但是将函数体重构;注意:子类重写父类的函数,除了函数体以外的部分,直接复制父类的即可
1 class fu: 2 def func(self): 3 print('辟邪剑法...') 4 5 class zi(fu): 6 def func(self): 7 super().func() 8 print('葵花宝典...') 9 10 # 实例化子类对象 11 zi = zi() 12 13 zi.func() # 得到 辟邪剑法 14 # 葵花宝典
......
下一篇: python学习------文件的读与写