梦想是指引我们前行的星光,无论夜有多黑,它总能照|

园龄:粉丝:关注:

📂python
🔖元类
2021-01-25 16:16阅读: 102评论: 0推荐: 0

元类 (exec和__call__)

目录:

  • 元类介绍
  • class关键字创建类的流程分析
  • 自定义元类控制类StanfordTeacher的创建
  • 自定义元类控制类StanfordTeacher的调用
  • 再看属性查找

一 元类介绍

如果一切皆为对象,那么类StanfordTeacher本质也是一个对象,既然所有的对象都是调用类得到的,那么StanfordTeacher必然也是调用了一个类得到的,这个类称为元类

于是我们可以推导出===>产生StanfordTeacher的过程一定发生了:StanfordTeacher=元类(...)

 

 

type元类

  • 类本身也是对象, 是通过实例化元类得到的对象, 元类是类的类
  • python默认的元类是type, 也可以自定义元类来实现控制类的创建,控制类的调用
  • 通过type元类来创建类 MyClass = type(class_name, class_bases, class_dict)
    • class_name 类名
    • class_bases 父类, 以元祖形式传入
    • class_dict 类的属性, 以字典的形式传入 (类的名称空间)
复制代码
# 用type元类来创建一个Chinese类
def __init__(self, name, gender, age):
    self.name = name
    self.gender = gender
    self.age = age

class_name = 'Chinese'
class_bases = (object,)
class_dict = {'country': 'China', '__init__': __init__}

Chinese = type(class_name, class_bases, class_dict)

c1 = Chinese('bigb', 'male', 18)

print(c1.name)  # bigb
print(c1.country)   # China
复制代码

 

二 class关键字创建类的流程分析

class关键字帮我们创建一个类应该细分为以下四个过程

创建类的两种方式

方式一:使用class关键字

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

方式二:就是手动模拟class创建类的过程):将创建类的步骤拆分开,手动去创建

复制代码
#准备工作:

#创建类主要分为三部分

  1 类名

  2 类的父类

  3 类的__dict__


