Python中的类、实例以及方法,MRO继承解析顺序以及Mixin类

1. 什么是类和实例

要求: 并说明类与实例之间的关系

是Python通过定义属性和方法,对待求解问题的概括抽象。其中属性用于描述该类的特性,方法用于描述该类支持的对属性的操作集合。在Python中,类是一种用户自定义数据类型,通过类,可以实现对现实问题的抽象和概括以及归纳。

实例是类的具体实现,是对类的填充和丰富,是类的具象化表现。

下面用实例代码说明类以及类的实例对象之间的关系。

具体如下面的代码,定义了Dog类,同时创建了该类的实例对象——名叫Miracle的狗狗。

class Dog:
	def __init__(self, name, age, gender):
    	self.name = name
	    self.age = age
    	self.gender = gender

	def __repr__(self):
    	return 'Dog Name: {}\nDog Age: {}\nDog Gender: {}'.format(
        	self.name,
	        self.age,
    	    self.gender
	    )


dog_miracle = Dog('Miracle', 5, 'Male')

print(dog_miracle)

上述代码中,class Dog就是类的定义语句,第15行代码创建了该类的实例对象——一个名叫Miracle、年龄为5岁的雄性狗狗。

2. 类的实例方法、类方法和静态方法

**要求:**举例说明如何定义类的实例方法、类方法和静态方法,并总结它们的应用场景。

类的实例方法就是在类中定义的普通函数,其第一个参数必须是该类的实例对象,在Python中通常用self表示这个参数,实例方法通常由该类的实例对象调用,调用过程中,会自动将该实例对象作为参数传递给self

类方法也是在类中定义的,但是不同于普通的实例方法,它的第一个参数使用该类自身作为参数,且该方法在定义的时候需要使用classmethod装饰器进行装饰。通常既可以由该类直接调用,也可以通过该类的实例对象调用,调用的时候,会自动将该类自身作为参数产地给类方法。

静态方法也是在类中定义的,这个方法在定义的时候需要使用staticmethod装饰器进行装饰,既可以通过该类的实例对象调用,也可以通过该类直接调用。在调用的过程中,并不会像实例方法和类方法那样隐式传递参数。

类的实例方法,通常用于操作实例对象的属性,所以其第一个参数通常为self;而类方法的第一个参数是该类自身cls,在实例方法中可以构建该类的实例对象,即通常用于工厂方法(工厂方法返回该类的实例对象)。

而静态方法在调用的时候,既不会被隐式传递self参数也不会被传递cls参数,该方法通常表示其只是适用于类本身,并不适合于该类创建的实例对象,所以静态方法通常用于该类中贝类调用的应用函数。

下面通过具体的代码说明上述三种方法的作用以及定义和调用方式。

下面的代码中定义了一个学生分数类StudentScore,在其中定义了一个类方法build_obj,除此之外,还定义了一个用于检查成绩合法性的静态方法valid_score。在build_obj调用valid_score检查输入的分数是否合法,如果合法,则在build_obj中生成该类的实例对象并返回该对象。如果检查的分数不合法,则抛出异常,build_obj函数会处理异常。具体代码如下所示:

"""
在类中分别实现普通实例方法、类方法以及静态方法
"""


class StudentScore:
	def __init__(self, name, course, score):
    	self.__name = name
    	self.__course = course
    	self.__score = score

	def __repr__(self):
    	return 'StudentScore({}\'s {} score is {})'.format(
        	self.__name,
        	self.__course,
        	self.__score
     	)

	def show_score(self):
    	print(self)

	@classmethod
	def build_obj(cls, csv_str):
    	try:
        	vld_csv_str = cls.valid_score(csv_str)
	    except:
    	    print('{} is invalid'.format(csv_str))
	    else:
    	    return cls(*vld_csv_str)

	@staticmethod
	def valid_score(csv_str):
    	nm, crs, scr = csv_str.split(',')
	    scr = int(scr)
    	if scr < 0 or scr > 100:
        	raise ValueError('The score {} is invalid'.format(scr))
	    else:
    	    return nm, crs, scr


if __name__ == '__main__':
	student_score_lst = [
	   	'张三,物理,80',
    	'李四,物理,70',
	    '王五,化学,90',
    	'赵六,化学,85',
	    'Error,英语,101',
    	'Exception,英语,-5'
	]

	result_lst = []
	for rec in student_score_lst:
    	result = StudentScore.build_obj(rec)
	    if result is not None:
    	    result_lst.append(result)

	print(result_lst)

上述代码的输出结果如下所示:

Error,英语,101 is invalid
Exception,英语,-5 is invalid
[StudentScore(张三's 物理 score is 80), StudentScore(李四's 物理 score is 70), StudentScore(王五's 化学 score is 90), StudentScore(赵六's 化学 score is 85)]

Process finished with exit code 0

上述的结果列表中并不包含无效的成绩结果。实现了该类的作用。

3. MRO是什么,描述其查找顺序

所谓MRO(Method Resolution Order),是指在多重继承环境中,属性或者方法的解析顺序。当一个类继承了多个类的时候,此时类中方法以及属性的解析顺序就遵照MRO规则。MRO采用了C3 Linearization算法构建父类列表,Python-2.3开始引入这个方法。关于该方法的概括如下:

