函数是对象
在Python中,一切皆为对象。我们之前常用的函数,本身也是一个实例化对象。
# ==== 函数也是对象 ==== def func(): pass print(func.__class__) # ==== 我们还可以为函数对象进行增加属性的操作:虽然没人这样做 ==== func.x = "随便写的: x" func.y = "随便写的: y" print(func.x) print(func.y) # ==== 执行结果 ==== Ps:可以看到,函数是类function的实例对象。这充分验证了Python中一切皆对象的概念。 """ <class 'function'> 随便写的: x 随便写的: y """
类也是对象
我们使用
class
定义出的类,本身也是某一个类的实例对象... 我知道这可能很颠覆三观。但是事实如此...
type
类是所有类的元类,即metaclass
...
# ==== 类也是对象 ==== class Foo(object): pass print(Foo.__class__) print(object.__class__) print(dict.__class__) print(list.__class__) # ==== 执行结果 ==== Ps:可以看见,不管是自定义的类,还是Python自带的类,甚至是object类,都是type类的实例对象。 """ <class 'type'> <class 'type'> <class 'type'> <class 'type'> """
什么是元类
经过上面的例子,我们可以看到所有类都是由
type
类进行实例化而得到的,因此。元类即是对类进行实例化的类。元类的作用:
1.用于生产出类对象
2.用于生产出类对象的实例对象
浅析class内部机制
一个类定义后发生的4件事
我们使用
class
定义一个类,实际底层都是由type
类进行实例化的。那么对于一个类定义后发生的事情总体有以下四步:1.拿到类名。
2.拿到类的基类。
3.执行类体代码,创建类的局部命名空间。
4.调用元类,默认为
type
类,得到实例化出的类对象。

# ==== 常规定义类 ==== class People(object): def __init__(self, name, age): self.name = name self.age = age def say(self): print("{0}:{1}".format(self.name, self.age)) # ==== 测试 ==== print(People.__dict__) p1 = People("Yunya",18) p1.say() # ==== 执行结果 ==== """ {'__module__': '__main__', '__init__': <function People.__init__ at 0x0000029FBF9DFF70>, 'say': <function People.say at 0x0000029FC7DA6700>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None} Yunya:18 """

# ==== 一个类定义后发生的4件事 ==== # 类有三大特征: # 1、类名 class_name = "People" # 2、类的基类 class_bases = (object,) # <-- 注意,必须逗号分隔。tuple类型 # 3、执行类体代码拿到类的命名空间 class_dic = {} # <-- 类的局部命名空间 class_body = """ def __init__(self,name,age): self.name = name self.age = age def say(self): print("{0}:{1}".format(self.name,self.age)) """ exec(class_body,{},class_dic) # 位置1:执行的代码,位置2:全局命名空间,位置3,执行代码期间出现的局部命名空间的变量全部存放于此 print(class_dic) # 该字典中存放了类中的变量名对应的内存地址。 # 4、调用元类 People = type(class_name,class_bases,class_dic) # ==== 测试 ==== p1 = People("Yunya",18) p1.say() # 可以成功执行! # ==== 执行结果 ==== Ps:虽然可以用这种方式来实例化出类对象,但是我们并不推荐这样做。相比于直接使用class机制,这样做法显得复杂并且对比class_dic与People.__dict__来看明显使用class机制会完善的多。 """ {'__init__': <function __init__ at 0x000001908A7B61F0>, 'say': <function say at 0x000001908AA0FF70>} Yunya:18 """

#exec:三个参数 #参数一:包含一系列python代码的字符串 #参数二:全局作用域(字典形式),如果不指定,默认为globals() #参数三:局部作用域(字典形式),如果不指定,默认为locals() #可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中 g={ 'x':1, 'y':2 } l={} exec(''' global x,z x=100 z=200 m=300 ''',g,l) print(g) #{'x': 100, 'y': 2,'z':200,......} print(l) #{'m': 300}
自定义元类
元类如何生产出类对象
我们继续探讨关于上节标黄的地方。元类如何生产出类对象。
元类生产出类对象会经过两个步骤:
1.调用 元类自己的
__new__
方法(创建出空的对象)2.调用 元类自己的
__init__
方法(将空对象构造为类对象,可以理解为为其__dict__
填充数据)
注意:
__new__
方法其实是在__init__
方法之前执行的。因为我们之前在学习__init__
方法中可以看到参数self是一个空对象,这个空对象实际上就是
__new__
制造出的。