#类名
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)
"""
复制代码

步骤一(先处理类体 --> 名称空间):类定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以实现定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程相似,只是后者将__开头的属性变形),生成类的局部空间名称,即填充字典

class_dic={}
exec(class_body,globals(),class_dic)


print(class_dic)
#{'country': 'China', 'talk': <function talk at 0x101a560c8>, '__init__': <function __init__ at 0x101a56668>}

步骤二:调用元类type(也可以自定义)来产生类Chinense

复制代码
Foo=type(class_name,class_bases,class_dic) #实例化type得到对象Foo,即我们用class定义的类Foo


print(Foo)
print(type(Foo))
print(isinstance(Foo,type))
'''
<class '__main__.Chinese'>
<class 'type'>
True
'''
复制代码

 

四 自定义元类控制类StanfordTeacher的创建

一个类没有声明自己的元类,默认他的元类就是type,除了使用内置元类type,我们也可以通过继承type来自定义元类,然后使用metaclass关键字参数为一个类指定元类.

复制代码
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    pass

# StanfordTeacher=Mymeta('StanfordTeacher',(object),{...})
class StanfordTeacher(object,metaclass=Mymeta): 
    school='Stanford'

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

    def say(self):
        print('%s says welcome to the Stanford to learn Python' %self.name)
复制代码

 

自定义元类可以控制类的产生过程,类的产生过程其实就是元类的调用过程,即StanfordTeacher=Mymeta('StanfordTeacher',(object),{...}),调用Mymeta会先产生一个空对象StanfordTeacher,然后连同调用Mymeta括号内的参数一同传给Mymeta下的__init__方法,完成初始化,于是我们可以

复制代码
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    def __init__(self,class_name,class_bases,class_dic):
        # print(self) #<class '__main__.StanfordTeacher'>
        # print(class_bases) #(<class 'object'>,)
        # print(class_dic) #{'__module__': '__main__', '__qualname__': 'StanfordTeacher', 'school': 'Stanford', '__init__': <function StanfordTeacher.__init__ at 0x102b95ae8>, 'say': <function StanfordTeacher.say at 0x10621c6a8>}
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)  # 重用父类的功能

        if class_name.islower():
            raise TypeError('类名%s请修改为驼峰体' %class_name)

        if '__doc__' not in class_dic or len(class_dic['__doc__'].strip(' \n')) == 0:
            raise TypeError('类中必须有文档注释,并且文档注释不能为空')

# StanfordTeacher=Mymeta('StanfordTeacher',(object),{...})
class StanfordTeacher(object,metaclass=Mymeta): 
    """
    类StanfordTeacher的文档注释
    """
    school='Stanford'

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

    def say(self):
        print('%s says welcome to the Stanford to learn Python' %self.name)
复制代码

 

五 自定义元类控制类StanfordTeacher的调用

 调用一个对象,就是触发对象所在类中的__call__方法的执行

 类本身也是个对象, 因此在调用类实例化对象的时候, 就会触发元类当中的__call__ 方法

复制代码
class MyMeta(type):
    def __call__(self, *args, **kwargs):
        # 产生一个空对象
        obj = self.__new__(self)  # self是类对象

        # 初始化空对象
        self.__init__(obj, *args, **kwargs)

        # 返回初始化好的对象
        return obj


class Chinese(object, metaclass=MyMeta):  # foo = MyMeta('foo', (object, ) {...})
    country = 'China'

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

    def kongfu(self):
        print('降龙十八掌!')


# 这里调用了类对象Chinese, 因此会触发Chinese的类(元类)中的__call__方法
c1 = Chinese('bigb', 'male', 18)

print(c1.name)

'''
1. __call__中,会调用chinese内的__new__生成了一个空对象
2. __call__中,会调用Chinese内的__init__初始化这个空对象 
3. __call__返回了这个对象,并赋值给了c1
'''
复制代码

 

现在 我们可以在此基础上通过修改 __call__ 的逻辑从而控制类的调用过程 

复制代码
# 通过元类让Chinese类实例化出来的对象的属性变为私有属性
class Mymeta(type):
    def __init__(self,class_name,class_bases,class_dic):
        #控制类Foo的创建
        super(Mymeta,self).__init__(class_name,class_bases,class_dic)

    def __call__(self, *args, **kwargs):
        #控制Foo的调用过程,即Foo对象的产生过程
        obj = self.__new__(self)
        print(self)
        print(self.__new__ is object.__new__)
        self.__init__(obj, *args, **kwargs)
     # 将对象的属性变成私有属性(对象._类__属性名)
        obj.__dict__={'_%s__%s' %(self.__name__,k):v for k,v in obj.__dict__.items()}
     #obj.__dict__ = {f'_{self.__name__}__{k}': v for k, v in obj.__dict__.items()}
return obj class Foo(object,metaclass=Mymeta): # Foo=Mymeta(...) def __init__(self, name, age,sex): self.name=name self.age=age self.sex=sex obj = Foo('SS',11,'MALE') print(obj.__dict__)
复制代码

 

五 再看属性查找

属性查找应该分成两层,一层是对象层(基于c3算法的MRO)的查找,另外一个层则是类层(即元类层)的查找

#查找顺序:
#1、先对象层:StanfordTeacher->Foo->Bar->object
#2、然后元类层:Mymeta->type


Mymeta下的__call__里的self.__new__在StanfordTeacher、Foo、Bar里都没有找到__new__的情况下,会去找object里的__new__,而object下默认就有一个__new__,所以即便是之前的类均未实现__new__,也一定会在object中找到一个,根本不会、也根本没必要再去找元类Mymeta->type中查找__new__

 但我们还是推荐在__call__中使用self.__new__(self)去创造空对象,因为这种方式会检索三个类StanfordTeacher->Foo->Bar,而object.__new__则是直接跨过了他们三个

 

六 作业  记得要看要写

1、在元类中控制把自定义类的数据属性都变成大写

复制代码
class Mymetaclass(type):
    def __new__(cls,name,bases,attrs):
        update_attrs={}
        for k,v in attrs.items():
            if not callable(v) and not k.startswith('__'):
                update_attrs[k.upper()]=v
            else:
                update_attrs[k]=v
        return type.__new__(cls,name,bases,update_attrs)

class Chinese(metaclass=Mymetaclass):
    country='China'
    tag='Legend of the Dragon' #龙的传人
    def walk(self):
        print('%s is walking' %self.name)


print(Chinese.__dict__)
'''
{'__module__': '__main__',
 'COUNTRY': 'China', 
 'TAG': 'Legend of the Dragon',
 'walk': <function Chinese.walk at 0x0000000001E7B950>,
 '__dict__': <attribute '__dict__' of 'Chinese' objects>,                                         
 '__weakref__': <attribute '__weakref__' of 'Chinese' objects>,
 '__doc__': None}
'''
复制代码

2、在元类中控制自定义的类无需__init__方法

​   1.元类帮其完成创建对象,以及初始化操作;

  2.要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument

  3.key作为用户自定义类产生对象的属性,且所有属性变成大写

复制代码
class Mymetaclass(type):
    # def __new__(cls,name,bases,attrs):
    #     update_attrs={}
    #     for k,v in attrs.items():
    #         if not callable(v) and not k.startswith('__'):
    #             update_attrs[k.upper()]=v
    #         else:
    #             update_attrs[k]=v
    #     return type.__new__(cls,name,bases,update_attrs)

    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError('must use keyword argument for key function')
        obj = object.__new__(self) #创建对象,self为类Foo

        for k,v in kwargs.items():
            obj.__dict__[k.upper()]=v
        return obj

class Chinese(metaclass=Mymetaclass):
    country='China'
    tag='Legend of the Dragon' #龙的传人
    def walk(self):
        print('%s is walking' %self.name)


p=Chinese(name='lili',age=18,sex='male')
print(p.__dict__)
复制代码

3、在元类中控制自定义的类产生的对象相关的属性全部为隐藏属性

复制代码
class Mymeta(type):
    def __init__(self,class_name,class_bases,class_dic):
        #控制类Foo的创建
        super(Mymeta,self).__init__(class_name,class_bases,class_dic)

    def __call__(self, *args, **kwargs):
        #控制Foo的调用过程,即Foo对象的产生过程
        obj = self.__new__(self)
        self.__init__(obj, *args, **kwargs)
        obj.__dict__={'_%s__%s' %(self.__name__,k):v for k,v in obj.__dict__.items()}

        return obj

class Foo(object,metaclass=Mymeta):  # Foo=Mymeta(...)
    def __init__(self, name, age,sex):
        self.name=name
        self.age=age
        self.sex=sex


obj=Foo('lili',18,'male')
print(obj.__dict__)
复制代码

4、基于元类实现单例模式

复制代码
#步骤五:基于元类实现单例模式
# 单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间
# 如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了
#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)
        return cls.__instance

obj1=Mysql('1.1.1.2',3306)
obj2=Mysql('1.1.1.3',3307)
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
复制代码

 

 涉及到的知识点

exec方法

  • exec()方法可以执行字符串形式的python代码块
  • 使用方法exec(object, global=None, local=None)
    • object: 字符串类型的python代码块
    • global: 代表全局名称空间, 必须是字典, 默认为None, 如传参则表明该代码块在全局名称空间中运行
    • local: 代表局部名称空间, 可以是任何映射, 默认为None, 如传参则表明该代码块在局部空间中运行
复制代码
code = '''
x = 0
sum = x + y + z
print(sum)
'''
y = 1
z = 2

global_dict = {'y': 2, 'z': 3}

local_dict = {'y': 3, 'z': 4}

exec(code)
'''
y = 1
z = 2
x = 0
sum = x + y + z
print(sum)
'''

exec(code, global_dict)
'''
相当于
y = 2
z = 3
x = 0
sum = x + y + z
print(sum)
'''

exec(code, global_dict, local_dict)
'''
相当于
y = 2
z = 3
def exec_func():
    y = 3
    z = 4
    x = 0
    sum = x + y + z
    print(sum)
    
exec_func()
'''

'''
3
5
7
'''
复制代码

 

__call__

对象后面加括号,触发执行。

注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Foo:
    def __call__(self, *args, **kwargs):
        print('实例执行 obj()')
        
f1 = Foo()
f1()    # 调用Foo类下的__call__()
Foo()    # 触发__call__()方法,一切接对象

 

__new__和__init__

见:https://www.cnblogs.com/yunlong-study/p/14325491.html

 

代码参考

复制代码
'''
通过自定义元类来实现:
1. 类名首字母必须大写
2. 类中必须有文档注释

'''

class MyMeta(type):

    def __init__(self, class_name, class_bases, class_dict):
        print(class_name)  # chinese
        print(class_bases) # (<class 'object'>,)
        print(class_dict)  # {'__module__': '__main__', '__qualname__': 'chinese', 'country': 'China', '__init__': <function chinese.__init__ at 0x0000000009FBFD90>, 'kongfu': <function chinese.kongfu at 0x0000000009FBFE18>}

        # 类名首字母必须大写
        if not class_name.istitle():
            raise TypeError('类的首字母必须大写!')

        # 类中必须有注释
        if not class_dict.get('__doc__'):
            raise TypeError('类中必须有文档注释!')

        # 调用type中的__init__方法初始化对象
        super().__init__(class_name, class_bases, class_dict)


class chinese(object, metaclass=MyMeta):  # foo = MyMeta('foo', (object, ) {...})
    country = 'China'

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

    def kongfu(self):
        print('降龙十八掌!')
        
'''
    raise TypeError('类的首字母必须大写!')
TypeError: 类的首字母必须大写!
'''


# 将类名大写, 再运行
class Chinese(object, metaclass=MyMeta):  # foo = MyMeta('foo', (object, ) {...})
    country = 'China'

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

    def kongfu(self):
        print('降龙十八掌!')
        
        
'''
    raise TypeError('类中必须有文档注释!')
TypeError: 类中必须有文档注释!
'''
复制代码

 

本文作者:云龙

本文链接:https://www.cnblogs.com/yunlong-study/p/14324656.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   云long  阅读(102)  评论(0编辑  收藏  举报
(评论功能已被禁用)
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开