zoukankan      html  css  js  c++  java
  • python之路---面向对象编程(二)

    类的继承

      1.在python3中,只有新式类,新式类的继承方式为:广度优先。而python2中,经典类的继承方式为:深度优先。那么我们来看看深度优先和广度优先的区别吧

      如下图,为类之间的继承关系。B,C继承A,D继承B,E继承C,F继承D,E

                             

      深度优先:

    class A:
        # def foo(self):
        #     print 'from A'
        pass
    class B(A):
        # def foo(self):
        #     print 'from B'
        pass
    class C(A):
        # def foo(self):
        #     print 'from C'
        pass
    class D(B):
        # def foo(self):
        #     print 'from D'
        pass
    class E(C):
        # def foo(self):
        #     print 'from E'
        pass
    class  F(D,E):
        pass
    
    f=F()
    f.foo() #D--->B---->A--->E--->C   
    继承之深度优先

      我们将F实例化生成对象f,调用Foo方法,然后依次注销父类中对应的方法,发现f.foo()会按照 D--->B---->A--->E--->C   的顺序继承。即从左继承到底,如果没有再从右开始继承。

     深度优先存在缺点,如果C或者E中,与A同时都存在Foo方法,则F会继承到A的foo方法。我们在子类中重写方法,就是为了能够自定义我们所要的方法,但是深度优先绕过了子类定义的方法,这样会引起bug。所以,在新式类中,采用了广度优先的方法

      广度优先:  

    class A:
        # def foo(self):
        #     print('from A')
        pass
    class B(A):
        # def foo(self):
        #     print('from B')
        pass
    class C(A):
        # def foo(self):
        #     print('from C')
        pass
    class D(B):
        # def foo(self):
        #     print('from D')
        pass
    class E(C):
        # def foo(self):
        #     print('from E')
        pass
    class  F(D,E):
        pass
    
    f=F()
    f.foo()  #D--->B--->E--->C--->A---object
    广度优先

      我们发现,广度优先的执行顺序为:D--->B--->E--->C----->A---->object。广度优先解决了绕过子类,直接调用父类的方法这个bug。我们注意到,新式类都是继承于object类,所以 ,新式类的继承图可以理解为:

                        

      让 我们再来看一种情况,继承关系如下:

                

       这时候,执行顺序为F---->D---->B---->E---->C,该顺序是否跟深度优先很像呢,从左边执行到头,再从右边开始。如果结合之前我们说的,新式类都需要继承object,这时候继承关系如下:

                        

        执行顺序为:F---->D---->B---->E---->C---->object,所以该顺序是符合广度优先的原则的。所以,我们在使用新式类继承的时候,不要忽略了object类。而且,在新式类中,可以使用 __mor__ 来查看继承的优先级。

      2.在子类中,继承 父类的初始化方法,可以使用:父类名.__init(self)__(),来进行调用,但是使用该方法,如果父类名称发生了改变,则需要大量修改父类名称,造成了额外的工作量,所以使用了super来继承。

         python3: super().__init__()

         python2: super(子类名,self).__init__()    #等于父类名.__init__()

    多态和多态性    

      多态:一个事物的多种形态。例如动物类有人,狗,猪等形态。

      多态性:提供统一的接口,可以传入不同类型的值,但是调用的逻辑都是一样的,执行的结果不同。

      多态性的实现基础为:

        1.继承

        2.python默认不处理函数的形参,所以 可以参入 不同类型的值
    import abc
    class Animal(metaclass=abc.ABCMeta):
        @abc.abstractmethod
        def run(self):
            pass
        @abc.abstractmethod
        def walk(self):
            pass
    
    class People(Animal):
        def run(self):
            print('People in running')
        def walk(self):
            print('People in walking')
    
    class Dog(Animal):
        def run(self):
            print('Dog in running')
        def walk(self):
            print('Dog in walking')
    
    #--------------------------以上为多态-----------------------
    
    
    p1=People()
    d1=Dog()
    #--------------------------以下为多态性----------------------
    def run(obj):
         obj.run()
    
    def walk(obj):  #传入的obj没有类型限制,可以传入不同类型的值
         obj.wakl()  #调用的逻辑都一样,执行的结果不一样
    
    run(p1)
    run(d1)
    多态和多态性

      

    封装

      一.封装的定义和作用

      封装:封装,顾名思义,就是使用容器将一堆东西收纳,隐藏起来。不让外界所看到。但是程序中的封装,不光光是胡乱的收纳数据,隐藏数据的目的。而是按照一定规律将数据进行排序,封装 ,对外部提供可用的功能。

      在程序中,封装具有两个作用:

      1.封装数据的主要原因是:保护隐私

      2.封装方法的主要原因是:隔离复杂度。例如我们平时使用len()函数,我们只需要调用这个方法,就返回序列的长度,我们无需关系它是如何去实现的。

      二.如何进行封装

      封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口。

      第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装。这一属性和方法就不会直接暴露出来了。例如A类下有个方法test()。我们需要通过A.test()来进行访问,而不能是直接test()来访问。

      第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。(定义变量名时,使用__开头来进行自动变形,达到隐藏的目的)。

      ps:

      1.类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

      2.只有在定义时使用__命令的变量才会变形。在代码执行过程中,则不会进行变形

    class A:
        __x=1  #_A__x
        def __y(self):  #_A__y
            print('y')
    A.__B=2   #__B
    print(A.__dict__)  #从字典中,我们可以发现__B并未发生变形。在程序执行过程中,再
                        #进行赋值操作,并不会对变量进行变形。
    '''
    {'__module__': '__main__', '_A__x': 1, 
    '_A__y': <function A.__y at 0x00000000027BB8C8>,
    '__dict__': <attribute '__dict__' of 'A' objects>,
    '__weakref__': <attribute '__weakref__' of 'A' objects>, 
    '__doc__': None, '__B': 2}
    '''
    __开头 的变量名自动变形 

      这种自动变形的特点:

      1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果

      2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。

      3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。

      python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的

      其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,只不过显得稍微傻逼一点点

      python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__,详见面向对象进阶

      如果要在外部访问这些隐藏的属性,不建议使_类名__属性 的方法来进行调用,而是再定义一个方法来返回这些隐藏值,例如:

    class A:
        __x=1     #_A__x
        def x(self):   #定义x方法,来返回_A__x的值
            return self.__x   #self._A__x
    a=A()
    print(a.x())

        封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。

    Property 

      property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值。使用property装饰的函数,调用时,无需带上括号。property优先级高于__init__中定义的变量。

      例如:我们创建一个人类,输入年龄和出生年份。可以查询他的年龄。

    import time
    class People:
        def __init__(self,name,birth_year):
            self.birth=birth_year
            self.name=name
    
        def age(self):
            t=time.localtime()
            return  int(t.tm_year)-int(self.birth)
    p1=People('test','1990')
    print(p1.age())   #age应该是人的属性,而不是方法。
                      #人的年龄每年都在变化,所以在内部处理时,是个动态的过程。而不是一个静态属性
    不使用property
    import time
    class People:
        def __init__(self,name,birth_year):
            self.birth=birth_year
            self.name=name
        @property
        def age(self):
            t=time.localtime()
            return  int(t.tm_year)-int(self.birth)
    p1=People('test','1990')  
    print(p1.age) #使用property进行装饰,在外部调用方式就跟调用属性一样 
    使用property

      ps:

      1.这时候我们不能对age进行赋值操作,否则会报错。

    import time
    class People:
        def __init__(self,name,birth_year):
            self.birth=birth_year
            self.name=name
        @property
        def age(self):
            t=time.localtime()
            return  int(t.tm_year)-int(self.birth)
    p1=People('test','1990')
    p1.age=34
    #AttributeError: can't set attribute
    报错内容

      2.property优先级高于__init__中定义的变量。

    class People:
        def __init__(self,name,):
            #print(self.name)  不能这么定义变量名,否则会造成死循环
            self.name=name  #由于property优先级更高,所以在下面self.name已经被定义了。这是是常量赋值给常量,所以报错
    
        @property    #------函数一,查询
        def name(self):
           return self.name
    
    p1=People('test')
    #报错内容:AttributeError: can't set attribute
    报错内容

      那我们为什么要用property??

      将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则。

      同时,property还对类进行了封装。在其他语言的编程中,类的封装如下:

    ps:面向对象的封装有三种方式:
    【public】
    这种其实就是不封装,是对外公开的
    【protected】
    这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开
    【private】
    这种封装对谁都不公开

      python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现.可以在property定义其他业务逻辑,来做限制。

    import time
    class People:
        def __init__(self,name,):
            self.__name=name  #将所有数据属性都隐藏起来
        @property    #------函数一,查询
        def name(self):
           return self.__name
    
        @name.setter    #---------函数二,将self.__name的值进行更改
        def name(self,values):
            self.__name=values
    
        @name.deleter
        def name(self):
            del self.__name
    p1=People('test')
    print(p1.name)   #会调用函数一,进行查询 ,返回 test
    
    p1.name='test1'  #调用函数二,将值更改为test1
    print(p1.name)
    
    print(p1.__dict__)     #{'_People__name': 'test1'}
    del p1.name     #调用函数三,删除变量self._People__name
    print(p1.__dict__)   #k空字典

      如果不使用装饰器来封装,也可以定义不同的功能函数,再将函数名传入property来进行对设置。

    class Foo:
        def __init__(self,val):
            self.__NAME=val #将所有的数据属性都隐藏起来
    
        def getname(self):
            return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)
    
        def setname(self,value):
            if not isinstance(value,str):  #在设定值之前进行类型检查
                raise TypeError('%s must be str' %value)
            self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
    
        def delname(self):
            raise TypeError('Can not delete')
    
        name=property(getname,setname,delname) #不如装饰器的方式清晰,将返回结果赋予变量name
      #  property(self, fget=None, fset=None, fdel=None, doc=None))   fget为查询函数  fset为更改函数 fdel为删除函数
    另一组传统的方法

      总结:

        property的用途如下:1.将动态方法封装为属性提供给用户。例如年龄的获取

                  2.将数据做下定制返回给用户,如果用户名,可以先判断输入是否为字符串,如果不是,则报错。如果是,正常返回。

     

    绑定方法与非绑定方法(staticmethod,classmethod)

      在类内部函数的用途主要有三种:

      1.绑定到对象的方法

        只要是在类内部定义的,并且没有被任何装饰器修饰过的方法,都是绑定到对象的。

    class People:
        def run(self): #绑定到对象的方法
            pass
        def walk():  #也是绑定到对象的方法,只是对象.walk,会自动把对象传给walk方法,但是walk没有对应参数,会引起报错
            pass

        绑定到对象:指的是:就是给对象去用的

        使用方式:对象.对象的绑定方法。

        特性:调用时会把对象本身当中第一个参数传给对象的绑定方法

      2.绑定到类的方法

        在类内部定义的,并且被装饰器classmethod修饰过的,都是绑定到类的。

    class People:
        @classmethod
        def run(cls):
            print(cls)
        @classmethod
        def walk():  #也是绑定到类的方法,只是类名.walk,会自动把类传给walk方法,但是walk没有对应参数,会引起报错
            print('hahah')
    
    People.run()   #<class '__main__.People'>
    People.walk()  # walk() takes 0 positional arguments but 1 was given  

        绑定到类:指的是:就是给类 去用的

        使用方式:类名.类的绑定方法。

        特性:调用时会把类名本身当中第一个参数传给对象的绑定方法

      3.解除绑定方法

        即不与类绑定,不与对象绑定,不与任何内容绑定。

        绑定的特性:自动传值(绑定到类的方法,自动传入类;绑定到对象的方法,自动传入对象)

        解除绑定的特性:不管是类还是对象来调用,都没有自动传值了

        所以说,staticmethod就是相当于一个普通的工具包。

    class People:
        @staticmethod
        def run():   
            print('run')
        @staticmethod
        def walk():   
            print('walk')
        def obj():  #如果不使用staticmethod来装饰,那么就是对象的绑定方法。会自动传值进去,而报错
            print('obj')
        
    People.run()   
    p1=People()
    p1.walk()

      从上面三种方法,可以看出,主要分为绑定方法非绑定方法。   

    一:绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):

        1. 绑定到类的方法:用classmethod装饰器装饰的方法。

                    为类量身定制

                    类.boud_method(),自动将类当作第一个参数传入

                  (其实对象也可调用,但仍将类当作第一个参数传入)

        2. 绑定到对象的方法:没有被任何装饰器装饰的方法。

                   为对象量身定制

                   对象.boud_method(),自动将对象当作第一个参数传入

                 (属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)

      二:非绑定方法:用staticmethod装饰器装饰的方法

         1. 不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已

        注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说

      补充:

        classmethod的用例:

        我们有个父类Date,他有一个共有方法:now(),如果这时候我们创建子类Mytime来继承他。我们要实例化子类的一个对象进行操作。

        首先是要staticmethod方法,发现与我们需求不符

    import time
    class Date:
        def __init__(self,year,mouth,day):
            self.year=year
            self.mou=mouth
            self.day=day
    
        @staticmethod
        def now():
            t=time.localtime()
            obj=Date(t.tm_year,t.tm_mon,t.tm_mday)
            return obj
        def __str__(self):
            return 'From Date'
    
    class Mytime(Date):
        def __str__(self):
            return 'From Mytime'
    
    t1=Mytime.now()
    print(t1)  #From Date  ,可以得知,t1实际上是Date实例化的对象。这个与我们想要的不符
    staticmethod

        接下来,我们更改该classmethod方法

    import time
    class Date:
        def __init__(self,year,mouth,day):
            self.year=year
            self.mou=mouth
            self.day=day
    
        @classmethod
        def now(cls):
            t=time.localtime()
            obj=cls(t.tm_year,t.tm_mon,t.tm_mday)
            return obj
        def __str__(self):
            return 'From Date'
    
    class Mytime(Date):
        def __str__(self):
            return 'From Mytime'
    
    t1=Mytime.now()
    print(t1)  #From Mytime   ,子类Mytime的对象
    classmethod

      

        

      

  • 相关阅读:
    关于C的一些理解
    Linux下/proc目录简介
    python学习链接
    “段寄存器”的故事[转](彻底搞清内存段/elf段/实模式保护模式以及段寄存器)
    内核态与用户态
    bzoj1087: [SCOI2005]互不侵犯King 状压dp
    bzoj1007: [HNOI2008]水平可见直线 单调栈维护凸壳
    bzoj1015: [JSOI2008]星球大战starwar
    bzoj1002: [FJOI2007]轮状病毒 生成树计数
    bzoj1013: [JSOI2008]球形空间产生器sphere
  • 原文地址:https://www.cnblogs.com/white-small/p/7044586.html
Copyright © 2011-2022 走看看