首页 > 其他 > 详细

第七章 面向对象(13):元类

时间:2019-07-19 00:34:46      阅读:58      评论:0      收藏:0      [点我收藏+]

7.16 元类 (metaclass) ,一切皆对象

参考

元类介绍

  • 储备知识:exec的使用
    • 参数1:字符串形式的命令
    • 参数2:全局作用域(字典形式),如果不指定默认就使用globals()
    • 参数3:局部作用域(字典形式),如果不指定默认就使用locals()

exec可以在引号里声明一个名字,这个名字产生到局部作用域

g = {
    'x':1,
    'y':2
}

l = {}

# 我们可以把exec当成一个函数来执行
exec("""
global x, m
x = 10
m = 100

z = 3
""", g, l)

print(g['x'], g['y'], g['m'])
print(l['z'])

执行结果:

10 2 100
3
  • python当中一切皆对象,那么对象可以怎么用? 只要是对象,就可以有下面4中特性:
    • 都可以被引用,x = obj ,这个obj可以是对象,也可以是模块
    • 都可以当做函数的参数传入
    • 都可以当做函数的返回值
    • 都可以当做容器类的元素, l = [func,time模块,obj,int,str] 都可以当做元素
# 实际上python中的类也是对象,Foo = type() 的实例化对象
class Foo:
    pass
    
obj = Foo()
print(type(obj))
print(type(Foo))

# 用来产生类的类称之为元类,默认所有用class定义的类,他们的元类是type

介绍了以上概念,我们来说明什么是元类 上例中的type()就是元类。 元类:用来定义类的类

所以定义类有2中方式:
+ 方式1:class关键字定义
+ 方式2:type关键字定义

方式1:

class Chinese:  # Chinese = type(...)
    country = 'China'
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def talk(self):
        print('%s is talking' % self.name)

方式2:我们用元类定义一个类,再实例化 类的3要素:类名,类的父类们,类的命名空间

# 定义类的3要素:类名,类的父类们,类的命名空间
class_name = 'Chinese'  # 类名
class_bases = (object,)  # 继承
class_body = """
country = 'China'

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

def talk(self):
    print('%s is talking' % self.name)
"""  # 注意内容要贴左边写

class_dic = {}
exec(class_body, globals(), class_dic)  # 用exec来执行
# print(class_dic)  # obj.__dict__就是返回local命名空间的属性

Chinese1 = type(class_name, class_bases, class_dic)  # 由元类实例化了一个类(注意,不是对象),效果相当于方式1的代码内容

obj1 = Chinese1('aaa', 11)  # 实例化对象
print(obj1.name, obj1.age)  # 输出结果:aaa 11

自定义元类的创建

自定义元类来控制类的行为。

自定义元类一般只修改一部分,所以一般情况下继承type

class Mymeta(type):
    def __init__(self, class_name, class_bases, class_dic):  # 上一节中type实例化类的时候需要指定类的三要素,所以我们这里也需要初始化3个参数
        print(class_name)  # 可以自定义类的行为
        print(class_bases)  # 可以自定义类的行为
        print(class_dic)  # 可以自定义类的行为
        if not class_name.istitle():  # 自定义行为,强制首字母大写
            raise TypeError('类名的首字母必须是大写')  # 异常处理
            
        print(class_dic)
        if '__doc__' not in class_dic or class_dic['__doc__'].isspace():  # 自定义注释规则
            raise TypeError('类必须有注释')
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)
        

# 首字母大写,有注释,这个类可以创建
class Chinese(object, metaclass=Mymeta):  # 默认值:Chinese(object, metaclass=type)
    '''
    comment
    '''
    country = 'China'
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def talk(self):
        print('%s is talking' % self.name)
        
# class ch(metaclass=Mymeta):  # 首字母不是大写会报错
#     pass

# class Ch(metaclass=Mymeta):  # 没有注释会报错
#     pass

自定义元类控制类的实例化行为

