python面向对象编程第2版_python之路---面向对象编程(二)

类的继承

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

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

深度优先:

classA:#def foo(self):

#print 'from A'

pass

classB(A):#def foo(self):

#print 'from B'

pass

classC(A):#def foo(self):

#print 'from C'

pass

classD(B):#def foo(self):

#print 'from D'

pass

classE(C):#def foo(self):

#print 'from E'

pass

classF(D,E):passf=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。所以,在新式类中,采用了广度优先的方法

广度优先:

classA:#def foo(self):

#print('from A')

pass

classB(A):#def foo(self):

#print('from B')

pass

classC(A):#def foo(self):

#print('from C')

pass

classD(B):#def foo(self):

#print('from D')

pass

classE(C):#def foo(self):

#print('from E')

pass

classF(D,E):passf=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默认不处理函数的形参,所以 可以参入 不同类型的值

importabcclass Animal(metaclass=abc.ABCMeta):

@abc.abstractmethoddefrun(self):pass@abc.abstractmethoddefwalk(self):pass

classPeople(Animal):defrun(self):print('People in running')defwalk(self):print('People in walking')classDog(Animal):defrun(self):print('Dog in running')defwalk(self):print('Dog in walking')#--------------------------以上为多态-----------------------

p1=People()

d1=Dog()#--------------------------以下为多态性----------------------

defrun(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.只有在定义时使用__命令的变量才会变形。在代码执行过程中,则不会进行变形

classA:__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': ,

'__dict__': ,

'__weakref__': ,

'__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__,详见面向对象进阶

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

classA:__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__中定义的变量。

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

importtimeclassPeople:def __init__(self,name,birth_year):

self.birth=birth_year

self.name=namedefage(self):

t=time.localtime()return int(t.tm_year)-int(self.birth)

p1=People('test','1990')print(p1.age()) #age应该是人的属性,而不是方法。

#人的年龄每年都在变化,所以在内部处理时,是个动态的过程。而不是一个静态属性

不使用property

importtimeclassPeople:def __init__(self,name,birth_year):

self.birth=birth_year

self.name=name

@propertydefage(self):

t=time.localtime()return int(t.tm_year)-int(self.birth)

p1=People('test','1990')print(p1.age) #使用property进行装饰,在外部调用方式就跟调用属性一样

使用property

ps:

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

importtimeclassPeople:def __init__(self,name,birth_year):

self.birth=birth_year

self.name=name

@propertydefage(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__中定义的变量。

classPeople:def __init__(self,name,):#print(self.name) 不能这么定义变量名,否则会造成死循环

self.name=name #由于property优先级更高,所以在下面self.name已经被定义了。这是是常量赋值给常量,所以报错

@property#------函数一,查询

defname(self):returnself.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定义其他业务逻辑,来做限制。

importtimeclassPeople:def __init__(self,name,):

self.__name=name #将所有数据属性都隐藏起来

@property #------函数一,查询

defname(self):return self.__name@name.setter#---------函数二,将self.__name的值进行更改

defname(self,values):

self.__name=values

@name.deleterdefname(self):del self.__namep1=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来进行对设置。

classFoo:def __init__(self,val):

self.__NAME=val #将所有的数据属性都隐藏起来

defgetname(self):return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)

defsetname(self,value):if not isinstance(value,str): #在设定值之前进行类型检查

raise TypeError('%s must be str' %value)

self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME

defdelname(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.绑定到对象的方法

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

classPeople:def run(self): #绑定到对象的方法

pass

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

pass

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

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

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

2.绑定到类的方法

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

classPeople:

@classmethoddefrun(cls):print(cls)

@classmethoddef walk(): #也是绑定到类的方法,只是类名.walk,会自动把类传给walk方法,但是walk没有对应参数,会引起报错

print('hahah')

People.run()#

People.walk() #walk() takes 0 positional arguments but 1 was given

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

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

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

3.解除绑定方法

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

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

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

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

classPeople:

@staticmethoddefrun():print('run')

@staticmethoddefwalk():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方法,发现与我们需求不符

importtimeclassDate:def __init__(self,year,mouth,day):

self.year=year

self.mou=mouth

self.day=day

@staticmethoddefnow():

t=time.localtime()

obj=Date(t.tm_year,t.tm_mon,t.tm_mday)returnobjdef __str__(self):return 'From Date'

classMytime(Date):def __str__(self):return 'From Mytime't1=Mytime.now()print(t1) #From Date ,可以得知,t1实际上是Date实例化的对象。这个与我们想要的不符

staticmethod

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

importtimeclassDate:def __init__(self,year,mouth,day):

self.year=year

self.mou=mouth

self.day=day

@classmethoddefnow(cls):

t=time.localtime()

obj=cls(t.tm_year,t.tm_mon,t.tm_mday)returnobjdef __str__(self):return 'From Date'

classMytime(Date):def __str__(self):return 'From Mytime't1=Mytime.now()print(t1) #From Mytime ,子类Mytime的对象

classmethod

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值