小甲鱼python入门笔记(四)

目录

十六、类和对象

1、类、class关键字

2、封装 

self参数

3、继承

(1)多重继承

(2)组合

(3)绑定

(4)旁门左道,使用类来模拟字典

4、构造函数__init__()

5、重写

钻石继承

super()

MRO顺序

6、Mixin模式

7、多态

(1)有关继承的多态

(2)自定义函数实现多态接口

(3)鸭子类型

 8、“私有变量”

单个下横线开头的变量

单个下横线结尾的变量

9、效率提升之道

__slots__类属性

10、python会魔法——魔法方法

(1)__new__()方法

(2)__del__(self)方法

(3)运算相关的魔法方法

(4)属性访问相关的函数和魔法方法

11、索引、切片、迭代协议

(1)针对索引、切片的魔法方法__getitem__()、__setitem__()

__setitem__()魔法方法

(2)针对可迭代对象的魔法方法__iter__(self)、__next__(self)

12、代偿

(1)__contains__(self, item)

(2)代偿

13、跟比较运算相关的魔法方法

14、给人看还是给程序看

(1)__call__(self, [, args...])

(2)跟字符串相关的魔法方法:__str__(self)、__repr__(self)

15、property()

16、类方法和静态方法

(1)类方法@classmethod

(2)静态方法@staticmethod

(3)对(1)中的统计实例化对象的数量功能进行优化

17、描述符及property()实现原理

(1)描述符

(2)使用描述符实现property案例的功能

(3)使用描述符创造一个property()函数

(4)实现getter()、setter()、deleter()三个方法

18、数据描述符、非数据描述符

描述符的第四个魔法方法__set_name__(self, name, owner)

19、函数、方法、静态方法、类方法的底层实现原理

20、类装饰器

21、type()函数和__init_subclass__

type()第二种用法:根据传入的三个参数,返回一个新的type对象

__init_subclass__(),python3.6新添加的类方法

22、元类(metaclass)

最简单的元类

23、元类的应用

(1)给所有的类添加一个属性

(2)对类名的定义规范做限制

(3)修改对象的属性值

(4)限制类实例化时的参数传递方式

(5)禁止一个类被实例化

(6)只允许实例化一个对象


十六、类和对象

对象 = 属性 + 方法

1、类、class关键字

类名用大写字母开头

属性是类中的变量,方法是类中的函数

2、封装 

在创建对象之前,通过类将相关的属性和方法给打包到一起。然后再通过类来生成相应的对象。

python处处皆对象

self参数

>>> class C:
	def hello():
		print('hello')
		
>>> c = C()
>>> c.hello()
Traceback (most recent call last):
  File "<pyshell#82>", line 1, in <module>
    c.hello()
TypeError: hello() takes 0 positional arguments but 1 was given
>>> class C:
	def getSelf(self):
		print(self)
		
>>> c = C()
>>> c.getSelf()
<__main__.C object at 0x000002036622F5B0>
>>> c
<__main__.C object at 0x000002036622F5B0>

传递到方法中self参数的实参就是实例对象本身

一个类可以生成无数个对象,当调用类中的方法时,python为了明确是哪一个实例对象在调用该方法,于是使用self参数将调用该方法的实例对象的信息进行了传递。

3、继承

>>> class A:
	x = 1
	def hello(self):
		print('你好')
		
>>> class B(A):#继承自A
	def bb(self):
		print('我是B的对象')
>>> b = B()
>>> b.x
1
>>> b.hello()
你好
>>> b.bb()
我是B的对象

当子类继承父类时,子类拥有和父类相同的方法和相同的属性,调用子类的方法时,会默认调用子类的方法,属性也是。

>>> class B(A):
    x = 123
	def hello(self):
		print('我是B的对象')

>>> b = B()
>>> b.x
123
>>> b.hello()
我是B的对象

isinstance():判断某个对象是否属于某个方法

>>> isinstance(b, B)
True
>>> isinstance(b, A)
True

