08-08 元类

一. 元类介绍

"""
什么是元类?
    引入: python之中一切皆对象, 类也是对象.
    即可调用类实例化产出对象, 那也可调用元类实例化产生类

"""


# 关系: 元类 --> 实例化 --> 类(People) --> 实例化 --> 对象(obj)
class People(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say(self):
        print(f'姓名:{self.name}  年龄:{self.age}')


# 1. 如何得到对象?
# obj = 类()
obj = People('egon', 19)

# 2. 如何得到类?
# People = 调用类()

# 3. 查看内置的元类:
"""
1、type是内置的元类
2、我们用class关键字定义的所有的类以及内置的类都是由元类type实例化产生
"""
print(type(obj))     # type对象查看到对象中的类。<class '__main__.People'>
print(type(People))  # type类查看到People的元类。<class 'type'>
print(type(int))     # 内置类int的元类也是type. <class 'type'>

二. class关键字创造类People的步骤

1. exec的用法介绍:

"""
参数一:包含一系列python代码的字符串
参数二:全局作用域(字典形式),如果不指定,默认为globals()
参数三:局部作用域(字典形式),如果不指定,默认为locals()
可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中
"""
global_dic = {
    'x': 1,
    'y': 2
}
local_dic = {}

exec("""
global x,z  # 这里在局部名称空间local_dic中拿到全局名称空间中的x,z进行修改.  
x=100
z=200

m=300  # 这里在局部名称空间local_dic中定义了{"m": 300}这个名字
""", global_dic, local_dic)

# print(global_dic)  # {'x': 100, 'y': 2,'z':200,......}
print(local_dic)  # {'m': 300}

2. 实例: 由exec来创建类的名称空间.

# 类有三大特征:
# 2-1. 类名
class_name = 'People'
# 2-2. 类的基类
class_bases = (object,)
# 2-3. 执行类体代码拿到的类的名称空间
class_dict = {}
class_body = """
def __init__(self, name, age):
        self.name = name
        self.age = age

def say(self):
    print(f'姓名:{self.name}  年龄:{self.age}')
"""
exec(class_body, {}, class_dict)
# print(class_dict)  # {'__init__': <function __init__ at 0x000001FB9A58C940>, 'say': <function say at 0x000001FB9A58CA60>}

# 2-4. 调用元类实例化产生类
People = type(class_name, class_bases, class_dict)

# 实例化创建的类People产生对象obj, 完成默认class机制关键类People的步骤
obj = People('egon', 18)
print(obj)  # <__main__.People object at 0x000001FB9A4BA1F0>
obj.say()   # 姓名:egon  年龄:18

三. 自定义元类来控制类的产生

class MyMeta(type):  # 注意: 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    # cls=当前所在的类MyMeta  # *args, **kwargs调用MyMeta时括号内的参数. 返回一个空对象
    def __new__(cls, *args, **kwargs):
        # return type.__new__(cls, *args, **kwargs)
        # return super(MyMeta, cls).__new__(cls, *args, **kwargs)
        return super().__new__(cls, *args, **kwargs)
	
    # self=执行__new__创建的空对象
    # class_name=People
    # class_bases=()
    # class_dict=执行People类体代码产生的名称空间. ==> People.__dict__
    def __init__(self, class_name, class_bases, class_dict):
        # type.__init__(self, class_name, class_bases, class_dict)
        # super(MyMeta, self).__init__(class_name, class_bases, class_dict)
        super().__init__(class_name, class_bases, class_dict)
        
        # 自定义元类控制类People产生!!!: 必须要有文档注释+类名首字母必须大写+类中必须定义func可调用函数
        if class_name.islower():
            raise NameError("类名必须大写!")

        # '__doc__' not in class_dict  # 判断文档注释不存在
        # class_dict['__doc__'].strip(' \n')  # 判断文件注册存在且为空和换行的情况.
        # 提示: 为什么这里使用'字典[]'取值不会出现'__doc__'没有报错的情况, 由成员运算or的短路运算发现运行到第一个条件只有为False的情况下才会执行后面的第二个条件, 那么第一个条件成立表明'__doc__'一定是存在的, 所以后面可以这么使用.
        print(class_dict)
        if '__doc__' not in class_dict or not class_dict['__doc__'].strip(' \n'):
            raise TypeError("类中必须指定文档注释, 且不能为空!")

        if 'func' not in class_dict or not callable(class_dict['func']):
            raise NameError("类中必须定义func这个可调用函数!")

# People = MyMeta('People', (object, ), {执行类体代码产生People的名称空间})
# 调用Mymeta发生三件事: 调用Mymeta就是调用type.__call__
'''
1. 调用Mymeta类内的__new__方法造了一个空对象 ==> People.
2. 调用Mymeta类内的__init__方法, 完成刚刚造好的空对象初始化的操作.
3. 返回初始化好的对象
'''
class People(metaclass=MyMeta):
    """文档注释"""
    # func = 0
    def func(self):
        pass
    
# 强调!!: 
'''
只要是调用类, 那么就是一次调用
1. 类内的__new__
2. 类内的__init__
'''    

四. __call__使用介绍

# __call__触发条件: 在对象的类中定义, 实现在对象被调用时触发.
class People:
    def __init__(self, name):
        self.name = name

    def __call__(self, *args, **kwargs):
        print(self)     # <__main__.People object at 0x000001A79667F490>
        print(args)     # (1, 2, 3)
        print(kwargs)   # {'a': 4}
        return 111

obj = People('egon')
# 如果想然一个'对象'可以加括号调用, 需要在该'对象'的类中添加一个方法__call__
res = obj(1, 2, 3, a=4)
print(res)  # 111

# 总结:
"""
对象() --> 类内定义了__call__
类()  ---> 自定义元类内定义了__call__
自定义元类() --> 内置元类内定义了__call__
"""

五. 自定义元类控制类的调用(类的对象的产生)

class MyMeta(type):
    # 调用People触发了MyMeta中定义的__call__的执行.
    def __call__(self, *args, **kwargs):
        # 1. MyMeta.__call__函数会先调用People内的__new__, 实例化产生一个空对象people_obj
        print(self)  # <class '__main__.People'>
        print(args, kwargs)  # ('egon', 18) {}
        people_obj = self.__new__(self)  # People.__new__
        # people_obj = object.__new__(self)  # 不推荐使用, 推荐使用上面的形式. 这种形式直接指明道姓获取object中的__new__, 即使People类中定义了__new__想用自己的都用不了.
        print(self.__new__ is object.__new__)  # False ==> People.__new__ is object.__new__

        # 2. MyMeta.__call__函数接着调用People内的__init__完成空对象people_obj的初始化操作
        # super(MyMeta, self).__init__(people_obj, *args, **kwargs)
        self.__init__(people_obj, *args, **kwargs)

        # 自定义元类控制类的对象的产生这一步可以对象的属性进行操作!!!!
        # 将对象初始化完毕之前对对象中所有的属性进行隐藏操作 (注意: 考虑对象中可能默认就指定的隐藏属性)
        people_obj.__dict__ = {attr if attr.startswith(f"_{self.__name__}__") else f'_{self.__name__}__{attr}': value for
                        attr, value in obj.__dict__.items()}
        # 3. MyMeta.__call__函数会返回一个初始化好的对象people_obj
        return people_obj


# 调用自定义元类MyMeta, 产生类People, 发生了三件事:
'''
People = MyMeta(class_name, class_bases, class_dict) ==> type.__call__干了三件事
1. type.__call__函数会先调用MyMeta中的__new__, 产生一个空类对象people_obj
2. type.__call__函数会调用MyMeta中的__init__, 为这个people_obj空类对象进行初始化.
3. type.__call__函数会返回一个返回值, 返回值会返回一个初始好的people_obj空类对象
'''


class People(metaclass=MyMeta):
    def __init__(self, name, age):
        self.name = name
        self.__age = age
        self._score = {}

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


# 调用类People, 产生obj对象, 发生了三件事:
'''
obj = People('egon', 18) ==> MyMeta.__call__干了三件事
1. MyMeta.__call__函数会先People内的__new__, 产生一个空对象obj
2. MyMeta.__call__函数接着调用People内的__init__, 为obj进行初始化
3. MyMeta.__call__函数会返回一个返回值, 返回值为初始化好的obj对象
'''
obj = People('egon', 18)
print(obj.__dict__)

六. 属性查找

# 属性查找的原则:对象-》类-》父类   切记!!!:父类 不是 元类
"""
属性查找:
1. 对象的属性查找: 只会找到object. 不会找元类.
2. 类的属性查找: 在找到object没有找到时, 会去元类中找.
使用__new__创建空对象的二种方式:
    obj = object.__new__(self)  
    obj = self.__new__(self)  # 推荐用这一种, 这种查找方式会按照继承的方式查找, 而object直接找到源底部的__new__方法, 如果需要使用自己的__new__或者其它父类的确无法使用, 有局限性.
"""

class Mymeta(type):
    n = 444

    def __call__(self, *args, **kwargs):  # self=<class '__main__.Teacher'>
        print(self.__new__)
        obj = self.__new__(self)  # Teacher.__new__
        # obj=object.__new__(self)
        # print(self.__new__ is object.__new__)  # True
        self.__init__(obj, *args, **kwargs)
        return obj


class Bar(object):
    # n = 333

    # def __new__(cls, *args, **kwargs):
    #     print('Bar.__new__')
    pass


class Foo(Bar):
    # n = 222

    # def __new__(cls, *args, **kwargs):
    #     print('Foo.__new__')
    pass


class Teacher(Foo, metaclass=Mymeta):
    # n = 111

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

    # def __new__(cls, *args, **kwargs):
    #     print('Foo.__new__')
obj = Teacher('lili', 18)
# 对象的属性查找
# print(obj.__dict__)  # {'name': 'lili', 'age': 18}
# print(obj.n)

# 类的属性查找
print(Teacher.n)

七. 实现单例模式的六种方式(面试必要会3种)

0. 单例介绍

什么是单例模式?
    即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间
        
什么时候使用?
    如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了. 也就是说你调用类实例化对象,为对象进行了复制,
    单例模式是一个软件的设计模式,为了保证一个类, 
    
实现的思路:
    单例的本质就是调用类去实例化出指向同一内存地址的对象,所以我们这里的关注点就是基于这个类.   

1. 类方法classmethod实现

# settings.py文件内容如下
"""
HOST = '127.0.0.1'
PORT = 8080
"""

import settings

class MySql:
    __instance = None   # instance 实例

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

    @classmethod
    def singleton(cls):   # singleton 单例模式
        if not cls.__instance:
            cls.__instance = cls(settings.IP, settings.PORT)
        return cls.__instance

obj1 = MySql('127.0.0.1', 8080)
obj2 = MySql('127.0.0.1', 8080)
print(obj1 is obj2)  # False

# 使用单例模式
obj1 = MySql.singleton()
obj2 = MySql.singleton()
print(obj1 is obj2)  # True

2. 通过调用类实例化对象时自动触发的__new__来实现

class MySql:
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = object.__new__(cls)
        return cls._instance

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

3. 自定义元类实现

"""
思路
利用元类特性: 使用自定义元类实例化类的时候为类绑定一个默认的对象
"""
import settings
class MyMeta(type):
    def __init__(self, class_name, class_bases, class_dict):
        # self._instance = object.__new__(self)
        # self.__init__(self._instance, settings.IP, settings.PORT)

        # 上述两步可以合成下面一步
        self._instance = super().__call__(settings.IP, settings.PORT)
        super().__init__(class_name, class_bases, class_dict)
    
    # 控制Ftp的调用形式: 如果调用Ftp传参了,那么将不是一个单例模式. 如果只是Ftp直接加括号调用, 那么就返回文件中拿到的单例.
    def __call__(self, *args, **kwargs):
        if args or kwargs:
            obj = self.__new__(self)
            self.__init__(obj, *args, **kwargs)
            return obj
        return self._instance

# MySql = MyMeta('MyMeta', (), {执行类体代码产生的名称空间})
class MySql(metaclass=MyMeta):
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port

obj1 = MySql('127.0.0.1', 8080)
obj2 = MySql('127.0.0.1', 8080)
print(obj1 is obj2)  # False

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

4. 函数装饰器实现

'''
利用装饰器语法糖的特性: MySql = wrapper = singleton(MySql) 其中MySql就是被装饰的类.
'''

import settings

def singleton(cls):
    _instance = cls(settings.IP, settings.PORT)

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

@singleton   # MySql = wrapper = singleton(MySql)
class MySql:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port

obj1 = MySql('127.0.0.1', 8080)
obj2 = MySql('127.0.0.1', 8080)
print(obj1 is obj2)  # False

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

5. 类装饰器实现

'''
利用装饰器语法糖的特性: MySql = Singleton(MySql), 装饰MySql类就是对Singleton类的实例化. 调用MySql时就会触发实例化该对象的类Singleton中的__call__方法. (提示: self.cls就是MySql.cls  --> 这里的cls就是原来的类名)
'''

import settings
class Singleton:
    _instance = None
    def __init__(self, cls):
        self.cls = cls

    def __call__(self, *args, **kwargs):
        if args or kwargs:
            return self.cls(*args, **kwargs)
        if not self._instance:
            self._instance = self.cls(settings.IP, settings.PORT)
        return self._instance

@Singleton  # MySql = Singleton(MySql)
class MySql:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port

obj1 = MySql('127.0.0.1', 8080)
obj2 = MySql('127.0.0.1', 8080)
print(obj1 is obj2)  # False

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

6. 导入模块实现

"""
利用模块只要导入一次, 多次导入都是以第一次为准.  
"""
# 创建singleton.py文件放入以下内容:
'''
import settings

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

instance = MySql(settings.IP, settings.PORT)
'''

import singleton
obj1 = singleton.MySql('127.0.0.1', 8080)
obj2 = singleton.MySql('127.0.0.1', 8080)
print(obj1 is obj2)  # False

obj1 = singleton.instance  
obj2 = singleton.instance
print(obj1 is obj2)  # True
posted @ 2020-04-15 22:00  给你加马桶唱疏通  阅读(176)  评论(0编辑  收藏  举报