Wikipedia does a great job explaining the algorithm. It can be reduced to the following steps:

  1. Linearization (i.e. resolution order) is a class itself and a merge of the linearizations of its parents and a list of the parents itself
  2. Linearization of the class with no parents equals to the class itself.
  3. Merge process is done by selecting the first head of the lists which does not appear in the tail of any of the lists. Where head is the first element of the list, and tail is all but first elements of the list. The heads are repeatedly selected and added to the resulting MRO until all the lists are exhausted.
  4. If a head cannot be selected while not all the lists are exhausted merge is impossible to compute due to inconsistent orderings of dependencies in the inheritance hierarchy and no linearization of the original class exists.

以下面的复杂继承关系图谱作为示例,说明该算法是如何构建出MRO继承顺序的。
在这里插入图片描述以K1这个类的继承关系作为演示,此时K1的MRO继承关系构建过程如下所示:

// first, find the linearizations of K1's parents, L(A), L(B), and L(C),
// and merge them with the parent list [A, B, C]
L(K1) := [K1] + merge(L(A), L(B), L(C), [A, B, C])
// class A is a good candidate for the first merge step, because it only
// appears as the head of the first and last lists
    = [K1] + merge([A, O], [B, O], [C, O], [A, B, C])
// class O is not a good candidate for the next merge step, because it also
// appears in the tails of list 2 and 3; but class B is a good candidate
    = [K1, A] + merge([O], [B, O], [C, O], [B, C])
// class C is a good candidate; class O still appears in the tail of list 3
    = [K1, A, B] + merge([O], [O], [C, O], [C])
// finally, class O is a valid candidate, which also exhausts all remaining lists
    = [K1, A, B, C] + merge([O], [O], [O])
    = [K1, A, B, C, O]
  1. 首先,按照线性关系查找K1的父类,找到L(A), L(B), L(C)这三个类,将这三个类合并为父类列表:[A, B, C]。此时K1的继承关系为:L(K1) := [K1] + merge(L(A), L(B), L(C), [A, B, C])
  2. 此时,类A是第一步合并的比较好的候选类,因为它是第一个和最后一个列表的开头,此时的继承关系为:L(K1) := [K1] + merge([A, O], [B, O], [C, O], [A, B, C])
  3. 此时类O对于下一次合并来说,并不是一个很好的候选类,因为它出现在第2个和第3个列表的末尾。不过类B倒是一个不错的候选项,此时的继承关系为:L(K1) := [K1, A] + merge([O], [B, O], [C, O], [B, C])
  4. 此时,类C是一个不错的候选项,而类O仍然出现在第3个列表的末尾,所以此时的继承关系为:L(K1) := [K1, A, B] + merge([O], [O], [C, O], [C])
  5. 最后,类O是一个有效的候选项,因为此时已经遍历完成了父类列表,此时的继承关系为:L(K1) := [K1, A, B, C] + merge([O], [O], [O])。由于这一步的合并操作中,只剩下类O,所以直接将类O加入到父类列表中即可。得到K1类最终的父类列表:L(K1) := [K1, A, B, C, O]

所以,上图中,最终K1类的MRO解析顺序为:K1 -> A -> B -> C -> O

下面通过代码,模拟上面的继承关系,具体如下所示:

定义三个类:A,B,C,使用这三个类作为父类,派生出类K1。具体代码如下所示:

"""
定义三个类:A,B,C,使用这三个类作为父类,派生出类K1。
"""


class A:
	a = 1
	def __init__(self):
    	self.x = 1

	def __repr__(self):
    	return 'in class A'


class B:
	b = 2
	def __init__(self):
    	self.y = 2

	def __repr__(self):
    	return 'in class B'


class C:
	c = 3
	def __init__(self):
    	self.z = 3

	def __repr__(self):
    	return 'in class C'


class K1(A, B, C):       # (<class '__main__.K1'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>)
# class K1(A, C, B):       # [<class '__main__.K1'>, <class '__main__.A'>, <class '__main__.C'>, <class '__main__.B'>, <class 'object'>]
# class K1(C, B, A):       # (<class '__main__.K1'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
	pass

k1 = K1()
print(k1.__dict__)
print(K1.__mro__)
print(k1.x)
# print(k1.y)       # K1类继承了A的方法,所以A中的__init__()方法覆盖了此后父类中的__init__()方法,就会造成这个类中只有x属性,而没有其他属性
# print(k1.z)       # K1类继承了A的方法,所以A中的__init__()方法覆盖了此后父类中的__init__()方法,就会造成这个类中只有x属性,而没有其他属性
print(k1)
print(K1.__dict__)
print(k1.a, k1.b, k1.c)     # 父类的类属性可以被子类继承,但是父类中定义的实例属性是无法全部被子类继承的,只会继承第一实现了__init__()方法的父类中的实例属性。
# print(K1.mro())    # 与K1.__mro__的输出结果相同
# print(k1.__mro__)  # 实例对象中没有这个属性
# print(k1.mro())    # 实例对象中同样没有这个方法

