从小白到小黑 python学习之旅 日常总结 30(Mixins机制 派生 组合 多态性与鸭子类型 绑定方法与非绑定方法)
Mixins机制 # 多继承的正确打开方式:
mixins机制核心:
就是在多继承背景下尽可能地提升多继承的可读性
让多继承满足人的思维习惯=》什么"是"什么
mixins机制 应该来说就是一种命名规范和常量全部大写一样 是约定俗成的
简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的
所以 Mixins机制本质仍是多继承 同样要遵守父类的关系 只不过是 我们心中要把他想成某种混入的功能 要和父类有区分
class Vehicle: pass class FlyableMixin: #要把Mixin想象成能的功能 本质还是父类 但我们要把他当功能去看 def fly(self): pass class CivilAircraft(FlyableMixin,Vehicle): # 民航飞机 #通常Mixin结果的类放在左边 pass class Helicopter(FlyableMixin,Vehicle): # 直升飞机 #通常Mixin结果的类放在左边 pass class Car(Vehicle): # 汽车 pass
使用Mixin类实现多重继承要非常小心
首先它必须表示某一种功能,而不是某个物品类(是具有函数属性和数据属性的父类),python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀
其次它必须责任单一,如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin,但是只能继承一个标识其归属含义的父类
然后,它不依赖于子类的实现,就是说其他子类继承他也能实现,不是只有这个子类继承了它,它才能实现,不依赖于子类的实现
最后,子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。(比如飞机照样可以载客,就是不能飞了)
在子类派生的新方法中如何重用父类的功能的两种方式
方式一:指名道姓调用某一个类下的函数=》不依赖于继承关系
class OldboyPeople: def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex def f1(self): print('%s say hello' %self.name) class Teacher(OldboyPeople): def __init__(self,name,age,sex,level,salary): OldboyPeople.__init__(self,name,age,sex) self.level = level self.salary=salary tea_obj=Teacher('egon',18,'male',10,3000) print(tea_obj.__dict__) #{'name': 'egon', 'age': 18, 'sex': 'male', 'level': 10, 'salary': 3000}
super()#只能用在新式
#推荐 方式二:super()调用父类提供给自己的方法=》严格依赖继承关系
调用super()会得到一个特殊的对象,该对象会参照发起属性查找的那个类的mro中的 调用super()的类 的后一个类里找属性 #重点
class OldboyPeople: def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex def f1(self): print('%s say hello' %self.name) class Teacher(OldboyPeople): def __init__(self,name,age,sex,level,salary): # super(Teacher,self).__init__(name,age,sex) #python 2的用法 super().__init__(name,age,sex) # 调用的是方法,自动传入对象 #python 3的用法 self.level = level self.salary=salary print(Teacher.mro()) #[<class '__main__.Teacher'>, <class '__main__.OldboyPeople'>, <class 'object'>] tea_obj=Teacher('egon',18,'male',10,3000) print(tea_obj.__dict__) #{'name': 'egon', 'age': 18, 'sex': 'male', 'level': 10, 'salary': 3000}
案例
#调用super()会得到一个特殊的对象,该对象会参照发起属性查找的那个类的mro中的 调用super()的类 的后一个类里找属性 #重点 class A: # 调用super()的类 def test(self): super().test()
#发起属性查找的那个类的mro[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>] #调用super()的类<class '__main__.A'> 的后一个 <class '__main__.B'> class B: #调用super()的类 的后一个类 def test(self): print('from B')
class C(A,B): pass print(C.mro()) # 发起属性查找的那个类的mro #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>] obj=C() # 发起属性查找的那个类 #from B obj.test()
组合
在一个类中以另外一个类的对象作为数据属性,称为类的组合。 #之前类的使用 就是用的组合
class School: school_name = 'OLDBOY' def __init__(self, nickname, addr): self.nickname = nickname #别名 self.addr = addr #地址 self.classes = [] #班级 def related_class(self, class_obj): # self.classes.append(班级名字) # self.classes.append(class_name) self.classes.append(class_obj) def tell_class(self): # 改 # 打印的班级的名字 print(self.nickname.center(60,'=')) # 打印班级开设的课程信息 for class_obj in self.classes: class_obj.tell_course() # 一:学校 # 创建校区 造对象 school_obj1=School('老男孩魔都校区','上海') school_obj2=School('老男孩帝都校区','北京') class Class: def __init__(self, name): self.name = name self.course = None def related_course(self, course_obj): # self.course = course_name self.course = course_obj def tell_course(self): print('%s' % self.name,end=" ") self.course.tell_info() # 打印课程的详细信息 # 二:班级 # 创建班级 造对象 class_obj1 = Class('脱产14期') class_obj2 = Class('脱产15期') class_obj3 = Class('脱产29期') # 为学校开设班级 # 上海校区开了:脱产14期,上海校区开了脱产15期 school_obj1.related_class(class_obj1) # (组合的概念)在 学校的类 里 以班级类的对象 作为数据属性 school_obj1.related_class(class_obj2) # (组合的概念)在 学校的类 里 以班级类的对象 作为数据属性 # 北京校区开了:脱产29期 school_obj2.related_class(class_obj3) # (组合的概念)在 学校的类 里 以班级类的对象 作为数据属性 class Course: def __init__(self,name,period,price): self.name=name self.period=period self.price=price def tell_info(self): print('<课程名:%s 周期:%s 价钱:%s>' %(self.name,self.period,self.price)) # 三:课程 # 创建课程 course_obj1=Course('python全栈开发','6mons',20000) course_obj2=Course('linux运维','5mons',18000) # 为班级关联课程对象 class_obj1.related_course(course_obj1) # (组合的概念)在 班级的类 里 以课程类的对象 作为数据属性 class_obj2.related_course(course_obj2) # (组合的概念)在 班级的类 里 以课程类的对象 作为数据属性 class_obj3.related_course(course_obj1) # (组合的概念)在 班级的类 里 以课程类的对象 作为数据属性 school_obj1.tell_class() #==========================老男孩魔都校区=========================== #脱产14期 <课程名:python全栈开发 周期:6mons 价钱:20000> #脱产15期 <课程名:linux运维 周期:5mons 价钱:18000> school_obj2.tell_class() #==========================老男孩帝都校区=========================== #脱产29期 <课程名:python全栈开发 周期:6mons 价钱:20000>
多态性与鸭子类型
多态指的是一类事物有多种形态
#比如动物有多种形态:人、狗、猪 #以下用程序表达是 子类可以代表着父类的各个状态 但多态性不仅仅局限于父类子类中 它也算是一种编程思想 class Animal: pass class People(Animal): pass class Dog(Animal): pass class Pig(Animal): pass
为何要有多态
多态会带来的特性:多态性
多态性指的是可以在不考虑对象具体类型的情况下而直接使用对象
#以下程序的解释 就是子类对象不用考虑子类的具体有什么功能 看到父类有此功能 就可以直接调用此功能 但多态性不仅仅局限于父类子类中 它也算是一种编程思想 class Animal: # 统一所有子类的方法 def say(self): print('动物基本的发声频率。。。',end=' ') class People(Animal): def say(self): super().say() print('嘤嘤嘤嘤嘤嘤嘤') class Dog(Animal): def say(self): super().say() print('汪汪汪') class Pig(Animal): def say(self): super().say() print('哼哼哼') obj1=People() obj2=Dog() obj3=Pig() #看到父类有say 就直接调say obj1.say() # 动物基本的发声频率。。。 嘤嘤嘤嘤嘤嘤嘤 obj2.say() # 动物基本的发声频率。。。 汪汪汪 obj3.say() # 动物基本的发声频率。。。 哼哼哼 # 定义统一的接口,接收传入的动物对象 def animal_say(animal): animal.say() # 因为动物(父类)有say 所以就放心传入他的子类就行了 animal_say(obj1) # 动物基本的发声频率。。。 嘤嘤嘤嘤嘤嘤嘤 animal_say(obj2) # 动物基本的发声频率。。。 汪汪汪 animal_say(obj3) # 动物基本的发声频率。。。 哼哼哼
Python中一切皆对象,本身就支持多态性
举个栗子 len的多态性
# 我们可以在不考虑三者类型的情况下直接使用统计三个对象的长度 s.__len__() l.__len__() t.__len__() # Python内置了一个统一的接口 len(s) len(l) len(t)
多态性的好处在于增强了程序的灵活性和可扩展性
以上我们得知,多态性的本质在于不同的类中定义有相同的方法名,这样我们就可以不考虑类而统一用一种方式去使用对象,可以通过在父类引入抽象类的概念来硬性限制子类必须有某些方法名
import abc # 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化 class Animal(metaclass=abc.ABCMeta): @abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法 def talk(self): # 抽象方法中无需实现具体的功能 pass class Cat(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准 def talk(self): pass cat=Cat() # 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化
鸭子类型
其实我们完全可以不依赖于继承,只需要制造出外观和行为相同对象,同样可以实现不考虑对象类型而使用对象,
这正是Python崇尚的“鸭子类型”(duck typing):
如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”。
比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度
#就是看以下功能都能读和写 像是一个文件 那我们就把它当做文件去看 调用时也可以把它当做文件 不用考虑 就会去调读写的功能 其实它们并没有直接的关系 class Cpu: def read(self): ... def write(self): ... class Mem: def read(self): ... def write(self): ... class Txt: def read(self): ... def write(self): ... obj1=Cpu() obj2=Mem() obj3=Txt() #要假装以上类有一个共同的父类:文件 功能:读写 !!!但是不要写出来.... obj1.read() obj1.write() obj2.read() obj2.write() obj3.read() obj3.write()
绑定方法与非绑定方法
类中定义的函数分为两大类:绑定方法和非绑定方法
绑定方法:特殊之处在于将调用者本身当做第一个参数自动传入
其中绑定方法又分为绑定到对象的对象方法和绑定到类的类方法。
1、绑定给对象的方法:调用者是对象,自动传入的是对象
2、绑定给类的方法:调用者类,自动传入的是类
clsaamathod
在类中正常定义的函数默认是绑定到对象的,而为某个函数加上装饰器@classmethod后,该函数就绑定到了类。
class Mysql: def __init__(self,ip,port): self.ip=ip self.port=port def func(self): print('%s:%s' %(self.ip,self.port)) @classmethod # 将下面的函数装饰成绑定给类的方法 def from_conf(cls): # cls 是约定俗成 绑定给类的 传的参数 return print(cls) obj2=Mysql.from_conf() #<class '__main__.Mysql'>
绑定到类的方法就是专门给类用的,但其实对象也可以调用,只不过自动传入的第一个参数仍然是类,也就是说这种调用是没有意义的,并且容易引起混淆,这也是Python的对象系统与其他面向对象语言对象系统的区别之一,比如Smalltalk和Ruby中,绑定到类的方法与绑定到对象的方法是严格区分开的
非绑定方法 静态方法
没有绑定给任何人:调用者可以是类、对象,没有自动传参的效果
staticmethod
为类中某个函数加上装饰器@staticmethod后,该函数就变成了非绑定方法,也称为静态方法。该方法不与类或对象绑定,类与对象都可以来调用它,但它就是一个普通函数而已,因而没有自动传值那么一说
import uuid class MySQL: def __init__(self,host,port): self.id=self.create_id() self.host=host self.port=port @staticmethod def create_id(): return uuid.uuid1() conn=MySQL('127.0.0.1',3306) # 类或对象来调用create_id发现都是普通函数,而非绑定到谁的方法print(MySQL.create_id()) 1cd3fb87-7c26-11ea-8693-0c84dc453882 print(conn.create_id()) 1cd3fb88-7c26-11ea-83fc-0c84dc453882
总结绑定方法与非绑定方法的使用:若类中需要一个功能,该功能的实现代码中需要引用对象则将其定义成对象方法、需要引用类则将其定义成类方法、无需引用类或对象则将其定义成静态方法。

浙公网安备 33010602011771号