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

 

  

 

    

 

  

posted @ 2017-06-26 16:26  小聪傻大  阅读(238)  评论(0编辑  收藏  举报