zoukankan      html  css  js  c++  java
  • 26-高级特性之自定义类(1)

    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('
    ')
        
            
    #########################################################################################
  • 相关阅读:
    LeetCode 到底怎么刷?GitHub 上多位大厂程序员亲测的高效刷题方式
    第 15 篇:接口的单元测试
    【译】GitHub 为什么挂?官方的可行性报告为你解答
    微服务常用的几种部署方式——蓝绿部署,滚动升级,灰度发布/金丝雀发布
    .net生成PDF文件的几种方式
    读取私有字体信息并进行相关判断
    Windows Locale Codes
    总结C#获取当前路径的7种方法
    Devexpress aspxgridview oncustomcallback 无刷新更新数据
    ArcGIS Pro 版本、版本号、发布年代
  • 原文地址:https://www.cnblogs.com/LS1314/p/8504508.html
Copyright © 2011-2022 走看看