Day 30 元类 单例模式

Day 30

元类

控制类的产生(写底层代码时)

1、python中一切皆对象,包括类,

class Teacher:
    def __init__(self,name ,age):
        self.name = name
        self.age = age
        
    def teach(self):
        print("我正在教书!")


teacher = Teacher("dota", 18)  # 调用Teacher类 得到 对象teaccher
							   # 调用元类 得到 Teacher 类

print(type(teacher))
print(type(Teacher))
print(type(teacher.teach))
------------------------------------
>>> <class '__main__.Teacher'>
>>> <class 'type'>
>>> <class 'method'>
	例子中`type(Teacher)`说明`Teacher`本身也是一个对象,可以将其赋值给其他对象,对其添加属性,将其作为函数参数传递等等。

结论:默认的元类是type,默认情况下我们用class关键字定义的类都是由type产生的

那么class关键字都做了那些事情呢?

1、先拿到一个类名

class_name = 'Teacher'

2、然后拿到类的父类

class_bases = (object, ) 根据多继承原则,这里设置为元组

3、在运行类体代码,将产生的名字放在名称空间中

class_dic = {}
class_body = """
    def __init__(self,name ,age):
        self.name = name
        self.age = age
        
    def teach(self):
        print("我正在教书!")
"""
exec(class_body, {}, class_dic)
print(class_dic)
-----------------------------------------
>>> 

4、调用元类(传入类的三大要素:类名、基类、类的名称空间)得到一个元类的对象,然后将元类的对象赋值给变量名Teacher就是我们用class自定义的哪个类

Teacher = type(class_name, class_bases, class_dic)

自定义元类

我们知道了类是通过type方法定义出来的,那么我们可不可以自己定义一个元类呢?答案是肯定的

class Mymeta(type):  # 只有继承了type类的类才是自定义的元类
	pass

这只是自定义元类的一个模板

自定义元类来控制Teacher类的产生,但是我们要给他定义一下规范,那么开始吧。

1、类名必须用驼峰体

2、至少继承一个父类

3、必须要有文件注释,并且注释内容不为空

import re


class Mymeta(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("必须要有注释,且注释不能为空!")
          
 class Teacher(object, metaclass=Mymeta):
    """
    注释不能为空!
    """
    def __init__(self,name, age):
        self.name = name
        self.age = age

    def teach(self):
        print("我正在讲......")

自定义元类来控制Teacher类的调用

对象是否能被调用,取决于是否有__call__方法

此时我们的元类Mymeta是没有__call__方法的,所以我们要给他定义一个

调用Teacher类做的事情:

  • 先创建一个老师的空对象
  • 调用老师类内的__init__方法,然后将老师的空对象连同括号内的参数的参数一同传给__init__
  • 将初始化好的老师对象赋值给变量名res
class Mymeta(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):
        # 创建一个老师的空对象
        teacher_obj = object.__new__(self)
        # 调用老师类内的__init__函数,然后将老师的空对象连同括号内的参数一同传给__init__
        self.__init__(teacher_obj, *args, **kwargs)
        return teacher_obj
--------------------------------------------------------

注意这里的__call__ 方法其实底层已经定义好了,我们自定义只是为他附加额外的功能,就像下面的私有属性。不定义其实也没有影响,不定义就是按照默认的功能实现

补充:如何将类的对象全部设为私有

通过覆盖实现属性私有

class Mymeta(type):
    def __init__(self, class_name, class_base, class_dic):

        if not re.match("[A-Z]", class_name):
            raise BaseException("命名应该为驼峰体!")

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

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

    # res = OldboyTeacher("egon", 18)
    def __call__(self, *args, **kwargs):
        # 1. 先创建一个老师的空对象
        teacher_obj = object.__new__(self)
        # 2.调用老师类内的 __init__函数,然后将老师的空对象连同括号内的参数的参数一同传给__init__
        self.__init__(teacher_obj, *args, **kwargs)           
        # 2.1 获取对象的字典
        # teacher_obj.__dict__
        # 2.2 创建新的对象字典
        # dic = {k:v for k,v in teacher_obj.__dict__}
        # 2.3 将方法(k)设置为私有
        # dic = {"_%s__%s"%(self.__name__, k):v}
        # 2.4 将原来的字典覆盖
        teacher_obj.__dict__ = {"_%s__%s" % (self.__name__, k): v for k, v in teacher_obj.__dict__}

        # 3.将初始化好的老师对象赋值给变量名res
        return teacher_obj

单例模式

单例是一种设计模式,应用该模式的类指挥生成一个示例,单个实例,永远指向一个对象,创建单例模式节约内存

创建单例模式的三种方法

1、基于@classmethod

class MySQL:
    _instance = None  # 初始化单例对象

    def __init__(self, ip, port):  # 自定义的ip和port
        self.ip = ip
        self.port = port

    @classmethod  # 类方法
    def settings(cls):
        if cls._instance:  # 如果存在单例对象
            pass  返回单例对象
        else:  # 没有的话使用自定义的ip和port
            cls._instance = cls("127.0.0.1", 3306)
        return cls._instance
    

# 这里因为需要两个不同的的ip所以 创建两个对象
my1 = MySQL("123.0.0.1", 3306) 
my2 = MySQL("123.0.0.2", 3306)
print(my1, my2)
# 这里就直接使用默认的了
my3 = MySQL.settings()
my4 = MySQL.settings()
print(my3, my4)    
---------------------------------
>>> <__main__.MySQL object at 0x00000255CDF0CFD0> <__main__.MySQL object at 0x00000255CDF0CF60>  # 不同对象
>>> <__main__.MySQL object at 0x00000255CDF0CE10> <__main__.MySQL object at 0x00000255CDF0CE10>  # 相同对象

我们来看看没有 _instance 会怎么样!

    def settings(cls):
        # if cls.__instance:
        #     pass
        # else:
        cls.__instance = cls("127.0.0.1", 3306)
        return cls.__instance
        
my3 = MySQL.settings()
my4 = MySQL.settings()   
---------------------------------
>>> <__main__.MySQL object at 0x000001B6C7B8DF98> <__main__.MySQL object at 0x000001B6C7B8DF60>  # 不同

其实后面两种方法实现的原理是相通的

基于元类

class Mymeta(type):
    _instance = None  # 默认在类内加  _ 表示不想让外界访问

    def __init__(self, class_name, class_bases, class_dic):
        self._instance = object.__new__(self)  # Mysql 类的对象
        self.__init__(self._instance, "120.0.0.1", 3306)

    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


my1 = MySQL("123.0.0.1", 3306)
my2 = MySQL("123.0.0.2", 3306)
print(my1, my2)
my3 = MySQL()
my4 = MySQL()
print(my3, my4)
--------------------------------
>>> <__main__.MySQL object at 0x0000023595A1DB38> <__main__.MySQL object at 0x0000023595A1DC18>
>>> <__main__.MySQL object at 0x0000023586A6F2B0> <__main__.MySQL object at 0x0000023586A6F2B0>

基于装饰器

def outter(func):
    _instance = func("120.0.0.1", 3306)

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

    return wrapper
    
@outter
class MySQL():

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


my1 = MySQL("123.0.0.1", 3306)
my2 = MySQL("123.0.0.2", 3306)
print(my1, my2)
my3 = MySQL()
my4 = MySQL()
print(my3, my4) 
-----------------------------------
>>> <__main__.MySQL object at 0x00000196906FEBA8> <__main__.MySQL object at 0x00000196906FEC88>
>>> <__main__.MySQL object at 0x00000196FFFDF278> <__main__.MySQL object at 0x00000196FFFDF278>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值