05. 元类知识

一、python中一切皆对象

1.1 所有的对象都是实例化(或者叫通过调用类)而得到的

class People:
	def __init__(self,name):
		self.name = name
	def run(self):
		pass

peo1 = People('cc')

调用类的过程称之为实例化,如上面的peo1对象就是通过调用类People得到的

1.2 类本质上也是一种对象

那么类这种对象是怎么实例化得到的呢?或者说类是通过调用什么类产生的呢?
答案就是:元类
即上面的类People是通过People=元类(…)产生的

print(type(People))  # <class 'type'>

证明:

People也是调用了type这个元类而产生的People,即默认的元类就是type
默认情况下,使用class关键字定义的类都是由type实例化产生的

在这里插入图片描述#

二、class关键字创建类的流程

2.1 类的组成部分

① 类名:class_name = 'People'
② 基类:base_class = (object, )	
③ 类的名称空间:class_dic,由执行类体代码而得到的

调用type时会依次传入以上几种参数

2.2 class关键字在创建类的流程细分为四部分

# 1、先拿到一个类名
class_name = 'People'

# 2、拿到类的基类的名字
class_bases = (object,)

# 3、再运行类体代码,将产生的名字放进类的名称空间中
class_dic = {}
class_body = """
def __init__(self,name):
	self.name = name
def run(self):
	pass
"""
exec(class_name, class_bases, class_dic)

# 4、调用元类,传入类的三大元素:(类名,基类,类的名称空间)得到一个元类的对象,
# 然后将元类的对象赋值给变量名People,
# 类People就是我们用class自定义的那个类
People = type(class_name, class_bases, class_dic)

三、自定义元类

一个类如果没有声明自己的元类,那么默认他的元类就是type,
除了使用内置元类type,我们也可以通过继承type来自定义元类,然后使用metaclass参数来指定这个类的指定元类
只有继承了type的类才是自定义的元类,否则就是一个自定义的普通的类
示例:

class Mymeta(type):
	pass
class People(object, metaclass=Mymeta):
	def __self__(self,name,age):
		self.name = name
		self.age = age
	def talk(self):
		print('hello!')
# 1、先拿到一个类名:"People"
# 2、然后拿到类的父类:(object,)
# 3、再运行类体代码,将产生的名字放到名称空间中{...}
# 4、调用元类(传入类的三大要素:类名、基类、类的名称空间)得到一个元类的对象,
然后将元类的对象赋值给变量名People
People = Mymeta("People",(object,),{...})

四、使用元类来控制 类的产生

import re


class Mymeta(type):  # 只有继承了type类的类才是自定义的元类
    def __init__(self, class_name, class_bases, class_dic):
        # print(self)  # 类<class '__main__.People'>
        # print(class_name)  # People
        # print(class_bases)  #  <class 'object'>
        # print(class_dic)  # 

        if not re.match("[A-Z]", class_name):
            raise BaseException("类名格式必须用驼峰体")

        if len(class_bases) == 0:
            raise BaseException("至少继承一个父类")

        # print("文档注释:",class_dic.get('__doc__'))
        doc=class_dic.get('__doc__')
        if not (doc and len(doc.strip()) > 0):
            raise BaseException("必须要有文件注释,并且注释内容不为空") 
class Peolpe(object, metaclass=Mymeta):
	"""
	People的文档注释
	"""
	def __init__(self, name, age):
		self.name = name
		self.age = age
	def talk(self):
		print('hello!')

六、使用自定义元类来控制 类的调用

调用类People做的事情:

① 先创建一个类的空对象
② 调用类的__init__方法,然后将People类的空对象连同括号内的参数一起传给__init__
③ 将初始化好的对象赋值给变量名p1

示例:

import re

class Mymeta(type):  # 只有继承了type类的类才是自定义的元类
    def __init__(self, class_name, class_bases, class_dic):

        if not re.match("[A-Z]", class_name):
            raise BaseException("类名必须用驼峰体")

        if len(class_bases) == 0:
            raise BaseException("至少继承一个父类")

        doc = class_dic.get('__doc__')
        if not (doc and len(doc.strip()) > 0):
            raise BaseException("必须要有文件注释,并且注释内容不为空")

    def __call__(self, *args, **kwargs):
        # 1、先创建一个老师的空对象
        tea_obj = object.__new__(self)
        
        # 2、调用老师类内的__init__函数,然后将老师的空对象连同括号内的参数的参数一同传给__init__
        self.__init__(obj, *args, **kwargs)
        # 将People类对象的属性隐藏起来(变成私有的)
        obj.__dict__ = {"_%s__%s" %(self.__name__,k): v for k, v in tea_obj.__dict__.items()}

        # 3、将初始化好的老师对象赋值给变量名res
        return obj
        
