魔法方法与元类
目录面向对象的魔法方法
魔法方法:类中定义的双下方法都称为魔法方法,这种方法需要人为调用,在特定的条件下回自动触发运行。
eg:__init__创建空对象之后自动触发给对象添加独有的数据
1.__init__
对象添加独有数据的时候自动触发
初始化创建好的对象,初始化的前提是:“给实例对象赋值”。
如果不定义__init__方法,系统会提供一个默认的__init__方法。
如果定义了带参的__init__方法,系统不创建默认的__init__方法。__init__只能返回None,一般不写return。
2.___str__
对象被执行打印操作的时候自动触发,该方法返回的必须是字符串。
3.__call__
对象加括号调用的时候自动触发。
可调用对象:凡是可以把一对括号()应用到某个对象身上都可称之为可调用对象,判断对象是否为可调用对象可以用函数callable。例如,函数、内置函数、类都属于可调用对象。
在类中重新定义_call_方法,那么实例对象也将成为一个可调用对象,例:
class SalaryAcount:
def __call__(self,salary):
year=salary*12
return dict(month=salary,year=year)
s=SalaryAcount()
print(s(10000))
# {'month': 10000, 'year': 120000}
4.__getattr__与__getattribute__
"""
__getattr__对象点不存在的名字的时候自动触发
__getattribute__对象点名字就会自动触发 有它的存在就不会执行__getattr__
"""
重载_getattr_方法对类及其实例未定义的属性有效。如果访问的属性存在,就不会调用_getattr_方法。这个属性的存在,包括类属性和实例属性。
class ClassA:
x='a'
def __init__(self):
self.y='b'
def __getattr__(self, item):
return '报告老大,有人调用getattr方法,item:{}'.format(item)
a=ClassA()
print(a.x)# 输出结果a
print(a.y)# 输出结果b
print(a.z)# 输出结果,报告老大,有人调用getattr方法,item:z
通过类名直接调用类属性不会调用_getattr_和_getattribute_
通过实例化对象调用实例属性或者类属性都会首先调用_getattribute_,无论类存在与否
内置getattr和hasattr也会触发这个魔法方法_getattribute_
class ClassA:
x='a'
def __init__(self):
self.y='b'
def __getattribute__(self, item):
return '__getattribute__'
print(ClassA.x)# a
a=ClassA()
print(a.x)# __getattribute__
print(a.y)# __getattribute__
print(a.z)# __getattribute__
在__getattribute__代码块中,再次执行属性的获取操作时,会再次触发__getattribute__方法的调用,代码将会陷入无限递归
class ClassA:
x = 'a'
def __getattribute__(self, item):
print('__getattribute__')
return self.item
# 再次出现属性的获取操作,会再次触发__getattribute__的调用,相当于return self.__getattribute__(item)
a = ClassA()
a.x
为了避免无限递归,应该把获取属性的方法 __getattribute__指向一个更高的超类,例如object。
class ClassA:
x = 'a' # 类属性
def __getattribute__(self, item):
print('__getattribute__')
return super().__getattribute__(item)
# 或者使用return object.__getattribute__(self, item)
a = ClassA()
print(a.x)
# 输出__getattribute__
当同时定义__getattribute__和__getattr__时,__getattr__方法不会再被调用,除非显示调用__getattr__方法或引发AttributeError异常。
class A():
x = 1
def __getattribute__(self, item):
print('正在调用__getattribute__,属性{}'.format(item))
if item != 'x':
raise AttributeError
return super().__getattribute__(item)
def __getattr__(self, item):
print('正在调用__getattr__属性{}'.format(item))
return 'aa'
a = A()
print(a.x)
print(a.y)
总结:通过实例化对象调用实例属性或类属性的过程
首先调用getattribute方法,为避免无限递归循环,getattribute应当返回object.__getattribute(self,item)
object的getattribute方法会先查找实例的属性来查找返回,如果在实例的属性中未找到,则去类属性查找返回,如果类属性未找到,则调用getattr方法。
6.__setattr__
给对象添加或者修改数据的时候自动触发 对象.名字 = 值
class Person():
def __init__(self,name,age):
self.name=name
self.age=age
def __setattr__(self, key, value):
self.__dict__[key]=value
#此处不能写成self.key = value,会导致无线循环
p=Person('jason',28)
p.gender='man'
print(p.__dict__)
# {'name': 'jason', 'age': 28, 'gender': 'man'}
会执行三次print函数,是因为在__init__方法中,对象A初始化时给属性name和age赋值时,触发了__setattr__方法。 使用该方法是同样需要十分小心避免无限循环陷阱
如果定义__setattr__方法的同时定义了__getattribute__方法,那么在修改__dict__字典中的键值对时, 由于调用了self.__dict__属性,同样会触发__getattribute__方法,使用时应格外小心
class Person():
def __init__(self,name,age):
self.name = name
self.age = age
def __setattr__(self, key, value):
print('执行__setattr__')
self.__dict__[key] = value
def __getattribute__(self, item):
print('执行__getattribute__')
print('item:{}'.format(item))
#此处的item是__dict__
return super().__getattribute__(item)
p = Person('kuangfeng',20)
p.gender = 'man'
print(p.__dict__)
7. __enter__与__exit__
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
__exit__(self, exc_type, exc_val, exc_tb)
exit()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
8.__exit__
with上下文管理语法运行完毕之后自动触发(子代码结束)
基于魔法方法的笔试题
1.补全下列代码使得运行不报错即可
class Context:
pass
with Context() as f:
f.do_something()
解:
class Context:
def do_something(self):
pass
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
with Context() as f:
f.do_something()
2.自定义字典类型并让字典能够通过句点符的方式操作键值对
class MyDict(dict):
def __setattr__(self, key, value):
self[key]=value
def __getattr__(self, item):
return self.get(item)
obj=MyDict()
obj.name='jason'
obj.pwd=18
obj.hobby='read'
print(obj.__dict__)
# print(obj)# {'name': 'jason', 'pwd': 18, 'hobby': 'read'}
# print(obj.name)# jason
# print(obj.pwd)# 18
# print(obj.hobby)# read
# print(obj.__dict__)# 字典的名称空间 {}
# print(type(obj))# <class '__main__.MyDict'>
元类简介
一切源于一句话:python中一切皆为对象。让我们先定义一个类,然后逐步分析
class Teacher(object):
school='开心'
def __init__(self,name,age):
self.name=name
self.age=age
def say(self):
print('%s says welcome to the "开心" to learn Python' %self.name)
"""
所有的对象都是实例化或者说调用类而得到的(调用类的过程称为类的实例化),比如对象t1是调用类Teacher得到的
"""
t1 = Teacher('lili', 18)
print(type(t1)) # 查看对象t1的类是<class '__main__.Teacher'>
"""
如果一切皆为对象,那么类Teacher本质也是一个对象,既然所有的对象都是调用类得到的,那么Teacher必然也是调用了一个类得到的,这个类称为元类。
"""
print(type(Teacher))
# 结果为<class 'type'>,证明是调用了type这个元类而产生的Teacher,即默认的元类为type
创建类的两种方式
# 方式1:使用关键字class
class Teacher:
school_name = '老女儿'
def func1(self):pass
print(Teacher)
# <class '__main__.Teacher'>
print(Teacher.__dict__)
# {'__module__': '__main__', 'school_name': '老女儿', 'func1': <function Teacher.func1 at 0x000001720D67B4C0>, '__dict__': <attribute '__dict__' of 'Teacher' objects>, '__weakref__': <attribute '__weakref__' of 'Teacher' objects>, '__doc__': None}
# 方式2:利用元类type type(类名,类的父类,类的名称空间)
cls = type('Student', (object,), {'name':'jason'})
print(cls)
# <class '__main__.Student'>
print(cls.__dict__)
# {'name': 'jason', '__module__': '__main__', '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
"""
了解知识:名称空间的产生
1.手动写键值对
针对绑定方法不好定义
2.内置方法exec
能够运行字符串类型的代码并产生名称空间
"""
元类定制类的产生行为
"""
推导
对象是由类名加括号产生的 __init__
类是由元类加括号产生的 __init__
"""
"""所有的类必须首字母大写 否则无法产生"""
# 1.自定义元类:继承type的类也称之为元类
class MyMetaClass(type):
def __init__(self, what, bases=None, dict=None):
# 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 = '元类其实很有趣 就是有点绕'
class Student(metaclass=MyMetaClass):
info = '我是学生 我很听话'
print(Student)
print(Student.__dict__)
元类定制对象的产生行为
"""
推导
对象加括号会执行产生该对象类里面的 __call__
类加括号会执行产生该类的类里面的 __call__
"""
"""给对象添加独有数据的时候 必须采用关键字参数传参"""
class MyMetaClass(type):
def __call__(self, *args, **kwargs):
# 1.产生一个空对象(骨架)
# 2.调用__init__给对象添加独有的数据(血肉)
# 3.返回创建好的对象
# print(args) # ('jason', 18, 'male')
# print(kwargs) # {}
if args:
raise TypeError("你怎么回事 Jason要求对象的独有数据必须按照关键字参数传参 我看你是不想干了!!!")
return super().__call__(*args, **kwargs)
class Student(metaclass=MyMetaClass):
def __init__(self, name, age, gender):
# print('__init__')
self.name = name
self.age = age
self.gender = gender
# obj = Student('jason', 18, 'male')
obj = Student(name='jason',age= 18,gender= 'male')
print(obj.__dict__)
魔法方法之双下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('jason')
print(obj.name)
"""
__new__可以产生空对象
"""
设计模式简介及单例模式
设计模式分类
1.创建型模式,共五种:
工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
2.结构型模式,共七种:
适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
3.行为型模式,共十一种:
策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
设计模式的六大原则
开闭原则
开闭原则就是说对扩展开放,对修改关闭。
里氏代换原则
面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。
依赖倒转原则
这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。
接口隔离原则
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。
迪米特法则(最少知道原则)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
合成复用原则
原则是尽量使用合成/聚合的方式,而不是使用继承。
单例模式
类加括号无论执行多少次永远只会产生一个对象
目的:
当类中有很多非常强大的方法 我们在程序中很多地方都需要使用
如果不做单例 会产生很多无用的对象浪费存储空间
我们想着使用单例模式 整个程序就用一个对象
标签:__,name,getattribute,self,魔法,元类,print,方法,def
From: https://www.cnblogs.com/zhiliaowang/p/16871028.html