面向对象的魔法方法,元类
一、魔法方法
魔法方法:类中定义的双下方法都称为魔法方法。
不需要人为调用,在特定的条件下会自动触发运行
常见的魔法方法:
1. __init__方法:对象添加独有数据的时候自动触发
class C(object): def __init__(self,name): self.name = name # 给对象添加独有数据时自动触发 print('__init__') # __init__
obj = C(123) # 类名加括号
2. __str__方法:对象被执行打印操作的时候自动触发
类中没有__str__方法,打印对象得到的是内存地址:
class C(object): def __init__(self,name): self.name = name print('__init__') # __init__ obj = C(123) print(obj) # <__main__.C object at 0x00000149D9903A30>
类中有__str__方法,不执行打印操作不会触发:
class C(object): def __init__(self,name): self.name = name print('__init__') # __init__
def __str__(self):
return '哈哈哈'
obj = C(123)
类中有__str__方法,执行打印操作时才会触发:
class C(object): def __init__(self,name): self.name = name print('__init__') def __str__(self): return '哈哈哈' # 返回值必须为字符串类型,不然会报错 obj = C(123) print(obj) # 哈哈哈
注意__str__的返回值,只能是字符串类型,且不能返回对象本身:
class C(object): def __init__(self,name): self.name = name print('__init__') def __str__(self): return 123 # TypeError: __str__ returned non-string (type int) obj = C(123) print(obj) class C(object): def __init__(self,name): self.name = name print('__init__') def __str__(self): return f'{self}说:哈哈哈' # RecursionError: maximum recursion depth exceeded while calling a Python object obj = C(123) print(obj)
3.__call__方法:对象加括号调用的时候自动触发
class C(object): def __call__(self,*args, **kwargs): print('__call__') # __call__ obj = C() obj() # 对象加括号执行__call__
可以添加返回值,返回什么,则对象加括号就能接收到什么:
class C(object): def __call__(self,*args, **kwargs): print('__call__') # __call__ return 123 obj = C() res = obj() # 对象加括号执行__call__ print(res) # 123
我们给对象传入几个参数:
class C(object): def __call__(self,*args, **kwargs): print('__call__') # __call__ print(args,kwargs) # (123, 'abc') {'name': 'alex'} obj = C() res = obj(123,'abc', name = 'alex')
说明:*args用来接收多余的位置参数组成元组,**kwargs用来接受多余的关键字参数组成字典
4.__getattr__方法:对象在查找不存在的名字的时候会自动触发
class C(object): def __getattr__(self, item): print('__getattr__') # __getattr__ obj = C() obj.age # 对象查找不存在的名字自动触发
返回什么,就会接收到什么:
class C(object): def __getattr__(self, item): print('__getattr__') # __getattr__ return f'抱歉,您所要的{item}不存在' obj = C() print(obj.age) # 抱歉,您所要的age不存在
5.__getattribute__方法
对象查找名字就会自动触发,有它的存在就不会执行上面的__getattr__,很少用。
6.__setattr__方法:给对象添加或者修改数据的时候自动触发
对象.名字 = 值
class C(object): def __setattr__(self, key, value): print('__setattr__') # __setattr__ print(key,value) # key与value分别接收变量名和数据值 name alex obj = C() obj.name = 'alex' # 给对象添加数据值自动触发
7.__enter__方法:当对象被当做with上下文管理操作的开始自动触发
该方法返回什么,as后面的变量名就会接收到什么
8. __exit__方法:with上下文管理语法运行完毕之后自动触发(子代码结束)
class C(object): """当对象被当做with上下文管理操作的开始自动触发""" def __enter__(self): return 123 # 该方法返回什么,as后面的变量名就会接收到什么 """with上下文管理语法运行完毕之后自动触发(子代码结束)""" def __exit__(self, exc_type, exc_val, exc_tb): pass obj = C() with obj as f: print(f) # 123
魔法方法笔试题:
笔试题1.补全下列代码使得运行不报错即可
class Context: pass with Context() as f: f.do_something()
代码补全:
class Context: def do_something(self): pass """with上下文开始""" def __enter__(self): return self """with上下文结束""" def __exit__(self, exc_type, exc_val, exc_tb): pass with Context() as f: f.do_something()
思路:
1. with上下文管理,要有__enter__与__exit__方法
2. __enter__返回什么,as后面的变量名就会接收到什么,所以__enter__要返回self,来什么对象就返回什么对象
3. f.do_something()对象查找方法,所以在对象里再添加一个do_somethint的方法
笔试题2:自定义字典类型,并让字典能够通过句点符的方式操作键值对
class MyDict(dict): def __setattr__(self, key, value): # 2. 添加__setattr__方法 self[key] = value # 3. self指代的是字典,添加新的键值对 def __getattr__(self, item): # 6. 添加__getatter__方法 return self.get(item) # 7. 对象从键值对里取值 obj = MyDict() print(obj) # {} obj.name = 'alex' # 1:想给字典的数据添加一个键值对 print(obj) # 4. 添加的新的键值对可以正常执行 {'name': 'alex'} """5.想让对象通过句点符的方式取值""" print(obj.name) # 8. 对象可以正常取值 alex obj.age = 18 print(obj.age) # 18 print(obj) # {'name': 'alex', 'age': 18}
思路:
1. 自定义字典类,需要给字典添加新的键值对,联想到__setattr__给对象添加或者修改数据时自动触发,所以添加一个__setattr__方法
2. 通过self[key] = value添加键值对,这样就可以把新的键值对成功添加到字典里
3. 这时候通过对象句点符的方式取值,还是会报错,联想到__getattr__方法是对象在查找不存在的名字的时候自动触发,所以添加一个__getattr__方法
4. 返回self.get(item),这样对象点名字,就可以获取名字对应的数据值
二、元类简介
元类的推导流程
"""推导步骤1: 如何查看数据的数据类型"""
s1 = 'hello world' # str() l1 = [11, 22, 33, 44] # list() d1 = {'name': 'jason', 'pwd': 123} # dict() t1 = (11, 22, 33, 44) # tuple() print(type(s1)) # <class 'str'> print(type(l1)) # <class 'list'> print(type(d1)) # <class 'dict'> print(type(t1)) # <class 'tuple'>
"""推导步骤2:其实type方法是用来查看对象的类名"""
class Student: pass obj = Student() print(type(obj)) # <class '__main__.Student'>
"""推导步骤3:python中一切皆对象,我们好奇type查看类显示的是什么"""
class Student: pass obj = Student() print(type(obj)) # <class '__main__.Student'> print(type(Student)) # <class 'type'> class A:pass class B:pass print(type(A), type(B)) # <class 'type'> <class 'type'>
结论:我们定义的类其实都是由type类产生的>>>:元类(产生类的类)
创建类的两种方式
方式1:使用关键字class
class Teacher: school_name = '女子学院' def func1(self):pass print(Teacher) # <class '__main__.Teacher'>
方式2:利用元类type(了解)
type(类名,类的父类,类的名称空间)
cls = type('Student', (object,), {'name':'jason'}) print(cls) # <class '__main__.Teacher'>
补充:
了解知识:名称空间的产生
1.手动写键值对
针对绑定方法不好定义
2.内置方法exec
能够运行字符串类型的代码并产生名称空间
#exec:三个参数 #参数一:包含一系列python代码的字符串 #参数二:全局作用域(字典形式),如果不指定,默认为globals() #参数三:局部作用域(字典形式),如果不指定,默认为locals() #可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中 g={ 'x':1, 'y':2 } l={} exec(''' global x,z x=100 z=200 m=300 ''',g,l) print(g) #{'x': 100, 'y': 2,'z':200,......} print(l) #{'m': 300}
元类定制类的产生行为
我们现在知道了类是怎么产生的,就可以通过一些手段干预类和对象的产生行为。
推导:
- 对象是由类名加括号产生的,会执行类里的__init__
- 类是由元类加括号产生的,会执行元类里的__init__
- 所以元类可以用来拦截类的一些创建形为。
案例:
需求:所有的类必须首字母大写,否则无法产生
# 1.自定义元类:继承type的类称之为元类 class MyMetaClass(type): def __init__(self, what, bases=None, dict=None): # 形参是通过查看type的源码里的__init__方法得到的 """通过打印三个参数,发现what是类名,bases是父类,dict是名称空间""" # print('what',what) # print('bases',bases) # print('dict',dict) if not what.istitle(): raise TypeError('你不是不python程序员?懂不懂规矩?类名首字母应该大写') # 如果类名的首字母没有大写,抛出异常 super().__init__(what, bases, dict) # 调用父类方法 # 2. 指定类的元类:利用关键字metaclass指定类的元类 class myclass(metaclass= MyMetaClass): desc = '元类其实很有趣 就是有点绕' print(myclass) # 类的首字母没有大写,会报错 class Student(metaclass=MyMetaClass): info = '我是学生 我很听话' print(Student) # <class '__main__.Student'> print(Student.__dict__) # Student的名称空间
推导:
- 对象加括号会执行产生该对象类里面的 __call__
- 类加括号会执行产生该类的类里面的 __call__
案例:
需求:给对象添加独有数据的时候,必须采用关键字参数传参
第1步,按照位置传参
class MyMetaClass(type): def __call__(self, *args, **kwargs): print(args) # ('alex',18, 'famale') print(kwargs) # {} return super().__call__(*args, **kwargs) class Student(metaclass=MyMetaClass): def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender obj = Student('alex',18, 'famale')
第2步:如果按照位置传参,则主动报错,只能按照关键字参数传参
class MyMetaClass(type): def __call__(self, *args, **kwargs): print(args) # () print(kwargs) # {'name': 'alex', 'age': 18, 'gender': 'famale'} if args: raise TypeError("只能按照关键字传参") return super().__call__(*args, **kwargs) class Student(metaclass=MyMetaClass): def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender # obj = Student('alex',18, 'famale') # TypeError: 只能按照关键字传参 obj1 = Student(name='alex',age=18,gender='famale')
魔法方法之__new__
__new__ : 产生一个空对象
class MyMetaClass(type): def __call__(self, *args, **kwargs): # 1.先产生一个空对象(骨架) obj = self.__new__(self) # 2.调用__init__给对象添加独有数据(血肉) self.__init__(obj,*args,**kwargs) # 3.返回创建好的对象 return obj class Student(metaclass = MyMetaClass): def __init__(self, name): self.name = name obj = Student('alex') print(obj.name) # alex
三、设计模式简介
1. 设计模式:前人通过大量的验证创建出来解决一些问题的固定高效方法
2. IT行业
23种
创建型、结构型、行为型
详细博客: https://blog.csdn.net/qq_35669659/article/details/123145226
3. 单例模式:类加括号无论执行多少次永远只会产生一个对象
目的:
当类中有很多非常强大的方法 我们在程序中很多地方都需要使用
如果不做单例,会产生很多无用的对象浪费存储空间
我们想着使用单例模式,整个程序就用一个对象
单例模式实现的多种方式
方式1:用绑定给类的方法产生
class C1: __instance = None def __init__(self, name, age): self.name = name self.age = age @classmethod def singleton(cls): if not cls.__instance: cls.__instance = cls('alex', 18) return cls.__instance obj1 = C1.singleton() obj2 = C1.singleton() obj3 = C1.singleton() # 用绑定给类的方法产生单例 print(id(obj1)) # 1764532833008 print(id(obj2)) # 1764532833008 print(id(obj3)) # 1764532833008 obj4 = C1('lucy',19) # 方法并没有写死,如果想产生新的对象,则直接用类加括号产生 obj5 = C1('jsaon',26) print(id(obj4)) # 1764532879616 print(id(obj5)) # 2377895117152
方式2:
class Mymeta(type): def __init__(self, name, bases, dict): # 定义类Mysql时就触发 # 事先从配置文件中取配置来造一个Mysql的实例出来 self.__instance = object.__new__(self) # 产生对象 self.__init__(self.__instance,'alex', 18) # 初始化对象 # 上述两步可以合成下面一步 # self.__instance = super().call__(*args, **kwargs) super().__init__(name, bases, dict) def __call__(self, *args, **kwargs): #Mysql(...)时触发 if args or kwargs: # args或kwargs内有值 obj = object.__new__(self) self.__init__(obj,*args, **kwargs) return obj return self.__instance class Mysql(metaclass=Mymeta): def __init__(self,name,age): self.name = name self.age = age obj1 = Mysql() # 没有传值则默认从配置文件中读配置来实例化,所有的实例应该指向一个内存地址 obj2 = Mysql() print(id(obj1), id(obj2)) # 2135450405616 2135450405616 obj3 = Mysql('alex',18) obj4 = Mysql('lucy',19) # 根据所传参数的不同,产生不同的对象 print(id(obj3), id(obj4)) # 1865674785504 1865674785456
方式3:基于模块的单例模式
# 在模块里把所有的功能和方法都写好,要用的时候直接从模块里调 class C1: def __init__(self,name): self.name = name def func1(self): pass def func2(self): pass def func3(self): pass def func4(self): pass obj = C1('jason')
方式4: 定义一个装饰器实现单例模式
def outer(cls): # cls=Mysql _instance = cls('alex', 18) def inner(*args, **kwargs): if args or kwargs: obj = cls(*args, **kwargs) return obj return _instance return inner @outer # Mysql=singleton(Mysql) class Mysql: def __init__(self, name, age): self.name = name self.age = age obj1 = Mysql() obj2 = Mysql() obj3 = Mysql() print(obj1 is obj2 is obj3) # True obj4 = Mysql('alex', 18) obj5 = Mysql('lucy', 19) print(obj3 is obj4) # False
方式5:
class MyMetaClass(type): def __call__(self, *args, **kwargs): if not self.instance: obj = self.__new__(self) self.__init__(obj) self.instance = obj return self.instance class C1(metaclass=MyMetaClass): instance = None obj1 = C1() obj2 = C1() obj3 = C1() print(obj1 is obj2 is obj3) # True
四、
优势: 能够序列化python中所有的类型
缺陷: 只能够在python中使用,无法跨语言传输
案例:
需求: 产生一个对象并保存到文件中,取出来还是一个对象
class C1: def __init__(self, name, age): self.name = name self.age = age def func1(self): print('from func1') def func2(self): print('from fucn2') obj = C1('alex', 18) import pickle with open(r'a.txt', 'wb') as f: data = pickle.dump(obj, f) with open(r'a.txt','rb') as f: data = pickle.load(f) print(data) # <__main__.C1 object at 0x00000222A6F34BB0> data.func1() # from func1 data.func2() # from fucn2 print(data.name) # alex