# ==== 元类如何生产出类对象 ==== class MetaClass(type): # <-- 必须继承type类,才会被视为一个元类 """自定义元类""" def __new__(cls, *args, **kwargs): """ cls ---> 类本身(空的) args ---> ("类名",(类的继承关系,),{类的局部命名空间}) kwargs ---> 空 """ print("MetaClass.__new__ --> cls ",cls) print("MetaClass.__new__ --> args ",args) print("MetaClass.__new__ --> kwargs ",kwargs) # return super().__new__(cls,*args,**kwargs) # 注意,此时必须将属性全部传入。因为现在的对象是个空对象,或者可以采取下面的方式 return type.__new__(cls,*args,**kwargs) # 让父类 type 为我们造出空对象 def __init__(self,*args,**kwargs): """ cls ---> 类本身(空的) args ---> ("类名",(类的继承关系,),{类的局部命名空间}) kwargs ---> 空 """ print("MetaClass.__init__ --> self ",self) print("MetaClass.__init__ --> args ",args) print("MetaClass.__init__ --> kwargs ",kwargs) super(MetaClass, self).__init__(*args,**kwargs) # 让父类 __init__ 为我们将空对象转换为类对象 class People(object,metaclass=MetaClass): def __init__(self, name, age): self.name = name self.age = age def say(self): print("{0}:{1}".format(self.name, self.age)) # ==== 执行结果 ==== """ MetaClass.__new__ --> cls <class '__main__.MetaClass'> MetaClass.__new__ --> args ('People', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'People', '__init__': <function People.__init__ at 0x000001FC34DC7670>, 'say': <function People.say at 0x000001FC34DC7790>}) MetaClass.__new__ --> kwargs {} MetaClass.__init__ --> self <class '__main__.People'> MetaClass.__init__ --> args ('People', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'People', '__init__': <function People.__init__ at 0x000001FC34DC7670>, 'say': <function People.say at 0x000001FC34DC7790>}) MetaClass.__init__ --> kwargs {} """
元类如何生产出类对象的实例对象
当造好的类对象试图使用 类名() 的方式进行实例化,会发生以下几件事。
1.类对象调用元类下的
__call__
方法。2.元类下的
__call__
方法会去调用类对象下的__new__
方法创建空对象3.元类下的
__call__
方法会去调用类对象下的__init__
方法将空对象转换为类对象的实例对象4.元类下的
__call__
方法将返回造好的类对象的实例对象。
关于
__call__
方法,试图使用名字()
的方式将触发其实例化出自己类中的__call__
方法,有则执行,没有则抛出异常。比如我们定义的一个函数,使用
函数名()
的方式就代表是去调用元类type
中的__call__
方法,然后再到function
类中去执行其__new__
方法以及__init__
方法。

# ==== 有__call__方法 ==== class People(object): def __init__(self, name, age): self.name = name self.age = age def say(self): print("{0}:{1}".format(self.name, self.age)) def __call__(self, *args, **kwargs): print("调用了__call__") # ==== 测试 ==== p1 = People("Yunya",18) p1() # ==== 执行结果 ==== Ps:没有抛出异常了。 """ 调用了__call__ """

# ==== 无__call__方法 ==== class People(object): def __init__(self, name, age): self.name = name self.age = age def say(self): print("{0}:{1}".format(self.name, self.age)) # ==== 测试 ==== p1 = People("Yunya",18) p1() # ==== 执行结果 ==== Ps:由于People类中没有定义__call__方法,则其实例化对象不能使用 名字() 的方式进行调用 """ Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/learn/反射与自省.py", line 15, in <module> p1() TypeError: 'People' object is not callable """

# ==== 元类如何生产出类对象的实例对象 ==== class MetaClass(type): # <-- 必须继承type类,才会被视为一个元类 """自定义元类""" def __call__(self, *args, **kwargs): """ self --> 类对象本身(空的) <class '__main__.People'> args --> 传入的参数。 ('Yunya', 18) kwargs --> 空的,{} """ # # 构造出空对象 obj = self.__new__(self, *args, **kwargs) print(obj) # <__main__.People object at 0x0000020AEC3A02E0> print("__new__过后的对象的__dict__",obj.__dict__) # 将空对象转换为类对象People的实例对象 self.__init__(obj,*args,**kwargs) print("__init__过后的对象的__dict__",obj.__dict__) # 将类对象People的实例对象进行返回 return obj class People(object,metaclass=MetaClass): def __new__(cls, *args, **kwargs): return object.__new__(cls) def __init__(self, name, age): self.name = name self.age = age def say(self): print("{0}:{1}".format(self.name, self.age)) p1 = People("Yunya",18) # 相当于调用元类下的 __call__ print(p1.name) print(p1.age) # ==== 执行结果 ==== """ <__main__.People object at 0x0000020AEC3A02E0> __new__过后的对象的__dict__ {} __init__过后的对象的__dict__ {'name': 'Yunya', 'age': 18} Yunya 18 """
图片来源:
实例对象与类对象的查找顺序
类对象的属性查找顺序
现在该介绍终极版的属性查找顺序了。
细心的朋友应该发现,为什么类对象可以调用元类的
__call__
, 而类对象的实例对象却不能? 这里其实是有区别的。这里先介绍类对象的属性查找顺序。
类本身 --> 父类 --> object类 --> 元类