只是储备 ‘__call__‘ 方法,正常情况下对象不能直接被调用,定义这个方法就能调用了。 这个方法会在调用对象(类也是元类的对象)的时候触发

  • 调用类的时候会触发__call__,元类中的这个方法会做的3件事(即初始化对象的时候):
    • 第一:造一个空对象
    • 第二:初始化对象
    • 第三:返回对象
class Mymeta(type):  # 元类
    def __init__(self, class_name, class_bases, class_dic):  # 上一节中type实例化类的时候需要指定类的三要素,所以我们这里也需要初始化3个参数
        if not class_name.istitle():  # 自定义行为,强制首字母大写
            raise TypeError('类名的首字母必须是大写')  # 异常处理
        if '__doc__' not in class_dic or class_dic['__doc__'].isspace():  # 自定义注释规则
            raise TypeError('类必须有注释')
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)

    def __call__(self, *args, **kwargs):  # 元类下的类在实例化对象的时候会调用这个方法
        print("这是元类的__call__")
        # 造对象
        obj = object.__new__(self)  # 用object类的方法__new__来造对象的方法,这个只能在元类中使用
        # 初始化
        self.__init__(obj, *args, **kwargs)  # 传入参数
        # 返回对象
        return obj


# 首字母大写,有注释,这个类可以创建
class Chinese(object, metaclass=Mymeta):  # 默认值:Chinese(object, metaclass=type)
    '''
    comment
    '''
    country = 'China'

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

    def talk(self):
        print('%s is talking' % self.name)

    def __call__(self, *args, **kwargs):
        print('这是Chinese的__call__')


# 调用就是加'()'执行
# 调用了类Chinese(本质是对象),所以触发了__call__
# 调用对象(类),实际上是执行对象的类(元类)的__call__方法
c1 = Chinese('aaa', 18)  # 相当于执行 Chinese.__call__(self,'aaa', 18)
print(c1.__dict__)

c1()  # 调用对象,实际上是执行对象的类的__call__方法

自定义元类控制类的实例化行为的应用

我们以单例模式来说明:

x = 1  # 用一切皆对象的观点,实际上是 x = int(1)
y = 1  # 同样是 y = int(1)

我们知道在python中上述代码的变量(本质其实是对象),因为value相同,所以内存地址是相同的。(参考小数据池)

那么,在python中,内容相同的对象我们可以让他们用同样的内存地址吗? 答案是肯定的,我们以下例来看一下: 单例模式: 实现方法1:

class MySQL:

    __instance = None
    
    def __init__(self):  # 这个初始化方式生成的对象都是一样的
        self.host = '127.0.0.1'
        self.port = 3306
        
    @classmethod
    def singleton(cls):  # 所以我们用这个方法实例化对象,可以让实例化的对象都是同一个对象
        if not cls.__instance:
            obj = cls()
            cls.__instance = obj
        return cls.__instance   

obj1 = MySQL.singleton()  # 内存地址都相同
obj2 = MySQL.singleton()

实现方式2:元类的方式

class Mymeta(type):  # 元类
    def __init__(self, class_name, class_bases, class_dic):
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)
        self.__instance = None  #

    def __call__(self, *args, **kwargs):  # 元类下的类在实例化对象的时候会调用这个方法
        print("这是元类的__call__")
        if not self.__instance:
            obj = object.__new__(self)  # 实例化空对象
            self.__init__(obj)  # 这个实例化的空对象传入空对象作为参数
            self.__instance = obj
        return self.__instance
        # 返回对象
        return obj


class MySQL(object, metaclass=Mymeta):

    def __init__(self):  # 这个初始化方式生成的对象都是一样的
        self.host = '127.0.0.1'
        self.port = 3306


obj1 = MySQL()
obj2 = MySQL()

print(obj1)
print(obj2)

第七章 面向对象(13):元类

原文:https://www.cnblogs.com/py-xiaoqiang/p/11210487.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!