Mixins机制 派生与方法重用 组合 多态 绑定方法与非绑定方法 常用内置函数
1 Mixins机制
Mixins机制的核心就是在多继承的基础上尽可能地提升多继承代码的可读性。
Mixins机制是一种类的命名规范,具体指的是子类混合(Mix in)多个类的功能,而这些功能类采用统一的命名规范(例如后缀Mixin),以此标识这些类只是用来提供功能的,与子类并不存在从属"is-a"关系,但本质上Mixins机制仍属于继承,同样遵守”is-a”关系。
Mixin类的命名方式一般以 Mixin, able, ible 为后缀。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
class StudyMixn:
def study(self):
print(f'学生{self.name}正在学习。')
class Student(StudyMixin, Person):
def __init__(self, name, age, num):
super().__init__(name, age)
self.num = num
stu = Student('谢大脚', 25, 1200001)
stu.study() # 学生谢大脚正在学习。
Mixins机制要点
- Mixin类在本质上表示某项功能,而不是某种事物;
- 子类只能继承一个标识其归属含义的父类,但可以继承多个功能Mixin类,一般每个Mixin类只提供一类功能,多个功能就需要写多个Mixin类;
- Mixin类不依赖于子类;
- 子类即便没有继承某个Mixin类,也可以正常工作,只是缺少某个功能。
2 派生与方法重用
一个类中如何调用其它类的方法?
- 不依赖于继承,指名道姓地调用某一个类的函数。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 没有继承关系
class Student:
def __init__(self, name, age, num):
Person.__init__(self, name, age)
self.num = num
stu = Student('谢大脚', 25, 1200001)
- 严格依赖于继承关系,使用super()。
super(type[, object-or-type])
type – 类
object-or-type – 类,一般是 self
python2中super()只能用于新式类。
python3中super()中无需参数。
super()作为一个特殊的对象,会参照发起属性/方法查找动作的类的MRO列表去super()当前所在类的后面的类中查找属性/方法。
super()会查找所有的父类,以及父类的父类,直到找到所需的属性/方法为止,否则报错。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, name, age, num):
# super(Student, self).__init__(name, age) # python2
super().__init__(name, age) # python3
self.num = num
stu = Student('谢大脚', 25, 1200001)
print(stu.__dict__)
注意,super()对象会参照发起属性/方法查找动作的类的MRO列表查找。
class A:
def test(self):
print('From A')
print(super()) # <super: <class 'A'>, <C object>>
# 意思是C类的对象发起了查找动作,下一个查找的是A类
print(isinstance(super(), C)) # False
# super()是一个特殊的对象
super().test()
class D:
def test(self):
print('From D')
class B(D):
# super() 报错 RuntimeError: super(): no arguments
# 使用super()时自动传入至少一个参数(类),
# 因此无参数时必须放入类的绑定方法中。
pass
class C(A, B):
pass
c = C()
c.test()
# From A
# From D
上面的例子中,查找的是方法test(),发起方法查找动作的是对象c,是类C的实例,发起查找动作的类是C,因此类A中的super()对象会按照类C的MRO列表依次查找。
<class '__main__.C'>,
<class '__main__.A'>,
<class '__main__.B'>,
<class '__main__.D'>,
<class 'object'>
super()对象当前处于类A中,因此会按照类C的MRO列表去依次对A后面的类进行查找,即类B,D和object。
在
3 组合
一个对象拥有一个属性,该属性是另一个对象。
组合与继承都是用来解决代码的重用性问题的。
组合与继承的区别是:
继承表达的是一种“是”的关系,比如老师是人、学生是人,当类与类之间有很多相同的之处,应该使用继承;
而组合表达的是一种“有”的关系,比如老师有生日,老师有多门课程,当类与类之间有显著不同,并且较小的类是较大的类所需要的组件时,应该使用组合。
class Birthday:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def print_birthday(self):
print('出生年月日为:%s-%s-%s' % (self.year, self.month, self.day))
class People:
school = 'Old boy'
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
# Teacher类基于继承来重用People的代码,基于组合来重用Date类和Course类的代码
class Teacher(People): # 老师是人
def __init__(self, name, sex, age, year, month, day, title):
super().__init__(name, sex, age)
self.birthday = Birthday(year, month, day)
self.course_list = []
self.title = title
python = Course('python', '3mons', 3000.0)
linux = Course('linux', '5mons', 5000.0)
tch_wang = Teacher('王长贵', '男', 28, 1993, 3, 3, '金牌导师')
# 王长贵老师有两门课程
tch_wang.course_list.append(python)
tch_wang.course_list.append(linux)
# 重用Date类的功能
tch_wang.birthday.print_birthday()
# 重用Course类的功能
for obj in tch_wang.course_list:
obj.print_course()
4 多态与鸭子类型
4.1 什么是多态
多态指的是一类事物有多种形态。
4.2 为什么需要多态
利用多态性,可以在不用考虑对象具体类型的情况下而直接使用对象的共有方法,这就需要在设计时,把对象的使用方法进行统一。
class Animal:
# 父类统一了所有子类的共同方法。
# 不同种类的对象拥有共同的方法名
def eat(self):
pass
class Cat(Animal):
def eat(self):
print('猫吃鱼。')
class Dog(Animal):
def eat(self):
print('狗啃骨头。')
obj1 = Cat()
obj2 = Dog()
obj1.eat()
obj2.eat()
# 定义一个统一的接口,接收传入的动物对象。
def animal_eat(anim_obj):
anim_obj.eat()
animal_eat(obj1)
通过子类对象调用某个方法,且这个方法存在于父类中:
- 若这个方法被子类重写了,实际上调用的是子类已经重写的方法;
- 若这个方法未被子类重写,则调用的是父类中的方法。
4.3 鸭子类型
多态中父类的作用是为子类统一标准,但即便没有使用继承,我们也可以人为规定特定的类必须拥有某些方法,这就是Python语言推崇的鸭子类型。
“如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”。
比起继承的方式,鸭子类型在某种程度上实现了程序的低耦合度。
# Txt类有两个与文件类同名的方法,即read和write
class Txt:
def read(self):
pass
def write(self):
pass
# Disk类也有两个与文件类同名的方法,即read和write
class Disk:
def read(self):
pass
def write(self):
pass
上面的例子中Txt类和Disk类看起来都像文件类,因而就可以当文件一样去使用,然而它们与文件类并没有直接的关系。
4.4 抽象类
多态性的本质在于不同的类中定义有相同的方法名,这样我们就可以不考虑类而统一用一种方式去使用对象,可以通过在父类引入抽象类的概念来硬性限制子类必须有某些方法名。
抽象类中的抽象方法不负责实现具体的功能,只负责规定子类中必须有某些方法(功能)。
import abc
class A(metaclass=abc.ABCMeta):
@abc.abstractmethod
def test(self):
pass
# 一般是pass,因为子类必须重写父类的抽象方法,否则在子类实例化时报错。
# 非抽象方法,子类可以不重写。
def test1(self):
print(123)
class B(A):
def test(self):
print(123)
b = B()
# 如果子类没有重写父类的抽象方法,此处会报错。
# TypeError: Can't instantiate abstract class B with abstract methods test
b.test()
# a = A()
# 报错,拥有抽象方法的类是抽象类,抽象类不能实例化。
# TypeError: Can't instantiate abstract class A with abstract methods test
4.5 绑定方法与非绑定方法
类中定义的函数分为两大类:绑定方法和非绑定方法,其中绑定方法又分为绑定到对象的对象方法和绑定到类的类方法。
在类中正常定义的函数默认是绑定到对象的方法,而为某个方法加上装饰器@classmethod后,该函数就绑定给类了。
类方法通常用来在方法__init__的基础上提供额外的初始化实例的方式。
4.5.1 绑定方法
绑定给对象的方法,调用者是对象,自动传入的第一个实参是对象。
绑定给类的方法,调用者是类,自动传入的第一个实参是类。
装饰器@classmethod将方法绑定给类。
setting.py
IP = '127.0.0.1'
PORT = 3306
绑定给对象的方法
import setting
class Database:
# 绑定给对象的方法
def __init__(self, ip, port):
self.ip = ip
self.port = port
db_obj = Database(setting.IP, setting.PORT)
绑定给类的方法
class Database:
def __init__(self, ip, port):
self.ip = ip
self.port = port
@classmethod
def get_obj_from_config(cls):
return cls(setting.IP, setting.PORT)
db_obj = Database.get_obj_from_config()
类方法一般用于以一种新的方式造对象。
4.5.2 非绑定方法
非绑定方法是既不需要对象self,也不需要类cls的方法。
使用装饰器@staticmethod将一个方法变成静态方法(非绑定方法)。
类和对象都可以调用,不会自动传参。
class A:
def obj_method(self):
print(self)
pass
@classmethod
def cls_method(cls):
print(cls)
pass
@staticmethod
def static_method():
pass
a = A()
print(a.obj_method)
# <bound method A.obj_method of <__main__.A object at 0x地址1>>
a.obj_method()
# <__main__.A object at 0x地址1>
print(A.cls_method)
# <bound method A.cls_method of <class '__main__.A'>>
A.cls_method()
# <class '__main__.A'>
print(a.static_method)
# <function A.static_method at 0x地址2>
print(A.static_method)
# <function A.static_method at 0x地址2>
5 常用内置函数
- abs()
abs(x) 返回数字x的绝对值,其中 x 是数值表达式,可以是整数,浮点数,复数,如果参数是一个复数,则返回复数的模。
print(abs(3 + 4j)) # 5.0
- all() 是否都为真
all(iterable) 用于判断传入的可迭代参数 iterable 中的所有元素是否都为 True,如果是返回 True,否则返回 False。
空元组、空列表返回值为True。
print(all([1, 2, ''])) # False
print(all([])) # True
print(all(())) # True
- any() 是否都为假
any(iterable) 用于判断传入的可迭代参数 iterable 中的所有元素是否都为 False,如果是返回 False,否则返回 True。
print(any([])) # False
print(any(())) # False
- 进制转换
bin() — 二进制
oct() — 八进制
hex() — 十六进制 - callable()
用于判断一个对象是否是可调用的。
函数,类等 - ASCII码
chr()
chr() 将一个十进制整数转化为ASCII码表中对应的字符。
ord()
ord() 将一个字符转化为ASCII码表中对应的十进制整数。 - dir()
无参数时,返回一个列表,包含了当前范围内的变量、方法和定义的类型;
带参数时,返回一个列表,包含了参数包含的属性、方法。 - divmod()
divmod() 函数接收两个数字类型(非复数)参数 a 和 b,返回一个包含商和余数的元组 (a // b, a % b)。
item_sum = 1000
maxperpage = 30
page_qty, last_page_item_qty = divmod(item_sum, maxperpage)
print(page_qty, last_page_item_qty) # 33 10
- enumerate()
用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
lst = ['zero', 'one', 'two']
for i, elem in enumerate(lst):
print(i, lst[i], elem)
# 0 zero zero
# 1 one one
# 2 two two
- eval()
执行一个字符串表达式,并返回表达式的值。
d = eval('{"a": 97, "A": 65}')
print(d['a']) # 97
- frozenset()
利用可迭代的对象生成一个冻结的集合,冻结后集合不能再添加或删除任何元素。 - isinstance()
判断一个对象是否是一个是某个类或其子类的实例。
如果要判断两个类型是否相同推荐使用 isinstance()。
isinstance(object, classinfo)
object — 实例对象。
classinfo — 可以是直接或间接类名、基本类型或者由它们组成的元组。
print(isinstance('a', (int, float, str))) # True
isinstance() 与 type() 区别:
type() 不会认为子类是一种父类类型,不考虑继承关系。
isinstance() 会认为子类是一种父类类型,考虑继承关系。
class A:
pass
class B(A):
pass
print(isinstance(A(), A)) # True
print(type(A()) is A) # True
print(isinstance(B(), A)) # True
print(type(B()) is A) # False
- zip() 拉链函数
用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回用于生成这些元组的迭代器。
str1 = 'abcde'
lst1 = [1, 2, 3, 4, 5, 6, 7]
res = zip(str1, lst1, [1, 2, 3])
print(list(res)) # [('a', 1, 1), ('b', 2, 2), ('c', 3, 3)]
- 函数__import__()
用于动态加载类和函数 。
m = 'time'
t = __import__(m)
print(t.time())