元类、单例模式、相关练习

一. 元类

1.1 引入

一切都源于一句话:一切皆对象。

即可调用类实例化产生对象,也可以调用元类实例化产生类。

1.2 什么是元类?

元类就是用类来实例化产生类的类。

关系:元类—实例化—>类(People)—实例化—>对象(obj)

class People:
	def __init__(self,name,age)
		self.name=name
		self.age=age
		
	def say(self):
		print('{}-{}'.format(self.name,self.age))
		
# 如何得到对象:
obj=调用类()
obj=People('wth',20)

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

print(type(obj)) # ---> <class 'People'>
# 查看内置的元类:
# 1. type是内置的元类
# 2. 我们用class关键字定义的所有的类以及内置的类都是由元类type实例化产生的
print(type(People)) # ---> <class 'type'>
print(type(int)) # ---> <class 'type'>
查看内置的元类
1. type是内置的类
2. 我们用class关键字定义的所有的类以及内置的类都是由元类type实例化产生的

1.3 class关键字创造类People的步骤

1.3.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}
1.3.2 实例:由exec来创建类的名称空间

类有三大特征:

  1. 类名

    class_name = 'People'
    
  2. 类的基类

    class_bases = (object,)
    
  3. 执行类体代码拿到类的名称空间

    class_dic={}
    
    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_dic)
    # print(class_dict)  # {'__init__': <function __init__ at 0x000001FB9A58C940>, 'say': <function say at 0x000001FB9A58CA60>}
    
  4. 调用元类实例化产生类(在拿到了前面三者的基础上,传参调用)

    #              类名       类的基类    类的名称空间
    print(type(class_name, class_bases, class_dic)) 
    #  ---> <class '__main__.People'>
    
    # People只是类名,值才是真正的类
    People = type(class_name, class_bases, class_dic)
    

1.4 如何自定义元类来控制类的产生

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_dic=执行类体代码产生的名称空间.===>People.__dict__
	
	#           空对象,'People',(object,),{...}
	def __init__(self, calss_name, class_bases, class_dict):
		# type.__init__(self,class_name,class_bases,class_dict)
		super().__init__(class_name,class_bases,class_dict)

		# 自定义元类控制类People产生!!!必须有注释文档+类名首字母大写+类中必须定义func才可以调用函数
		if class_name.istitle():
			raise NameError('类名必须大写')
			
		# '__doc__' not in class_dict 判断文档注释不存在
		# class_dict['__doc__'].strip(' \n') 判断文档注释存在且为空和换行的情况
		# 提示:为什么这里使用’字典[]‘取值不会出现’__doc__‘没有报错的情况,由成员运算or的短路运算发现运行到第一个条件只有为False的情况才会执行后面的第二个条件,那么第一个条件成立表明'__doc__'是一定存在的,所以后面才可以这样使用
		if '__doc__' not in class_dict or not class_dicct['__doc__'].strip(' \n'):
			raise TypeError('类中必须指定文档注释,且不能为空!')
		if 'func' not in class_list or not callable(class_dict['func']):
			raise NameError('类中必须定义func这个可调用函数')
			 

# People=Mymeta('People',(object,),{执行类体代码产生的名称空间})
# 调用Mymeta发生的三件事:调用Mymeta就是调用type.__call__
'''
1. 调用Mymeta类内的__new__方法造了一个空对象 ==> People
2. 调用Mymeta类内的__init__方法,完成刚刚造好的空对象初始化的操作
3. 返回初始化好的对象
'''

class People(metaclass=Mymeta):
	def __init__(self,name,age):
		'''文档注释'''
		self.name = name
		self.age = age

	def func(self):
		print(f'{self.name}-{self.age}')

# 强调!!!
'''
只要是调用类,那么就是一次调用
1.类内的__new__
2.类内的__init__
'''

1.5 __call__使用介绍

# call触发条件:在对象的类(爸爸)中定义,实现在对象被调用(儿子)时触发。
calss 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)

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

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

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=self.__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.7 属性查找

