1. 基本语法

名词

解释

类创建一个新类型;是一个抽象的模板;

对象/实例

类的实例;每个对象拥有相同的方法,但数据可能不同;

属于一个类或对象的变量,用于存储数据;有两种类型:实例变量、类变量;

方法

属于一个类的函数;

属性

域和方法合称为属性;

实例变量

属于每个实例(类的对象)的域;

类变量

属于类本身的域;

class Dog(object): # 类,创建一个新类型,是一个抽象的模板

LEG_CNT = 4 # 类变量,该类的所有实例共享

def __init__(self, a): # 构造函数,在类实例化时自动调用, 定义需要一个额外的self参数。

self.a = a # 实例变量,每个实例单独赋值,不共享

def func0(self): # 实例方法,需要一个self参数,表示实例本身。

print(f'Calling Func0, a = {self.a}') # 可以访问实例变量

print(f'Calling Func0, LEG_CNT={self.LEG_CNT}') # 可以访问类变量

@classmethod # 这是个装饰器,它装饰的方法会变成类方法

def func1(cls): # 类方法需要cls参数,为类本身

print(f'Calling Func1, LEG_CNT={cls.LEG_CNT}') # 可以访问类变量

cls.LEG_CNT += 1 # 可以修改类变量

print(f'Calling Func1, LEG_CNT={cls.LEG_CNT}')

#cls.func0() # 调用实例方法,直接调用会报错: 需要self参数, 所以需要加上实例参数才能调用

#cls.a # 不能访问实例变量,报告无此属性

@staticmethod # 这是个装饰器,它装饰的方法会变成静态方法

def func2(): # 静态方法不需要额外的self/cls参数.

# 静态方法不需要(也不能)访问类对象或类实例的其它成员和方法(因为没有cls或self参数),

# 只是把函数嵌入到了类中,以方便继承或组织代码

print('Calling Static Func2')

>>> d0 = Dog(0)

>>> d0.func0() # 通过实例名调用实例方法

Calling Func0, a = 0

Calling Func0, LEG_CNT=4

>>>

>>> Dog.func1() # 通过class名调用类方法

Calling Func1, LEG_CNT=4

Calling Func1, LEG_CNT=5

>>> Dog.func0(d0) # 通过class名,传入实例,调用实例方法

Calling Func0, a = 0

Calling Func0, LEG_CNT=5

>>>

>>> Dog.func2() # 通过class名调用静态方法

Calling Static Func2

>>> d0.func2() # 通过实例名调用静态方法

Calling Static Func2

变量访问

命名方法

举例

访问限制

双下划线开头

__name

私有变量,外部不能访问。

双下划线开头、结尾

__name__

特殊变量,外部可以访问。

单下划线开头

_name

私有变量,但外部可以访问。

2. 多重继承

多重继承时,super()的执行顺序

例1:

class继承关系

graph LR
A[class C20 
 from C10 C11] -- 继承 --> B[class C10 
 from C00]A --> C[class C11 
 from C00]B --> D[class C00]
C --> Dclass C00():
def run(self):
print('run C00')
class C10(C00):
def run(self):
super(C10, self).run()
print('run C10')
class C11(C00):
def run(self):
super(C11, self).run()
print('run C11')
class C20(C10, C11):
def run(self):
super(C20, self).run()
print('run C20')
执行:
>>> C20.__mro__
(, , , , )
>>> c = C20()
>>> c.run()
run C00
run C11
run C10
run C20

解释:

C20.__mro__是一个元组,采用广度优先原则(同一层级的优先,不同于深度优先),每个super函数调用的都是C20.__mro__中下一个元素对应类的函数;

>>> C20.__mro__
(
,
,
,
,
,
)
/----------------\ /----------------\ /----------------\ /----------------\
| | |C20.__mro__[1]: | |C20.__mro__[2]: | |C20.__mro__[3]: |
->| | ->|cls __main__.C10| ->|cls __main__.C11| ->|cls __main__.C00|
/ |C20.run() | / |C10.run() | / |C11.run() | / |C00.run() |
/ \----------------/ / \----------------/ / \----------------/ / \----------------/
/ | / | / | / |
/----------------\ /----------------\ /----------------\ /----------------\ |
| c = C20() | |super(C20, self)| |super(C10, self)| |super(C11, self)| |
| c.run() | | .run() | | .run() | | .run() | |
| (1) | | (2) | | (3) | | (4) | |
\----------------/ \----------------/ \----------------/ \----------------/ | #output
| | | (5) print('run C11')--"run C00"
| | (6) print('run C11')---------------------------"run C11"
| (7) print('run C10')----------------------------------------------------"run C10"
(8) print('run C20')---------------------------------------------------------------------------- "run C20"