class People(object, metaclass=Mymeta):
    """
    People的文档注释
    """
    def __init__(self, name, age):
        self.name = name  
        self.age = age  

    def talk(self):
        print('hello!')

p1 = People('cc', 18)
print(p1.__dict__)  # {'_People__name': 'cc', '_People__age': 18}

七、单例模式

  • 单例:

      即单个实例,指的是同一个类的实例化多次的结果指向同一个对象,用于节省内存空间
      如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了
    

用来实现,调用类创建对象时,当括号内有参数时就使用括号内传入的,
当没有传入参数时,就默认使用类产生同一个对象
示例:
settings文件内容如下:

IP = '192.168.11.188'
PORT = 8080

7.1 实现方式一:使用classmethod定义一个类方法

import settings

class MySQL:
    __instance = None

    def __init__(self, ip, port):
        self.ip = ip
        self.port = port

    @classmethod
    def singleton(cls):
        if cls.__instance:
            return cls.__instance
        cls.__instance = cls(settings.IP, settings.PORT)
        return cls.__instance


obj1 = MySQL.singleton()
print(obj1)  # None

obj2 = MySQL('1.1.1.1', 8080)
obj3 = MySQL('1.1.1.2', 8080)
print(obj2)  # <__main__.MySQL object at 0x0000023FFBCA5A30>
print(obj3)  # <__main__.MySQL object at 0x0000023FFBCE11F0>

7.2 定制元类实现单例模式

import settings
class Mymeta(type):
    __instance = None

    def __init__(self, class_name, class_bases, class_dic):
        self.__instance = object.__new__(self)
        self.__init__(self.__instance, settings.IP, settings.PORT)
        super().__init__(class_name, class_bases, class_dic)

    def __call__(self, *args, **kwargs):
        if args or kwargs:
            obj = object.__new__(self)
            self.__init__(obj, *args, **kwargs)
            return obj
        else:
            return self.__instance


class Mysql(metaclass=Mymeta):
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port


obj1 = Mysql()
obj2 = Mysql()
print(obj1)  # <__main__.Mysql object at 0x0000022A0F754AF0>

print(obj1 is obj2)  # True

obj3 = Mysql('1.1.1.1', 8080)
print(obj3)  # <__main__.Mysql object at 0x0000022A0F725A30>

7.3 定义一个装饰器实现单例模式

import settings

def outer(func):
    _instance = func(settings.IP, settings.PORT)

    def wrapper(*args, **kwargs):
        if args or kwargs:
            return func(*args, **kwargs)
        return _instance
    return wrapper

@outer
class MySQL:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port

obj1 = MySQL()
obj2 = MySQL()
print(obj1 is obj2)  # True

obj3 = MySQL('127.0.0.1', 8080)
obj4 = MySQL('127.0.0.2', 8080)
print(obj3)  # <__main__.MySQL object at 0x000001F3BFE510D0>
print(obj4)  # <__main__.MySQL object at 0x000001F3BFE51430>

八、属性查找

属性查找分为两层:

一层是对象层(基于c3算法的MRO)的查找
另一层是类层(元类层)的查找

在这里插入图片描述
查找顺序:

先对象层:OldboyTeacher->Foo->Bar->object
然后元类层:Mymeta->type

解释:

当OldboyTeacher 产生的对象jason调用某个属性时,
			查找顺序为:jason->OldboyTeacher->Foo->Bar->object
当OldboyTeacher 自身调用某个属性时,
			查找顺序为:OldboyTeacher->Foo->Bar->object->Mymeta->type

九、补充知识

调用对象其实就是在调用对象类中定义的绑定方法__call__

class Foo:
    def __call__(self, *args, **kwargs):
        print('================>')
        print(self)
        print(args)
        print(kwargs)


obj1 = Foo()
obj1(1,2,3,a=1,b=2) 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值