码迷,mamicode.com
首页 > 其他好文 > 详细

反射与元类

时间:2018-04-17 19:37:47      阅读:163      评论:0      收藏:0      [点我收藏+]

标签:习题   please   strip   oba   完成   而不是   base   文件中   而且   

1.isinstance与issubclass  

  在介绍反射之前,先来介绍两个关于类的内置方法,第一个是用来判断对象是否是某一类的对象(以前常说的判断是否是某一类型,类与类型其实是一个概念),第二个则是用来判断某一类是否是继承了另一个类

l=list([1,2,3])
print(isinstance(l,list)) #True 
class People:
    def __init__(self):
        pass
class Chinese(People):
    def __init__(self):
        pass
print(issubclass(Chinese,People))#True
#两者用法一致,均是将判断的对象与对象可能是某一类型作为第一第二参数传入

2.反射

  先来给反射下一个定义:反射就是通过字符串来操作python代码中的变量,函数,甚至类与方法,(如何去理解这句话呢,试想我们如果想让打印机打印,是不是要给它一个打印的指令呢,现在如果说你必须通过输入的形式去直接命令计算机,让计算机能理解你输入的字符串到底是对应什么操作,关键在于我们提供input输入的都是字符串,而计算机是无法直接识别字符串的),那就要介绍反射中的四小龙了,

1.hasattr 判断一个变量是否能够加点调用一个名字,返回值为True 或 False

技术分享图片
class Foo:
    x=1
    def __init__(self):
        pass
    def tell(self):
        print(from tell)
print(hasattr(Foo,x))
View Code

2.getattr 直接获取一个变量中的名字的值

技术分享图片
class Foo:
    x=1
    def __init__(self):
        pass
    def tell(self):
        print(from tell)
obj=Foo()
getattr(obj,tell)() #from tell
View Code

3.setattr

技术分享图片
class Foo:
    x=1
    def __init__(self):
        pass
    def tell(self):
        print(from tell)
obj=Foo()
print(Foo.x) #1
setattr(Foo,x,11)
print(Foo.x)#11
View Code

4.delattr

技术分享图片
class Foo:
    x=1
    def __init__(self):
        pass
    def tell(self):
        print(from tell)
obj=Foo()
print(Foo.x) #1
delattr(Foo,x)
print(Foo.x) #AttributeError: type object Foo has no attribute x
View Code

  总结:上面这四个方法需要注意的地方有以下几点:1.括号里面的传入的前后参数之间,肯定是可以加点再加名字的形式调用(也就是说,只要是设计到通过点加名字就可以调用的形式,都可以转化成上面这四种方式去操作,比如:导入模块后应用模块中的名字,类中属性方法的调用等),2.传入的参数中要寻找的参数必须是以字符串的形式的传入,咋一看好像这种方式更加复杂,但是恰恰相反,请看例子

class People:
    country=China
    def __init__(self,name):
        self.name=name
    def tell(self):
        print(from tell)
    def check(self):
        print(from check)
p1=People(jby)
cmd=input(please input your cmd).strip()
if hasattr(p1,cmd):
    getattr(p1,cmd)()

  这样我们就可以直接将用户输入的字符串直接反射到对应的名字身上,从而直接调用~

3.类的内置方法补充

  1.__str__ 在对象,在遇到被打印的情况下,自动触发~

技术分享图片
class People:
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex
    def __str__(self):#__str__内置方法,遇到print自动触发返回字符串格式类型的数据(可自己定制)
        return <name:%s,age:%s,sex:%s>%(self.name,self.age,self.sex)
l=list([1,2,3])#用list类产生的对象为什么在打印的时候会直接打印对象里面包含的所有值而不是内存地址原因就是内部有__str__方法
print(l)
p1=People(alex,73,male)
print(p1)
View Code

  2.__del__ 在对象被删除的情况下(在对象被删除之前)自动触发(这里的删除可以是主动删除也可能是程序执行完毕,回收资源删除)

技术分享图片
class Myopen:
    def __init__(self,filename,mode=r,encoding=utf-8):
        self.filename=filename
        self.mode=mode
        self.encoding=encoding
        self.f=open(filename,mode=mode,encoding=encoding)
        #这里的open申请了系统资源,在程序关闭时应用程序会回收自己资源,但是系统资源没有被回收
    def __str__(self):
        return %s%self.f.read()
    def __del__(self):
        #这里的__del__会在对象被删除之前(被系统回收),执行将系统资源先回收了
        #所有__del__主要用于设计到回收资源这块的应用
        print(正在删除系统资源)
        self.f.close()
file=Myopen(settings.py)
print(file)
View Code

  3.__call__ 在对象被调用的情况下,自动触发

