python学习笔记25:基础语法之class
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继承关系
class 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__
(<class ‘__main__.C20’>, <class ‘__main__.C10’>, <class ‘__main__.C11’>, <class ‘__main__.C00’>, <class ‘object’>)
>>> c = C20()
>>> c.run()
run C00
run C11
run C10
run C20
解释:
C20.__mro__是一个元组,采用广度优先原则(同一层级的优先,不同于深度优先),每个super函数调用的都是C20.__mro__中下一个元素对应类的函数;
>>> C20.__mro__
(
<class '__main__.C20'>,
<class '__main__.C10'>,
<class '__main__.C11'>,
<class '__main__.C00'>,
<class 'object'>,
)
/----------------\ /----------------\ /----------------\ /----------------\
| | |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
<__main__.CTest0 object at 0x2b***>
>>> print(t0)
<__main__.CTest0 object at 0x2b***>
重构__str__()
>>> class CTest1():
... def __str__(self):
... return ‘Class {} obj’.format(self.__class__.__name__)
...
>>> t1 = CTest1()
>>> t1 # 直接输出对象,与默认情况相同
<__main__.CTest1 object at 0x2b***>
>>> print(t1) # 打印类实例,输出__str__()方法的返回值
<Class CTest1 obj>
3.2. __repr__()
repr:改变直接输出对象和 print(类实例) 时显示的内容
重构__repr__()
>>> class CTest2():
... def __repr__(self):
... return ‘Class {} obj’.format(self.__class__.__name__)
...
>>> t2 = CTest2()
>>> t2 # 直接输出对象,输出__repr__()方法的返回值
<Class Ctest2 obj>
>>> print(t2) # 打印类实例,输出__repr__()方法的返回值
<Class CTest2 obj>
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'<Person: {self.name}({self.age})>'
p = Person('n0', age=24)
print(p)
执行结果如下:
__new__() called. ('n0',) {'age': 24} # new方法先执行
__init__() called. # init方法后执行
<Person: n0(24)>
对于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。
<class ‘type’>
>>> print(type(h)) # h是通过Hello创建的,所以它的type是Hello。
<class ‘__main__.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,对象-关系映射,把关系数据库的行为映射为一个对象,即一个类对应一个表)框架中,类只能动态定义,因为类的定义取决于表的结构,只有使用者才能根据表的结构定义出对应的类。