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('
    ')
        
            
    #########################################################################################
  • 相关阅读:
    解决 Mac launchpad 启动台 Gitter 图标无法删除的问题
    React 与 React-Native 使用同一个 meteor 后台
    解决 React-Native mac 运行报错 error Failed to build iOS project. We ran "xcodebuild" command but it exited with error code 65. To debug build logs further, consider building your app with Xcode.app, by ope
    一行命令更新所有 npm 依赖包
    swift学习笔记
    IOS语言总结
    focusSNS学习笔记
    别小看锤子,老罗真的很认真
    windowsphone开发页面跳转到另一个dll中的页面
    【令人振奋】【转】微软潘正磊谈DevOps、Visual Studio 2013新功能、.NET未来
  • 原文地址:https://www.cnblogs.com/LS1314/p/8504508.html
Copyright © 2011-2022 走看看