zoukankan      html  css  js  c++  java
  • Python之路(第二十八篇) 面向对象进阶:类的装饰器、元类

    一、类的装饰器

    类作为一个对象,也可以被装饰。

    例子

      def wrap(obj):
          print("装饰器-----")
          obj.x = 1
          obj.y = 3
          obj.z = 5
          return obj
      ​
      @wrap  #将Foo类作为一个参数传入装饰器函数wrap,返回同时返回该对象,把新对象重新命名为Foo
      #即 Foo = wrap(Foo)
      class Foo:
          pass
      ​
      #执行结果:
      #装饰器-----
      ​
      print(Foo.__dict__) #输出结果可以看到,新的Foo类新增了x,y,z属性
    

      

    函数可以作为一个对象,也有__dict__方法

      
      def wrap(obj):
          print("装饰器-----")
          obj.x = 1
          obj.y = 3
          obj.z = 5
          return obj
      ​
      @wrap #test = wrap(test)
      def test():
          print("test-----")
      test.x = 10  #test的x属性被重新赋值
      print(test.__dict__) #输出结果可以看到,test作为一个函数也有__dict__方法,
      # 新的test函数新增了x,y,z属性
    

      

    类的装饰器应用

    例子

      class Type:
      ​
          def __init__(self,key,except_type):  #People对象的key,和期望的数据类型
              self.key = key
              self.except_type = except_type
      ​
          def __get__(self, instance, owner):
              return isinstance.__dict__[self.key]
      ​
          def __set__(self, instance, value):
              print("instance---",instance)
              if not isinstance(value,self.except_type):
                  print("您输入的类型不是%s"%self.except_type)
                  raise TypeError
              instance.__dict__[self.key] = value
      ​
          def __delete__(self, instance):
              isinstance.__dict__.pop(self.key)
      ​
      def deco(**kwargs):
          def wrapper(obj):      #类的装饰器
              for key,val in kwargs.items():
                  setattr(obj,key,Type(key,val))  #设置people类对象的每个参数的描述符
              return obj
          return wrapper
      ​
      @deco(name=str,age=int)
      class People:
      ​
          def __init__(self,name,age):
              self.name = name
              self.age = age
      ​
      ​
      p = People("nick",18)
      print(p.__dict__)
    

      

    二、自定义property

    装饰器也可以是一个类,在自定义property中要使用一个类作为装饰器

    例子

      
      class LazyProperty:
      ​
          def __init__(self, func):
              self.func = func
      ​
          def __get__(self, instance, owner):
              print("执行__get__")
              if not instance:  # 如果是用原类.属性来调用,,这时instance(对象)值为None,直接返回描述符对象
                  return self
              res = self.func(instance)  # 执行传入的函数属性,并把原对象作为参数传入
              return res
      ​
      ​
      class Room:
      ​
          def __init__(self, name, length, width):
              self.name = name
              self.length = length
              self.width = width
      ​
          @LazyProperty  # 这里相当于执行了area  = LazyProperty(area),这里的azyProperty(area)其实是非数据描述符,
          # 新的area已经是经过类LazyProperty装饰过的函数地址
          def area(self):
              return self.length * self.width
      ​
      ​
      r = Room("nick", 18, 10)
      print(r.area)  # 执行对象的方法,先在对象的属性字典里寻找,没有则在非数据描述符里寻找,找到非数据描述符里的__get__方法。
     
    

      

    实现延迟计算功能,即实现计算一次再次调用不再进行计算

      
      class LazyProperty:
      ​
          def __init__(self, func):
              self.func = func
      ​
          def __get__(self, instance, owner):
              print("执行__get__")
              if not instance:  # 如果是用原类.属性来调用,,这时instance(对象)值为None,直接返回描述符对象
                  return self
              value = self.func(instance)  # 执行传入的函数属性,并把原对象作为参数传入
              setattr(instance,self.func.__name__,value) #将每次调用的函数属性名字和值存入对象的__dict__,
              # self.func.__name__是获取被调用函数属性的名字
              return value
      ​
      ​
      class Room:
      ​
          def __init__(self, name, length, width):
              self.name = name
              self.length = length
              self.width = width
      ​
          @LazyProperty  # 这里相当于执行了area  = LazyProperty(area),这里的azyProperty(area)其实是非数据描述符,
          # 新的area已经是经过类LazyProperty装饰过的函数地址
          def area(self):
              return self.length * self.width
      ​
      ​
      r = Room("nick", 18, 10)
      print(r.__dict__)
      print(r.area)  # 执行对象的方法,先在对象的属性字典里寻找,没有则在非数据描述符里寻找,找到非数据描述符里的__get__方法。
      print(r.__dict__)
    

      

    三、property补充

    一个静态属性property本质就是实现了get,set,delete三种方法

    用语法糖可以实现property的类似属性的设置和删除,与一般的属性设置删除没有区别

      class People:
      ​
          def __init__(self):
              self.study = "8h"
      ​
          @property
          def study(self):
              print("获取study,执行描述符的__get__方法")
              return self.val
              # return self.study  #无线递归
      ​
          @study.setter
          def study(self,value):
              print("执行__set__方法")
              self.val = value
      ​
          @study.deleter
          def study(self):
              print("执行__delete__方法")
              del self.val
      ​
      p = People()
      print(p.study)  #获取对象的study属性 ,self.study实际上是存在self.val里
      p.study = "10h"  #执行property描述符的__set__方法,设置对象的属性,
      print(p.__dict__)
      del p.study  #执行property描述符的__delete_方法,删除对象的属性
      print(p.__dict__)
    

      

    四、元类

    exec()函数

    exec:三个参数

    参数一:字符串形式的命令

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

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

    exec会在指定的局部作用域内执行字符串内的代码,除非明确地使用global关键字

    例子

      
      g = {"x":1,"y":2}
      l = {"a":100}
      ​
      exec("""                     
      global x,y
      x = 10
      y = 100
      z = 100
      """,g,l)   # exec 当成一个函数的执行,需要指定全局作用域和局部作用域
      print(g)  #在输出结果中可以看到x,y的值发生了变化
      print(l) #新增加的作为局部作用域的属性
    

      

    定义类的两种方式

    (1)用class关键字定义类

      
    定义类的方式一:用class关键字定义

      class People:
      ​
          country = "china"
          def __init__(self,name):
              self.name = name
      ​
          def talk(self):
              print("在说话")
    

      

     

    (2)手动模拟class创建类的过程:将创建类的步骤拆分开,手动去创建

    准备工作:

    创建类主要分为三部分

      a 类名

      b 类的父类

      c 类体

      
      class_name = "People"  #设置类名
      class_bases = (object,)  #设置类的父类
      class_body = """
      country = "china"
      def __init__(self,name):
          self.name = name
      ​
      def talk(self):
          print("在说话")
      ​
      """
      class_dic = {}
      exec(class_body,globals(),class_dic) #这样执行一下得到类的名称空间(属性字典)
      print(class_dic)
      People2 = type(class_name,class_bases,class_dic) #用元类创建了类
    

      

    什么是元类?

    在python中,一切皆对象,一般的类也是一个类的对象,即这种起源、开始的类就称作元类。

    用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类(元类可以简称为类的类),内置的元类为type。

    元类的参数

    元类实例化创建一般的类有三个参数

    1、类名class_name="xxx"

    2、基类们class_bases=(object,),要继承的父类名,用元组

    3、类的名称空间class_dic,类的名称空间是执行类体代码而得到的,就是类的属性字典

    调用type时会依次传入以上三个参数

    例子

      
      class Foo: #一般的用class 关键字创建的类
          pass
      ​
      f = Foo()
      print(Foo)
      print(Foo.__dict__)
      ​
      t = type("FFo",(object,),{})  #由元类创造出来的一般类
      print(t)
      print(t.__dict__)
    

      

    输出结果

      
      <class '__main__.Foo'>
      {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
      <class '__main__.FFo'>
      {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'FFo' objects>, '__weakref__': <attribute '__weakref__' of 'FFo' objects>, '__doc__': None}
      ​
    

      

    元类创建类也可以添加属性和方法

    例子2

      
      class Foo:
          x = 1
          def __init__(self,name):
              self.name = name
      ​
      ​
      f = Foo("nick")
      print(Foo)
      print(Foo.__dict__)
      ​
      ​
      def __init(self,name):
          self.name = name
      ​
      t = type("FFo",(object,),{"__init__":__init,"x":1}) #可以直接在属性字典里为创建的类添加属性和方法
      print(t)
      print(t.__dict__)
      t=T("nick")
      print(t.__dict__)
    

      

    输出结果

      
      <class '__main__.Foo'>
      {'__module__': '__main__', 'x': 1, '__init__': <function Foo.__init__ at 0x00665DB0>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
      <class '__main__.FFo'>
      {'__init__': <function __init at 0x00665DF8>, 'x': 1, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'FFo' objects>, '__weakref__': <attribute '__weakref__' of 'FFo' objects>, '__doc__': None}
      {'name': 'nick'}
     
    

      

    自定义元类

    一个类没有声明自己的元类,默认它的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类

    例子1

      
      class MyType(type):
      ​
          def __init__(self,class_name,class_bases,class_dic):
              print("类名",class_name)
              print("基类",class_bases)
              print("类字典",class_dic)
              super().__init__(class_name,class_bases,class_dic)
      ​
      ​
      class People(object,metaclass=MyType):  #这里metaclass = MyType就执行MyType("People",(ovject,),{}),
          # 然后自动实例化调用MyType的__init__方法
          def __init__(self,name,age):
              self.name = name
              self.age = age
      ​
          def talk(self):
              print("在说话。。。")
      ​
      p = People("nick",18)
      print(p.__dict__)
    

      

    输出结果

      
      类名 People
      基类 (<class 'object'>,)
      类字典 {'__module__': '__main__', '__qualname__': 'People', '__init__': <function People.__init__ at 0x00625DF8>, 'talk': <function People.talk at 0x00625DB0>}
      {'name': 'nick', 'age': 18}
      ​
    

      

    自定义元类控制类的创建

    例子1

    对新建的类名的要求

      
      class MyMeta(type):
      ​
          def __init__(self,class_name,class_bases,class_dic):
              if not class_name.istitle():   #判断产生的类的首字母是不是大写
                  raise TypeError("类的首字母不是大写")
              super().__init__(class_name,class_bases,class_dic)
      ​
      class people(metaclass=MyMeta): #程序运行到这里会直接报错
      ​
          def __init__(self,name,age):
              self.name = name
              self.age = age
      ​
          def talk(self):
              print("在说话")
    

      

    例子2

    要求新建的类必须要有注释

      
      class MyMeta(type):
      ​
          def __init__(self,class_name,class_bases,class_dic):
              print(class_dic["__doc__"],bool(class_dic["__doc__"]))
              if  not class_dic["__doc__"].strip() or "__doc__" not in class_dic :  #判断新建类没有注释或者注释是空的
                  raise TypeError("新建类没有注释")
      ​
              super().__init__(class_name,class_bases,class_dic)
      ​
      class people(metaclass=MyMeta): #程序运行到这里会直接报错
          """
          """
          def __init__(self,name,age):
              self.name = name
              self.age = age
      ​
          def talk(self):
              print("在说话")
    

      

    自定义元类控制类的实例化过程

    自定义元类创建的类的对象实例化过程

    例子

      
      class MyType(type):
      ​
          def __init__(self,class_name,class_bases,class_dic):
              print("类名",class_name)
              print("基类",class_bases)
              print("类字典",class_dic)
              super().__init__(class_name,class_bases,class_dic)
      ​
          def __call__(self, *args, **kwargs):
              print("self----",self)
              obj = object.__new__(self) #创建一个新的对象,这里就是创建People类的对象,一个实例化的过程
              self.__init__(obj,*args,**kwargs) #执行对象的__init__方法,给对象属性字典加属性,这里的self是People类
              return obj
      ​
      class People(object,metaclass=MyType):  #这里metaclass = MyType就执行MyType("People",(ovject,),{}),
          # 然后自动实例化调用MyType的__init__方法,生成一个people类
          def __init__(self,name,age):
              self.name = name
              self.age = age
      ​
          def talk(self):
              print("在说话。。。")
      print("实例化之前")
      p = People("nick",18)  #这里触发了__call__方法,自己的类里没有call方法就找元类,执行元类的call方法
      #执行call方法之后得到新的对象赋值给p,
      print(p.__dict__)
      print(p.name)
    

      

    - 创建类时,先执行type的__init__。
    - 类的实例化时,执行type的__call__,__call__方法的的返回值就是实例化的对象。

    __call__内部调用:
    - 类.__new__,创建对象
    - 类.__init__,对象的初始化

    单例模式

    单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间

    单例模式主要是优化内存,无论你实例化多少次,始终用同一个对象

    例子1

    如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了

    创建单例模式的方式1

      
      #用类实现单例模式
      ​
      class MySQL():
      ​
          __instance = None   #先定义一个空
      ​
          def __init__(self):
              self.host = "127.0.0.1"
              self.port = "8090"
      ​
          @classmethod
          def singleton(cls):
              if not cls.__instance:  #如果之前没有实例则第一次创建一个实例对象,之后如果有实例则直接返回该实例对象
                  obj = cls()
                  cls.__instance = obj  #将第一次创建的对象赋值给类属性,方便下次调用
              return cls.__instance
      ​
      obj1 = MySQL.singleton()  #创建新的对象
      obj2 = MySQL.singleton()
      print(obj1 is obj2)
    

      

    创建单例模式的方式2,用元类创建单例模式

      
    #第二种方式,用元类实现单例模式

      class Mymeta(type):
      ​
          def __init__(self,class_name,class_bases,class_dic):
              super().__init__(class_name,class_bases,class_dic)
              self.__instance = None  #这里的self是根据这个元类创建的类,也可以把这个创建的类暂时理解为类的对象
      ​
          def __call__(self, *args, **kwargs):
              if not self.__instance:
                  obj = object.__new__(self)   #用__new__方法创建类的对象
                  self.__init__(obj,*args, **kwargs)  #运行类的__init__方法,为新建对象的属性字典赋值
                  self.__instance = obj   #第一次创建对象时将类(self)的属性重新赋值为刚创建的obj
              return self.__instance
      ​
      ​
      class Mysql(metaclass=Mymeta):
      ​
          def __init__(self):
              self.host = "127.0.0.1"
              self.port = 8090
      ​
      obj3 = Mysql()  # 创建类的对象,调用元类的__call__方法
      obj4 = Mysql()
      print(obj3 is obj4)
     
    

      

     

     

     

  • 相关阅读:
    字符串(url)拼接变量
    elementUI table数据显示效果(二)
    异常(转)
    PHP 的异常处理、错误的抛出及错误回调函数 (转)
    详细解读PHP类的封装 (转)
    什么是抽象类
    什么是类,什么是对象,类和对象之间的关系
    魔术方法
    类的声名
    self
  • 原文地址:https://www.cnblogs.com/Nicholas0707/p/9537990.html
Copyright © 2011-2022 走看看