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__()
多态和多态性
多态:一个事物的多种形态。例如动物类有人,狗,猪等形态。
多态性:提供统一的接口,可以传入不同类型的值,但是调用的逻辑都是一样的,执行的结果不同。
多态性的实现基础为:
1.继承
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','')
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','')
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','')
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
python之路---面向对象编程(二)的更多相关文章
- Python进阶之面向对象编程(二)
Python面向对象编程(二) .note-content {font-family: "Helvetica Neue",Arial,"Hiragino Sans GB& ...
- python之路---面向对象编程(一)
一.设计思想的发展 面向机器(代码复杂,效率低,学习成本高,开发周期长)-------->面向过程(扩展性差,不适用多变的需求改变)----------->面向对象(扩展性好,但是可控性差 ...
- Python入门之面向对象编程(二)python类的详解
本文通过创建几个类来覆盖python中类的基础知识,主要有如下几个类 Animal :各种属性.方法以及属性的修改 Dog :将方法转化为属性并操作的方法 Cat :私人属性讲解,方法的继承与覆盖 T ...
- python之路----面向对象进阶二
item系列 __getitem__\__setitem__\__delitem__ class Foo: def __init__(self,name,age,sex): self.name = n ...
- Python学习之==>面向对象编程(二)
一.类的特殊成员 我们在Python学习之==>面向对象编程(一)中已经介绍过了构造方法和析构方法,构造方法是在实例化时自动执行的方法,而析构方法是在实例被销毁的时候被执行,Python类成员中 ...
- GO语言的进阶之路-面向对象编程
GO语言的进阶之路-面向对象编程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 当你看完这篇文章之时,我可以说你的Golang算是入门了,何为入门?就是你去看Docker 源码能看 ...
- Scala进阶之路-面向对象编程之类的成员详解
Scala进阶之路-面向对象编程之类的成员详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Scala中的object对象及apply方法 1>.scala 单例对象 ...
- Python 中的面向对象编程
面向对象编程(Object-oriented programming, OOP)是一种基于对象概念的编程范式,可包含属性(attribute)形式的数据以及方法(method)形式的代码.另一种对 O ...
- Python之路 - 网络编程之粘包
Python之路 - 网络编程之粘包 粘包
随机推荐
- caffe-ssd的GPU在make runtest的时候报错:BatchReindexLayerTest/2.TestGradient,where TypeParam=caffe::GPUdevice(<float>)(<double>)
make runtest报错:BatchReindexLayerTest/2.TestGradient,where TypeParam=caffe::GPUdevice<float> Ba ...
- 关于EasyUI查询功能的二级联动
EasyUI 二级联动 data-options="multiple:true" 属性可实现对于车牌号的多选.
- .Net 多线程 (1) Task
多线程是一种有效提高程序工作效率的方法.当然为了效率需要使用更多的cpu,内存等资源. 并发是两个队列交替使用一台咖啡机,并行是两个队列同时使用两台咖啡机,如果串行,一个队列使用一台咖啡机,那么哪怕前 ...
- git的基本用法
作业要求来自https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/2097 一:以下是git的基本使用方法: 1:首先先进行账号注册. 2:然 ...
- sublime Text 正则表达式功能使用介绍
sublime Text 正则表达式功能使用介绍 1.打开sublime Text ,然后按 CTRL+H打开替换面板 2.如下图,勾选正则表达式功能,然后填上正则表达式和替换内容. 3.替换后结果如 ...
- vector、map 判断某元素是否存在、查找指定元素
一.vector 1.判断某元素是否存在 vector<string> vStr; int nRet = std::count(vStr.begin(), vStr.end(), &quo ...
- 单元测试系列之六:JUnit5 技术前瞻
更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注个人的微信号,感谢! 原文链接:http://www.cnblogs.com/zishi/p/6868495.html JUnit ...
- 【2.0】SpringBoot连接MySql 8.0的url设置
jdbc:mysql://localhost:3306/enterprise?useUnicode=true&&useSSL=false&&characte ...
- springboot2.0 最大上传文件大小遇到的错误Failed to bind properties under 'spring.servlet.multipart.max-file-size'
错误: 解决: 把100Mb改为100MB
- 特效 左右滑动轮播图jQuery思路
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...