issubclass():检测一个类是否为某个类的子类

>>> issubclass(B, A)
True

(1)多重继承

>>> class A:
	x = 1
	def hello(self):
		print('你好')
>>> class B():
	x = 123
	def hello(self):
		print('这里是B')
		
>>> class C(A, B):
	pass

>>> c = C()
>>> c.x
1
>>> c.hello()
你好

多重继承时,多个父类拥有同样的属性和方法,子类对象调用时优先级从左至右。

(2)组合

>>> class A:
	def hello(self):
		print('这里是A')
		
>>> class B:
	def hello(self):
		print('这里是B')
		
>>> class C:
	def hello(self):
		print('这里是C')

>>> class ABC:
	a = A()
	b = B()
	c = C()
	def hello(self):
		self.a.hello()#a, b, c三个变量是属性,需要使用self进行绑定
		self.b.hello()
		self.c.hello()
		
>>> abc = ABC()
>>> abc.hello()
这里是A
这里是B
这里是C

(3)绑定

实例对象跟类的方法进行绑定

实例能拥有自己的属性,如果要通过类的方法对属性进行操作,则需要使用self进行绑定。

>>> class A:
    x = 120
	def set_x(self, i):
		self.x = i #对属性进行操作,需要self绑定
		
>>> a = A()
>>> a.set_x(520)
>>> a.x
520

若没有这个self,那么直接对x进行操作,只是在set_x()函数内部创建了一个局部变量x

>>> class A:
	x = 100
	def set_x(self, i):
		x = i
		
>>> a = A()
>>> a.set_x(250)
>>> a.x
100
>>> A.x
100

直接对类中的属性进行更改(不建议这样操作),使用该类实例化的对象的该属性也会随之改变。因为此时实例化的对象并没有自己的属性

>>> class A:
	x = 150
	
>>> a = A()
>>> A.x = 666
>>> a.x
666
>>> a.__dict__
{}

(4)旁门左道,使用类来模拟字典

通常使用空类的实例来模拟字典

>>> class A:
	pass

>>> a = A()
>>> a.x = 123
>>> a.y = 'Love'
>>> a.z = [1, 2, 3]
>>> print(a.x, a.y, a.z)
123 Love [1, 2, 3]

>>> d = {'x':123, 'y':'Love', 'z':[1, 2, 3]}#正常使用字典
>>> print(d['x'], d['y'], d['z'])
123 Love [1, 2, 3]

4、构造函数__init__()

在类中定义__intit__()方法,就可以在实例化对象的时候实现一些变量的初始化

如果创建类的时候,没有添加构造函数,python解释器会自动创建一个不执行任何操作的默认构造函数;也就是说,只要创建类,一定会伴随着一个构造函数诞生。只不过可以自定义一个构造函数,也可以由python解释器自动创建一个默认的构造函数。

>>> class go:
	def __init__(self, x, y):
		self.x = x
		self.y = y
	def add(self):
		return self.x + self.y
	def mul(self):
		return self.x * self.y
	
>>> a = go(3, 4)
>>> a.__dict__
{'x': 3, 'y': 4}
>>> a.add()
7
>>> a.mul()
12

5、重写

子类可以重新定义父类已有的属性和方法来对父类中同名的属性和方法进行覆盖。

可以在子类中直接调用父类的方法,即调用未绑定的父类方法。但会造成钻石继承的问题。

>>> class gozi(go):
	def __init__(self, x, y, z):
		go.__init__(self, x, y)#调用未绑定的父类方法
		self.z = z
	def add(self):
		return go.add(self) + self.z#调用未绑定的父类方法
	def mul(self):
		return self.x * self.y * self.z
	
>>> b = gozi(2, 3, 4)
>>> b.__dict__
{'x': 2, 'y': 3, 'z': 4}
>>> b.add()
9
>>> b.mul()
24

钻石继承

>>> class A:
	def __init__(self):
		print('这里是A')

>>> class B1(A):
	def __init__(self):
		A.__init__(self)
		print('这里是B1')

