面向对象
创建类
通过class
关键字定义,类名最好以大写字母开头,举例:
class Test:
pass
实例化
直接调用类即可实例化,举例:
class Test: pass
t = Test()
定义属性/方法
举例:
class Test:
# 定义属性a
a = 1
# 定义构造方法
def __init__(self, b):
# 定义对象属性b
self.b = b
# 定义对象方法m(后面会介绍几种方法的定义)
def m(self, v):
print(self.a, self.b, v)
调用对象属性和方法
通过.
来调用,举例:
class Test:
# 定义属性a
a = 1
# 定义构造方法
def __init__(self, b):
# 定义对象属性b
self.b = b
# 定义方法m
def m(self, v):
print(self.a, self.b, v)
t = Test(2)
t.m(3)
# 1 2 3
类属性/对象属性
在定义类时定义的属性被称为类属性,在实例化的对象里定义的属性为对象属性,简单来说对象在调用属性时,会优先调用对象属性,如果该属性不存在,才会去类中查找该属性,并调用(实际上调用属性顺序远不止这么简单,后面会介绍),举例:
class A:
# 定义类属性a
a = 0
a = A()
print(a.a) # 0,对象属性a不存在,读取的是类属性a
a.a = 10 # 设置对象属性a
print(a.a) # 10,读取的是对象属性
print(A.a) # 0,类属性没有被修改
A.a = 100 # 修改类属性
print(A.a) # 100
print(a.a) # 10,读取的是对象属性
del a.a # 删除对象属性
print(a.a) # 100,对象属性被删除,读取的是类属性
注:
上面说的是简单的情况,实际的调用顺序后面会介绍,并且之后会有相关的魔法方法,如果自定义了相关魔法方法,情况和默认的又不一样了
类继承
在类名的括号里加上父类名,且python支持多继承关系,举例:
class A: pass
class B: pass
# 继承自B类
class C(B): pass
# 继承自C和A类
class D(C, A): pass
多继承的调用关系参照后面介绍的mro
顺序,一般来说就是在非菱形继承(多个父类继承自另一个相同的父类)的情况下基于深度优先,在菱形继承的情况下基于广度优先
所有类的基类
所有类都继承于object
类(除了object
类,因此,object
类是唯一父类为空的类),可以通过__bases__
属性查看一个类的所有基类(直接父类),举例:
class A: pass
print(A.__bases__)
print(object.__bases__)
# (<class 'object'>,)
# ()
重写
子类中可以通过重写定义同名方法来重写父类方法,如果希望调用父类方法,可以通过super()
获取父类(单继承的情况下super()
指向父类,多继承就未必,后面会介绍),然后调用,举例:
class A:
def test(self):
print(1)
class B(A):
# 重写父类A的test方法
def test(self):
# 调用父类A的test方法
super().test()
print(2)
b = B()
b.test()
# 1
# 2
私有属性
默认定义的属性和方法都是公有的,如果希望改成私有的,可以就在前面加上双下划线__
,举例:
class A:
__a = 1
a = A()
print(A.__a)
print(a.__a)
# 上面两个print都会报错:不存在__a属性
但实际上python中没有真正将属性变为私有的方法,上面的实质并不是将属性变为私有,而是将属性重命名成:_类名__属性
,举例:
class A:
__a = 1
a = A()
print(A._A__a)
print(a._A__a)
# 1
# 1
可以看出该方式只是将对外暴露的属性名给重命名了,通过新的命名方式,还是可以在外部调用该属性。因此在python里并没有真正的私有属性,不过该方式能够很好的解决继承类的属性名冲突问题,例如:
class A:
a = 1
class B(A):
a = 2
a = B()
print(a.a) # 2
此时在B的实例化对象里就无法获取A中的a属性,但是如果使用私有属性,因为属性名会被修改成以类名开头的格式,因此不会导致属性名冲突,举例:
class A:
__a = 1
class B(A):
__a = 2
a = B()
print(a._A__a, a._B__a) # 1 2
实例化相关方法
__new__
在对象生成之前执行,接收当前类作为参数,可以自定义类的生成过程,举例:
class A:
def __new__(cls):
print("创建对象")
obj = super().__new__(cls)
obj.a = 1
return obj
a = A()
print(a.a)
# 创建对象
# 1
可以看出在创建对象前执行,并且可以进行一些类相关的操作,如示例中给对象添加了属性值。由于__new__
返回处理后的类,然后根据返回的类生成对象,所以假如__new__
里不返回实例化的对象,那么后面的方法如__init__
也就不会执行了
__init__
在对象生成之后执行,接收当前对象作为参数,可以进行一些初始化操作,完善对象,举例:
class A:
def __init__(self, a):
print("初始化对象")
self.a = a
a = A(1)
print(a.a)
# 初始化对象
# 1
__init__
返回必须返回None
对象,否则会抛出TypeError
异常
python中的对象特征
- 身份:内存地址,可以通过id函数查看,代表其唯一的身份
- 类型
- 值
例如a=1
,说明变量a
指向了一个地址,这个地址的数据是int
类型,值为1
方法
python中支持定义对象方法、类方法、静态方法,以及借助其他模块定义抽象方法
对象方法
对象方法会将对象本身传入第一个参数,因此方法的第一个参数需要接收一个对象,举例:
class A:
# 定义对象方法,self代表对象本身
def o_m(self, x):
print(self, x)
a = A()
# 调用对象方法,会自动将a对象传入第一个参数,等价于:A.o_m(a, 1)
a.o_m(1)
# <__main__.A object at 0x000001BCD5A396D8> 1
静态方法
通过类调用,静态方法不会主动接收类和对象,可以理解成类中定义的函数,通过@staticmethod
语法糖装饰的方法即为静态方法,举例:
class A:
# 定义静态方法,staticmethod不加也能以静态方式调用
@staticmethod
def s_m(x):
print(x)
# 调用静态方法
A.s_m(1)
A().s_m(1)
# 1
# 1
注:
对象方法中能够使用到各种实例属性和类属性,但前提是必须要先实例化才能调用,而有些方法希望不用实例化也可以调用,此时就可以使用静态方法。静态方法可以理解为在某个类命名空间下定义的函数
注2:
如果静态方法不加上staticmethod
装饰器,那么该方法是静态方法还是对象方法将由调用方式来决定,举例:
class A:
def t_m(x):
print(x)
A.t_m(111)
# 通过类调用则表现为静态方法
A().t_m()
# 通过对象调用则表现为对象方法
# 111
# <__main__.A object at 0x0000024EEC3DA6A0>
所以如果以类方式去调用静态方法,则无需加staticmethod
装饰器。但如果加上staticmethod
装饰器,那么无论是类调用还是对象调用,都表现为静态方法,举例:
class A:
@staticmethod
def t_m(x):
print(x)
A.t_m(111)
# 通过类调用表现为静态方法
A().t_m(111)
# 通过对象调用也表现为静态方法
# 111
# 111
类方法
类方法会将类本身传入第一个参数,通过@classmethod
语法糖装饰的方法即为类方法,举例:
class A:
# 定义类方法,第一个参数为类本身
@classmethod
def c_m(cls, x):
print(cls, x)
# 调用类方法,等价于:A.c_m(A, 1)
A.c_m(1)
# <class '__main__.A'> 1
注:
在静态方法或者对象方法当中假如要使用到类属性或者类方法,那么就只能通过类.xxx
来进行调用,而这样会带来一个问题——当类名修改了以后,静态方法里也得跟着修改,而类方法就能解决这个问题
抽象方法
抽象方法只负责定义方法,而不负责实现,继承的父类中如果含有抽象方法,则子类必须实现该方法,常见的定义抽象方法有两种方式:抽象方法中抛异常;借助元类定义
抽象方法中抛异常
可以在定义的抽象方法当中抛出未实现的异常,若子类没有实现该方法,那么在调用该方法时就会报错,举例:
class A():
# 定义抽象方法,未实现则无法调用
def a_m(self):
raise NotImplementedError("未实现的抽象方法")
class B(A):
def a_m(self):
print(111)
class C(A): pass
B().a_m()
C().a_m()
# 111
# NotImplementedError: 未实现的抽象方法
可以看出C类没有实现该方法,于是调用时报错。但该方式实现的抽象方法必须在调用时才会报错,如果希望实例化时就报错,那么可以通过第二种方式实现
借助元类定义
通过abc
模块下的abstractmethod
装饰实现,并且定义抽象方法的类需要设置元类为ABCMeta
(元类后面会介绍),举例:
from abc import ABCMeta, abstractmethod
# 定义元类
class A(metaclass=ABCMeta):
# 定义抽象方法
@abstractmethod
def a_m(self):
pass
class B(A):
def a_m(self):
print(111)
class C(A): pass
B().a_m()
C()
# 111
# TypeError: Can't instantiate abstract class C with abstract methods a_m
可以看出B类中实现了对应的抽象方法,所以可以正常使用,而C类中没有实现,所以在实例化时就会报错
属性描述符
@property
可以设置对象只读,以及设置修改对象时必须通过对应的setter
方法修改,举例:
class Person:
def __init__(self):
self._name = "default"
self._age = 18
@property
def name(self):
return self._name
@name.setter
# name属性的setter方法
def name(self, new_name):
print("修改name对象")
self._name = new_name
@property
# age没有setter方法,为只读属性
def age(self):
return self._age
p = Person()
print(p.name, p.age)
p.name = "aaa"
print(p.name, p.age)
# p.age = 20
# age只读,这句会报错
# 结果:
# default 18
# 修改name对象
# aaa 18
参考:https://www.liaoxuefeng.com/wiki/1016959663602400/1017502538658208
魔法方法
可以理解为类的生命周期方法(或者说类在各种行为中的表现方式),在实例化对象中执行特定行为时将触发,是python中的类能够功能如此强大的重要特性,魔法方法都以双下划线__
开头和结尾,举例:
class A:
# 定义对象的构造方法
def __init__(self, *args):
print("初始化对象")
self._data = args
# 定义获取对象长度的触发方法
def __len__(self):
return len(self._data)
# 定义索引对象内容时的触发方法
def __getitem__(self, index):
return self._data[index]
# 定义对象被调用的触发方法
def __call__(self):
print("对象被调用")
a = A(1,2,3)
print(len(a))
print(a[1])
a()
# 初始化对象
# 3
# 2
# 对象被调用
魔法方法详细参考我的另一篇:Python 魔法方法总结
类相关内置函数
type(obj)
获取当前对象的所属类,举例:
class A: pass
a = A()
print(type(a))
# <class '__main__.A'>
type
函数还可以用于创建类(实际上类默认就是通过type
来创建的),此时需要传入三个参数,分别是:类名、基类和属性,举例:
def test(self): pass
A = type("A", (), {"x": 1, "y": test})
print(A)
# <class '__main__.A'>
注:
type
创建类是元类编程的基础,后面会介绍
issubclass(clsA ,clsB)
判断A类是否为B类的子类,结果返回一个布尔对象,并且第二个参数可以传入一个元组,只要第一个参数类是元组中其中一个类的子类就返回True
,举例:
print(issubclass(dict, list))
print(issubclass(dict, (object, list)))
# False
# True
isinstance(obj , cls)
判断一个对象是否为某个类的实例,使用方法和issubclass
类似,举例:
print(isinstance({}, list))
print(isinstance({}, (dict, list)))
# False
# True
注:
type
/isinstance
区别:type
是判断是否为同一个类(不包括父类),而isinstance
则包括父类的判断,举例:
>>> class A: pass
>>> class B(A): pass
>>> isinstance(B(), B)
True
>>> isinstance(B(), A)
True
# 包括父类也能判断
>>> type(B()) is B
True
>>> type(B()) is A
False
# 不判断父类
hasattr(obj ,attr)
反射相关,判断对象里面是否有指定的属性或方法,举例:
class A:
a = 1
def b(self):
pass
print(hasattr(A(), "a"))
print(hasattr(A(), "b"))
print(hasattr(A(), "c"))
# True
# True
# False
getattr(obj ,attr [,default])
反射相关,获取一个对象中指定属性的值,其中可以对不存在的属性设置默认值,否则会抛出AttributeError
异常,举例:
class A:
a = 1
def b(self):
pass
print(getattr(A(), "a"))
print(getattr(A(), "b"))
print(getattr(A(), "c", "default"))
print(getattr(A(), "c"))
# 1
# <bound method A.b of <__main__.A object at 0x00000274558496D8>>
# default
# AttributeError: 'A' object has no attribute 'c'
setattr(obj ,attr ,value)
反射相关,给对象的指定属性赋值,如果属性不存在则会创建该属性,举例:
class A:
a = 1
a = A()
setattr(a, "a", 2)
print(a.a)
setattr(a, "b", 1)
print(a.b)
# 2
# 1
delattr(obj ,attr)
反射相关,删除对象中指定属性,举例:
class A:
a = 1
def __init__(self):
self.b = 2
a = A()
print(hasattr(a, "a"))
# a是类属性,要从类中删除
delattr(A, "a")
print(hasattr(a, "a"))
print(hasattr(a, "b"))
# b是对象属性,要从对象中删除
delattr(a, "b")
print(hasattr(a, "b"))
property(get ,set ,del)
描述符相关,能够定义一个属性的get
/set
/del
操作,举例:
class A:
def setv(self, v):
print("设置v的值")
self._v = v
def getv(self):
print("获取v的值")
return self._v
def delv(self):
print("删除v")
del self._v
v = property(getv, setv, delv)
a = A()
a.v = 100
print(a.v)
del a.v
# 设置v的值
# 获取v的值
# 100
# 删除v
super(cls, obj)
获取指定类的父类(一般情况下,后面会说明),并将当前对象转成指定类的对象,举例:
class A:
def __init__(self):
self.x = 1
def test(self, y):
print("A", self.x, y)
class B(A):
def __init__(self):
self.x = 2
def test(self, y):
super(B, self).test(y)
# 获取B的父类A,并将当前对象转成A类,从而调用A类的test方法
b = B()
b.test(2)
# A 2 2
可以看出通过super
函数,对象b转成A类的对象,但属性x
并没有因为转成A类而改变。
super
函数并非类中才能使用,举例:
class A:
def __init__(self):
self.x = 1
def test(self, y):
print("A", self.x, y)
class B(A):
def __init__(self):
self.x = 2
super(B, B()).test(100)
# A 2 100
注:
在单继承的情况下super
的确可以简单地理解为寻找父类,但实际上其是寻找mro
(方法解析顺序,后面会介绍)列表中的指定类的下一个索引类,详细参考:
https://www.jianshu.com/p/de7d38c84443
类对象内置属性
__class__
创建当前对象的类,举例:
class A: pass
a = A()
print(a.__class__, A.__class__)
# <class '__main__.A'> <class 'type'>
可以看到创建类的类默认是type
,这个涉及到元类编程,后面会介绍
__name__
当前类的名称,举例:
class A: pass
a = A()
print(A.__name__)
# A
__dict__
python自省机制实现的核心(自省机制:通过一定的机制查询到对象内部的结构),一个类(类在python中本身也是对象)/对象所有的属性是通过__dict__
属性(字典对象)进行管理的,举例:
class A:
a = 1
class B(A):
def __init__(self):
self.b = 2
b = B()
print(b.__dict__) # 类实例对象属性
# {'b': 2}
print(B.__dict__) # 类对象属性
# {'__module__': '__main__', ...}
print(A.__dict__)
# {'__module__': '__main__', 'a': 1, ...}
可以看出首先b中有实例属性b,B中有类属性一大堆,而属性a则在A类里,当b获取属性a时,先寻找自身的__dict__
当中是否存在属性a,不存在则去找B里面的,然后再去找A里面的。
并且我们也可以通过__dict__
来对属性进行增删改之类的操作,举例:
class A:
a = 1
class B(A):
def __init__(self):
self.b = 2
b = B()
b.__dict__["c"] = 3
print(b.c)
# 3
print(b.__dict__)
# {'b': 2, 'c': 3}
注:
可以通过dir
函数能得到对象能够获取到的所有属性(__dict__
只能获取到当前对象的,而通过__mro__
查找到的属性无法展示,dir
则可以),举例:
class A:
a = 1
class B(A):
def __init__(self):
self.b = 2
b = B()
print(b.__dict__)
# {'b': 2}
print(dir(b))
# ['__class__', '__delattr__', '__dict__', ..., 'a', 'b']
python自省机制参考:https://kb.cnblogs.com/page/87128/
__slots__
限制实例化的对象中允许有哪些属性,在python中是默认允许对象动态添加属性和方法的,比如下面的代码:
class A:
def __init__(self):
self.b = 1
A.c = 2
# 类A添加属性c
a = A()
a.d = 3
# 对象a添加属性d
print(a.b, a.c, a.d)
可以看到定义好的类A后来有给添加的属性c
和d
,但这样有可能会给代码带来一些风险,所以有时候我们要限制能够添加的属性,此时可以通过__slots__
属性来设置,举例:
class A:
__slots__ = ['b', 'c']
# 限制实例化的对象只能有b和c属性
def __init__(self):
self.b = 1
a = A()
a.c = 3
# 对象添加属性c
# a.d = 3
# 对象只能有b和c属性,因此无法添加,这句会报错
A.d = 2
# 类A添加属性d,这句是可以的
print(a.b, a.c, a.d)
__bases__
能够查看当前类的所有直接父类,举例:
>>> class A: pass
>>> A.__bases__
(<class 'object'>,)
# 默认是object(python3下不指定基类,默认继承object)
>>> class B(A): pass
>>> B.__bases__
(<class '__main__.A'>,)
>>> class C(B): pass
>>> C.__bases__
(<class '__main__.B'>,)
__bases__
只列出直接父类,如果希望查看所有的父类(包括间接的),可以通过__mro__
属性查看
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.__bases__)
print(D.__mro__)
# (<class '__main__.B'>, <class '__main__.C'>)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
还有__base__
属性可以查看其super
指向的类,举例:
class A:pass
class B(A):pass
class C(A):pass
class D(B, C):pass
print(D.__bases__)
print(D.__base__)
# (<class '__main__.B'>, <class '__main__.C'>)
# <class '__main__.B'>
__subclasses__
查看该类的所有子类,举例:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(A.__subclasses__())
# [<class '__main__.B'>, <class '__main__.C'>]
可以发现该方法只能查看到该类的直接子类,如果想要查看所有子类,可以封装如下:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
class E(D): pass
def get_all_subclass(c):
queue = [c]
res = []
while queue:
c = queue.pop(0)
sub_cs = c.__subclasses__()
for sub_c in sub_cs:
if not sub_c in res:
queue.append(sub_c)
res.append(sub_c)
return res
print(get_all_subclass(A))
# [<class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.E'>]
注:
通过dir(类)
会发现__subclasses__
、__base__
等属性并不在里面,那为何类能使用这些属性呢?因为所有的类最终都是基于type
类(或者说对象)来创建的,我们可以查看type
属性如下:
print(dir(type))
# ['__abstractmethods__', '__base__', '__bases__', ..., '__subclasscheck__', '__subclasses__', ..., 'mro']
可以发现这些属性都是存在于type
类中的,而所有的类可以认为是type
类创建的对象,因此对象里自然能够使用这些方法
__module__
查看类所在模块,举例:
class A: pass
print(A.__module__, type(A.__module__))
# __main__ <class 'str'>
但可以看出该方式获取的是字符串的模块名,如果希望获取模块对象本身,可以通过__import__
函数动态导入模块,或者从sys.modules
(存放已载入模块的字典对象)中获取已载入的模块,举例:
import sys
class A: pass
# 通过动态导入获取模块
print(__import__(A.__module__))
# 从已载入模块的字典中找寻模块
print(sys.modules.get(A.__module__))
# <module '__main__' from 'xxx:\\test.py'>
# <module '__main__' from 'xxx:\\test.py'>
__annotations__
函数/类标注,指定期望的参数类型以及返回值类型,举例:
class A:
aaa: int = 1
def test(aaa: str, bbb:int = 1) -> list:
pass
print(test.__annotations__)
print(A.__annotations__)
# {'aaa': <class 'str'>, 'bbb': <class 'int'>, 'return': <class 'list'>}
# {'a': <class 'int'>, 'return': <class 'str'>}
相关概念
鸭子类型
当一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子
什么意思呢?例如在Python里,我可以实现多个类,只要他们都实现了相同的方法,那么不论换哪个,都能正常使用,举例:
class Bird:
def walk(self):
print("Bird walk...")
class Duck:
def walk(self):
print("Duck walk...")
duck = None
for animal in [Bird, Duck]:
duck = animal()
duck.walk()
这里duck可以变成鸟,也能变成鸭子,并且使用对应的方法,因为没有像静态语言中的类型检查,他不论变成哪个类的实例都没问题,只要实现了需要的方法,就能够被正常调用
而在静态语言里(如Java),鸟就是鸟,鸭子就是鸭子,他们两就算是功能一模一样,也无法进行转换,更无法调用,举例:
public class Test {
public static void main(String[] args) {
Duck duck = new Duck();
duck.walk();
// duck = new Bird();
// 这句会报错,因为他们两个类不同,无法直接转换
}
}
class Bird {
public void walk() {
System.out.println("Bird walk...");
}
}
class Duck {
public void walk() {
System.out.println("Duck walk...");
}
}
所以此时必须要他们继承自同一个类/或者接口,然后使用父类/接口实例化,从而才能转换,举例:
public class Test {
public static void main(String[] args) {
Animal duck = new Duck();
// 使用同一个接口实例化,才能成功转换
duck.walk();
duck = new Bird();
duck.walk();
}
}
interface Animal {
public void walk();
}
class Bird implements Animal{
public void walk() {
System.out.println("Bird walk...");
}
}
class Duck implements Animal {
public void walk() {
System.out.println("Duck walk...");
}
}
因此python的多态可以很容易的实现(动态类型的优势),假如我们有多个类,他们实现了相同的方法,那么我们可以很容易的随意修改这个对象指向这几个类,从而调用方法
再举个例子,例如list的extend方法需要传入的是一个可迭代的对象(会隐式调用该对象的迭代器),因此该方法的传入对象不一定非得传入一个list或者什么指定类,而是只要实现了可迭代方法的类都可以,举例:
li = []
s = {1,2,3}
li.extend(s)
# s虽然是集合,但作为可迭代对象,因此可以传入
说白了,python中的一个类,他可以有多重身份,例如实现了迭代器相关的魔法方法,他就可以是一个迭代器,实现了enter和exit,他就可以是一个上下文管理器,而且这些身份可以兼有
而在静态语言当中,基本上就是指定了传入的对象类型
面向对象进阶部分
类的执行机制
和函数的区别
类和函数在定义上有一点很重要的不同就是:函数只有在调用了以后才会开始执行函数里面的语句,而类在调用前就已经执行了初始化定义语句分配好了内存(实际上是编译时会创建类对象,并对类对象进行一些初始化操作),比如下面的定义了一个函数和类:
def a():
print(1)
class A():
print(2)
def __init__(self):
print(3)
if __name__ == '__main__':
pass
# 2
可以看出类还未实例化就已经执行了创建类对象的相关语句,即:类已经完成了初始化定义和内存分配
类的执行顺序
不管有没调用类,只要定义了以后,就会默认执行一些初始化语句,所以类在程序中的执行顺序为:类里的最外层语句(初始化定义语句)>__new__
>__init__
,比如下面的类:
print(0)
class A():
print(1)
def __init__(self):
print(4)
print(2)
if __name__ == '__main__':
print(3)
a = A()
结果为:
0
1
2
3
4
python解释器是逐行执行,所以类外部的执行顺序和类无关,只看执行的语句在哪一行
通过类/对象调用方法的区别
通过类调用方法和对象调用方法的主要区别就是:通过类调用时,相当于直接调用一个函数,而通过对象调用时,会默认第一个参数传入对象本身(类方法则传入类本身),因此obj.method(...)
等价于:cls.method(obj, ...)
,举例:
class A:
def test(self, x):
print(self, x)
a = A()
a.test(1)
A.test(a, 1)
# <__main__.A object at 0x00000229993F96D8> 1
# <__main__.A object at 0x00000229993F96D8> 1
类继承时的方法解析顺序(MRO)
新式类和经典类
如果一个类或者他的父类继承了object
类则是新式类,否则就是经典类,在python3之后都是新式类(新式类如果不设置继承父类,默认继承object
类),而对于类继承时的方法解析顺序,在经典类中采用深度优先(因为经典类没有要求必须继承object
类,所以可以采用深度优先,像新式类因为必须继承object
类,则不适合采用深度优先,否则对于object
拥有的方法,多继承的情况下就不太可能轮到别的类上进行查询),而python3中采用C3算法遍历继承的父类关系
C3算法
简单来说:对于有相同父类的继承关系,采用广度优先;无相同父类的继承关系,采用深度优先,例如一个继承关系如下的类:
object
|
/ \
A E
/ \ |
B C |
\ / |
D F
\ /
X
可以看出B
和C
有相同父类,而D
和F
无相同父类,因此B
和C
之间采用广度优先,B
和C
之间采用深度优先,打印其mro
关系,如下:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
class E: pass
class F(E): pass
class X(D, F): pass
for cls in X.__mro__:
print(cls)
# <class '__main__.X'>
# <class '__main__.D'>
# <class '__main__.B'>
# <class '__main__.C'>
# <class '__main__.A'>
# <class '__main__.F'>
# <class '__main__.E'>
# <class 'object'>
遍历过程
按照深度优先遍历,如果有相同的父类,则需先遍历完子类再遍历父类,如果遇到重复的父类,则只保留最后一个,因此上面的示例中,计算过程如下:
- 首先,对于
D
和F
类进行深度优先,因此先遍历D
类:X->D
- 由于D类的父类当前也是多重继承,并且
B
和C
类拥有共同父类A
,因此也是先遍历B
类:B
- 然后在遍历
C
类,遍历完成即可遍历父类A
:C->A
- 最后遍历
F
类:F->E->object
- 将之前遍历的结果合起来就是:
X->D->B->C->A->F->E->object
参考:
https://www.cnblogs.com/bashaowei/p/8508276.html
https://blog.csdn.net/u011467553/article/details/81437780
https://hanjianwei.com/2013/07/25/python-mro/
http://www.chinaoc.com.cn/p/1196683.html
super调用顺序
前面介绍的super
函数实际上就是基于mro
顺序,获取下一个类的内容,而并非单纯的找自身父类,举例:
class A:
def __init__(self):
print("A")
class B(A):
def __init__(self):
print("B")
super().__init__()
class C(A):
def __init__(self):
print("C")
super().__init__()
class D(B, C):
def __init__(self):
print("D")
super().__init__()
d = D()
print(D.__mro__)
# D
# B
# C
# A
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
可以看出当D的super返回的是B,而B的父类是A,但其super却是C,也就是mro的下一个,最后才是A
None对象
在解释器启动时,会通过None类型生成一个None对象,全局只有一个,举例:
>>> type(None)
<class 'NoneType'>
# 可以看出是NoneType类生成的对象
>>> None is None
True
# 可以看出None对象是唯一的
上面的例子为什么能说明None唯一(很简单,因为对象的id是唯一的,不过如果觉得前面讲的有点绕,可以继续看下面解释):
首先实例化的对象,肯定是互相不同的(因为新分配了空间,id肯定唯一),举例:
>>> class A: pass
>>> id(A())
2396138626464
>>> id(A())
2396138625624
# 可以看出两次实例化的地址不同
而None
作为NoneType
类的对象,必然每次实例化的id也不相同,但这里却相同,所以也就说明了其唯一
这里可以再说一下类,类本身也是对象,但类的id会发现也唯一,举例:
>>> class A: pass
>>> A is A
True
这也可以看出解释器在最开始就会通过type
实例化生成各种类对象,并且这些类对象都是唯一的
注:
由于python内部的优化机制,一些简单的整数、字符串等都会先预分配id,所以对于一些简单的数据,可能实例化多个的id是一样的,举例:
>>> id(int(1))
1660742096
>>> id(int(1))
1660742096
而None
其实也属于被优化的一部分
抽象方法实现原理
这里如果分析源码,可以发现该方式就是首先给绑定了abc.abstractmethod
装饰器的方法添加一个__isabstractmethod__
属性,并且值为True
,源码如下:
def abstractmethod(funcobj):
funcobj.__isabstractmethod__ = True
return funcobj
然后当实例化时,通过元类获取当前类以及所有父类的抽象方法名(即获取所有存在__isabstractmethod__
属性且值为True
的方法,因为通过反射获取方法时,假如子类实现了该方法,那么该方法默认是不存在__isabstractmethod__
属性的,假如子类没有实现该方法,那么就会去父类中找该方法,而父类中该方法存在__isabstractmethod__
属性,并且值为True
),并保存到类的__abstractmethods__
属性当中,而解释器则会读取这个属性,并报出对应错误信息,下面是加注释的源码:
def __new__(mcls, name, bases, namespace):
# 先生成对应的类对象
cls = super().__new__(mcls, name, bases, namespace)
# 获取类对象的抽象方法(存在属性__isabstractmethod__且值为True的方法)
abstracts = {name
for name, value in namespace.items()
if getattr(value, "__isabstractmethod__", False)}
for base in bases:
# 获取所有父类中的抽象方法名
for name in getattr(base, "__abstractmethods__", set()):
# 获取当前类对象的该方法
value = getattr(cls, name, None)
# 如果当前类的该方法的__isabstractmethod__属性值为True,则说明是从父类中继承来的抽象方法,那么加入到当前类的抽象方法里
# 如果不为True,说明当前类或者父类里重写了对应的方法,因为方法被重写,因此__isabstractmethod__属性也就不存在了
if getattr(value, "__isabstractmethod__", False):
abstracts.add(name)
# 将当前类的所有抽象方法加入到__abstractmethods__属性当中
cls.__abstractmethods__ = frozenset(abstracts)
# 弱引用相关操作...
cls._abc_registry = WeakSet()
cls._abc_cache = WeakSet()
cls._abc_negative_cache = WeakSet()
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
return cls
通过上面的分析,我们可以发现python主要根据属性__abstractmethods__
来判断当前类的抽象方法,所以根据抽象方法的实现原理,我们也可以这样实现:
class AnimalBase:
# 往类属性__abstractmethods__当中指定当前类的抽象方法
__abstractmethods__ = frozenset({"walk", "eat"})
def __new__(cls):
# 定义一个集合存放所有当前类的抽象方法
abstract = set()
for base in cls.__mro__:
# 遍历当前类及父类的所有抽象方法
for abcmethod in getattr(base, "__abstractmethods__", set()):
# 如果父类的抽象方法在当前类中没有,则添加到当前类的抽象方法当中
if not abcmethod in cls.__dict__:
abstract.add(abcmethod)
# 设置当前类的__abstractmethods__属性,注意是当前类,而不是当前类实例化的对象,因为该属性是绑定在类上的
cls.__abstractmethods__ = frozenset(abstract)
return super().__new__(cls)
class Duck(AnimalBase):
pass
duck = Duck()
注:
__abstractmethods__
是一个类属性(不是类实例化后对象的属性),用于声明当前类的所有抽象方法
抽象基类
存在抽象方法的类即为抽象基类,抽象基类不能直接实例化,主要用于制定一些规范。
内置抽象基类
假如我们希望自定义一个集合类型的类,例如类似tuple
的类,那么我们就需要实现集合相关的魔法方法,如:__len__
/__iter__
/__contains__
/,此时如果我们要判断这个类符不符合序列类的要求,可能就会进行这样的判断:
class A: pass
def is_sequence(cls):
return hasattr(cls, "__len__") and hasattr(cls, "__iter__") and hasattr(cls, "__contains__")
# 判断是否实现了序列类的所有魔法方法
print(is_sequence(A))
这样需要我们进行一系列的判断,编写判断条件也十分的麻烦。而在collections.abc
模块下提供了很多内置数据类型结构的抽象基类,能够更加简单地帮我们判断是否为某一种数据类型,例如前面判断集合类的代码就能够改成:
from collections.abc import Collection
class A: pass
print(issubclass(A, Collection))
而实际上这些基类也是通过重写了issubclass
行为的魔法方法,这里就拿集合的源码来分析:
class Collection(Sized, Iterable, Container):
__slots__ = ()
@classmethod
def __subclasshook__(cls, C):
if cls is Collection:
return _check_methods(C, "__len__", "__iter__", "__contains__")
# _check_methods检查指定类的父类当中是否存在这些方法,源码放在下面
return NotImplemented
def _check_methods(C, *methods):
# 基于mro顺序遍历父类方法,查看指定的方法是否实现
mro = C.__mro__
for method in methods:
for B in mro:
if method in B.__dict__:
if B.__dict__[method] is None:
return NotImplemented
break
else:
return NotImplemented
return True
可以看出其内部实现了__subclasshook__
魔法方法来进行子类判断,所以只要实现了指定的魔法方法,就是当前基类的子类
collections.abc
模块下提供的这些抽象基类除了前面的用处以外,也让我们能够更好地参考其提供的所有内置基本数据类型结构,下面是加注释的源码一览:
__all__ = [
"Awaitable",
# await语法后面必须接的awaitable对象基类
"Coroutine",
# 协程基类
"AsyncIterable",
# 异步可迭代对象基类
"AsyncIterator",
# 异步迭代器基类,如使用async for语法必须实现该基类的方法
"AsyncGenerator",
# 异步生成器基类
"Hashable",
# 可哈希的对象基类,只有可哈希的对象才能存入像集合、字典的key当中
"Iterable",
# 可迭代的对象基类
"Iterator",
# 迭代器基类
"Generator",
# 生成器基类
"Reversible",
# 可逆序的基类(可以使用reversed方法)
"Sized",
# 存在size长度的基类(可以使用len方法)
"Container",
# 可判断是否存在某元素的基类(可以使用in语法)
"Callable",
# 可以当函数调用的基类
"Collection",
# 集合基类(这个集合代表可以存放多个元素,并且能迭代、获取尺寸、判断是否存在)
"Set",
# 不可变集合的基类,如frozenset
"MutableSet",
# 可变集合的基类,如set
"Mapping",
# 不可变映射的基类,如MappingProxyType
"MutableMapping",
# 可变映射的基类,如dict
"MappingView",
# 可获取映射类长度的基类,使映射类可以使用(len)方法
"KeysView",
# 可以迭代key的基类,例如使映射类可以使用keys()方法
"ItemsView",
# 可以迭代key, value的基类,例如使映射类可以使用items()方法
"ValuesView",
# 可以迭代value的基类,例如使映射类可以使用values()方法
"Sequence",
# 不可变序列的基类,如tuple
"MutableSequence",
# 可变序列的基类,如list
"ByteString",
# 字节数据相关的基类(实际上就是继承了不可变序列类),如bytes
]
property动态属性
通过property
装饰器,我们可以设置动态属性,使得一个属性可以动态计算:
class A:
def __init__(self, birth, year):
self._birth = birth
self._year = year
@property
def age(self):
return self._year - self._birth
a = A(2000, 2020)
print(a.age)
# 20
可以看到我们访问age
属性,是通过指定age
方法获得的,因此使用property
使我们能够将一个属性通过方法来计算获取
描述符
属性描述符
通过描述符相关的魔法方法(__set__
/__get__
/__delete__
),可以定义对一个属性读/写/删操作的行为,例如通过属性描述符实现类型校验:
class IntField:
def __get__(self, instance, owner):
return self.val
def __set__(self, instance, val):
# 对age字段赋值时进行校验
if not type(val) is int:
raise TypeError
self.val = val
def __delete__(self, instance):
self.val = None
class People:
age = IntField()
p = People()
p.age = 100
p.age = "dasdas"
# TypeError
数据描述符/非数据描述符
- 数据描述符:实现了
__get__
和__set__
方法 - 非数据描述符:只实现了
__get__
方法
举例:
class DataField:
# 数据描述符
def __get__(self, instance, owner):
return self.val
def __set__(self, instance, val):
self.val = val
class NonDataField:
# 非数据描述符
def __get__(self, instance, owner):
return "aaa"
两者除了一个可以进行操作,一个不行以外,在属性的查找顺序上也存在区别
调用属性执行顺序
调用属性时,会先调用__getattribute__
方法(之后可能会调用的__get__
/__getattr__
等方法都是在该方法的处理逻辑当中调用的)
例如调用的属性不存在,此时如果定义了__getattr__
方法,就会调用该方法,否则抛出AttributeError
错误
完整逻辑如下:
- 如果属性是在其类对象或者基类的
__dict__
属性当中,并且属性是数据描述符,那么将调用__get__
方法,否则进入2 - 如果属性是在当前实例对象的
__dict__
中,则直接返回__dict__[attr]
,否则进入3 - 如果属性是在其类对象或者基类的
__dict__
属性当中,并且属性是非数据描述符,则调用其__get__
方法,否则进入4 - 如果属性是在其类对象或者基类的
__dict__
属性当中,并且属性不是描述符(没有实现__get__
方法),则返回__dict__[attr]
,否则进入5 - 进入5则说明该属性不存在,此时如果当前类实现了
__getattr__
方法,则调用该方法,否则进入6 - 抛出
AttributeError
错误
举例:
class DataField:
# 数据描述符
def __get__(self, instance, owner):
print("data...")
return 100
def __set__(self, instance, val):
self.val = val
class NonDataField:
# 非数据描述符
def __get__(self, instance, owner):
print("nondata...")
return "aaa"
class People:
name = NonDataField()
age = DataField()
def __init__(self):
self.name = "bbb"
self.age = 0
p = People()
print(p.name, p.age)
# data...
# bbb 100
可以看到这里定义了一个People
类,里面的name
和age
属性分别是非数据描述符和数据描述符,然后实例化的时候又定义了name
和age
属性。
在调用这两个属性的时候,因为People
类的__dict__
当中存在数据描述符age
,因此根据上面查找的第一步,调用了__get__
方法,返回了属性值100
;而name
作为非数据描述符,且在当前的实例对象当中也有name
属性,因此根据上面查找的第二步,返回了实例对象的name
属性值bbb
元类编程
元类
元类就是能够创建类的类,而type
函数能够创建类(需要传递三个参数:类名/基类/属性值):
>>> Test = type('Test', (object,), {'a':1})
# 创建名为Test,继承于object类,存在属性a的值为1的类
>>> test = Test()
>>> test.a
1
上面的创建方式等价于:
class Test(object):
a = 1
因此type
本身就是一个元类,而我们编写的类,只要其继承type
,就是一个元类,通过元类,我们能够控制类实例化的过程(当一个类没有设置元类时,会默认使用type
来创建该类)
实际上class
语法本质上也就是相当于调用了type
方法进行类的创建。而元类则可以理解成基于type
方式创建对象的类,当在类当中指定元类以后,将会调用元类的方法,并在元类创建类时通过修改传入的类名、继承类、属性等方式来达到动态修改类的目的,举例:
class AddAttrMetaClass(type):
def __new__(meta_class, class_name, class_parents, class_attrs):
class_attrs['add_attr'] = '在元类里添加的属性'
print("创建新类,类名:{}, 继承父类:{}, 存在属性:{}".format(class_name, class_parents, class_attrs))
return type.__new__(meta_class, class_name, class_parents, class_attrs)
class Test(metaclass = AddAttrMetaClass):
pass
test = Test()
# 结果:
# 创建新类,类名:Test, 继承父类:(), 存在属性:{'__module__': '__main__', '__qualname__': 'Test', 'add_attr': '在元类里添加的属性'}
可以看出上面代码中Test
类指定了AddAttrMetaClass
为其元类,因此在创建类时会先执行AddAttrMetaClass
里的__new__
方法,该方法通过修改class_attrs
属性,从而达到修改创建的Test
类中含有的属性。
动态创建类的方式
- 在函数内部定义类(python解释器在初始化时只是声明函数,而函数内部的类则是在函数执行时的对应栈帧内创建,因此可以看做是一种动态地创建类),通过函数创建:
def create_class(cls):
class A: pass
class B: pass
di = {
"A": A,
"B": B
}
if cls in di:
return di[cls]
return None
a = create_class("A")()
print(a)
# <__main__.create_class.<locals>.A object at 0x000001D83A2294A8>
- 通过
type
创建,举例:
def y(self):
print(self.x)
A = type("A", (), {"x": 1, "y": y})
# 创建A类
a = A()
# 实例化A类
a.y()
# 调用a对象的y方法
print(A)
# 1
# <class '__main__.A'>
类的实例化过程
python解释器在启动时,会先创建所有唯一的类对象,而这些类对象就是通过元类来进行创建,例如下面代码:
class M(type):
def __new__(cls, name, bases, attrs):
print(1)
return super().__new__(cls, name, bases, attrs)
class A(metaclass=M):
def __new__(cls):
print(3)
return super().__new__(cls)
print(2)
a = A()
a = A()
# 1
# 2
# 3
# 3
可以看出因为A
指定了元类M
,因此在解释器启动时,通过元类M
创建了类对象A,因此输出1,然后才开始执行我们的主流程语句,输出2,之后再生成A的实例化对象时,只是对A
的类进行实例化,没有创建类对象的操作,因此不再输出1,而是执行A
的实例化方法,输出3
元类中__call__
的作用
类都是通过的__call__
创建的,因此显然__call__
的本质是创建类,也就是调用类的__new__
和__init__
方法,举例:
class A(type):
def __init__(self, object_or_name, bases, dict):
super().__init__(object_or_name, bases, dict)
print(1)
def __new__(cls, name, base, attr):
print(0)
return super().__new__(cls, name, base, attr)
def __call__(self, *args, **kwargs):
print(2)
return super().__call__(*args, **kwargs)
class B(metaclass=A):
def __new__(cls):
print(3)
return super().__new__(cls)
def __init__(self):
print(4)
b = B()
print(b)
# 0
# 1
# 2
# 3
# 4
# <__main__.B object at 0x00000246D5B2F668>
可以发现如果把元类的__call__
方法最后一行注释,则不会输出3
和4
,即不会创建B
类了
type
type本身也是一个类,但他同时也是一个对象(自身的实例),因此可以看到type的基类是object,但是object又是由type生成,而type函数则是查看创建该类的类,举例:
>>> type(object)
<class 'type'>
# 创建object的类是type
>>> type(type)
<class 'type'>
# 创建type的类也是type
>>> type.__bases__
(<class 'object'>,)
# type也是继承于object
>>> class A(type): pass
>>> class B(metaclass=A): pass
>>> type(B)
<class '__main__.A'>
# B的元类是A,所以创建B的类是A
因为一切都是对象,而对象很容易修改,所以python中的类即使定义好了也可以动态修改(猴子补丁之类的),因为可以理解为修改的是type生成的对象
type/object/类之间关系
在Python中,一切皆对象,而type
能够生成类(因此默认情况下类是type
的对象),对象本身也是类的对象,而type
则是自身的对象,举例:
>>> class A:pass
>>> a = A()
>>> type(a)
<class '__main__.A'>
# 生成a的类是A
>>> type(A)
<class 'type'>
# 可以看出生成A的类是type
>>> type(A) is type
True
>>> type(object)
<class 'type'>
# 生成object的类是type
>>> type(type)
<class 'type'>
# 生成type的类也是type
>>> b = 1
>>> type(b)
<class 'int'>
>>> type(int)
<class 'type'>
# 可以看出一般情况下,类都是type的对象
但实际上,类都是其元类的对象,举例:
>>> class B():pass
>>> class C(B):pass
>>> type(C)
<class 'type'>
# 可以看出C是type的对象
>>> class B(type):pass
# 元类需要继承自type类
>>> class C(metaclass=B):pass
>>> type(C)
<class '__main__.B'>
# 可以看出这次C是元类B的对象
因此一个类如果不指定元类,默认元类就是type
元类实现orm
很多ORM框架都是基于元类来实现的,这里我们简单模拟一下Django中的ORM操作:
class BaseField:
"""字段基类,实现一些公共方法"""
def __get__(self, instance, owner):
return self._val
def __set__(self, instance, val):
raise NotImplementedError
def _check_number(self, val):
"""检查是否为数字"""
if not val is None:
if not issubclass(type(val), (int, float)):
raise TypeError
return True
else:
return False
def _check_str(self, val):
"""检查是否为字符串"""
if not val is None:
if not issubclass(type(val), str):
raise TypeError
return True
else:
return False
class NumberField(BaseField):
"""数字校验字段类"""
def __init__(self, col_name, min_val=None, max_val=None):
self._val = None
self.col_name = col_name
self.min_val = min_val
self.max_val = max_val
self._check_number(min_val)
self._check_number(max_val)
def __set__(self, instance, val):
if not self._check_number(val):
raise ValueError("need number")
if (not self.min_val is None) and val < self.min_val:
raise ValueError("below the min_val")
if (not self.max_val is None) and val > self.max_val:
raise ValueError("over the max_val")
self._val = val
class CharField(BaseField):
"""字符串校验字段类"""
def __init__(self, col_name, max_len=None):
self._val = None
self.col_name = col_name
self.max_len = max_len
self._check_number(max_len)
def __set__(self, instance, val):
if not self._check_str(val):
raise ValueError("need str")
if (not self.max_len is None) and len(val) > self.max_len:
raise ValueError("over the max_len")
self._val = val
class ModelMeta(type):
"""model生成元类"""
def __new__(cls, name, bases, attrs):
if name == "BaseModel":
return super().__new__(cls, name, bases, attrs)
fields = {}
# 存储所有字段属性
for k, v in attrs.items():
# 非Meta和以下划线开头的属性都添加进字段
if not k.startswith("_") and k != "Meta":
fields[k] = v
_meta = attrs.get("Meta")
table_name = name.lower()
# meta类处理
if not _meta is None:
meta_table_name = getattr(_meta, "table_name", None)
if not meta_table_name is None:
table_name = meta_table_name
del attrs["Meta"]
# 将字段相关属性设置到类当中
attrs["table_name"] = table_name
attrs["_meta"] = _meta
attrs["fields"] = fields
return super().__new__(cls, name, bases, attrs)
class BaseModel(metaclass=ModelMeta):
def __init__(self, *args, **kwargs):
# 初始化将传入的配置注入到属性当中
for k, v in kwargs.items():
setattr(self, k, v)
super().__init__()
def save(self):
"""生成插入语句逻辑"""
fields = []
values = []
for k, v in self.fields.items():
col = v.col_name
val = getattr(self, k, "")
fields.append(col)
values.append(val)
if len(fields) < 1:
raise AttributeError
sql = "insert {}({}) value({});".format(self.table_name, str(fields)[1:-1].replace("'", ""), str(values)[1:-1])
print(sql)
class People(BaseModel):
age = NumberField(col_name="age", min_val=1, max_val=100)
name = CharField(col_name="name", max_len=10)
class Meta:
table_name = "tb_people"
people = People(name="aaa", age=30)
people.save()
# insert tb_people(age, name) value(30, 'aaa');
元类实现单例模式
懒汉式
懒汉式只需要在实例化该类时再创建对应的实例,因此直接在__new__
方法里控制单例的创建即可,举例:
import threading
class A:
ins = None
lock = threading.Lock()
def __new__(cls):
# 如果已经创建就直接返回ins
if cls.ins is None:
with cls.lock:
# 假如多个线程同时创建对象,那么可能在同一时间,对应的ins都是none,
# 因此都能进入这里,所以这里需要再进行一次判断是否ins已经创建
if cls.ins is None:
cls.ins = super().__new__(cls)
return cls.ins
a = A()
b = A()
print(a is b)
# True
饿汉式
饿汉式需要在一开始就创建好对应的对象,因此我们可以在元类当中控制——当创建类时就实例化当前类的对象,举例:
class Meta(type):
def __new__(cls, object_or_name, bases, dict):
cls = super().__new__(cls, object_or_name, bases, dict)
# 在创建类时创建实例
cls.ins = cls()
return cls
class A(metaclass=Meta):
def __new__(cls):
if not getattr(cls, "ins", None):
cls.ins = super().__new__(cls)
return cls.ins
print(A.ins)
print(A() is A())