# ==== 类对象的属性查找顺序 ==== class MetaClass(type): # <-- 必须继承type类,才会被视为一个元类 """自定义元类""" name = "MetaClass" class A(object): # name = "A" pass class B(A): # name = "B" pass class C(B,metaclass=MetaClass): pass print(C.name) # ==== 执行结果 ==== """ MetaClass """
实例对象的属性查找顺序
实例对象还是通过
MRO
列表进行找。但是并不会找到元类中去
实例对象本身 --> 实例化出自己的类 --> 父类 --> object类
所以!实例对象是拿不到元类中的
__call__
的,故不能进行名字()
的使用!

# ==== 实例对象属性查找顺序 ==== class MetaClass(type): # <-- 必须继承type类,才会被视为一个元类 """自定义元类""" name = "MetaClass" class A(object): # name = "A" pass class B(A): # name = "B" pass class C(B,metaclass=MetaClass): pass c1 = C() print(c1.name) # ==== 执行结果 ==== """ Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/learn/反射与自省.py", line 19, in <module> print(c1.name) AttributeError: 'C' object has no attribute 'name' """
练习题
类的命名规则检测

class MetaClass(type): # <-- 必须继承type类,才会被视为一个元类 """自定义元类""" def __init__(self,*args,**kwargs): if not args[0].istitle(): raise NameError("类名必须是大写!") super(MetaClass,self).__init__(*args,**kwargs) class student(object,metaclass=MetaClass): pass # ==== 执行结果 ==== """ Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/learn/元类编程.py", line 11, in <module> class student(object,metaclass=MetaClass): File "C:/Users/Administrator/PycharmProjects/learn/元类编程.py", line 6, in __init__ raise NameError("类名必须是大写!") NameError: 类名必须是大写! """
类的文档信息检测

class MetaClass(type): # <-- 必须继承type类,才会被视为一个元类 """自定义元类""" def __init__(self,*args,**kwargs): if "__doc__" not in args[2]: raise SyntaxError("{0}类必须要有文档注释".format(args[0])) # SyntaxError 语法错误 super(MetaClass,self).__init__(*args,**kwargs) class student(object,metaclass=MetaClass): pass # ==== 执行结果 ==== """ Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/learn/元类编程.py", line 11, in <module> class student(object,metaclass=MetaClass): File "C:/Users/Administrator/PycharmProjects/learn/元类编程.py", line 6, in __init__ raise SyntaxError("{0}类必须要有文档注释".format(args[0])) # SyntaxError 语法错误 SyntaxError: student类必须要有文档注释 """
基于元类实现单例模式

# 单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间 # 如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了 #settings.py文件内容如下 # HOST='1.1.1.1' # PORT=3306 #方式一:定义一个类方法实现单例模式 import settings class Mysql: __instance=None def __init__(self,host,port): self.host=host self.port=port @classmethod def singleton(cls): if not cls.__instance: # 如果没有就实例化出一个,如果有就直接返回 cls.__instance=cls(settings.HOST,settings.PORT) # 进行 Mysql.__init__(settings.HOST,settings.PORT)) 并赋值 return cls.__instance obj1=Mysql('1.1.1.2',3306) obj2=Mysql('1.1.1.2',3306) print(obj1 is obj2) #False obj3=Mysql.singleton() obj4=Mysql.singleton() print(obj3 is obj4) #True #方式二:定制元类实现单例模式 import settings class Mymeta(type): def __init__(self,name,bases,dic): #定义类Mysql时就触发 # 事先先从配置文件中取配置来造一个Mysql的实例出来 self.__instance = object.__new__(self) # 产生对象 self.__init__(self.__instance, settings.HOST, settings.PORT) # 初始化对象 # 上述两步可以合成下面一步 # self.__instance=super().__call__(*args,**kwargs) super().__init__(name,bases,dic) def __call__(self, *args, **kwargs): #Mysql(...)时触发 if args or kwargs: # args或kwargs内有值 obj=object.__new__(self) self.__init__(obj,*args,**kwargs) return obj return self.__instance class Mysql(metaclass=Mymeta): def __init__(self,host,port): self.host=host self.port=port obj1=Mysql() # 没有传值则默认从配置文件中读配置来实例化,所有的实例应该指向一个内存地址 obj2=Mysql() obj3=Mysql() print(obj1 is obj2 is obj3) obj4=Mysql('1.1.1.4',3307) #方式三:定义一个装饰器实现单例模式 import settings def singleton(cls): #cls=Mysql _instance=cls(settings.HOST,settings.PORT) def wrapper(*args,**kwargs): if args or kwargs: obj=cls(*args,**kwargs) return obj return _instance return wrapper @singleton # Mysql=singleton(Mysql) class Mysql: def __init__(self,host,port): self.host=host self.port=port obj1=Mysql() obj2=Mysql() obj3=Mysql() print(obj1 is obj2 is obj3) #True obj4=Mysql('1.1.1.3',3307) obj5=Mysql('1.1.1.4',3308) print(obj3 is obj4) #False