进一步理解面向对象-->面向对象编程的三大特性与类装饰器
面向对象编程有三大特性:封装、继承、多态
封装
在理解面向对象编程思想时,作为开发者,我们可以把一个类理解为一个''容器'',而将对象理解为一个使用者,如果你认可这种类比,那么''容器''的比喻其实就是一种封装思想的体现
一个类将其中所含有的各数据、功能整合到一起,这种可以类比于''容器''的整合正是面向对象思想最核心的特性,反映此思想最大的优点。
相较于很适用于流程化固定、类工厂化的面向过程,面向对象很适用于人们现实生活中的场景,即我们可以在代码中''像上帝一样''地赋予我们在生活中看到任一类事物它所具有的主观的能力、形态、与另一类的关系,甚至可以粗暴理解为该类事物存在所造成的影响。在业务逻辑中需要某一类的一个对象的某种功能、数据的体现时,我们将其调用就可以了
比如你是一位上帝,你创造了一个类叫''狗狗'',你让它优雅地叫两声来表达它开心的心情,那么在面向对象编程中,就可以类比于你虽然只是一个开发者,但你创造了一个代码世界里的''狗狗''---> class Dog,你也可以在任何时候让它叫出声来---> def speak(self...),调用其有的方法就好了
这种优点的衍生物是代码的可拓展性、可维护性更高,比如你想给狗狗再加一个给另一只可爱狗狗示爱的功能,就在class Dog 中加一个方法 def show_love(self...),你的狗狗进化成了一只更为有艺术感的狗狗,它不想讲话只想唱歌,那就把你写过的def speak(self...)删掉或修改成def sing(self...)就好了,这里体现的就是面向对象编程的可扩展性和可维护性
而如果你走进了上述文字的场景,尤其是体验上帝创造万物的爽快感的那一part,这个''创造''/''赋予''/''上色'' 的概念就正是面向对象编程中''封装''的思想,它的直接体现就是将一类所有的客观上具有的、主观上表现的整合在了其中
隐藏属性
隐藏属性:将封装的属性进行隐藏操作
操作:在属性前加__前缀,就会实现对外隐藏的效果
这种操作没有严格意义上限制外部访问,只是一种语法意义上的变形 这种隐藏是对外不对内,即在类的内部还是可以访问到的,因为__开头的属性是在检查类结构体语法时统一发送变形的,编程时还没有
代码实现
class foo:
__x = 1 # 加了__后该属性被隐藏,类和对象就访问不到了
def __test(self): # 在内存中变成了_foo__test,即外部可以通过这种方式访问
print('from test')
def test2(self):
print(self.__x)
print(self.__test())
obj = foo()
obj.test2() # 类中的方法是可以访问到隐藏属性的,即可以曲线救国把需要的属性访问到
为什么要隐藏?
1.隐藏数据属性
class People:
def __init__(self, name):
self.name = name
def get_name(self): # 开一个接口用于访问隐藏属性
print(self.name)
即可用于实现更多的控制逻辑,比如管理员功能
2.隐藏方法属性
使得一些只给内部用的功能得以实现 使用方法:隐藏+类中开接口
继承
继承是一种新建类的思想,新建类时的继承,与我们生活中的''继承''大同小异,正如我们一般所认知的继承----> 孩子从父母那里继承,面向对象编程中也有所谓的''爸爸''和''儿女'',对应于''父类(基类)''和''子类(派生类)''
相对于现实生活中儿女从父母那里继承的是遗产、家业等,子类继承的是父类所有的方法、数据、属性,当你想用的时候,子类实例化的对象可以直接调用,是和在调用该对象所属的那一类的东西一样的,就像你爸爸不幸离开了你,他留给你的包含巨额的银行卡,你直接刷就行了,就像它是你自己的一样
代码示例
class Whuter:
school = 'whut'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
class Student(Whuter): # ()内即所声明的父类
def __init__(self, name, age, sex, id):
Whuter.__init__(self, name, age, sex) # 父类的方法可以在子类中通过 父类名.方法名调用
self.id = id
def choose_course(self):
print(f'{self.name}正在选课...')
class Teacher(Whuter):
def __init__(self, name, age, sex, salary, level):
# 指名道姓地跟父类Whuter去要__init__
Whuter.__init__(self, name, age, sex) # 调用的是一个方法,故需要传入self
self.salary = salary
self.level = level
def score(self):
print(f'{self.name}老师正在打分...')
xiaoming = Student('xiaoming', 20, 'male', '12345577')
xiaoming.choose_course()
print(xiaoming.school)
print(Sub1.__bases__) # 查看继承的父类
Python的多继承
python不同于大部分比较容易实现面向对象编程的语言只能单继承的特性,它是可以多继承的,即一个子类可以继承多个父类中的方法、数据、属性等,这么听起来是不是很好?就像你有两个爸爸,他们的遗产都给你了
哎~实则不然啊!天下哪有免费的午餐!
确实,python的多继承确实可以最大化的减少代码的冗余,但就拿你继承了两个爸爸给你遗产的遗产去想:你入党申请书上父亲的那一栏到底写谁的名字??这样的伦理问题对应到面向对象的代码中会造成的问题就是代码的可读性变差,业务逻辑混乱,你想想,如果一个子类的太爷爷爷爷爷爷出问题了,要你改bug,这就像你如果有多个爸爸,你怎么能通过家谱去准确溯源到你的太爷爷爷爷爷爷呢?
这也是面向对象编程在思维上的一种强调,对象和类之间的关系是一种谁''是''谁的关系,大家井水不犯河水,不是一家人不禁一家门,都是同样的道理,我们在做业务需求时应该让我们的代码要有清晰的逻辑!
多继承造成的菱形问题
如果A、B、C、D是具有上图的继承关系,实现代码如下:
class A:
def test(self):
print('from A')
class B(A):
def test(self):
print('from B')
class C(A):
def test(self):
print('from C')
class D(B, C):
pass
obj = D()
obj.test()
问:这个实例化D的对象obj调用的test,会是谁的?
不好说吧?咋办咧?
这就是菱形(钻石)问题, 即一个类的父类所继承的父类最终会汇聚到一个非object类(新式类)
要解决这个,我们就要讨论一下多继承的原理了
Python多继承原理
python的继承原理:
对于定义的每一个类,python都会计算出一个方法(C3算法)解析顺序(MRO)列表,该MRO列表就是一个简单的所有基类的线性顺序列表
类以及该类的对象访问属性都是参照该类的mro列表
mro列表遵从如下三条准则:
1.子类会先于父类被检查 2.多个父类会根据它们在列表中的顺序被检查 3.如果对下一个类存在两个合法的选择,选择第一个父类
那么我们print(D.mro()),就可以知道会调用谁的test()了~ 类D以及类D的对象访问属性都是参照该类的mro列表,会打印它找到的第一个父类的test
大家可以自行去尝试一下
总结:类相关的属性查找(类名.属性 该类的对象.属性)的顺序都是参照着该类的mro
大家也可以不用每次想知道某个属性查找的顺序都去找子类的mro,如果你已经将一个非菱形继承的继承关系像图一样画了出来,大家也可以直接判断,依据是:
对于Python3:查找顺序是广度优先的 对于Python2:查找顺序是深度优先的
总结:使用多继承需要规避几种问题: 1.继承结构尽量不要过于复杂,保证可读性 2.要满足继承的思维--->"是""包含"
多继承的正确打开方式:mixins机制
那Python也不可能傻,且作为一种较为新颖的语言,不可能搞出一种大家都觉得不好用的开发功能,还甚至在迭代吧?
嘿嘿!免费的午餐来了!它是mixins机制
mixins机制并非是什么高大上难以理解的东西,大家首先要记住,它的作用是一种类似于标记的作用,来帮助开发者区分出哪个是子类的父类,即既避免了代码的不清晰,又尽量减少了代码的冗余
多继承的正确打开方式:mixins机制
mixins机制核心:就是在多继承背景下尽可能多提升多继承代码的可读性,
让多继承满足人的思维习惯,是一种规范机制、标签,旨在解决菱形问题
通过mixins这种标识机制,即类所对应的父类可以认为是唯一的、使得代码与逻辑关系更加清晰,
而由mixins标识的类只是为其添砖加瓦
作用方法:类似于定义常量
mixins的责任应该单一,比如对于如下的飞行器类,其主要所具有的功能应为飞行相关,基于民用飞机和直升飞机这两类,可以将其理解为''功能类''
class Vehicle:
pass
class FlyableMixin: # 在一些不需要定义成一类(即不必成为某个类的父类的类)后加Mixin,即表示了该类是作为某一功能来由其他类继承(包含)的
# 标识该类是用于混入功能的
def fly(self):
pass
class CivilAircraft(FlyableMixin, Vehicle):
pass
class Helicopter(FlyableMixin, Vehicle):
pass
class Car(Vehicle):
pass
多态
多态实际上就是把父类和子类的关系展开来讲,即一个孩子不能有多个爸爸,但一个爸爸可以有多个孩子呀!
多态:指的是同一种事物有多种形态,即同一父类可以有多个子类
比如动物这个类中可以有人、狗、猫等多种子类,它们在形态上必然是有所不同的,多态在概念上与继承类似,是继承概念的衍生物 多态性指的是可以在不考虑对象具体类型的情况下而直接使用对象,这种概念的由来是继承同一父类的子类有一些功能、数据是相同的 通过继承,可以直接在实例化后使用父类的属性,以此可以方便使用者的使用
代码实现:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f'{self.name}正在发出....')
class People(Animal):
def speak(self):
super().speak()
print('嘤嘤嘤的声音...')
class Dog(Animal):
def speak(self):
super().speak()
print('汪汪汪的声音...')
class Pig(Animal):
def speak(self):
super().speak()
print('哼哼哼的声音...')
xiaoming = People('xiaoming')
xiaoming.speak()
多态思想的体现:鸭子类型
鸭子类型的原始概念是:如果一个生物它像鸭子一样走路,发出像鸭子一样的叫声,且在河里游泳,那就认为它是鸭子 虽然你可能觉得这毫无逻辑....但在面向对象中是有说法的...
鸭子类型在面向对象中,即不需要让几个类去继承同一个父类以达到一种谁"是"谁的关系,只需要将这几个类写的比较像,达到可以统一使用的标准,(比如几个类都具有同样的功能)就也可以认为是一种多态 概率举例:linux中,认为一切皆是文件,对于文件操作,无外乎就是‘读’和‘写’
代码实现
class Cpu:
def read(self):
print('cpu read')
def write(self):
print('cpu write')
class Mem:
def read(self):
print('memory read')
def write(self):
print('memory write')
class Txt:
def read(self):
print('txt read')
def write(self):
print('txt write')
试一试上述代码,你会发现,调用起来就好像上述几个类有同一个父类一样!
类的装饰器
property
重温装饰器的概念:装饰器是在不修改被装饰对象源代码以及调用方式的情况下为被装饰对象加上新的功能的可调用对象
对于property: property是用来绑定给对象的方法伪造成一个数据属性,是一种封装思想 可以使得用户使用的更加便捷
代码示例
方法一:
class People:
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
def set_name(self, new):
if type(new) is not str:
print('必须传入字符串类型')
return
self.__name = val
def del_name(self):
print('不让删除')
name = property(get_name, set_name, del_name)
方法二:
class People:
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self, new):
if type(new) is not str:
print('必须传入字符串类型')
return
self.__name = val
@name.deleter
def name(self):
print('不让删除')
使用:
xiaoming = People('xiaoming') # 实例化对象
print(xiaoming.name) # 调用了get_name方法
xiaoming.name = 18 # 调用了set_name方法
del xiaoming.name # 调用了del_name方法
对比逻辑清晰性,推荐使用方法二哦~
抽象基类
直接上代码,这是一种强制措施,即可以规定一些父类有的方法子类必须要有,否则就会报错
(metaclass=abc.ABCMeta)规定了基类,@abc.abstractmethod规定了子类中必须出现的父类的方法
记得要引入模块abc哦~
import abc
class Animal(metaclass=abc.ABCMeta): # 规定基类后,基类就不能再实例化了
def __init__(self, name):
self.name = name
@abc.abstractmethod # 规范了子类中必须有父类的方法,是一种强制措施
def speak(self):
print(f'{self.name}正在发出....')
class People(Animal):
def speak(self): # 在规范后,如果子类没有基类强制要有的方法,就会报错
super().speak()
print('嘤嘤嘤的声音...')
class Dog(Animal):
def speak(self):
super().speak()
print('汪汪汪的声音...')
class Pig(Animal):
def speak(self):
super().speak()
print('哼哼哼的声音...')
xiaoming = People('xiaoming')
xiaoming.speak()
绑定方法(类)与非绑定方法 -->classmethod与staticmethod
类的绑定方法与classmethod
绑定方法:特殊之处在于将调用者本身作为第一个参数自动传入
1.绑定给对象的方法:调用者是对象,自动传入的是对象 2.绑定给类的方法:调用者是类,自动传入的是类 使用@classmethod装饰器
@ classmethod 是针对与类的绑定方法,加上这个装饰器,即证明被装饰的方法传入的类,调用该方法的也应是类
ip = '2.2.2.2'
port = 8090
class Mysql:
def __init__(self, ip, port):
self.ip = ip
self.port = port
def func(self):
print(f'{self.ip},{self.port}')
@classmethod # 绑定给类的方法,即传入的参数为类
def create_obj(cls): # 注意如果传入的是类的话,一般将传入的参数命名为cls,
# 就像传入的如果是对象的话,参数一般命名为self
return cls(ip, port)
obj1= Mysql('1.1.1.1', 8080)
obj2 = Mysql.create_obj()
obj2.func()
非绑定方法与staticmethod
非绑定方法 ----->静态方法
即不需要对象传进来也不需要类传进来的方法,可以直接当函数写
调用者可以是类、对象,但没有了自动传参的效果
class Mysql:
def __init__(self, ip, port):
self.ip = ip
self.port = port
self.id = self.create_id()
@staticmethod # 将下述方法装饰成一个静态方法
def create_id():
import uuid
return uuid.uuid4()
obj1= Mysql('1.1.1.1', 8080)
print(obj1.create_id())
以上内容若有不正之处,恳请您不吝指正!
转载自:https://juejin.cn/post/7145836229014061087