c.run(), 调用C20.run()

C20.run()第一行,super(C20, self).run(),mro[1],调用C10.run()

C10.run()第一行,super(C10, self).run(),mro[2],调用C11.run()

C11.run()第一行,super(C11, self).run(),mro[3],调用C00.run()

C00.run()没有super,打印’run C00’, C00.run()执行完毕

C11.run()第二行,打印‘run C11’,C11.run()执行完毕

C10.run()第二行,打印‘run C10’,C10.run()执行完毕

C20.run()第二行,打印‘run C20’,C20.run()执行完毕

3. 魔术方法

3.1. __str__()

str:改变 print(类实例)时显示的内容。

普通class

>>> class CTest0():

... pass

...

>>> t0 = CTest0()

>>> t0

>>> print(t0)

重构__str__()

>>> class CTest1():

... def __str__(self):

... return ‘Class {} obj’.format(self.__class__.__name__)

...

>>> t1 = CTest1()

>>> t1 # 直接输出对象,与默认情况相同

>>> print(t1) # 打印类实例,输出__str__()方法的返回值

3.2. __repr__()

repr:改变直接输出对象和 print(类实例) 时显示的内容

重构__repr__()

>>> class CTest2():

... def __repr__(self):

... return ‘Class {} obj’.format(self.__class__.__name__)

...

>>> t2 = CTest2()

>>> t2 # 直接输出对象,输出__repr__()方法的返回值

>>> print(t2) # 打印类实例,输出__repr__()方法的返回值

3.3. __iter__()

iter()方法:返回一个迭代对象;

next()方法:迭代__iter__()返回的对象时,会调用__next__()方法拿到循环的下一个值,直到遇到StopIteration错误;

class Fib():

def __init__(self):

self.a, self.b = 0, 1

def __iter__(self):

return self

def __next__(self):

self.a, self.b = self.b, self.a+self.b

if self.a > 100:

raise StopIteration()

return self.a

>>> #对class 实例进行循环

>>> for i in Fib():

... print(i, end=‘ ’)

...

1 1 2 3 5 8 13 21 34 55 89

3.4. __getitem__()

使用__iter__()方法虽然可以对类实例做循环,但不能用下标取元素,

使用__getitem__()方法可以实现“用下标取元素“;

class Fib():

def __getitem__(self, n):

a, b = 1, 1 # 如果n==0,则不进入for循环,直接返回1,保证[0]==1

for i in range(n):

a, b = b, a+b

return a

>>> #对类实例进行取下标

>>> Fib()[8]

34

>>> f = Fib()

>>> f[8]

34

使用__getitem__()可实现“按下标取元素“,但不能处理切片、不能“按下标赋值”、不能删除元素,这些都可以通过添加相应的方法来完成。

拦截对[‘’]方式的属性调用

3.5. __setitem__()

class A:

def __init__(self, cfg={}):

self.cfg = cfg

def __setitem__(self, k, v):

self.cfg[k] = v

def __getitem__(self, k):

return self.cfg[k]

3.6. __getattr__()

当调用不存在的属性时,正常情况下会报告AttributeError: ‘XX’ object has no attribute ‘YY’;

可以通过__getattr__()方法动态返回一个属性,这时如果调用不存在的属性时,Python就会调用__getattr__(self, ‘attr’)来获取属性.

class Student():

def __init__(self):

self.name = ‘Jim’

def __getattr__(self, attr):

if attr == ‘score’:

return 99

raise AttributeError(‘\’Student\’ object has no attribute xx’)

>>> # 尝试调用属性score:

>>> s = Student()

>>> s.name

‘Jim’

>>> s.score # 本没有score这个属性,但通过__getattr__()方法获取了返回值

99

拦截属性取值语句, 即 a = obj.xx

3.7. __setattr__()

拦截属性的赋值语句, 即 obj.xx = yy

class F(object):

def setattr(self, key, value):

self.dict[key] = value

3.8. __call__()

使用__call__()方法可以使类实例变得“可调用”,类实例就象一个函数一样;

class Student():

def __init__(self, name):

self.name = name

def __call__(self):

print(f’My name is {self.name}’)

