Python从进阶到高级—通俗易懂版(二)
# ==================================
# Author : Mikigo
# Env :deepin 20.4 os
# ==================================
1、类变量和实例变量
(1)类变量是在类里面直接定义的变量,它可以被类对象访问和赋值,也可以被实例对象访问和赋值。
class Test:
b = 1
def __init__(self): # 构造函数
self.a = 1
T = Test()
print(T.b)
print(Test.b)
T.b = 2 # 通过实例对象赋值
print(T.b)
Test.b = 2 # 通过类对象赋值
print(Test.b)
1
1
2
2
b 是类变量,都能被访问和赋值,没问题哈。
(2)实例变量是在构造函数里面定义的变量,它可以被实例对象访问和赋值,不能被类对象访问和赋值。
class Test:
b = 1
def __init__(self):
self.a = 1
T = Test()
print(T.a)
T.a = 2
print(T.a)
print(Test.a)
Test.a = 2
print(Test.a)
1
2
Traceback (most recent call last):
File "/tmp/pycharm_project_16/123.py", line 12, in <module>
print(Test.a)
AttributeError: type object 'Test' has no attribute 'a'
a 是实例变量,你看实例对象访问和赋值正常的,类对象访问就报错了。
2、类方法、静态方法和实例方法
(1)实例方法又称对象方法,是类中最常见的一种方法。
class Test:
def obj_method(self):
print("this is obj method")
实例方法参数必须传入 self
,self
表示实例对象本身,实例方法的调用也必须通过实例对象来调用:
Test().obj_method()
(2)类方法
class Test:
@classmethod
def cls_method(cls):
print("this is class method")
可以通过类对象调用,也可以通过实例对象调用。
Test.cls_method()
Test().cls_method()
注意两点:
- 方法前面必须加装饰器
classmethod
,装饰器是Python
中的一种语法糖,后面会讲到,记住这种固定用法,这种写法也是初代装饰器的用法。 - 参数传入
cls
,cls
表示类对象,但是注意不是必须的写法,写cls
是一种约定俗成的写法,方便我们理解,也就是说这里你写self
从语法上也是不会有问题的。这就是为什么有时候我们将一个实例方法改成类方法,直接在方法前面添加了装饰器,而没有改self
,仍然能正常执行的原因。
(3)静态方法,实际上就是普通的函数,和这个类没有任何关系,它只是进入类的名称空间。
class Test:
@staticmethod
def static_method():
print("this is static method")
不需要传入任何参数。同样,可以通过类对象调用,也可以通过实例对象调用。
Test.static_method()
Test().static_method()
我看到一些社区大佬都表现出对静态方法的嫌弃,他们觉得既然静态方法和函数没有关系,何不如在类外面写,直接写在模块里面岂不快哉。咱们不予评价,存在即合理。
3、类和实例属性的查找顺序
这里需要引入一个概念:MRO(Method Resolution Order)
,直译过来就是“方法查找顺序”。
大家知道类是可以继承的,子类继承了父类,子类就可以调用父类的属性和方法,那么在多继承的情况下,子类在调用父类方法时的逻辑时怎样的呢,如果多个父类中存在相同的方法,调用逻辑又是怎样的呢,这就是 MRO
。
在 Python2.3
之前的一些查找算法,比如:深度优先(deep first search
)、广度优化等,对于一些菱形继承的问题都不能很好的处理。这部分内容比较多且杂,可以自己查阅资料。
在 Python2.3
之后,方法的查找算法都统一为叫 C3
的查找算法,升级之后的算法更加复杂,采用的特技版拓扑排序,这里也不细讲,可以自己查阅资料,我们只需要关心现在方法查找顺序是怎样的就行了。
来,这里举例说明:
class A:
pass
class B:
pass
class C(A, B):
pass
print(C.__mro__)
__mro__
可以查看方法的查找顺序。
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
可以看到,对于 C 来讲,它里面的方法查找顺序是 C — A — B,没毛病哈,很清楚。
现在升级一下继承关系,试试菱形继承:
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
print(D.__mro__)
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
D 的查找顺序是 D — B — C — A
说明什么问题?我在这噼里啪啦说了这么多,到底想说啥?
想象一下,如果你在 B 和 C 里面都重载了 A 里面的一个方法,此时如果你想调用的是 C 里面的方法,实际上是无法调用的,因为根据方法的查找顺序,会先找到 B 里面的方法。
因此,重点来了:在 Python 中虽然是支持多继承的,但是在实际项目中不建议使用多继承,因为如果继承关系设计得不好,很容易造成逻辑关系的混乱,原因就是 MRO
。
Ruby 之父在《松本行弘的程序世界》书中,讲到三点多继承的问题:
- 结构复杂化:如果是单一继承,一个类的父类是什么,父类的父类是什么,都很明确,因为只有单一的继承关系,然而如果是多重继承的话,一个类有多个父类,这些父类又有自己的父类,那么类之间的关系就很复杂了。
- 优先顺序模糊:假如我有A,C类同时继承了基类,B类继承了A类,然后D类又同时继承了B和C类,所以D类继承父类的方法的顺序应该是D、B、A、C还是D、B、C、A,或者是其他的顺序,很不明确。
- 功能冲突:因为多重继承有多个父类,所以当不同的父类中有相同的方法是就会产生冲突。如果B类和C类同时又有相同的方法时,D继承的是哪个方法就不明确了,因为存在两种可能性。
看看这是大佬说的,不是我说的。
那有同学要问了,我写的功能很复杂啊,必须要继承多个类,怎么办,难受!
实际上有一种比较流行且先进的设计模式:Mixin
混合模式,完美解决这个问题。
举个简单的例子:
class Animal:
pass
# 大类
class Mammal(Animal):
pass
# 各种动物
class Dog(Mammal):
pass
class Bat(Mammal):
pass
现在动物们没有任何技能,咱们需要给动物们增加一下技能:
class RunnableMixIn:
def run(self):
print('Running...')
class FlyableMixIn:
def fly(self):
print('Flying...')
注意 Mixin
的类功能是独立的,命名上也应该使用 MixIn
结尾,这是一种规范。
需要 Run 技能的动物:
class Dog(Mammal, RunnableMixIn):
pass
需要 Fly 技能的动物:
class Bat(Mammal, FlyableMixIn):
pass
有点感觉了没,Mixin
类的特点:
- 功能独立、单一;
- 只用于拓展子类的功能,不能影响子类的主要功能,子类也不能依赖
Mixin
; - 自身不应该进行实例化,仅用于被子类继承。
Mixin
设计思想简单讲就是:不与任何类关联,可与任何类组合。
本文来自博客园,作者:mikigo,转载请注明原文链接:https://www.cnblogs.com/mikigo/p/16812944.html