Python之路(七)——Python OOP(基础篇)
本节内容:
- 面向对象(OO)是思想
- 类和对象
- 属性和方法
- 静态属性与@Property
- 类方法
- 静态方法
- 抽象与抽象类
- 接口
- 继承
- 组合
- 多态
- 封装
一、面向对象(OO)是思想
def person(name, age, sex): #初始化 def init(name, age, sex): person ={} person["name"] = name person["age"] = age person["sex"] = sex person["say"] = say person["eat"] = eat return person def say(person): print("My name is :%s,i'am %s old." % (person["name"], person["age"])) def eat(person): print("%s is eating" % person["name"]) return init(name, age, sex) p_lw = person("老王", 42, '男') print(p_lw["name"]) p_lw["eat"](p_lw) #结果输出: # 老王 # 老王 is eating
结论:OO是一种思想,class 定义类只是更加方便地实现OO思想而不是只有定义了class才是面向对象编程,同样即使定义了class也有可能不是面向对象编程
二、类和对象
前言
- 面向对象是一种思想、方法论,与面向过程、函数式编程一同组成了主流的三种编程思想
- 面向对象涵盖:面向对象分析——OOA 、面向对象设计——OOD、面向对象编程——OOP,本节重点记录Python中的OOP方法而非OO思想
- 类、对象的属于都是发生在特定的场景下,即不是绝对的
类与对象
类:一组事物共有的属性和方法的集合(有时也叫属性集合:数据属性,方法属性),可理解为蓝图、模板。
对象:一个个单独的事物,也叫实例
class Person: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def say(self): print("My name is :%s,i'am %s old." %(self.name,self.age)) def eat(self): print("%s is eating" %self.name) #实例化一个Person 对象——老王 p_lw = Person("老王", 42, '男') #调用 p_lw.eat() # 老王 is eating
初始化构造函数__init__
用于对象实例化且被隐式调用,有如下特性:
- 名称唯一:__init__
- 至少有一个参数self,表示实例本身且在对象实例化时自动传入对象名(地址)
- 没有返回值
self关键字用于指代当前处理的对象
class Student(): def __init__(self): print("当前对象的地址是:%s"%self) if __name__ == '__main__': student1 = Student() print(student1) student2 = Student() print(student2) #结果为: # 当前对象的地址是:<__main__.Student object at 0x000002A415378AC8> # <__main__.Student object at 0x000002A415378AC8> # 当前对象的地址是:<__main__.Student object at 0x000002A415378E10> # <__main__.Student object at 0x000002A415378E10>
三、属性和方法
类的属性操作
#类属性和方法 class Chinese: country = "中国" def __init__(self,name,age): self.name = name self.age = age def chadui(self): print("%s 又他妈插队了!" %self.name) #属性增删改查 print(Chinese.country) #中国 p1 = Chinese("王八蛋", 22) print(p1.country) # 中国 print(p1.__dict__) # {'name': '王八蛋', 'age': 22} #结论: .操作符 在本对象中__dict__查找,没有 找类 Chinese.fs = "黄种人" print(Chinese.__dict__)# 新加 'fs': '黄种人' print(p1.fs) #同样可以找到 黄种人 #修改 Chinese.country = "中华人民共和国" print(Chinese.country, p1.country) # 中华人民共和国 中华人民共和国 def sleep(self): print("%s 正在睡觉." %self.name) # 王八蛋 正在睡觉. Chinese.sleep = sleep p1.sleep() #结论: Python对OOP 方面的语法不是严格控制,需要程序员自行控制
对象的属性操作
class Chinese: country = "中国" def __init__(self,name,age): self.name = name self.age = age def chadui(self): print("%s 有他妈插队了!" %self.name) #实例化一个对象 lw = Chinese("老王", 42) print(lw.__dict__) # {'name': '老王', 'age': 42} lw.aihao = "抽烟" print(lw.__dict__) # {'name': '老王', 'age': 42, 'aihao': '抽烟'} print(Chinese.__dict__) #没有aihaoshuxing def sahuang(self): print("%s 就喜欢撒谎" %self.name) lw.sahuang = sahuang lw.sahuang(lw) # 老王 就喜欢撒谎 #上述对象函数与类无关业余其他对象无关,仅语法可以,没有实际意义 print(lw.__dict__) print(Chinese.__dict__) # 没有撒谎函数 #拿了绿卡 lw.country = "美国" #赋值语句都是新建,对象不能修改类属性 print(lw.__dict__) # {'name': '老王', 'age': 42, 'aihao': '抽烟', 'sahuang': <function sahuang at 0x000001D6B602B6A8>, 'country': '美国'} # 删除——遣回中国 del lw.country print(lw.country) #中国
结论:对象的属性单独维护在对象中,独立于类的属性。对象查找属性时先从本对象__dict__中查找,没有在到类__dict__中查找
四、静态属性
这个专有名词歧义很大,首先类有自己的属性,这些属性相对于对象来说就是静态的属性。而这里的静态属性是指:在类的方法(仅有self参数)前加入装饰器@property
class Room: style = "别墅" #类属性 def __init__(self, name, addr, length, width, height): self.name = name self.addr =addr self.length = length self.width = width self.height = height def get_height(self): print("%s 房间的高度为: %s" % (self.name, self.height)) def ruzhu(self, owner): print("%s 入住了 %s" % (owner, self.name)) @property def area(self): return self.length * self.width # def cal_area(self): # return self.length * self.width room1 = Room("山村人家" ,"峦峰岗",100,50,20) room1.ruzhu("张三") # 张三 入住了 山村人家 #求room1 的面积 # print(room1.cal_area()) # 5000 #方法二、使用@property 装饰器 print(room1.area) # 5000 print(room1.__dict__) #没有area 属性的 #从外部看来就是一个对象属性
结论(个人理解):Python 中 类其实可以没有属性,即:属性在有值的情况下才有意义。所以类中仅保存这类事物的共有方法即可。例如:一个人他虽然有国籍,名字,年龄,但是人这个类其实是没有这些的,Python 中把这类属性直接保存在每个对象属性中,那有时候需要对象的间接属性,如:取Room 占多少平米,通过@property 修饰符把一个方法转换成一个属性(至少表面上看起来是这样的)
五、类方法
类中的方法不与对象绑定(默认绑定即第一个参数为self),而与类自身进行绑定(self ->cls)。可以定制化调用__init__方法创建对象
class Dog: def __int__(self,name,color): self.name = name self.color = color def jiao(self): print("%:汪汪汪!" %self.name) @classmethod def about_me(cls): print("这个是一个Dog 类") Dog.about_me()
六、静态方法
类中不与对象(self)也不与类(cls) 绑定的方法。一般是作为工具方法来使用。
import time class TimeTest: def __init__(self,hour,min,sec): self.hour = hour self.min = min self.sec = sec #静态方法,仅作用域限定在类中,其实就是一个独立的函数,可以有参数 @staticmethod def show_currtime(var1,var2): print(time.asctime()) TimeTest.show_currtime(1,2)
七、抽象与抽象类
抽象:现实生活中首先识别到一个个具体的对象,在对象的基础上提取共有属性。这个动作也叫泛化
OOA(面向对象分析)步骤<简述>:
- 识别一个个对象
- 提取对象特征,对象间的关系
- 抽象特征,组成类
- 循环第三步骤,形成类的层级关系图
抽象类
类的抽象,更抽象地描述事物
import abc #抽象类,不能被实例化,只能被继承使用 class Shape_2D(metaclass=abc.ABCMeta): type = "二维图形" #初始化方法被抽象定义,so 不能实例化一个Shape_2D 对象 @abc.abstractmethod def __init__(self): ''' 抽象方法 ''' pass class Circle(Shape_2D): type="圆" def __init__(self,x,y,radius): pass class Line(Shape_2D): type = "线" def __init__(self,x1,y1,x2,y2): pass c1 = Circle(1,2,3) print(c1.type) # 圆
八、接口
语法与抽象类相同,表示一组特定功能提供统一的对外功能
import abc class I_Pay(metaclass=abc.ABCMeta): #支付 @abc.abstractmethod def pay(self): pass #退款 @abc.abstractmethod def tuikuan(self): pass class AppleMobile(I_Pay): def pay(self): print("苹果手机支付") def tuikuan(self): print("苹果手机退款") class XMMobile(I_Pay): def pay(self): print("小米手机支付") def tuikuan(self): print("小米手机退款")
抽象类 vs 接口
- 抽象类:还是一个类,仅对类进一步抽象;接口:特定功能的规范性描述
- 抽象类:描述什么是什么关系,例:战斗机、民用机是飞机,麻雀是鸟;接口:描述什么有什么功能的关系,例:飞机,鸟都有飞翔的功能
注意:在OO中所有概念(类,抽象类,接口等)都是在特定场景下,而非绝对
九、继承
即抽象的逆过程。 对象与类 '延续共有属性' 叫 : 实例化; 类与类 '延续共有属性 '叫继承,存在父-子关系。
MRO
一个元祖,记录继承顺序。
#继承树 # A # B C # D E # F class A: def test(self): print("A") class B(A): def test(self): print("B") 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") f = F() f.test() #新式类广度优先:左边 F->D->B->E->C->A #老式深度优先: F->D->B->A->E->C print(F.__mro__) # (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, # <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
super关键字
子类引用父类内容,使用super关键字
class Stuff: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def say(self): print("我的名字 %s" %self.name) class Teacher(Stuff): def __init__(self,name,age,sex,zc): super().__init__(name,age,sex) # super(Teacher, self).__init__(name,age,sex) self.zc = zc t1 = Teacher("老张",12,'男','副高') t1.say() # 我的名字 老张
十、组合
类与类的关系除了继承,还有组合、聚合、关联
- 组合:一个类是其中一个类的组成部分,且紧密相关。例:Linux硬盘操作模块与LinuxOS内核,独立于Linux内核硬盘操作这个模块无法使用
- 聚合:一个类是一个类的一部分,但是可独立使用。例:发动机与汽车,发动机可以独立出来使用
- 关联:最常用,两个类都是独立的,但存在某种关系。例如:学校老师绩效考核系统中,学校是一个类,老师是一个类,但这里的老师与学校存在关联
十一、多态
功能函数参数采用父类类型定义,调用时根据传入不同的子类对象实际调用其子类自身的方法,从而同一函数看起来呈现不同的形态。没有继承就没有多态
#多态 class Animal: def __init__(self): pass def move(self): pass class Dog(Animal): def __init__(self): pass def move(self): print("Dog is running") class Bird(Animal): def __init__(self): pass def move(self): print("Bird is flying") #函数让传输的动物移动两次 def run(animal): animal.move() animal.move() if __name__ == "__main__": dog1 = Dog() bird1 = Bird() run(dog1) run(bird1) #结果: # Dog is running # Dog is running # Bird is flying # Bird is flying #好处: 调用程序仅判断一类事物,仅针对同一类型进行处理。例如:新增一个动物,本程序亦然运行无恙
十二、封装
目的
封装的目的就是隐藏复杂性。例如: 撒尿是经过一系列的动作,但是你本身是不清楚的(医生除外),掏出你的枪开干就完事了。
封装的最终结果做到内外有别,仅给出对方需要的数据,不少也不多。
方法
Python 对封装没有提供特有的关键字,仅通过变量名和约定来完成
- _foo:保护的方法/属性 protect
- __foo:私有方法/属性,只允许类的内部访问 private
- foo:普通变量名,可以被外部访问 public
class Employee: def __init__(self,name,age,race,salary): self.name = name self.age = age self._race = race self.__salary = salary e = Employee('老王',55,'黑人',50000) print(e._race) # 可以访问但不建议使用,提供给子类访问的 # print(e.__salary) # AttributeError: 'Employee' object has no attribute '_salary' print(e._Employee__salary) # 一定要看也可以 #结论: Python 中很多功能都是不强制,讲究约定俗称