>>> #尝试调用类实例

>>> s = Student(‘Jim’)

>>> s() # 此句会调用类的__call__()方法

My name is Jim

3.9. __new__()

new()与__init__()的区别:

new()方法先调用, init()方法后调用.

new()是class级别的方法, 用于控制一个新instance的生成过程.

new()需要一个参数cls, (但又不需要声明它是@classmethod).

new()必须返回实例化出来的实例.

init()是instance级别的方法, 用于初始化一个新实例.

class Person(object):

def __new__(cls, *args, **kwargs):

print(f'__new__() called. {args} {kwargs}')

return super(Person, cls).__new__(cls) #NOTE, there is no args

def __init__(self, name, age):

print('__init__() called.')

self.name = name

self.age = age

def __str__(self):

return f''

p = Person('n0', age=24)

print(p)

执行结果如下:

__new__() called. ('n0',) {'age': 24} # new方法先执行

__init__() called. # init方法后执行

对于p = Person(name, age),

首先使用参数name和age来执行Person.new(name, age), new()会返回Person类的一个实例, 通常是使用这种方式: super().new(cls, …).

然后使用__new__()返回的实例调用__init__()方法.

new()方法的使用场景:

继承不可变的class(比如str, int, tuple等)时. 如永远是正数的整型.

class PosInt(int):

def __init__(self, i):

super().__init__() #NOTE: here takes no parameters.

i0 = PosInt(-3)

print(i0) # -3

class PosInt(int):

def __new__(cls, i):

return super().__new__(cls, abs(i))

i0 = PosInt(-3)

print(i0) # 3

实现单例模式, 通过__new__()方法返回实例.

3.10. __eq__()

重新定义类的==行为.

class CTest():

def __init__(self, i_value):

self.i_value = i_value

def __eq__(self, other):

#当两个实例的value相差<4时, 认为相等

if abs(self.i_value-other.i_value)<4:

return True # 返回True表示相等

else:

return False # 返回False表示不等

if __name__ == '__main__':

t1 = CTest(1)

t2 = CTest(2)

t7 = CTest(7)

print(t1 == t2) # True

print(t1 == t7) # False

4. 控制class的创建

4.1. type()

作用:可以动态创建类。

以下代码定义了一个类:

class Hello(object):

def hello(self, name=‘world’):

print(‘Hello, %s’%(name))

等价于以下代码,使用type(),Python解释器遇到class定义时,就是调用type()创建class的。

def fn(self, name=‘world’):

print(‘Hello, %s’%(name))

Hello=type(‘Hello’, (object,), dict(hello=fn)) #创建Hello class

创建class对象时,type的3个参数:

class名称;

继承的父类集合(如果只有一个父类,tuple单元素需要一个逗号);

class的method名称与函数绑定(这里把函数fn绑定到method hello上);

对于上面两种class定义的方式,以下测试结果相同

>>> h = Hello()

>>> h.hello()

Hello, world

>>> print(type(Hello)) # Hello是通过type创建的,所以它的type是type。

>>> print(type(h)) # h是通过Hello创建的,所以它的type是Hello。

4.2. metaclass

metaclass可以控制类的创建行为(创建类或修改类)

metaclass、类、实例,三者的关系:根据metaclass创建类、根据类创建实例;

定义metaclass:metaclass是类的模板,所以从type类派生;

class ListMetaclass(type): # metaclass的类名总以Metaclass结尾,以方便识别

def __new__(cls, name, bases, attrs):

attrs[‘add’] = lambda self, value: self.append(value)

return __new__(cls, name, bases, attrs)

new()方法的参数:

待创建的类对象;

类名字;

类的父类集合;

类的方法集合;

利用metaclass来定制类(指导类的创建方式)

class MyList(list, metaclass=ListMetaclass):

pass

使用metaclass参数,Python创建MyList时,会通过ListMetaclass.new()来创建,所以MyList这个类多了一个方法:add()

测试:

>>> L = MyList()

>>> L

[]

>>> L.add(1) # L有add()方法

>>> L

[1]

>>> L2 = list()

>>> L2.add(1) # L2没有add()方法

AttributeError: ‘list’ object has no attribute ‘add’

在ORM(Object Relational Mapping,对象-关系映射,把关系数据库的行为映射为一个对象,即一个类对应一个表)框架中,类只能动态定义,因为类的定义取决于表的结构,只有使用者才能根据表的结构定义出对应的类。