第33-35行中,证实了父类列表中的出现顺序,会决定MRO中的解析顺序,MRO的父类解析顺序与K1类的父类继承列表中指定的顺序是保持一致的。

上述代码的执行结果如下所示:

{'x': 1}
(<class '__main__.K1'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>)
1
in class A
{'__module__': '__main__', '__doc__': None}
1 2 3

Process finished with exit code 0

多重继承会导致事情变得很复杂,很难弄清确切的继承路径,所以在实际开发过程中,除非必要,否则应该尽量避免使用过多的多重继承。

4. Mixin是什么,描述其应用场景

Mixin类与装饰器技术通常是以非侵入目标类定义的方式,增强目标类的功能。目标类通过继承Mixin类,实现自我功能的丰富,但是又无需对目标类的定义做出较大修改。

由于Mixin类并没有定义新的类型,只是用于目标类的父类,完善目标类的功能特性,所以通常并不会将Mixin类实例化,所以一般不会在Mixin类中定义__init__()方法。Mixin类通常也不单独使用,一般是作为目标类的父类而被使用。所以Mixin类的本质是通过多重继承将Mixin类中定义的功能注入到目标类中。Mixin类通常放在目标类继承列表中的第一个,以便在解析目标类的实例对象属性或者方法的时候,可以尽早得到相应的属性与方法。

接下来通过代码演示Mixin类的使用方式。

定义一个Person类,只提供一个名字属性,作为一个基本的数据类型抽象;另外定义一个Employee类,继承自Person类,其中除了包含名字属性之外,还定义了技能、亲属依赖关系等属性。另外,想要将Employee类的实例对象可以转化为字典打印,也需要使其能够支持转换为JSON格式打印输出。这两个功能明显不是Employee类逻辑上应该支持的功能,作为附加属性,可以考虑使用装饰器实现,但是此处使用Mixin类实现。

定义一个MixinDict类将Employee类的实例对象格式化为字典的形式打印;再定义一个MixinJson类将Employee类的实例对象转换为JSON格式输出。

具体代码如下所示:

"""
Mixin类的示例
"""

import json


class Person:
	def __init__(self, name):
    	self.name = name


class MixinDict:
	def to_dict(self, dct):
    	"""
	    flat a dict
    	before flat -> {'name': 'Joy', 'skills': 'Python Programming', 'dependents': {'wife': 'Jane', 'children': ['Alice', 'Bob', 'Justin']}}
	    ------------------------------
    	after flat -> {'name': 'Joy', 'skills': 'Python Programming', 'wife': 'Jane', 'children': ['Alice', 'Bob', 'Justin']}

	    :param dct:
    	:return:
	    """
    	res_dict = {}
	    temp_dct = {}
    	for key, val in dct.items():
        	if isinstance(val, dict):
            	temp_dct = self.to_dict(val)
	        else:
    	        res_dict[key] = val
	    else:
    	    res_dict.update(temp_dct)
	    return res_dict


class MixinJson:
	def to_json(self, ori_dct):
    	flat_dct = self.to_dict(ori_dct)
	    return json.dumps(flat_dct)


class Employee(MixinDict, MixinJson, Person):
	def __init__(self, name, skills, dependents):
    	super().__init__(name)
	    self.skills = skills
    	self.dependents = dependents


if __name__ == '__main__':
	# e1 = Employee(
	#     name = 'Joy',
	#     skills = ['Python Programming', 'Project Management'],
	#     dependents = {'wife': 'Jane', 'children': ['Alice', 'Bob', 'Canne']}
	# )
	e1 = Employee('Joy', 'Python Programming', {'wife': 'Jane', 'children': ['Alice', 'Bob', 'Justin']})

	print('original dict: ', e1.__dict__)
	print('-' * 30)

	e1_dct = e1.__dict__

	e1_flat_dct = e1.to_dict(e1_dct)
	print('flat dict: ', e1_flat_dct)

	print('=' * 30)
	e1_json = e1.to_json(e1_flat_dct)
	print('flat json: ', e1_json)

上述代码的执行结果如下所示:

original dict:  {'name': 'Joy', 'skills': 'Python Programming', 'dependents': {'wife': 'Jane', 'children': ['Alice', 'Bob', 'Justin']}}
------------------------------
flat dict:  {'name': 'Joy', 'skills': 'Python Programming', 'wife': 'Jane', 'children': ['Alice', 'Bob', 'Justin']}
==============================
flat json:  {"name": "Joy", "skills": "Python Programming", "wife": "Jane", "children": ["Alice", "Bob", "Justin"]}

Process finished with exit code 0

上述代码就是通过MixinDict类以及MixinJson这两个Mixin类,增强了Employee这个类的功能。

5. References

[1]. Python’s @classmethod and @staticmethod Explained
[2]. Class method vs Static method in Python
[3]. The Python 2.3 Method Resolution Order
[4]. Method resolution order in Python Inheritance
[5]. Method Resolution Order (MRO) in new-style classes
[6]. Python Method Resolution Order and C3 linearization algorithm
[7]. Python mixin

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值