加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Python > 正文

Python 元类编程

发布时间:2020-12-20 10:09:13 所属栏目:Python 来源:网络整理
导读:一切皆对象 函数是对象 在Python中,一切皆为对象。我们之前常用的函数,本身也是一个实例化对象。 # ==== 函数也是对象 ==== def func(): pass print (func. __class__ ) ==== 我们还可以为函数对象进行增加属性的操作:虽然没人这样做 ==== func.x = " 随

一切皆对象

函数是对象

  在Python中,一切皆为对象。我们之前常用的函数,本身也是一个实例化对象。

# ==== 函数也是对象 ====

def func():
    pass

print(func.__class__)

 ==== 我们还可以为函数对象进行增加属性的操作:虽然没人这样做 ====

func.x = "随便写的: x"
func.y = 随便写的: y"

print(func.x)
(func.y)

 ==== 执行结果 ==== Ps:可以看到,函数是类function的实例对象。这充分验证了Python中一切皆对象的概念。

"""
<class 'function'>
随便写的: x
随便写的: y
"""

类也是对象

  我们使用class定义出的类,本身也是某一个类的实例对象... 我知道这可能很颠覆三观。但是事实如此...

  type类是所有类的元类,即metaclass...

 ==== 类也是对象 ====

class Foo(object):
    print(Foo.)