# 属性查找的原则:对象-》类-》父类   切记!!!父类不是元类
"""
属性查找:
1.对象的属性查找:只找到了object,不会找元类
2.类的属性查找:在找到了object没有找到时,回去元类中找
使用__new__创建空对象的两种方式:
	obj=object.__new__(self)
	obj=self.__new__(self)  # 推荐使用下面这种,这种查找方式会按照继承的方式去找,而第一种是指名道姓朝object要__new__方法。如果需要使用自己爹__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__(self,name,age):
	# 	print('Foo.__new__')

obj=Teacher('lili',18)
# 对象的属性查找
# print(obj.__dict__) # {'name':'lili','age':18}
# print(obj.n)

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

二、实现单例模式的六种方式(面试必要会三种)

2.0 单例模式

什么是单例模式?

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

什么时候使用?

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

单例模式是一个软件的设计模式,为了保证一个类,

实现的思路:

单例的本质就是调用类去实例化出指向同一内存地址的对象,所以我们这里的关注点就是基于这个类.

2.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.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

2.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

2.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

2.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

2.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

三、练习

3.1 在元类中控制把自定义类的数据属性都变成大写(提示: 内置数据属性不修改)

"""
思路: 明白包含类的名称空间在自定义元类中只有__init__方法和__new__方法. 而__init__方法只能控制类的产生或者对类添加新的属性. 因此我们这里用__new__方法
    参数介绍:
        __new__方法的第一个参数是自定义元类本身cls
        __new__方法的*args, **kwargs其中只有args有值. 其中args拿到的是(类名class_name, 类继承的基类class_bases, 类的名称空间class_dict),
    返回值: __new__方法必须返回一个对象(自定的类). 因为本题是争类的数据属性的修改, 所以可以对类的名称空间class_dict进程操作,  操作完毕以后再创建的新对象(自定义的类)的class_dict是更新过后的calss_dict.
"""

class MyMeta(type):
    # cls=MyMeta  args=('School', (), {执行类体代码产生的名称空间})  kwargs={}
    def __new__(cls, *args, **kwargs):
        class_name, class_bases, class_dict = args
        # 判断:
        # 1) 如果属性值是可被掉用的则是函数属性, 不是则是数据属性
        # 2) 或者如果是以__开头的属性则是内置的属性, 不是则是自定义的属性
        class_dict = {attr if callable(class_dict[attr]) or attr.startswith('__') else attr.upper(): value for attr, value in class_dict.items()}
        return super().__new__(cls, class_name, class_bases, class_dict)

# School = MyMeta('School', (), {执行类体代码产生的名称空间})
class School(metaclass=MyMeta):
    school_name = '老男孩'

    def func(self):
        pass


print(School.__dict__)

3.2 在元类中控制自定义的类无需`__init__方法

'''
思路: 明白调用对象(自定义的类)就是触发自定义元类的__call__方法. 
    参数介绍: 
        __call__方法的第一个参数self是被调用对象(自定义的类), 
        __call__方法的接下来的*args, **kwargs会拿到被调用对象(自定义的类)时括号内的值.
'''

class MyMeta(type):
    # self=People  args=()  kwargs={'name': 'egon', ....}
    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError('must use keyword argument!')
        people_obj = self.__new__(self)
        # self.__init__(people_obj, *args, **kwargs)
        for attr, value in kwargs.items():
            # people_obj.__dict__[attr.upper()] = value
            setattr(people_obj, attr.upper(), value)
        return people_obj

class People(metaclass=MyMeta):
    pass

# MyMeta.__call__(People, name='egon', age=18):
obj = People(name='egon', age=18)
print(obj.__dict__)  # {'NAME': 'egon', 'AGE': 18}

3.3 在元类中控制自定义的类产生的对象相关的属性全部为隐藏属性

# 解题思路: 在自定义元类中使用__call__.
class MyMeta(type):
    # self=Student args=(name值, age值) 或 kwargs={'name': 值, 'age': 值}
    def __call__(self, *args, **kwargs):
        student_obj = self.__new__(self)
        self.__init__(student_obj, *args, **kwargs)
        # 注意: student_obj对象独有属性中可能已经指定了隐藏属性, 我们需要判断
        student_obj.__dict__ = {attr if attr.startswith(f'_{self.__name__}') else f'_{self.__name__}__{attr}': value for attr, value in student_obj.__dict__.items()}
        return student_obj

class Student(metaclass=MyMeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.__balance = 0
        self._score = {}

# MyMeta.__call__(Student, 'egon', 18):
obj = Student('egon', 18)
print(obj.__dict__)

3.4 基于元类实现单例模式

3.5 写一个类,有个name属性,如果name赋值为非字符串,就不让放

# 提示: __setattr__, __getattr__, __delattr__系列都是点拦截属性(注意: 反射的本质也是通过点)
# 实现方式一:  __setitem__和__setattr__综合运用
"""
class Person:
    def __init__(self, name):
        self.name = name

    # __setitem__: 将`对象.属性=值`的操作构造成`对象['属性']=值`的这种字典的增值操作
    def __setitem__(self, key, value):
        setattr(self, key, value)

    # __setattr__: 添加/修改属性就会触发它的执行. (注意: 实例化是属性的添加)
    def __setattr__(self, key, value):
        if isinstance(value, str):
            self.__dict__[key] = value
        else:
            raise TypeError("对不起, 请输入字符串!")


