语言的分类:
面向机器:汇编语言
面向过程:问题规模小,可以步骤化,按部就班处理
C语言
面向对象OOP:一种认识世界、分析世界的方法论。将万事万物抽象为类
C++、Java、Python
面向过程和面向对象:
面向过程:
面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么问题
优点:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可
缺点:一套流水线或流程就是用来解决一个问题,代码牵一发动全身
面向对象:
万事万物皆可为对象
优点:解决了程序的扩展性,对某一个对象单独修改,会立刻反映到整个体系中。
缺点:可控性差,无法像面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果
**属性:它是对象状态的抽象,用数据结构来描述
**操作:它是对象行为的抽象,用操作名和实现该操作的方法来描述
Python面向对象:
OOP(Object Oriented Programming):
在Python中,用变量表示特征,用函数表示方法,因而具有相同特征和方法的一类事物就是’类’,对象则是这一类事物中具体的一个!
序言:
1,一切皆对象
2,对象是数据和操作的封装
3,对象是独立的,但是对象之间可以相互作用
面向对象三要素
**封装
将数据和操作糅合到一起
隐藏数据:对外部隐藏有关对象工作原理的细节,如人会开车,但是不需要去了解汽车构造的原理
------------封装------------
封装,指的是向外部隐藏不必要的细节,封装让你无需知道对象的构造就能使用它
1,将数据和操作组织到类中,即属性和方法
2,将数据隐藏,给使用者提供操作(方法);使用者通过操作就可以获取或者修改数据
getter,setter
3,通过访问控制,适当把数据和操作给用户,该隐藏的隐藏起来;例如保护成员或私有成员
**继承(Inheritance )
多复用,继承来的就不用自己写(可基于通用类创建专用类)
多继承少修改,OCP(Open closed Principle,开闭原则),使用继承来改变,体现个性化
------------继承------------
继承定义方法:在需要继承的类名后面添加括号,括号中写的是继承的类名
使用定义:
**如果类定义时,没有基类列表,等同于继承自object。在Python3中,object类是所有对象的基类。
class 子类名(基类1[,基类2...]):
语句块
继承:一个类可以是一个或多个类的子类,在这种情况下,子类将继承超类的所有方法。
1,在面向对象中,从父类继承,就可以直接拥有父类的属性和方法,这样可以减少代码、多复用;同时子类可以定义自己的属性和方法。
2,继承可以让子类从父类获取特征(属性和方法)
父类:称为基类、超类; Animal是Cat的父类
子类:称为派生类;Cat是Animal的子类
代码示例:
class Animal:
def shout(self):
print('Animal shouts')
class Cat(Animal):
pass
# tom = Cat()
# print(tom.shout())
Python支持多继承,继承也可以多级:
继承的特殊属性与方法:
__base__:类的基类
# print(Cat.__base__.__name__)
__bases__:类的基类元组
# print(tom.__class__.__bases__)
__mro__:显示方法查找顺序,基类的元组
# print(tom.__class__.__mro__)
mro():显示方法查找顺序,int.mro()
# print(Cat.mro())
__subclasses__():类的子类列表,int.__subclasses__()
# print(Animal.__subclasses__())
继承中的初始化:
super():调用父类的属性
代码示例:
class A:
def __init__(self, c):
self.a = 'a'
self.b = 'b'
self.c = c
class B(A):
def __init__(self, c):
self.a1 = 'a1'
self.b1 = 'b1'
super().__init__(c)
c = B('six')
print(c.__dict__)
**多继承:OCP原则:多用'继承'、少修改
*Python2中为了兼容,分为古典类(旧式类)和新式类!
*Python3中全部都是新式类;新式类都是继承自object,新式类可以使用super。
多继承的定义和实现:
class ClassName(基类列表):
类语句体
继承的优点和作用:增强基类、实现多态
*多态:一个类继承自多个类就是多继承,它将具有多个类的特征。
多继承弊端:多继承可能会带来二义性;例如,猫和狗都继承自动物类,如果一个类都继承了猫类和狗类,同时猫和狗都有shout方法,那么子类究竟应该继承谁的shout?
*解决办法:深度优先或者广度优先
*最好避免多继承
------MRO------
Python使用MRO(Method resolution order)解决基类搜索顺序问题!
1,C3算法,在类被创建出来的时候,就计算出一个MRO有序列表;
2,Python3唯一支持的算法,同时C3算法解决多继承的二义性
------Mixin------
**Mixin、装饰器
装饰器:用装饰器增强一个类,把功能给类附加上去,哪个类需要,就装饰。
Mixin:Mixin就是其它类混合进来,同时带来了类的属性和方法;
------Mixin类------
使用:Mixin类通常在继承列表的第一个位置;如 PrintableWord(PrintableMixin, word)
重点:Mixin是类,就可以继承
1,Mixin本质上就是多继承实现的;
2,Mixin体现的是一种组合的设计模式;
Mixin类的使用原则:
1,Mixin类中不应该显式的出现__init__初始化方法
2,Mixin类通常不能独立工作,因为它是准备混入别的类中的部分功能实现
3,Mixin类的祖先类也应该是Mixin类
代码:
class Document:
def __init__(self, content):
self.content = content
class Word(Document): pass
class PrintableMixin:
def print(self):
print(self.content, 'Mixin')
class PrintableWord(PrintableMixin, word): pass
--------继承总结--------
继承时,公有的,子类和实例都可以随意访问;
私有成员被隐藏,子类和实例不可直接访问,当私有变量所在的类内部的方法中可以访问这个私有变量。
属性查找顺序:
实例自身__dict__ ---> 实例类__dict__ ---> 实例类的父类__dict__ ---> object
1,如果没有找到抛出异常
2,先找到就立即返回
**多态:继承加覆盖实现了多态
面向对象编程最灵活的地方,动态绑定
可对不同类型的对象执行相同的操作
------------多态------------:继承和覆盖就是多态
多态,意思是'有多种形态',无需知道对象的内部细节就可以使用它;意味着即便你不知道变量指向的是哪种对象,也能够对其进行操作,且操作的行为将随着对象所属的类型(class)而异
多态和方法:多态可以让对象自己去处理操作,并返回自己想要的结果
多态形式多样:
1,无需知道对象是什么样就能对其执行操作时,都是多态在起作用
例如:1 + 2,'Fish' + 'lincens'
上述表明,加法运算符既可用于数值,也可用于字符串
Python类定义:类也是一种对象;每个对象都属于特定的类,并被称为该类的实例!
1,必须使用class关键字定义 # class Person:
2,类名必须是用大驼峰命名 # ObjectName
3,类定义完成后,就产生了一个类对象(Python中一切皆对象),同时ClassName这个标识符分配给了这个类对象!!
示例代码:
class Person:
NAME = 'TOM'
def bite(self):
return self
类对象及属性:
*类对象:类的定义就会产生一个类对象
*类属性:类定义中的变量和类中定义的方法都是类的属性
*类变量:示例中'NAME'就是类Person的变量;类变量必须是全大写
类实例化:在类名后面加上一个括号,就调用类的实例化方法,创建一个类的个体。
示例代码:
tom = Person()
1,tom就是Person这个类的实例化,是Person类实例化的个体
2,bite(self)中self指代实例化的个体本身
3,方法必须时对象的Method,不是普通的函数对象function,一般至少有一个参数,第一个参数可以是self,self表示这个对象本身!
*实例创建过程:
实例(对象)创建之前需要了解__new__和__init__之间的区别
**__new__处理对象创建;
**而__init__处理对象初始化;并且返回值是None,不可以修改!
代码:
class A:
def __new__(cls):
print("A.__new__called")
return super(A, cls).__new__(cls)
def __init__(self):
print("A.__init__called")
返回结果:
A.__new__called
A.__init__called
结论:__new__在调用类名(实例化)创建对象时自动调用,而__init__在__new__每次由__new__返回时被调用,而将实例返回传递给__init__且self作为参数。
重点:
1,__init__默认return None,因为我们不从构造函数返回任何东西。因为它的目的只是改变新创建的实例的新状态。
2,如果没有定义__new__和__init__,默认从父类或者祖先类(object)继承。
*实例变量和类变量:
实例变量是每一个实例自己的变量,是自己独有的;类变量是类的变量,是类的所有实例共享的属性和方法。
__name__:对象名
__class__:对象的类型
__dict__:对象的属性的字典
__qualname__:类的限定名
Python中每一种对象都拥有不同的属性,函数、类都是对象,类的实例也是对象。
总结:
变量访问:
*类的变量,也是这个类的实例的,其实例都是可访问
*实例的变量,只是这个实例的,通过类是访问不到的
属性增加:
实例(对象)可以动态的给自己增加属性。实例.__dict__或实例.变量名都可以访问到
**实例属性的查找顺序:
实例使用.来访问属性,会先找自己的__dict__,如果没有,然后通过实例.__class__.__dict__找到自己类,在类中__dict__中查找
注意:
1,如果实例使用__dict__[变量名]访问变量,将不会按照上面的查找顺序查找变量;因为这里指明使用字典的key查找,不是属性查找。
2,类变量使用全大写来命名。
***属性装饰器:
示例代码:
class Person:
def __init__(self, name, age=18):
self.name = name
self.__age = age
@property # 只读
def age(self):
return self.__age
@age.setter # 可修改
def age(self, age):
self.__age = age
@age.deleter # 可删除
def age(self):
del self.__age
print('del')
把实例的属性保护起来,不让外部直接访问,外部使用getter读取属性和setter方法设置属性。
property 装饰器:
*后面跟的函数名就是以后的属性名,这个必须有,有了它至少是只读属性
setter 装饰器:
*与属性名同名,且接受2个参数,第一个是self,第二个是将要赋值的值;有了setter,属性可写
deleter 装饰器:
*可以控制是否删除属性
注意:property 装饰器必须在前,setter、deleter装饰器在后
优点:property 装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏效果
***classmethod 和 staticmethod
@classmethod
1,在类定义中,使用@classmethod装饰器修饰的方法
2,必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即类本身
3,cls这个标识符可以是任意合法名称,建议不要修改
4,通过cls可以直接操作类的属性
示例代码:
class Person:
def method(self):
print('This is method')
@classmethod
def class_method(cls):
print('class = {0.__name__} {0}'.format(cls))
cls.Height = 170
# Person.class_method()
@staticmethod
1,在类定义中,使用@staticmethod装饰器修饰的方法
2,调用时,不会隐式的传入参数
静态方法,只是表明这个方法属于这个名词空间,函数归在一起,方便组织管理。
示例代码:
class MyClass:
@staticmethod
def smeth():
print('This is a static method')
# MyClass.smeth()
方法调用总结:
1,类除了普通方法都可以用调用,普通方法需要对象的实例作为第一参数
普通方法:
def method(self):
print('This is method')
2,实例可以调用所有类中定义的方法(包括类方法、静态方法),普通方法传入实例自身,静态方法和类方法需要找到实例的类。
***访问控制:
私有属性:使用双下划线开头的属性名。
self.__name = name
私有变量的本质:
*类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python解释器会将其改名,转换为:_类名__变量名;所以使用原来的名称访问不到
保护变量:在变量名前使用一个下划线
self._name = name
*保护变量在属性中并没有改变名称,和普通属性一样,解释器不做任何特殊处理。最好不要直接使用。
私有方法:使用单下划线、双下划线命名方法。
def __init__(self):
def _gets():
1,单下划线的方法,解释器不做任何改变
2,双下划线的方法,是私有方法,解释器会改名;_类名__方法名。
注意:方法变量在类的__dict__中。
**对象销毁:类中可以定义 __del__ 方法,称为析构函数(方法)
**__del__:对象的销毁;建议不要手动执行
作用:销毁类的实例的时候调用,以释放占用的资源
注意:这个方法不能引起对象的真正销毁,只是对象销毁的时候自动调用它
优点:使用del语句删除实例,引用计数减1。当引用计数为0时,会自动调用__del__方法。
只有垃圾回收(gc),才会真正清理对象,并且在之前自动调用 __del__ 方法。建议不要手动调用这个方法。
**方法的重载(overload):
解释:同一个方法名,但是参数数量、类型不一样,就是同一个方法的重载。
重点:Python没有重载,同时也不需要
***Python中,方法(函数)定义中,形参非常灵活,不需要指定类型,参数个数也不固定(可变参数),一个函数的定义可以实现很多种不同形式实参的调用。
***方法的重写、覆盖override
方法的重写、覆盖:只要自己类定义了,就按照(实例查找顺序)字典的搜索顺序,找到就返回。
类方法、静态方法都可以覆盖(classmethod、staticmethod)
示例代码:
class Animal:
def shout(self):
print('Animal shouts')
class Cat(Animal):
def shout(self):
print('Miao')
c = Cat()
print(c.shout())
补丁:可以通过修改或替换类的成员,使用者调用的方式没有改变;但是,类提供的功能可能已经改变了。
1,在运行时,对属性、方法、函数等进行动态替换
2,目的:通过替换、修改来增强、扩展原有代码的能力。
3,慎用
示例代码: # 下面代码也可以写在同一个模块执行
# Persons.py
class Person:
def get_score(self):
ret = {'english':78}
return ret
# patch.py
def get_score(self):
return dict(name=self.__class__.__name__,englist=69)
from Persons import Person
from patch import get_score
def monkeypatch():
Person.get_score = get_score
monkeypatch()
if __name__ == '__main__':
print(Person().get_score())
# 返回 {'name': 'Person', 'englist': 69}
总结:使用补丁可以返回补丁定义的逻辑结果
魔术方法(magic):
-------特殊属性-------
__name__:类、函数、方法等的名字
__module__:类定义所在的模块名
__class__:对象或类所属的类
__bases__:类的基类元组,顺序为它们在基类列表出现的顺序
__doc__:类、函数的文档字符串,如果没有定义则为None
__mro__:类的mro,class.mro()返回的结果的保存在__mro__中
__dict__:类或实例的属性,可写的字典
-------查看属性-------
__dir__:返回类或者对象的所有成员名称列表。
1,dir()函数就是调用__dir__(),如果提供__dir__(),则返回属性的列表,否则会尽量从__dict__属性中收集信息
1,如果dir([obj])参数obj包含方法__dir__,该方法被调用
2,如果参数obj不包含__dir__(),该方法将最大限度地收集参数信息
重点:
1,dir()对于不同类型的对象具有不同的行为
*:如果对象是模块对象,返回的列表包含模块的属性名
*:如果对象是类型或者类对象,返回的列表包含类的属性名,及它的基类的属性名
*:否则,返回列表包含对象的属性名,它的类的属性名和类的基类的属性名
************魔术方法************
创建、初始化、销毁
__new__,__init__,__del__
__hash__:内建函数hash()调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash。
1,__hash__方法只是返回一个hash值作为set的key,但是(去重),还需要__eq__来判断两个对象是否相等
2,hash值相等,只是hash冲突,不能说明两个对象是相等的
解决办法:一般__hash__方法和__eq__配合使用
*__hash__方法是为了作为set或者dict的key,所以(去重)配合使用__eq__方法。
list为什么不可hash?
1,在list源码中定义了__hash__ = None
示例代码:
class A:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __hash__(self):
return id(self.name)
def __repr__(self):
return self.name
__eq__:对应==操作符,判断两个对象是否相等,返回bool值
示例代码:
class A:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __hash__(self):
return id(self.name)
def __repr__(self):
return self.name
def __eq__(self, other):
return self.age == other.age
__bool__:
*内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值;
*没有定义 __bool__(),就找 __len__()返回长度;
1,长度非0为真
2,如果 __len__()也没有定义,那么所有实例对象都返回真
示例代码:
class A:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __bool__(self):
return True
可视化:
__repr__:
*内建函数 repr()对一个对象获取字符串表达形式
*调用 __repr__ 方法返回字符串表达形式,如果 __repr__ 没有定义,就返回object的定义显示内存地址信息。
__str__:
*str() 函数、内建函数 format()、print()函数调用,需要返回对象的字符串表现形式
*如果没有定义 __str__,就去调用 __repr__ 方法返回字符串表达形式;如果 __repr__ 没有定义,就返回对象的内存地址信息
__bytes__:
*bytes()函数调用,返回一个对象的bytes表达形式,即返回bytes对象
示例代码:
import json
class A:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __repr__(self):
return "repr: {},{}".format(self.name, self.age)
def __str__(self):
return "str: {},{}".format(self.name, self.age)
def __bytes__(self):
return "{} is {}".format(self.name, self.age).encode()
# return json.dumps(self.__dict__).encode()
运算符重载:operator模块提供以下的特殊方法,可以将类的实例使用下面的操作符来操作
<,<=,==,>,>=,!=:比较运算符
__lt__:
__le__:
__eq__:
__gt__:
__ge__:
__ne__:
+,-,*,/,%,//,**,divmod:
__add__:
__sub__:
__mul__:
__truediv__:
__mod__:
__floordiv__:
__pow__:
__divmod__:
示例代码:
class A:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __sub__(self, other):
return self.age - other.age
+=,-+,*=,/=,%=,//=,**=:
例如:
+=背后的特殊方法是 __iadd__ (用于'就地加法'),但是如果一个类没有实现这个方法,Python会退一步调用 __add__
*实现了 __iadd__ ;就像调用了a.extend(b)一样。
*没有实现 __iadd__ ; a += b 效果就变成了 a = a + b,先计算a + b,得到一个新对象,然后赋值给a。
可变序列一般都实现了 __iadd__ 等方法;而不可变序列根本就不支持这个操作。
__iadd__:
__isub__:
__imul__:
__itruediv__:
__imod__:
__ifloordiv__:
__ipow__:
示例代码:
class A:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return (self.x + other.x, self.y + other.y)
def __iadd__(self, other):
# print(self + other) # 下面错误打印调试
# return A(self + other) # 错误,返回的是一个元组,但是A需要两个参数
return A(*(self + other)) # 因为(self + othre)返回的是一个元组,所以需要把参数结构传给A,否则会引发缺少y参数异常
# return A(self.x + other.x, self.y + other.y) # 正确
def __sub__(self, other):
return (self.x - other.x, self.y - other.y)
def __isub__(self, other):
return A(*(self - other))
def __str__(self):
return "str: {},{}".format(self.x, self.y)
容器和大小:
__len__:内建函数 len(),返回对象的长度(>=0的整数)
*如果把对象当作容器类型看,就如同list或者dict。
*bool()函数调用的时候,如果没有 __bool__()方法,则会看 __len__()方法是否存在,存在并且长度非则为真。
__iter__:迭代容器时,调用;
*返回一个新的(迭代器)对象
__contains__:in 成员运算符;
*如果没有 __contains__,就调用 __iter__ 方法遍历
__getitem__:实现self[key]访问;
*序列对象,key接受整数为索引,或者切片
*对于set和dict,key为hashable;key不存在引发KeyError异常
__setitem__:是这是值的方法
*与 __getitem__ 的访问类似,
__missing__:字典或其子类使用 __getitem__()调用时,key不存在执行该方法
示例代码:
class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def additem(self, item):
self.items.append(item)
def __iter__(self):
# yield self.items
return iter(self.items)
def __getitem__(self, index):
return self.items[index]
def __setitem__(self, key, value):
self.items[key] = value
def __str__(self):
return str(self.items)
def __add__(self, other):
self.items.append(other)
return self # 实现链式编程
cart = Cart()
cart + 4 + 5 + 6 # 实现了链式编程
可调用对象:Python一切皆对象,函数也是对象
*可调用对象:定义一个类,并实例化得到一个个体,将实例像函数一样调用
__call__:类中定义一个该方法,实例就可以像函数一样调用
示例代码:写一个斐波那契数列,使用缓存思想?
class Fibonaci:
def __init__(self):
self.items = [0, 1, 1]
def __call__(self, index):
return self[index]
def __iter__(self):
return iter(self.items)
def __len__(self):
return len(self.items)
def __getitem__(self, index):
length = len(self.items)
if index < 0:
raise IndexError("Wrong Index")
elif index < length:
return self.items[index]
for i in range(length, index+1):
self.items.append(self.items[-2] + self.items[-1])
return self.items[index]
def __str__(self):
return str(self.items)
__repr__ = __str__
if __name__ == '__main__':
fib = Fibonaci()
print(fib(10), len(fib))
print(fib(5), len(fib))
上下文管理:当一个对象同时实现了 __enter__()和 __exit__()方法,它就属于上下文管理的对象。
*上下文管理是安全的
__enter__:进入与此对象相关的上下文;
*如果存在该方法,with语句会把该方法的(返回值)作为绑定到 as 子句后中指定的标识符(变量名)上。
__exit__:退出与此对象相关的上下文。
exit三个参数:如果上下文退出时没有异常,这3个参数都为None
*exc_type:异常类型
*exc_val:异常的值
*traceback:异常的追踪信息
__exit__:方法返回一个等效True的值,则压制异常;否则,继续抛出异常
上下文应用场景:
*增强功能,在代码执行的前后增强代码,以增强其功能,类似装饰器的功能。
*资源管理:打开了资源需要关闭
*权限验证:在执行代码之前,做权限的验证;在 __enter__中处理
代码示例:
class Point:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
return True
*实例化对象的时候,并不会调用enter;进入with语句块调用 __enter__ 方法,然后执行语句体,最后离开with语句块时,调用 __exit__ 方法
上下文总结:
*业务逻辑简单可以使用contextlib.contextmanager装饰器方式(见模块文章)
*如果业务复杂,用类的 __enter__ ,__exit__ 方法方便
反射:指的是程序被加载到内存中执行的时候。
反射:指的是运行时获取类型定义的信息;反射具有更大的灵活性
*一个对象能够在运行时,像照镜子一样,反射出其类型信息
具有反射能力的函数:
type(),isinstance(),callable(),dir(),getattr()
问题:我们可以通过字典直接对实例对象进行属性增加或修改!
Python提供了内置的函数:
getattr(object,name[,default]):通过name返回object的属性信息
*当属性不存在,将使用default返回
*如果没有default,则抛出AttributeError;name必须是字符串
setattr(object, name, value):object 的属性存在,则覆盖;不存在,新增
hasattr(object, name):判断对象是否有这个名字的属性;name必须是字符串
示例代码:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return "{}{}".format(self.x, self.y)
def show(self):
print(self)
p1 = Point(4, 5)
print(p1.__dict__)
setattr(p1, 'z', 200)
print(p1.__dict__)
示例代码二:
class Dispatch:
def __init__(self):
self._run()
def cmd1(self):
print("I'm cmd1")
def cmd2(self):
print("I'm cmd2")
def _run(self):
while True:
cmd = input(">>>>").strip()
if cmd == 'quit':
break
getattr(self, cmd, lambda : print('Unknown command {}'.format(cmd)))()
if __name__ == '__main__':
print(Dispatch.__dict__)
Dispatch()
-----反射的魔术方法-----
__getattr__:一个类的属性会按照继承关系找;找不到就会调用
*如果找不到,就会执行 __getattr__()方法
*如果没有这个方法,就会抛出AttributeError异常;表示找不到属性
查找顺序:实例的字典--->实例类的字典--->继承的类的字典--->object 的字典--->找不到--->调用 __getattr__()
__setattr__:实例通过点(.)设置属性,如果self.x = x;就会调用 __setattr__()
*属性要加到实例的 __dict__ 中,就需要自己完成
*__setattr__()方法;可以拦截对实例属性的增加、修改操作
*如果要设置生效,需要自己操作实例的 __dict__
示例代码:
class Dispatch:
def __init__(self):
pass
def __getattr__(self, item):
return "{}".format(item)
def __setattr__(self, key, value):
print("{}={}".format(key, value))
self.__dict__[key] = value
if __name__ == '__main__':
s = Dispatch()
print(s.__dict__)
setattr(s, 'x', 123)
print(s.x)
__delattr__:可以阻止通过实例删除属性的操作;
*通过类依然可以删除属性
__getattribute__:实例的所有属性访问(调用)都从这个方法开始
*第一个都会调用 __getattribute__ 方法,它阻止了属性的查找
*该方法返回(计算后)值或者抛出一个 AttributeError 异常
*它的 return 值将作为属性查找的结果
*如果抛出 AttributeError 异常,直接调用 __getattr__ 方法,表示属性没有找到