>>> class B2(A):
	def __init__(self):
		A.__init__(self)
		print('这里是B2')
	
>>> class C(B1, B2):
	def __init__(self):
		B1.__init__(self)
		B2.__init__(self)
		print('这里是C')
		
>>> c = C()
这里是A
这里是B1
这里是A
这里是B2
这里是C

 A被调用了两次,因为C调用B1、B2,B1、B2又分别调用了A。

为解决这个问题,可以使用super()函数

super()

在父类中搜索指定的方法,并自动绑定self参数

使用super函数去查找父类的方法,可以自动按照MRO顺序去搜索父类的相关方法,并且自动避免重复调用的问题。

>>> class A:
	def __init__(self):
		print('这里是A')
		
>>> class B1(A):
	def __init__(self):
		super().__init__()
		print('这里是B1')
		
>>> class B2(A):
	def __init__(self):
		super().__init__()
		print('这里是B2')
		
>>> class C(B1, B2):
	def __init__(self):
		super().__init__()
		print('这里是C')
		
>>> c = C()
这里是A
这里是B2
这里是B1
这里是C

MRO顺序

Method Resolution Order        方法解析顺序
出现同名的属性和方法python的查找覆盖顺序。

#mro方法
>>> C.mro()
[<class '__main__.C'>, <class '__main__.B1'>,
 <class '__main__.B2'>, <class '__main__.A'>, 
<class 'object'>]#object是所有类的基类
>>> B1.mro()
[<class '__main__.B1'>, <class '__main__.A'>, <class 'object'>]

#mro属性
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.B1'>, 
<class '__main__.B2'>, <class '__main__.A'>, 
<class 'object'>)
>>> B1.__mro__
(<class '__main__.B1'>, <class '__main__.A'>, <class 'object'>)

6、Mixin模式

一种设计模式(利用编程语言已有的特性,针对面向对象开发过程中反复出现的问题而设计出来的解决方案),Mixin直译理解就是混入、补充的意思,它是多继承的一种。在多继承中,查找顺序是按MRO继承链中的顺序进行的。在不修改原有类代码结构的前提下,向类中添加新方法。

一个简单Mixin案例

  稍微难点的Mixin案例

MRO继承链

运行结果

 

7、多态

同一个运算符、函数或对象在不同的场景下具有不同的作用效果。

>>> 3 + 5#运算符的多态
8
>>> 'L' + 'ove'
'Love'
>>> len('Love')#函数的多态
4
>>> len(['lala', 'jiji', 'xixi'])
3

(1)有关继承的多态

重写就是实现类继承的多态

正方形、圆形、三角形都继承自Shape类,都重写了构造函数和area()方法,即为多态的体现。

(2)自定义函数实现多态接口

 

向animal()函数中传入不同的对象,可以调用不同对象同样的方法,animal()则具有了多态性

(3)鸭子类型