技术分享图片
class Foo:
    def __init__(self):
        pass
    def __str__(self):
        return aaa
    def __del__(self):
        pass
    def __call__(self, *args, **kwargs):#(调用对象时就会自动触发此方法的执行将对象当作第一个参数传给self,将对象括号内的参数传给args和kwargs)
        print(执行了__call__,args,kwargs)

obj=Foo()
obj(1,2,3,a=2,b=3,c=1)#执行了__call__ (1, 2, 3) {‘a‘: 2, ‘b‘: 3, ‘c‘: 1}
View Code

4.元类

  1.python中一切皆对象,相信你已经似乎搞清楚了,那这里要揭示我们平常用class定义出来的类,它其实也是一个对象,它也是通过类实例化得到的,这个类就叫做元类!

在介绍元类之前需要先了解一个方法:exec,先介绍它的一个牛逼之处,它可以执行字符串中的代码,与我们执行程序文件无异,也会产生名称空间并将执行产生的名字全部丢到名称空间中,默认情况下会将产生的名字全部存放到局部名称空间中,了解到这里之后就可以介绍exec具体的用法了

exec——它需要三个参数,第一个就是字符串形式的代码,第二个是全局作用域(字典),第三个就是局部作用域(字典)。后面两个都是在执行了第一个参数内的代码后exec会自动将产生的全局作用名放入中间的参数中,将参数的局部作用名放入最后一个参数中

技术分享图片
global x
x=1

y=2
z=3
‘‘‘
g={}
l={}
exec(code,g,l)
print(g,l) #g={‘x‘:1},l={‘y‘:2,‘z‘:3}
View Code

  2.元类:

    元类是类的类,是类的模板

    元类是用来控制如何创建类的,正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为

    元类的实例化的结果为我们用class定义的类,正如类的实例为对象(f1对象是Foo类的一个实例Foo类是 type 类的一个实例)

    type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象

  3.创建类的两种方式:

    第一种就是通过class关键字来自动帮我们调用type创造出类

    第二种就是我们自己定义出元类从而创建类

  在创建类之前需要准备的知识点有,创建类必须要有三个关键参数:1.类名,2.父类名,3.类的名称空间。还记得我们在通过类创造对象时的步骤吗?这里先回顾一下,第一步调用类产生一个空的对象,第二步触发类中__init__方法的自动执行完成初始化,第三步就是将初始化的结果返回给空对象。

#准备工作:

#创建类主要分为三部分
类名
类的父类
类体
#类名
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)
print(class_dic)
#{‘country‘: ‘China‘, ‘talk‘: <function talk at 0x101a560c8>, ‘__init__‘: <function __init__ at 0x101a56668>}
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
‘‘‘

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

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

  我们看到,type 接收三个参数:

  • 第 1 个参数是字符串 ‘Foo’,表示类名

  • 第 2 个参数是元组 (object, ),表示所有的父类

  • 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法

  补充:若Foo类有继承,即class Foo(Bar):.... 则等同于type(‘Foo‘,(Bar,),class_dic)

  自定义元类控制类的行为

#一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的行为,工作流程是什么)
技术分享图片
#知识储备:
    #产生的新对象 = object.__new__(继承object类的子类)

#步骤一:如果说People=type(类名,类的父类们,类的名称空间),那么我们定义元类如下,来控制类的创建
class Mymeta(type):  # 继承默认元类的一堆属性
    def __init__(self, class_name, class_bases, class_dic):
        if __doc__ not in class_dic or not class_dic.get(__doc__).strip():
            raise TypeError(必须为类指定文档注释)

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

        super(Mymeta, self).__init__(class_name, class_bases, class_dic)


class People(object, metaclass=Mymeta):
    country = China

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

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

#步骤二:如果我们想控制类实例化的行为,那么需要先储备知识__call__方法的使用
class People(object,metaclass=type):
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def __call__(self, *args, **kwargs):
        print(self,args,kwargs)


# 调用类People,并不会出发__call__
obj=People(egon,18)

# 调用对象obj(1,2,3,a=1,b=2,c=3),才会出发对象的绑定方法obj.__call__(1,2,3,a=1,b=2,c=3)
obj(1,2,3,a=1,b=2,c=3) #打印:<__main__.People object at 0x10076dd30> (1, 2, 3) {‘a‘: 1, ‘b‘: 2, ‘c‘: 3}

#总结:如果说类People是元类type的实例,那么在元类type内肯定也有一个__call__,会在调用People(‘egon‘,18)时触发执行,然后返回一个初始化好了的对象obj


