2. 自定义类:
__slots__() | 限制本类的instance的所有属性(以tuple的形式写死了,不能再定义更多的属性),但无法限制其子类 | 定义实例属性时 |
__len__() | 类似统计元素个数 | len(实例名) |
__iter__() | 将普通实例变成Iterable对象 | for 迭代某个Iterable对象时 |
__next__() | 逐个取Iterable对象的单个元素 | for 迭代某个Iterable对象; next(对象名) |
__getitem__() | 让对象具有下标操作(类比集合的下标操作) | 对象名[] |
__setitem__() | | |
__delitem()__ | | |
__getattr__() | 外部调用一个当前对象不存在的属性(或方法),返回一个属性(或函数) | 当外部引用了一个当前对象不存在的属性或方法时 |
__call()__ | 让对象变成callable对象,即类似函数 | 对象名() |
#使用__xxx__定制类#__slots__:限制本类{无法作用于其子类}的所有instance的属性集合#比如:__slots__ = ('name','age'),在class的定义中设置,以后该class的所有instance只能有这两个attribution#__len__:'''如果一个类表现得像一个list,要获取有多少个元素,就得用 len() 函数。要让len()函数工作正常,类必须提供一个特殊方法__len__(),它返回元素的个数。例如,我们写一个 Students 类,把名字传进去:class Students(object): def __init__(self, *args): self.names = args #显然args是可变参数,可以传入多个Name,self.names相当于一个list def __len__(self): return len(self.names) #返回names的len只要正确实现了__len__()方法,就可以用len()函数返回Students实例的“长度”:'''#1.__str__():当使用print()一个class的一个instance时,会默认调用__str__(),我们可以自定义这个方法#__repr__():当直接在控制台输入这个instance变量名时{为调试服务},会默认执行__repr__(),我们也可以自定义它#偷懒的小技巧:先自定义__str__(),再直接__repr__ = __str__,即可实现两者统一,class Student_1(object): def __init__(self,name): self.__name = nameS1 = Student_1('Willian')print(S1,'\n')class Student_2(object): def __init__(self,name): self.__name = name def __str__(self): return ('Student_2 object:%s' % (self.__name)) __repr__ = __str__S1 = Student_2('Willian')print(S1,'\n')##########################################################################################2.__iter__():对一个类自定义一个__iter__()方法可以使它的所有对象变成Iterable#再定义一个__next__()方法可以,通过循环,不断调用next(对象名)返回一些东西,直至遇到StopIteration退出循环class Fib(object): def __init__(self): self.a,self.b = 0,1 #为了书写方便,暂时不写私有属性。这里是初始化计数器 def __iter__(self): return self #返回一个Iterable对象 def __next__(self): #待会儿循环会不断调用一个对象的这个next() self.a,self.b = self.b,(self.a+self.b) if (self.a > 10000): raise StopIteration() #当主调方得到StopIteration,循环停止 return self.a #当前的fib(i),i从1开始:1,1,2F1 = Fib()for n in F1: #以后不用多写F1 = Fib(),直接写 for n in Fib() #因为它这里只需要使用一个对象 print(n) #这个n即fib(i)print('\n')#首先,F1= Fib()时,系统执行了__iter__(),返回的F1变成了一个Iterable#此处通过for 迭代F1,底层其实是调用__next__()去了,之后就得到了fib(i)##########################################################################################3.__getitem__(): 让上面那个Fib()不仅是Iterable,还支持Fib()[]{即支持下标操作},更像list了#Fib()[1] #发现它不支持indexingclass Fib_1(object): def __getitem__(self,n): #当使用 Fib_1()[i]时,就会激发这个方法 a,b = 0,1 for x in range(n): #这个方法很笨,也许可以用generator优化,节省时间和空间 a,b = b,(a+b) return aF2 = Fib_1()for i in range(1,10): print(F2[i])print(Fib_1()[20],'\n') #本来Fib_1()就是个对象,只是赋给F2而已,两者本来就等价#继续优化__getitem__(),让他支持list的切片,让Fib()更像list:class Fib_2(object): def __getitem__(self,n): #对传入的index即n做多分支的处理:int,slice if isinstance(n,int): #如果n是int,说明只是Fib()[n]操作 a,b = 0,1 for x in range(n): #循环n次 a,b = b,(a+b) return a if isinstance(n,slice): #n是一个切片,要做截断和append出一个list返回 start = n.start stop = n.stop #切片本身也是[lo,hi),这个左闭右开区间的特点存在于python的所有地方 if start is None: start = 0 L = [] #初始化L a,b = 0,1 for x in range(stop): if (x >= start): #x处于[start,stop),是切片的一部分,就把当前的a追加到L L.append(a) a,b = b,(a+b) return L #返回一个listF3 = Fib_2()print(F3[5],'\n')print(F3[1:6],'\n') #暂时只是初步支持切片,更加强大的支持切片还需要对__getitem__()做改造#与__getitem__()类似,可实现__setitem__()和__delitem__(),让我们自定义的class的instance#可以具备和官方的list,tuple,dict,等等类型一样强大的功能。#这一切都得益于python的“鸭子类型”:通过定义函数支持“不是鸭子的类型长得像鸭子”##########################################################################################4.__getattr__():在自定义的class中添加__getattr__(),当外部调用一个class不存在的属性,#会激发这个方法,它会动态返回一个属性,或者函数{lambda也暂且认定为一个函数}class Stu_1(object): def __init__(self,name): self.name = names1 = Stu_1('hao')#print(s1.score,'\n')class Stu_2(object): def __init__(self,name): self.name = name def __getattr__(self,attr): #attr即为我们想要外部调用的“不存在属性或者方法” if (attr == 'score'): return 99 #访问了不存在的属性:返回99分 if (attr == 'age'): return lambda : 23#调用了不存在的方法,返回lambda表达式或者函数名 s2 = Stu_2('hao')print(s2.score,'\n')print(s2.age(),'\n') #等价于外部调用了一个不存在的方法,__getattr__()也能处理##########################################################################################5.__call__():调用instance本身会激发此方法:比如s3 = Stu_3(),若执行s3('hao'),#等价于Stu_3()('hao'),{一般是调用s3的属性和方法:s3.name, s3.age()}class Stu_3(object): def __init__(self,name): self.name = name def __call__(self,age): #age是调用instance本身时,传入的参数 print("name:%s, age:%d" % (self.name, age))s3 = Stu_3('haozhang')s3(23)print('\n')#使用callable()查看一个对象是否可以调用:即s3()这种用法print(callable(Stu_1), callable(Stu_2), callable(Stu_3),'\n') #结果是instance默认都有__call__(),都可以调用print(callable(abs), callable(max),'\n') #都可以print(callable([1,2,3]), callable('123456'), callable({}), '\n') #都不可以###########################################################################################6.链式调用:class Person(object): def name(self,name): self.name = name return self def age(self,age): self.age = age return self def show(self): print("my name is %s, my age is %d" % (self.name,self.age))P1 = Person()P1.name('haozhang').age(23).show() #注意这里全程只用到了一个对象P1,只是不停地被return和执行调用函数#首先调用name(),完成self.name赋值后返回P1这个对象,再用P1来调用age,之后返回P1,再来调用showprint('\n')##########################################################################################7.结合__init__(),__getattr__(),__call__(),以及链式调用,写一个支持RestAPI架构的API类class Chain(object): def __init__(self,path=''): #允许传入参数path,但默认参数为空,所以等价于:可传可不传 self.__path = path #调用完__init__()后,系统会自动返回一个instance给主调方那里 def __getattr__(self,path): return Chain('%s/%s' % (self.__path,path)) #在这儿,必须用户,手动创建并返回,一个instance给主调用方。 #等价于手动创建了一个对象Chain(),并传入一个新的path,其内容是: #{当前self.__path和path的字符拼接,二者中间/连接},这个Chain()会将这个新path #传入新的__init__()方法。之后循环迭代下去,直至链式调用停止 def __call__(self,attr): return Chain('%s/%s' % (self.__path,attr)) #其实可以复用:在外部写一句:__call__ = __getattr__,因为两者代码是一样的 def __str__(self): #把这个调用过程打印出来,激发时机:当执行print(调用)或者控制台输出“链式调用” return self.__path #因为整个本质上一直在拼接一个字符串给__path __repr__ = __str__ #让控制台可以直接输出 最终的self.__path {这句不可省略}C1 = Chain() #默认参数写了'',这里可以不写初始的pathprint(C1.status.user.timeline.list)print('\n')print(C1.users('Willian').repos)print('\n')#Chain().users('Willian').repos看做是:Chain()对象调用uesrs属性, #然后返回对象I,I再调用对象自身,并带参'Willian',即I('Willian') #之后再返回对象。再之后再调用属性repos{这次又要回到__getattr__()} #网友优化版:__getattr__和__call__不是重复创建类,而是不断return原来的类,较小内存消耗class Chain_2(object): def __init__(self,path=''): self.__path = path def __getattr__(self,path): self.__path = ('%s/%s' % (self.__path, path)) return self def __str__(self): return self.__path __call__ = __getattr__ #代码逻辑是相同的,直接复用代码C2 = Chain_2()print(C2.status.user.timeline.list)print('\n')print(C2.users('Willian').repos) #这里出问题了,因为C2一直没变过,此时应该换个新对象C3了print('\n')C3 = Chain_2()print(C3.users('Willian').repos) #这里出问题了,因为C2一直没变过,此时应该换个新对象C3了print('\n') #########################################################################################