print(object.print(dict.print(list. ==== 执行结果 ====  Ps:可以看见,不管是自定义的类,还是Python自带的类,甚至是object类,都是type类的实例对象。


<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
"""

什么是元类

  经过上面的例子,我们可以看到所有类都是由type类进行实例化而得到的,因此。元类即是对类进行实例化的类。

  元类的作用:

    1.用于生产出类对象

    2.用于生产出类对象的实例对象

浅析class内部机制

一个类定义后发生的4件事

  我们使用class定义一个类,实际底层都是由type类进行实例化的。那么对于一个类定义后发生的事情总体有以下四步:

    1.拿到类名。

    2.拿到类的基类。

    3.执行类体代码,创建类的局部命名空间。

    4.调用元类,默认为type类,得到实例化出的类对象。

 ==== 常规定义类 ====

 People(object):

    def __init__(self,name,age):
        self.name = name
        self.age = age

     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,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()
p1.say()   可以成功执行!

 ==== 执行结果 ==== Ps:虽然可以用这种方式来实例化出类对象,但是我们并不推荐这样做。相比于直接使用class机制,这样做法显得复杂并且对比class_dic与People.__dict__来看明显使用class机制会完善的多。


{'__init__': <function __init__ at 0x000001908A7B61F0>,'say': <function say at 0x000001908AA0FF70>}
Yunya:18
"""
一个类定义后发生的4件事

exec:三个参数

参数一:包含一系列python代码的字符串

参数二:全局作用域(字典形式),如果不指定,默认为globals()

参数三:局部作用域(字典形式),如果不指定,默认为locals()

可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中
g={
    'x':1,y':2
}
l={}

exec('''
global x,z
x=100
z=200

m=300
'''print(g) {'x': 100,'y': 2,'z':200,......}
print(l) {'m': 300}
exec参数补充

自定义元类

元类如何生产出类对象

  我们继续探讨关于上节标黄的地方。元类如何生产出类对象。

  元类生产出类对象会经过两个步骤:

    1.调用 元类自己的__new__ 方法(创建出空的对象)

    2.调用 元类自己的__init__ 方法(将空对象构造为类对象,可以理解为为其__dict__填充数据)

  注意:

    __new__方法其实是在__init__方法之前执行的。因为我们之前在学习__init__方法中可以看到参数self是一个空对象,

    这个空对象实际上就是__new__制造出的。

 ==== 元类如何生产出类对象 ====

class MetaClass(type):   <-- 必须继承type类,才会被视为一个元类
    自定义元类"""

    __new__(cls,*args,**kwargs):
        
        cls --->  类本身(空的)
        args --->   ("类名",(类的继承关系,),{类的局部命名空间})
        kwargs --->  空
        """
        MetaClass.__new__ --> cls nMetaClass.__new__ --> args nMetaClass.__new__ --> kwargs n return super().__new__(cls,**kwargs) #  注意,此时必须将属性全部传入。因为现在的对象是个空对象,或者可以采取下面的方式
        return type. 让父类 type 为我们造出空对象

    __init__(self,1)">MetaClass.__init__ --> self nMetaClass.__init__ --> args nMetaClass.__init__ --> kwargs n__init__(*args,1)"> 让父类 __init__ 为我们将空对象转换为类对象


class People(object,metaclass=MetaClass):

    
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','say': <function People.say at 0x000001FC34DC7790>})
MetaClass.__init__ --> kwargs 
 {}
"""
类如何生产出类对象

img

元类如何生产出类对象的实例对象

  当造好的类对象试图使用 类名() 的方式进行实例化,会发生以下几件事。

    1.类对象调用元类下的 __call__ 方法。

    2.元类下的__call__方法会去调用类对象下的__new__方法创建空对象

    3.元类下的__call__方法会去调用类对象下的__init__方法将空对象转换为类对象的实例对象

    4.元类下的__call__方法将返回造好的类对象的实例对象。

  关于__call__方法,试图使用 名字() 的方式将触发其实例化出自己类中的 __call__ 方法,有则执行,没有则抛出异常。

  比如我们定义的一个函数,使用函数名()的方式就代表是去调用元类type中的__call__方法,然后再到function类中去执行其__new__方法以及__init__方法。

 ==== 有__call__方法 ====

__call__(self,1)">kwargs):
        调用了__call__)
p1()

 ==== 执行结果 ==== Ps:没有抛出异常了。


调用了__call__
"""
有__call__方法

 ==== 无__call__方法 ====

 ==== 执行结果 ==== 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
"""
无__call__方法

 ==== 元类如何生产出类对象的实例对象 ====


        self    -->  类对象本身(空的)  <class '__main__.People'>
        args    -->  传入的参数。  ('Yunya',18)
        kwargs  -->  空的,{}
         # 构造出空对象
        obj = self.__new__(self,1)">kwargs)
        print(obj)  <__main__.People object at 0x0000020AEC3A02E0>
        __new__过后的对象的__dict__)
         将空对象转换为类对象People的实例对象
        self.__init__(obj,1)">__init__过后的对象的__dict__ 将类对象People的实例对象进行返回
        return obj



return object.__new__(cls)

     相当于调用元类下的 __call__
(p1.name)
(p1.age)


<__main__.People object at 0x0000020AEC3A02E0>
__new__过后的对象的__dict__ {}
__init__过后的对象的__dict__ {'name': 'Yunya','age': 18}
Yunya
18
"""
元类如何生产出类对象的实例对象

img

  图片来源:深入理解Python元类(原创)

实例对象与类对象的查找顺序

类对象的属性查找顺序

  现在该介绍终极版的属性查找顺序了。

  细心的朋友应该发现,为什么类对象可以调用元类的 __call__,而类对象的实例对象却不能? 这里其实是有区别的。这里先介绍类对象的属性查找顺序。

  类本身 --> 父类 --> object类 --> 元类

 ==== 类对象的属性查找顺序 ====

"""
    name = MetaClass A(object):
     name = "A"
     B(A):
     name = "B"
    class C(B,1)">MetaClass):
    (C.name)


MetaClass
"""
类对象的属性查找顺序

img

实例对象的属性查找顺序

  实例对象还是通过MRO列表进行找。但是并不会找到元类中去

  实例对象本身 --> 实例化出自己的类 --> 父类 --> object类

  所以!实例对象是拿不到元类中的 __call__ 的,故不能进行 名字() 的使用!

 ==== 实例对象属性查找顺序 ====

pass

c1 = C()
(c1.name)

"""
实例对象属性查找顺序

img

练习题

类的命名规则检测

"""

  kwargs):

        if not args[0].istitle():
            raise NameError(类名必须是大写!)

        super(MetaClass,1)">kwargs)


class student(object,1)">
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: 类名必须是大写!
"""
类的命名规则检测

类的文档信息检测

if __doc__" not in args[2]:
            raise SyntaxError({0}类必须要有文档注释".format(args[0]))   SyntaxError 语法错误

        super(MetaClass,in __init__
    raise SyntaxError("{0}类必须要有文档注释".format(args[0]))  # SyntaxError 语法错误
SyntaxError: student类必须要有文档注释
"""
类的文档信息检测

基于元类实现单例模式

 单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间
# 如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了settings.py文件内容如下 HOST='1.1.1.1' PORT=3306

方式一:定义一个类方法实现单例模式

import settings

 Mysql:
    __instance=None
    host
        self.port=port

    @classmethod
     singleton(cls):
        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(print(obj1 is obj2) False

obj3=Mysql.singleton()
obj4=Mysql.singleton()
print(obj3 is obj4) True


方式二:定制元类实现单例模式
 Mymeta(type):
    定义类Mysql时就触发
         事先先从配置文件中取配置来造一个Mysql的实例出来
        self.__instance = object.__new__(self)   产生对象
        self.__init__(self.__instance,settings.HOST,settings.PORT)   初始化对象
         上述两步可以合成下面一步
         self.__instance=super().__call__(*args,**kwargs)
        super().(name,dic)

    Mysql(...)时触发
        if args or kwargs:  args或kwargs内有值
            obj=object.(self)
            self.kwargs)
             obj
        return self.__instance

class Mysql(metaclass=Mymeta):
    port

obj1=Mysql()  没有传值则默认从配置文件中读配置来实例化,所有的实例应该指向一个内存地址
obj2=Mysql()
obj3=Mysql()
is obj2 is obj3)
obj4=Mysql(1.1.1.4)


方式三:定义一个装饰器实现单例模式
def singleton(cls): cls=Mysql
    _instance=cls(settings.HOST,settings.PORT)

    def wrapper(*args,1)">or kwargs:
            obj=cls(*args,1)"> _instance
     wrapper


@singleton  Mysql=singleton(Mysql)
 Mysql:
    port

obj1=Mysql()
obj2=is obj3) True

obj4=Mysql(1.1.1.3)
obj5=Mysql(False
基于元类实现单例模式

?

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读