Python之路(第二十八篇) 面向对象进阶:类的装饰器、元类

Stella981
• 阅读 827

一、类的装饰器

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

例子

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)
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Python3:sqlalchemy对mysql数据库操作,非sql语句
Python3:sqlalchemy对mysql数据库操作,非sql语句python3authorlizmdatetime2018020110:00:00coding:utf8'''
Wesley13 Wesley13
3年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Stella981 Stella981
3年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这