#步骤三:自定义元类,控制类的调用(即实例化)的过程
class Mymeta(type): #继承默认元类的一堆属性
    def __init__(self,class_name,class_bases,class_dic):
        if not class_name.istitle():
            raise TypeError(类名首字母必须大写)

        super(Mymeta,self).__init__(class_name,class_bases,class_dic)

    def __call__(self, *args, **kwargs):
        #self=People
        print(self,args,kwargs) #<class ‘__main__.People‘> (‘egon‘, 18) {}

        #1、实例化People,产生空对象obj
        obj=object.__new__(self)


        #2、调用People下的函数__init__,初始化obj
        self.__init__(obj,*args,**kwargs)


        #3、返回初始化好了的obj
        return obj

class People(object,metaclass=Mymeta):
    country=China

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

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



obj=People(egon,18)
print(obj.__dict__) #{‘name‘: ‘egon‘, ‘age‘: 18}

#步骤四:
class Mymeta(type): #继承默认元类的一堆属性
    def __init__(self,class_name,class_bases,class_dic):
        if not class_name.istitle():
            raise TypeError(类名首字母必须大写)

        super(Mymeta,self).__init__(class_name,class_bases,class_dic)

    def __call__(self, *args, **kwargs):
        #self=People
        print(self,args,kwargs) #<class ‘__main__.People‘> (‘egon‘, 18) {}

        #1、调用self,即People下的函数__new__,在该函数内完成:1、产生空对象obj 2、初始化 3、返回obj
        obj=self.__new__(self,*args,**kwargs)

        #2、一定记得返回obj,因为实例化People(...)取得就是__call__的返回值
        return obj

class People(object,metaclass=Mymeta):
    country=China

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

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


    def __new__(cls, *args, **kwargs):
        obj=object.__new__(cls)
        cls.__init__(obj,*args,**kwargs)
        return obj


obj=People(egon,18)
print(obj.__dict__) #{‘name‘: ‘egon‘, ‘age‘: 18}

#步骤五:基于元类实现单例模式,比如数据库对象,实例化时参数都一样,就没必要重复产生对象,浪费内存
class Mysql:
    __instance=None
    def __init__(self,host=127.0.0.1,port=3306):
        self.host=host
        self.port=port

    @classmethod
    def singleton(cls,*args,**kwargs):
        if not cls.__instance:
            cls.__instance=cls(*args,**kwargs)
        return cls.__instance


obj1=Mysql()
obj2=Mysql()
print(obj1 is obj2) #False

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

#应用:定制元类实现单例模式
class Mymeta(type):
    def __init__(self,name,bases,dic): #定义类Mysql时就触发
        self.__instance=None
        super().__init__(name,bases,dic)

    def __call__(self, *args, **kwargs): #Mysql(...)时触发

        if not self.__instance:
            self.__instance=object.__new__(self) #产生对象
            self.__init__(self.__instance,*args,**kwargs) #初始化对象
            #上述两步可以合成下面一步
            # self.__instance=super().__call__(*args,**kwargs)

        return self.__instance
class Mysql(metaclass=Mymeta):
    def __init__(self,host=127.0.0.1,port=3306):
        self.host=host
        self.port=port


obj1=Mysql()
obj2=Mysql()

print(obj1 is obj2)
View Code

 

5.单例模式

  指的是不管调用多少次产生的对象都指向同一个地址,这样的好处就在于充分的节省空间,但是仅用于每次实例化对象都是一样的结果(比如需要从配置文件中导入配置信息,而且一个程序中需要重复用到,这个时候用单例模式比较好)

import settings
class MySQL:
    __instance=None  #标识位
    def __init__(self,ip,port):
        self.ip=ip
        self.port=port
    @classmethod
    def singleton(cls):
        if not cls.__instance:
            obj=cls(settings.IP, settings.PORT)
            cls.__instance=obj
       return obj
return cls.__instance obj4=MySQL.singleton() obj5=MySQL.singleton() obj6=MySQL.singleton() print(obj4 is obj5 is obj6) #True

  上述这种单例模式其实我们在之前学绑定方法与分绑定方法时就已经接触过了,就是将方法从绑定给对象改为绑定给类,只需要再加个标识位加以控制即可做到单例模式,可以看出这里的单例模式其实是我们在内部进行了一步监控,如果产生的结果与我的标识符一致,那就直接将我的标识位上的对象给出去,而不继续执行实例化~

 6.练习题

练习一:在元类中控制把自定义类的数据属性都变成大写

技术分享图片
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}
‘‘‘
View Code

练习二:在元类中控制自定义的类无需__init__方法

技术分享图片
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=egon,age=18,sex=male)
print(p.__dict__)
View Code

 

  

 

反射与元类

标签:习题   please   strip   oba   完成   而不是   base   文件中   而且   

原文地址:https://www.cnblogs.com/Dominic-Ji/p/8868196.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!