Time will tell.
一、元类
元类用于创建类的类。在 python 中,万物皆对象,类当然也是对象。
对象是通过类实例化产生的,如果类也是对象的话,必然类对象也是由另一个类实例化产生的。
class Person:
pass
p = Person()
print(type(p), type(Person), type(object))
# <class '__main__.Person'> <class 'type'> <class 'type'>
# Person类是通过type类实例化产生的(object类内部是由C语言实现的)
# 直接调用type类来产生类对象(默认情况下所有类的元类都是type)
class Student:
pass
print(type(Student))
# <class 'type'>
二、类的基本组成
- 类的名字(字符类型)
- 类的父类们 (是一个元组或列表)
- 类的名称空间(字典)
三、元类的目的
可以高度地自定义类,例如控制一个类的名字必须以大驼峰的方式来书写。
类也是对象,也有自己的类。
创建类对象做一些限制
- 想到了初始化方法,我们只要找到了类对象的类(或者说元类),覆盖其中 init方法就能实现需求。
- 当然我们不能修改源代码,所以应该继承type来编写自己的元类,同时覆盖 init来完成需求。
'''
只要继承了type, 那么这个类就变成了一个元类
'''
class MyType(type): # 定义一个元类
def __init__(self, cls_name, bases, dict):
print(cls_name, bases, dict)
# Pig () {'__module__': '__main__', '__qualname__': 'Pig'}
if not cls_name.istitle():
raise Exception("好好写类名!")
super().__init__(cls_name, bases, dict)
pass
# class pig(metaclass=MyType): # 为Pig类指定了元类为MyType
# 直接报错,Exception: 好好写类名!
class Pig(metaclass=MyType):
pass
# # MyType("pig", (), {}) # Exception: 好好写类名!
print(MyType("Pig", (), {}))
# <class '__main__.Pig'>
元类中的 call 方法
-
当你调用类对象时,会自动执行元类中的 call方法,并将这个类作为第一个参数传入,以及后面的一堆参数。
-
覆盖元类中的call之后,这个类就无法产生对象对象无法实例化,必须调用super().call()来完成对象的创建并返回其返回值。
call与 init 的使用场景
- 想要控制对象的创建过程用 call
- 想要控制类的创建过程用 init
'''
需求:想要把对象的所有属性变成大写
'''
class MyType(type):
def __call__(self, *args, **kwargs):
new_args = [item.upper() for item in args]
return super().__call__(*new_args, **kwargs)
class Person(metaclass=MyType): # ---> Person = MyType("Person", (), {})
def __init__(self, name):
self.name = name
p = Person('jack')
print(p.name)
# JACK
# 要求创建对象时必须以关键字传参
# 覆盖元类的__call__
# 判断有没有传非关键字参数,有就不行
class MyMeta(type):
# def __call__(self, *args, **kwargs):
# obj = object.__new__(self)
# self.__init__(obj, *args, **kwargs)
# return obj
def __call__(self, *args, **kwargs):
if args:
raise Exception("非法传参(只能以关键字的形式传参!)")
return super().__call__(*args, **kwargs)
class MyClass(metaclass=MyMeta):
def __init__(self, name):
self.name = name
# my_class_obj = MyClass('123')
# # 报错Exception: 非法传参(只能以关键字的形式传参!)
my_class_obj = MyClass(name='123')
print(my_class_obj)
# <__main__.MyClass object at 0x000002161DD187B8>
一旦覆盖了call必须调用父类的call方法来产生对象并返回这个对象。
补充 new方法
当你要创建类对象时( 类 + () ),会首先执行元类中的 new方法,拿到一个空对象,然后会自动调用 init方法来对这个类进行初始化操作。
class MyMeta(type):
# def __call__(self, *args, **kwargs):
# obj = object.__new__(self)
# self.__init__(obj, *args, **kwargs)
# return obj
pass
如果你覆盖了 new方法,则必须保证 new方法必须有返回值,且必须是对应的类对象。
class Meta(type):
def __new__(cls, *args, **kwargs):
print(cls) # 元类自己
print(args) # 创建类需要的几个参数 类名,基类,名称空间
print(kwargs) # 空的
print("__new__ run")
# return super().__new__(cls,*args,**kwargs) # 等同于下面这句
obj = type.__new__(cls, *args, **kwargs)
return obj
def __init__(self, a, b, c):
super().__init__(a, b, c)
print("__init__ run")
class A(metaclass=Meta):
pass
print(A)
# <class '__main__.Meta'>
# ('A', (), {'__module__': '__main__', '__qualname__': 'A'})
# {}
# __new__ run
# __init__ run
# <class '__main__.A'>
new和 init都可以实现控制类的创建过程,但 init更简单。
四、最后来说说单例设计模式
1、设计模式用于解决某种固定问题的套路,如:MVC、MTV…
2、单例指的是一个类只能产生一个对象,可以节省空间。
3、为什么要用单例:
- 是为了节省空间,节省资源。
- 当一个类的所有对象属性全部相同时则没有必要创建多个对象。
class Single(type):
def __call__(self, *args, **kwargs):
if hasattr(self, 'obj'): # 判断是否存在已经有了的对象
return getattr(self, 'obj') # 有就把那个对象返回
obj = super().__call__(*args, **kwargs) # 没有则创建
print("new 了")
self.obj = obj # 并存入类中
return obj
class Student(metaclass=Single):
def __init__(self, name):
self.name = name
class Person(metaclass=Single):
pass
# 只会创建一个对象
Person() # 只会执行一次
# new 了
Person()
Person()
Person()
Person()
...
好啦,本章内容今天就分享到这里了。如果你还对后续内容感兴趣,或是对Python实例练习题、面试题、自动化软件测试感兴趣的话可以加入我们175317069一起学习。有各项学习资源发放,更有行业深潜多年的测试人技术分析讲解。期待你的加入!
最后祝愿你能成为一名优秀的工程师!
欢迎【评论】、【点赞】、【关注】~
Time will tell.(时间会证明一切)
网友评论