# p = Person(111)  # TypeError: 对不起, 请输入字符串!

p = Person('egon')
p['name'] = 'lqz'
print(p.name)   # lqz
"""


# 实现方式二: 自定义错误类型 +
class NotStrTypeError(BaseException):
    def __init__(self, value):
        self.value = value
        super(NotStrTypeError, self).__init__()

    def __str__(self):
        return self.value


class Person:
    def __setattr__(self, key, value):
        if isinstance(value, str):
            # self.__dict__[key] = value
            super().__setattr__(key, value)
        else:
            raise NotStrTypeError('对不起, 请输入字符串类型')


p = Person()
try:
    p.name = 1
except NotStrTypeError as e:
    # e就是异常对象, 通过声明__str__方法, 实现打印对象展示异常的文本提示
    print(type(e), e)

3.6 通过上下文管理器写一个mysql的连接,通过with管理

import pymysql


class MySQL:
    def __init__(self, *args, **kwargs):
        self.conn = pymysql.connect(*args, **kwargs)
        self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.cursor.close()
        self.conn.close()

        if exc_type:
            print(exc_type)
            print(exc_val)
            print(exc_tb)
        return True


with MySQL(
        host='127.0.0.1',
        port=3306,
        user='root',
        password='123',
        database='db1',
        charset='utf8',
        # autocommit=True
) as self:
    # 查
    '''
    sql = 'select username from user where password=123'
    affected_rows = self.cursor.execute(sql)

    print('affected_rows:', affected_rows)
    fetchall = self.cursor.fetchall()
    print(fetchall)     # [{'username': 'yang'}, {'username': 'egon'}]

    self.cursor.scroll(-2, 'relative')
    fetchone = self.cursor.fetchone()
    print(fetchone)     # {'username': 'yang'}

    # self.cursor.scroll(-1)
    self.cursor.scroll(0, 'absolute')
    fetchmany = self.cursor.fetchmany(2)
    print(fetchmany)    # [{'username': 'yang'}, {'username': 'egon'}]
    '''

    # 增
    """
    user_info = [
        ('lqz', '222'),
        ('wxx', '333'),
    ]
    sql = 'insert into user(username, password) values(%s, %s)'
    try:
        affected_rows = self.cursor.executemany(sql, user_info)
        print('affected_rows:', affected_rows)

        self.conn.commit()  # 提交
    except Exception as e:
        print(e)
        self.conn.rollback()

    self.cursor.execute('select * from user')
    fetchall = self.cursor.fetchall()
    print(fetchall)
    '''
    [{'id': 1, 'username': 'yang', 'password': '123'}, 
    {'id': 2, 'username': 'egon', 'password': '123'}, 
    {'id': 17, 'username': 'lqz', 'password': '222'}, 
    {'id': 18, 'username': 'wxx', 'password': '333'}]
    '''
    """

    # 删
    """
    sql = 'delete from user where id=18'
    try:
        self.cursor.execute(sql)
        self.conn.commit()  # 提交
    except Exception as e:
        print(e)
        self.conn.rollback()

    self.cursor.execute('select * from user')
    fetchall = self.cursor.fetchall()
    print(fetchall)
    '''
    [{'id': 1, 'username': 'yang', 'password': '123'}, 
    {'id': 2, 'username': 'egon', 'password': '123'}, 
    {'id': 17, 'username': 'lqz', 'password': '222'}]
    '''
    """

    # 改
    """
    sql = 'update user set username="egonDSB" where id=2'
    try:
        self.cursor.execute(sql)
        self.conn.commit()  # 提交
    except Exception as e:
        print(e)
        self.conn.rollback()

    self.cursor.execute('select * from user')
    fetchall = self.cursor.fetchall()
    print(fetchall)
    '''
    [{'id': 1, 'username': 'yang', 'password': '123'}, 
    {'id': 2, 'username': 'egonDSB', 'password': '123'},
    {'id': 17, 'username': 'lqz', 'password': '222'}]
    '''
    """
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值