面向对象的三大特性:集成 多态 封装
我们来学习一下在Python种三种特性的实现
继承
#继承demo
class Animal:
def __init__ (self,kind,age,sex):
self.kind = kind
self.age = age
self.sex = sex
class Person(Animal):
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
#Animal:父类 or 基类
#Person:子类 or 派生类
继承描述的是子类与父类之间的关系,是一种什么是什么的关系。要找出这种关系,必须先抽象再继承
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到
# ==========================第一部分
# 例如
# 猫可以:喵喵叫、吃、喝、拉、撒
# 狗可以:汪汪叫、吃、喝、拉、撒
# 如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,伪代码如下:
# 猫和狗有大量相同的内容
class cat:
def 喵喵叫(self):
print('喵喵叫')
def 吃(self):
print("吃东西")
def 喝(self):
print("喝水")
def 拉(self):
print("拉了")
class dog:
def wangwang(self):
print('旺旺叫')
def 吃(self):
print("吃东西")
def 喝(self):
print("喝水")
def 拉(self):
print("拉了")
#== == == == == == == == == == == == == 第二部分
#上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次,如果使用继承的思想,如下实现:
#动物:吃、喝、拉、撒
#猫:喵喵叫(猫继承动物的功能)
#狗:汪汪叫(狗继承动物的功能)
#伪代码如下:
class 动物:
def 吃(self):
print("吃东西")
def 喝(self):
print("喝水")
def 拉(self):
print("拉了")
# do something
# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class cat(动物):
def 喵喵叫(self):
print("喵喵叫")
# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class dog(动物):
def 汪汪叫(self):
print("汪汪叫")
#== == == == == == == == == == == == == 第三部分
# 继承的代码实现
class Animal:
def eat(self):
print("%s 吃 " % self.name)
def drink(self):
print("%s 喝 " % self.name)
def shit(self):
print("%s 拉 " % self.name)
def pee(self):
print("%s 撒 " % self.name)
class Cat(Animal):
def __init__(self, name):
self.name = name
self.breed = '猫'
def cry(self):
print('喵喵叫')
class Dog(Animal):
def __init__(self, name):
self.name = name
self.breed = '狗'
def cry(self):
print('汪汪叫')
########## 执行 #########
c1 = Cat('小白家的小黑猫')
c1.eat()
c2 = Cat('小黑的小白猫')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
继承与重用性
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时
我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
class Hero:
def __init__(self,nickname,aggressivity,life_value):
self.nickname=nickname
self.aggressivity=aggressivity
self.life_value=life_value
def move_forward(self):
print('%s move forward' %self.nickname)
def move_backward(self):
print('%s move backward' %self.nickname)
def move_left(self):
print('%s move forward' %self.nickname)
def move_right(self):
print('%s move forward' %self.nickname)
def attack(self,enemy):
enemy.life_value-=self.aggressivity
class Garen(Hero):
pass
class Riven(Hero):
pass
g1=Garen('草丛伦',100,300)
r1=Riven('锐雯雯',57,200)
print(g1.life_value)
r1.attack(g1)
print(g1.life_value)
'''
运行结果
243
'''
提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分大大生了编程工作量这就是常说的软件重用,不仅可以重用自己的类也可以继承别人的,比如标准库,来定制新的数据类型这样就是大大缩短了软件开发周期,对大型软件开发来说意义重大;
类的继承又分为:单继承与多继承
在学习单继承与多继承之前,在这里我们首先先学习一下类的类型
1.经典类(不继承object类)
2.新式类(继承object类)
3.Python2 经典类与新式类共存
4.Python3 全部都是新式类
单继承
class Animal:
a1 = "测试"
def __init__(self,kind,age,sex):
self.kind = kind
self.age = age
self.sex = sex
class Person(Animal):
a1 = "alex"
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
a = Person("人类",18,"男")
print(a.kind)
我们了解了单继承的语法,我们现在有这么一个需求:
子类有自己的属性,如何要调用父类的属性和方法又要调用子类的属性和方法?
class Animal:
def __init__(self,name,sex,age):
self.name = name
self.sex = sex
self.age = age
def eat(self):
print("%s正在吃饭" %(self.name))
class Person(Animal):
def __init__(self,name,sex,age,coat_color):
#super(Person,self).__init__(name,sex,age) python2写法
super().__init__(name,sex,age) #重构父类的__init__并给父类传递需要的参数,super()是一个特殊的对象默认会把self(对象自己)传递给父类当第一个参数(父类的self);
self.coat_color = coat_color
def eat(self):
print("%s人类%s正在吃饭" % (self.coat_color,self.name))
super().eat()
class Dog(Animal):
def __init__(self,coat_color):
self.coat_color = coat_color
def eat(self):
print("狗狗正在吃饭")
p2 = Person("王铁锤","女",18,"黄色")
p2.eat()
print(Person.__mro__) #查看类的mro列表
>>>(<class '__main__.Person'>, <class '__main__.Animal'>, <class 'object'>)
#super():是一个特殊对象,会按当前类所在mro列表中的位置的下一个类开始查找动态方法或静态属性
多继承
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
super().__init__()
print('A.__init__')
class B(Base):
def __init__(self):
super().__init__()
print('B.__init__')
class C(A,B):
def __init__(self):
super().__init__() # Only one call to super() here
print('C.__init__')
c = C()
print(C.mro()) #查看可的MON列表
#对于定义的每一个类,Python会计算出一个所谓的方法解析顺序(MRO)列表;这个MRO列表就是一个简单的所有基类的线性顺序表。例如:
print(C.mro())
>>>[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>]
为了实现继承,Python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。 我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
子类会先于父类被查找
多个父类会根据它们在列表中的从坐到右顺序被查找
如果对下一个类存在两个合法的选择,选择第一个父类
抽象类和接口类
在了解抽象类之前我们先谈谈接口的问题,这里的接口指的是:自己提供给使用者来调用自己功能的方式\方法\入口
那么我们为什么要使用接口呢?
我们提取一群类里的类似的功能的函数,比如 微信支付 支付宝支付 银联支付等等这些都是支付的功能,我们可以把这些功能提取出来定义成一个类,这个类不具体实现功能只是定义这些功能的统一的名称 比如交pay这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化的好处在于:
1. 归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
2. 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
2.1:就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
2.2:再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
#做出一个良好的接口
class Payment(object):
#规定了一个兼容接口
def pay(self):
pass
#微信支付
class WeChatPay(object):
def pay(self,money):
print('微信支付了%s'%money)
#支付宝支付
class AliPay(object):
def pay(self,money):
print('支付宝支付了%s'%money)
#苹果支付
class ApplePay(object):
def pay(self,money):
print('苹果支付了%s'%money)
def pay(obj,money):
obj.pay(money)
weixin = WeChatPay()
alipay = AliPay()
applepay = ApplePay()
#调用者无需关心具体实现细节,可以一视同仁的处理实现了特定接口的所有对象
pay(weixin,100)
pay(alipay,200)
pay(applepay,300)
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)
抽象类
什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
import abc
class Animal(metaclass=abc.ABCMeta): #metaclass=abc.ABCMeta:元类
@abc.abstractmethod
def eat(self):
pass
@abc.abstractmethod
def run(self):
pass
class People(Animal):
def eat(self):
print("pople is eating")
def run(self):
print("pople is runing")
a = People()
a.eat()
抽象类和抽象接口
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念
多态
Python默认支持多态
多态指的是一类事物有多种形态
动物有多种形态:人,狗,猪
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
@abc.abstractmethod
def talk(self):
pass
class People(Animal): #动物的形态之一:人
def talk(self):
print('say hello')
class Dog(Animal): #动物的形态之二:狗
def talk(self):
print('say wangwang')
class Pig(Animal): #动物的形态之三:猪
def talk(self):
print('say aoao')
p1 = People()
d1 = Dog()
p2 = Pig()
p1.talk()
p2.talk()
d1.talk()
#多态性:在不考虑对象具体类型的情况下直接使用对象下的方法,比如列表 字符串 元祖等可以不考虑对象的类型直接调对应的方法,比如上边的代码无论是人或者狗或者猪我们不需要考虑这个类具体的类型因为他们都属于动物类,那么动物类都用talk方法
#我们上边学到了接口的概念那么我们在这里就可以用到将三个雷进行统一化
def talk(obj):
obj.talk()
talk(p1)
talk(p2)
talk(d1)
鸭子类型
#python推崇的鸭子类型而不是抽象类的概念
#当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子;sqqqqqqcvx b
#在鸭子类型中关注的不是对象的类型本身,而是它是如何使用的;
#在定义类的时候没有必要硬性定义一个父类,比如下面的代码三个类不需要父类,只需要都有发出声音的方法就行,某一个类的增加功能不需要考虑其他类实现了解耦;
import abc
class Pig():
def speak(self):
print("哼哼")
class Dog():
def speak(self):
print("汪汪")
class Radio():
def speak(self):
print("radio speak")
封装
#广义的封装:将一些内容放到一个"容器"中
#狭义的封装:私有
class test:
name = "jim"
__name = "tom" #私有属性
def func(self): #动态方法
pass
def __init__(self): #特殊方法
self.name
self.__age = age #私有对象属性
def _func(self): #私有方法
pass
@property #属性:将方法伪装成一个属性,虽然在代码层面没有提升,但是会让代码看起来更合理;
def func1(self):
pass
@classmethod #类方法(用于对于类的修改)
def func2(cls): #cls会接收类名类似self接收对象名一样
pass
@staticmethod #静态方法:不依赖类与对象的函数,封装在类中用于代码整洁一体化
def func3():
pass
私有成员:私有变量 私有对象属性 私有方法
对于每一个类的成员而言都有两种形式:
公有成员,在任何地方都能访问
私有成员,只有在类的内部才能方法
私有成员和公有成员的访问限制不同
静态变量
公有静态字段:类可以访问;类内部可以访问;派生类中可以访问
私有静态字段:仅类内部可以访问;
对象属性
公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问;
私有普通字段:仅类内部可以访问;
方法:
公有方法:对象可以访问;类内部可以访问;派生类中可以访问
私有方法:仅类内部可以访问;
class test:
name = "jim" #公有静态属性
__name = "tom" #私有静态属性
def __init__(self,):
self.name = name #公有对象属性
self.__age = age #私有对象属性
def _func(self): #私有方法
pass
def test(self): #公有方法
pass
class A:
count = "china"
__name = "alex"
def __init__(self,name,age):
self.name = name
self.age = age
def func(self):
print(self.__name)
def __fuck(self):
pass
obj = A("jim", 28)
print(obj.coutry) # 类外可以访问
print(obj.__name) # 类外不可以访问
#总结:
#对于这些私有成员来说,他们只能在类的内部使用,不能再类的外部以及派生类中使用.
#ps:非要访问私有成员的话,可以通过 对象._类__属性名,但是绝对不允许!!!
#为什么可以通过._类__私有成员名访问呢?因为类在创建时,如果遇到了私有成员(包括私有静态字段,私有普通字段,私有方法)它会将其保存在内存时自动在前面加上_类名.
#__开头的属性只是一种语法意义上的变形,并不会真正限制外部访问;
#这种变形只在类定义阶段发生一次,类定义之后再新增的属性不会变形;
封装数据属性的意义:将静态变量或对象变量封装起来以后,对象无法直接使用只能在类中使用这又有什么用呢?
1.将属性封装了以后对象通过统一的类方法(接口)才能增加静态属性,并且根据对象的传值的合法性来决定是否给用户增加静态属性,这就有意义了对于某些特殊的属性我们就需要通过统一的接口才能修改,并且遵循接口的定义者的规范;
class Person:
def __init__(self):
pass
def eat(self):
print("%s人类%s正在吃饭" % (self.coat_color, self.__name))
def set_info(self,name,age,sex,coat_color):
if type(name) is not str:
raise TabError("name not is str")
elif type(age) is not int:
raise TabError("name not is int")
else:
self.__name = name
self.__age = age
self.__sex = sex
self.coat_color = coat_color
p = Person()
p.set_info("jim",18,"男人","黄种")
p.eat()
#raise:自定义触发异常触发异常后,后面的代码就不会再执行
封装函数属性的意义:隔离复杂度
1. 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!
2. 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
#隔离了复杂度,同时也提升了安全性
class ATM:
def __card(self):
print('插卡')
def __auth(self):
print('用户认证')
def __input(self):
print('输入取款金额')
def __print_bill(self):
print('打印账单')
def __take_money(self):
print('取款')
def withdraw(self):
self.__card()
self.__auth()
self.__input()
self.__print_bill()
self.__take_money()
a=ATM()
a.withdraw()
property
@property:property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
使用场景:对象需要得到一个动态的返回值,并且调用方式与调用数据属性一样的方式调用
class People:
def __init__(self,name,age,height,weight):
self.name = name
self.age = age
self.height = height
self.weight = weight
@property
def bim(self):
return self.weight / (self.height ** 2 )
p1 = People("jim",28,1.8,72)
print(p1.bim) #通过@property装饰的方法改变了调用方式
print(p1.name) #调用普通的数据属性
class test:
def __init__(self,name):
self.__name = name
@property #@property:将动态方法封装成数据属性(常用)
def name(self):
return self.__name
@name.setter #.setter修改属性
def name(self,obj):
self.__name = obj
@name.deleter #.deleter删除属性
def name(self):
del self.__name
a = test("jim")
print(a.name)
a.name="tom"
为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
classmethod
@classmethod:是一个内置的函数,我们了解类里的动态方法是给对象使用的,所以在定义方法时会自动有一个self关键字
这个关键字就是一个普通的位置参数而已,默认对象在调用方法时会自动将对象的内存地址传递给self这样我们就可以调用对象的属性
@classmethod 这个函数就是将一个普通类方法绑定给类使用,这样这个方法就需要通过类调用,而cls这个关键字就会默认接收类的内存地址
下面的方法还是普通的方法,不过是经过@classmethod装饰后,这个普通方法就通过cls这个关键字接收的不是对象的地址而是类的地址而已
还能通过类似cls.name = "tom" 修改类的静态属性
#settings.py
HOST="127.0.0.1"
PORT=3306
#classmethod_mode.py
import settings
class test:
def __init__(self,host,port):
self.host = host
self.port = port
def test1(self):
print(self.host,self.port)
@classmethod
def test2(cls):
print(cls)
return cls(settings.PORT,settings.HOST)
test.test2()
a = test(settings.PORT,settings.HOST)
a.test1()
staticmethod
@staticmethod:将类方法变成一个普通的函数
我们有这么一个核心文件里面是很多类,但这个时候我们需要写一个普通函数实现某个功能
在类外面也可以写一个同样的函数实现功能,但是这样打乱了逻辑关系,也增加了代码的维护难度
那么我们就写在某个公共类里并且通过@staticmethod装饰一下这样类就会知道它只是一个我类里面定义的普通函数
主要是为了代码整洁归类
#settings.py
HOST="127.0.0.1"
PORT=3306
#classmethod_mode.py
import settings
class test:
def __init__(self,host,port):
self.host = host
self.port = port
def test1(self):
print(self.host,self.port)
@staticmethod
def test2():
return (settings.PORT,settings.HOST)
print(test.test2())
#小补充
#isinstance:什么是否是什么实例
l = [1,2,3]
print(isinstance(l,list)) #l是不是列表
#issubclass:什么是谁的子类
print(issubclass(b,a) #True
issubclass:判断b是否是b的子类或孙类
反射:通过字符串操作对象(实例化 类 模块)
有一天我们想通过字符串来对实例 类 或者模块进行某些操作,比如我们通过input()接收的用户输入的信息
这个信息默认是字符串格式,那么我们拿到了这个字符串能通过它来调用对象的属性或类的方法呢?
当然可以这就是反射 把某个字符串反射到某个对象属性或类方法的内存地址上去
反射一共有四个内置函数,可以操作实例化对象 类 模块;
- hasattr():查找
- getattr():获取
- setattr():修改
-
delattr():删除
对实例化对象的示例
class a:
def __init__(self,name,age):
self.name = name
self.age = age
obj = a("jim",28)
ret = getattr(obj,"name",None)
print(ret)
print(hasattr(obj,"name"))
if hasattr(obj,name):
ret = getattr(obj,"name",None)
setattr(obj,"sex","男")
print(getattr(obj,"sex"))
对类
class a:
name = "kom"
def __init__(self):
pass
def func(self):
print("in func")
getattr(a,"name")
ret = getattr(a,"func")
print(ret(1))
对当前模块(文件)
import sys
def s1():
print 's1'
def s2():
print 's2'
this_module = sys.modules[__name__]
hasattr(this_module, 's1')
getattr(this_module, 's2')
对其他模块(文件)
import fs
print(getattr(fs,"n1"))
#方法1
a = getattr(fs,"A")
print(a.name)
#方法2
print(getattr(fs.A,"name"))
a.func2(1)
双下方法
class test:
def __init__(self,name,age)
self.name = name
self.age = age
def __str__(self): #__str__方法:在打印对象时默认返回出该方法的返回值(只能是字符串,可以是格式化后的字符串)
return "%s,%s" %(self.name,self,age)
def __repr__(self): #__repr__方法:在repr(对象)时默认输出该方法的返回值
return "太白"
a = test("jim",27)
print(a)
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs): #__call__:对象()自动触发__call__() 方法
print("__call__")
obj = Foo()
obj() # 对象()自动触发__call__()方法
#__del__方法:当内存中释放时自动触发,主要用于主动关闭mysql连接或关闭文件等操作;
#__init__方法:负责对象的初始化,在初始化前对象已经产生
#__new__方法:属于新式类里的方法,第一个参数是(cls),该方法在实例化的时候会自动调用用于创建对象,并将返回值返回给__init__方法的self参数;
#模拟重构__new__方法
class test:
def __init__(self):
print("is init")
def __new__(cls, *args, **kwargs): #__new__:对象的产生者
print("is new")
#super().__new__(cls)
return object.__new__(cls)
a = test()
class test2:
def __init__(self):
print("__init__ ")
print(self)
super(A, self).__init__()
def __new__(cls):
print("__new__ ")
self = super(A, cls).__new__(cls)
print(self)
return self
#输出
__new__
<__main__.A object at 0x1046c4a20>
__init__
<__main__.A object at 0x1046c4a20>
#从输出结果来看,__new__ 方法的返回值就是类的实例对象,这个实例对象会传递给 __init__ 方法中的self参数,以便实例对象可以被正确地初始化;
#__init__方法中除了self之外定义的参数,都将与__new__方法中除cls参数之外的参数是必须保持一致
class B:
def __init__(self, *args, **kwargs):
print("init", args, kwargs)
def __new__(cls, *args, **kwargs):
print("new", args, kwargs)
return super().__new__(cls)
B(1, 2, 3)
# 输出
new (1, 2, 3) {}
init (1, 2, 3) {}
设计模式
单例模式
由于类产生实例的过程是通过 __new__ 方法来控制的,因此重写该方法来单例模式是非常方便的;
每次初始化都返回同一个实例,所以两次初始化得到的对象的内存地址应该是一样的
class a:
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
obj = object.__new__(cls)
cls.__instance = obj
return cls.__instance
cc = a()
bb = a()
print(cc,bb)
#输出
<__main__.a object at 0x105513898> <__main__.a object at 0x105513898>
item
#对一个对象进行类似字典的操作,就会触发__item__的某个方法
__getitem__
__setitem__
__delitem__