2. 自定义类:
name |
说明 |
触发时机及功能 |
__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 = name
S1 = Student_1('Willian')
print(S1,'
')
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,'
')
#########################################################################################
#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,2
F1 = Fib()
for n in F1: #以后不用多写F1 = Fib(),直接写 for n in Fib() #因为它这里只需要使用一个对象
print(n) #这个n即fib(i)
print('
')
#首先,F1= Fib()时,系统执行了__iter__(),返回的F1变成了一个Iterable
#此处通过for 迭代F1,底层其实是调用__next__()去了,之后就得到了fib(i)
#########################################################################################
#3.__getitem__(): 让上面那个Fib()不仅是Iterable,还支持Fib()[]{即支持下标操作},更像list了
#Fib()[1] #发现它不支持indexing
class 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 a
F2 = Fib_1()
for i in range(1,10):
print(F2[i])
print(Fib_1()[20],'
') #本来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 #返回一个list
F3 = Fib_2()
print(F3[5],'
')
print(F3[1:6],'
') #暂时只是初步支持切片,更加强大的支持切片还需要对__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 = name
s1 = Stu_1('hao')
#print(s1.score,'
')
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,'
')
print(s2.age(),'
') #等价于外部调用了一个不存在的方法,__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('
')
#使用callable()查看一个对象是否可以调用:即s3()这种用法
print(callable(Stu_1), callable(Stu_2), callable(Stu_3),'
') #结果是instance默认都有__call__(),都可以调用
print(callable(abs), callable(max),'
') #都可以
print(callable([1,2,3]), callable('123456'), callable({}), '
') #都不可以
##########################################################################################
#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,再来调用show
print('
')
#########################################################################################
#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() #默认参数写了'',这里可以不写初始的path
print(C1.status.user.timeline.list)
print('
')
print(C1.users('Willian').repos)
print('
')
#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('
')
print(C2.users('Willian').repos) #这里出问题了,因为C2一直没变过,此时应该换个新对象C3了
print('
')
C3 = Chain_2()
print(C3.users('Willian').repos) #这里出问题了,因为C2一直没变过,此时应该换个新对象C3了
print('
')
#########################################################################################