鸭子类型(英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。

“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

并不关心对象是什么类型,到底是不是鸭子,只关心行为。

只要有animal()函数接收这个类的对象,然后并没有检查对象的类型,而是直接调用这个对象的intro和say方法。

python鸭子类型的灵活性在于它关注的是这个所调用的对象是如何被使用的,而没有关注对象类型的本身是什么。这里的自行车并不是动物,但是它有intro()方法和say()方法,他在这个地方就是一个动物了。

 8、“私有变量”

一种保护机制,就是指通过某种手段,使得对象中的属性或方法无法被外部所访问。

在python中,仅限从一个对象内部才能够访问的“私有变量”并不存在,引入了一种name mangling的机制(名字改变、名称改写、名称修饰)

语法就是在名字前面加上两个下横线。

python中“私有变量”的实现方式就是将类中的变量偷偷改了个名字,也就是name mangling机制,方法名也可以进行同样的操作。

名字改编是发生在类实例化对象的时候,如果试图通过动态添加属性的方式来添加一个“私有变量”是不可行的,添加的变量并不会被name mangling机制改名。

单个下横线开头的变量

仅供内部使用的变量,约定俗成的命名规则,看到该类变量不要随意的去访问、修改它。

单个下横线结尾的变量

当想使用python自带关键词作为变量的时候,可以使用下横线结尾以示区分。

9、效率提升之道

在python中类的属性是通过字典来进行存放的,但是使用字典虽然效率很高的,但是需要占用大量的空间,利用空间换时间。

若当一个类的对象只需要固定的几个属性,以后也不会有动态添加属性的需求,那么再利用字典来存放属性是对空间的浪费。针对这种情况,python设计了一个__slots__的类属性

__slots__类属性

 使用了__slots__类属性之后,无论是在实例化对象之后动态添加属性,还是在类的内部创建一个__slots__不包含的属性都是不被允许的。

使用了__slots__类属性之后,对象会划分一个固定大小的空间来存放指定的属性。__dict__属性也就没有了。

继承自父类的__slots__属性是不会在子类中生效,也会拥有__dict__属性。python只关注各个具体类中定义的__slots__属性

>>> class C:
	__slots__ = ['x', 'y']
	
>>> class E(C):
	pass

>>> e = E()
>>> e.x = 250
>>> e.y = 520
>>> e.z = 666
>>> e.__slots__
['x', 'y']
>>> e.__dict__
{'z': 666}

10、python会魔法——魔法方法

__init__(self[, ...])就是一个魔法方法,它可以在类实例化对象的时候自动进行调用

(1)__new__()方法

创建对象的方法

__new__(cls[, ...]),该方法在__init__()之前被调用的,事实上对象是由__new__()创建的,第一个参数cls是类。所以对象的实例化流程是先调用__new__()方法创建一个实例,然后将其传递给__init__()方法。

之所以这里能对不可变对象进行修改,是因为在实例对象被创建之前进行了拦截,然后对其进行了修改,最后才调用super().__new__()去创建真正的实例。
因为CapStr是继承自str的,所以由CapStr实例化的对象也继承了str的方法

(2)__del__(self)方法

 销毁对象时调用的方法

并不是使用了del就一定会触发__del__()方法,__del__()方法触发的条件是对象被销毁时它才会触发,python拥有垃圾回收机制garbage collection,意思是当检测到一个对象没有任何引用的时候才会将其销毁。
在该案例中,将c的引用赋值给了d,执行了del c之后,由于d还引用了原实例,所以并没有触发__del__()方法,在执行了del d之后,已经没有引用原实例的变量了,所以触发了垃圾回收机制,也就开始销毁原实例,也就触发了__del__()方法。

del方法可以通过创建一个该实例的新引用来推迟其销毁
方法一:创建一个全局变量将对象的引用传出去。

方法二:通过函数调用的形式(闭包),将对象的引用保存起来。
使用闭包将self保存在外部函数的x变量中,内部函数则是用来窃取这个self对象。
在创建对象时将闭包函数传入了实例中,而后在__del__()方法中调用闭包函数时是带参数self的,闭包就将这个self对象存储起来了。随后再次调用闭包,但是没有参数,于是闭包函数返回了之前保存下来的self对象。

(3)运算相关的魔法方法

__add__()方法
重写__add__()方法实现对加法运算的拦截,实现两字符串相加不是拼接而是计算字符数之和

__radd__()方法( r 反算数运算)

__radd__()方法调用前提:当两个对象相加的时候,①如果左侧的对象和右侧的对象不同类型,并且②左侧的对象没有定义__add__()方法,或者其__add__()返回Notlmplemented(表示该方法是未实现的),那么Python就会去右侧的对象中找查找③是否有__radd__()方法的定义。

__iadd__()方法( i 增强赋值运算)

如果+=左侧的对象没有实现__iadd__()方法,那么python会使用相应的__add__()方法和__radd__()方法来替代

 __int__()方法

位运算

与&                或|               非~           异或^            左移<<           右移>>

 __index__(self)方法

当对象作为索引值时会触发该方法。 

 

运算相关魔法方法合集

(4)属性访问相关的函数和魔法方法

python中为属性访问服务的BIF函数:hasaattr()、getattr()、setattr()、delattr()

hasaattr():检测对象是否有某个属性

getattr():获取对象中的某个属性值

setattr():设置对象中指定属性的值

delattr():删除对象中指定的属性

对应魔法方法

__getattr__(): 对应的是__getattribute__()魔法方法。

__getattr__()只有在用户试图去获取一个不存在的属性的时候才会触发。 

__setattr__()

直接对self.name进行赋值会出现无限递归的错误,因此应避免使用__setattr__()对属性进行直接赋值,可以使用对属性字典进行操作来实现,__delattr__()也会出现同样的问题。

11、索引、切片、迭代协议

(1)针对索引、切片的魔法方法__getitem__()、__setitem__()

__getitem__(self, index)魔法方法

__index__()魔法方法并不是当对象发生索引的时候触发的方法,而是当对象被作为索引值使用的时候才会触发。

当对象被索引的时候会调用__getitem__(self, index)魔法方法。
它既能响应单个下标的索引操作,又能支持代表范围的切片索引方式

slice():BIF函数,切片操作是该函数的语法糖

__setitem__()魔法方法

为索引或切片赋值的操作则会被__setitem__()魔法方法所拦截

从可迭代对象中获取元素也会触发__getitem__(self, index)魔法方法

(2)针对可迭代对象的魔法方法__iter__(self)、__next__(self)

对应的BIF函数为iter()、next()

如果一个对象定义了__iter__()魔法方法,那么他就是一个可迭代对象
如果一个可迭代对象定义了__next__()魔法方法,那么他就是一个迭代器

可迭代对象中是没有__next__()的,因此for语句对可迭代对象进行遍历执行的操作首先是将对象传入内置函数iter()中,以拿到一个相应的迭代器,而后利用__next__()魔法方法进行真正的迭代操作

对for语句进行模仿:

自己创造一个迭代器对象

12、代偿

(1)__contains__(self, item)

实现成员关系的检测,对应的运算符是in,notin

(2)代偿

以迭代为例,若未定义__iter__()、__next__()魔法方法,那么将对象放到迭代工具中时,python在找不到__iter__()、__next__()魔法方法的情况下,就会尝试去查找__getitem__()魔法方法。

 __contains__()魔法方法的代偿,若没有实现__contains__(),又使用in和notin进行成员关系判断,那么python会尝试查找__iter__()、__next__()魔法方法,使用迭代的方法去查找元素。

布尔测试,如果遇到bool()函数,python会寻找__bool__()魔法方法,如果未定义__bool__()魔法方法,那么python就会去寻找是否存在__len__()这个魔法方法的定义,如果有,那么__len__()魔法方法返回的值是非0,表示True,否则表示False

13、跟比较运算相关的魔法方法

将比较字符串的功能更改为比较字符串的长度

若不想让某个魔法方法生效,可以直接将其赋值为None

14、给人看还是给程序看

(1)__call__(self, [, args...])

python可以像调用函数一样去调用一个对象,要求就是需要这个对象的类中有__call__()魔法方法。

__call__()支持位置参数和关键字参数

定义__call__()魔法方法可以实现使用闭包实现的工厂函数的效果。

(2)跟字符串相关的魔法方法:__str__(self)、__repr__(self)

__str__()响应的是str()内置函数的魔法方法

__repr__()对应的内置函数为repr()

str()函数是将参数转换为字符串对象,是给人看的;repr()函数则是将对象转换为程序可执行的字符串,是给程序看的

eval():将字符串转换为语句执行,去掉引号。为repr()的反函数

这两个魔法方法返回的都是字符串类型

__repr__()这个魔法方法是可以对__str__()魔法方法进行代偿的,反过来不行

__str__()魔法方法定义的只能应用于对象出现在打印操作的顶层的情况,也就是对该对象直接进行打印

如果定义__repr__()魔法方法则可以避免这个问题

通过同时定义两个方法的实现,可以让对象在不同场景下支持不同的显示效果

15、property()

property()函数用于返回一个property属性对象

这个没有下横线的x就全权代理了_x,通过对x的访问和修改,都会影响到_x的值

使用__getattr__()、__setattr__()、__delattr__()三个魔法方法来实现以上案例的功能,很复杂。

装饰器是property()函数最经典的应用

将property()函数作为装饰器来使用,用于创建只读属性。原理是只赋值了property()的第一个参数,也就是实现获取的fget参数,另外两个参数为默认值None,表示不支持写入和删除。

用property()函数创建的属性对象拥有setter、getter、deleter三个方法

16、类方法和静态方法

(1)类方法@classmethod

专门用于绑定类的方法,使用@classmethod装饰器

funA:普通方法,绑定的是一个普通的对象object
funB:类方法,绑定的是一个类class
self参数与cls参数为约定俗成的命名,可以更改,但不建议。

类方法主要用于实现统计对象的数量或者通过创建一个列表来维护一个类对应的所有对象等需求

统计实例化对象的数量

如果创建跟类属性同名的实例属性,后者会覆盖类属性,但是此处使用了类方法get_count(),所以如果实例属性覆盖了类属性,依然不会对其有影响。

(2)静态方法@staticmethod

放在类里面的函数,跟普通函数的区别就是它可以放在类里面去,而且不需要绑定,即静态方法的参数不需要self,使用@staticmethod装饰器

使用静态方法模仿类方法实现统计实例化对象的数量的功能

使用了静态方法,如果实例属性覆盖了类属性,也不会对其有影响。

当操作不涉及类属性或者实例属性引用的时候,使用静态方法比较合适,类似于实现统计实例化对象的数量的功能则使用类方法更合适。

(3)对(1)中的统计实例化对象的数量功能进行优化

当涉及继承的时候,count的关系将会变得比较复杂,这时候引入add()类方法,可以避免混淆,不论子类或父类,哪个类去调用add()就会将其自身的类传进去,自增的count也是它们对应自身的count

17、描述符及property()实现原理

(1)描述符

定义:只要实现了__get__()、__set__()、__delete__()中任何一个或者多个方法的类,这个类就叫做描述符

拦截对象属性的读取、写入和删除的操作
__get__( self, instance, owner=None)
__set__(self, instance, value)
__delete__(self, instance)           要与__del__()魔法方法区分开

与魔法方法不同的是,它们管理的是其他类的属性,而不是自身类的属性

要使用描述符,只需要在另一个类中将描述符的实例化对象赋值给想要管理的属性

(2)使用描述符实现property案例的功能

property案例:

描述符实现:

(3)使用描述符创造一个property()函数

(4)实现getter()、setter()、deleter()三个方法


18、数据描述符、非数据描述符

对象方法绑定、静态方法、类方法以及类属性__slots__等都是基于描述符协议来实现的。

描述符只能用于类属性

描述符分类为数据描述符和非数据描述符,这是根据所实现的魔法方法的不同来进行划分的。

如果实现了__set__()、__delete__()任意一个方法,那么就是数据描述符

如果只实现了__get__()方法,则为非数据描述符

进行如此分类的原因在于优先级,当发生属性访问的时候,优先级从高到低依次是:数据描述符、实例对象属性、非数据描述符、类属性

>>> class D:
	def __get__(self, instance, owner):
		print('get')

		
>>> class C:
	x = D()

	
>>> c = C()
>>> c.x
get
>>> c.x = 'nihao'
>>> c.x #因为优先级问题,未打印数据描述符中的get,而是实例对象属性
'nihao'
>>> C.x #类属性优先级较低,因此打印的是非数据描述符的get
get

赋值操作属于类属性,被数据描述符拦截了

优先级是定义在__getattribute__()魔法方法的默认实现,__getattribute__()管理的是属性的获取

描述符的第四个魔法方法__set_name__(self, name, owner)

在实际的开发工作中,通过描述符拦截了一个属性,进行操作之后通常还需要进行访问、赋值、删除等操作,最后仍然是要写入到实例对象的属性中去,也就是在描述符中对实例对象的__dict__()字典进行操作,其中的关键就是instance参数代表的就是描述符所拦截的属性所在的实例对象。

实现在描述符中对实例对象的__dict__()字典进行操作的功能

以上的实现方法有一些瑕疵,于是就有了__set_name__()魔法方法

19、函数、方法、静态方法、类方法的底层实现原理

通过描述符实现的

但是,没听懂......

20、类装饰器

与函数装饰器的用法相似


类装饰器的作用就是在类被实例化对象之前对其进行拦截和干预

用类来做装饰器,用于装饰函数,统计函数被调用的次数

在调用say_hi()的过程中,由于@Counter相当于say_hi=Counter(say_hi),将say_hi()作为参数传递进了类Counter中,在Counter中将say_hi()函数改变为了类Counter的实例化对象,从而触发了__call__()魔法方法。

21、type()函数和__init_subclass__

虽然可以是用type()函数来检测对象类型,但是并不推荐,推荐使用的是isinstance(),因为isinstance()函数会考虑到子类的情况。

type()的一些小用法

type()第二种用法:根据传入的三个参数,返回一个新的type对象

class type(name, bases, dict, **kwds)
name:指定创造类的名字
bases:指定创造类的父类,为元组类型
dict:指定创造类的属性和方法,为字典类型
kwds:可选收集参数,当且仅当需要时,该收集参数将被传递给适当的元类机制(通常为__init_subclass_())




__init_subclass__(),python3.6新添加的类方法

用于加强父类对子类的管理,子类D定义完成之后,其父类的__init_subclass__()方法就会被触发

在子类中定义的x会被父类的__init_subclass__()方法所覆盖

当使用type()函数来构造class D这种继承了定义过__init_subclass__()的父类时,如果需要给__init_subclass__()传递参数,type()函数的第四个参数就排上了用场。

使用type()实现以上代码

type()第四个参数为搜集参数,可以多个参数同时传递

22、元类(metaclass)

元类是99%的用户都无需关注的终极魔法,如果你犹豫是否需要使用元类,那么你肯定是不需要的,因为真正需要用到元类的人,是不会纠结这个问题的。 

类是创造对象的模板,元类就是创造类的模板,type本身就是一个元类,所以它能够用来创造类。
类之间存在继承关系,元类也一样,所有元类都继承自type
想要创建一个元类,就需要让它继承自type

最简单的元类

如果没有MetaC()这个元类,直接执行type(C),返回的是<class 'type'>,而在以下代码中,相当于元类在类和type之间架上了MetaC()这个桥梁

__init__()并非实例化对象调用的第一个魔法方法,而是__new__(),现在来定义元类和类里面的__init__()和__new__()魔法方法

元类MetaC的__new__()方法是在类C定义完成的时候被触发的
类C的__new__()方法是在类实例化对象的时候被触发的

类C的super.__new__()调用的是object的new,而不是MetaC的new,因为当一个类没有指定父类的时候,就会直接找到object。不能将元类与父类搞混淆了,元类是比类更高一个级别。

在类里面的__call__()方法就是拦截对象被当做函数调用时候的操作,__call__()方法定义在元类中实现的是拦截类实例化对象的操作

23、元类的应用

(1)给所有的类添加一个属性

使用__new__()实现

使用__init__()实现

(2)对类名的定义规范做限制

(3)修改对象的属性值

把对象的所有字符串属性值都变为大写,重新定义的是__call__(),是因为要操作的是类实例化的过程,把它在实例化的过程中传入的属性值变为大写。

(4)限制类实例化时的参数传递方式

实现类在实例化对象的时候,只能通过关键字参数进行参数传递

(5)禁止一个类被实例化

如果类不能实例化对象,还能通过静态方法、类方法的形式来进行使用

(6)只允许实例化一个对象

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值