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之路 - 网络编程之粘包 粘包
随机推荐
- GIEC2019第六届全球互联网经济大会北京站震撼来袭!
GIEC2019第六届全球互联网经济大会将于2019年8月26日-27日在北京召开,以“智慧零售数字商业”为主题,将邀请政府官员.企业高管.专家学者共议新形势下如何利人工智能和数字化的商业模式促进零售 ...
- SQL Server 之 事务与隔离级别实例讲解
SQL Server 之 事务与隔离级别实例讲解 SQL Server 实现了6个隔离级别来防止并发情况下,类似企图并发的访问或修改同一数据时问题的发生.本文将带你体验全部6个隔离级别.正如你接下来将 ...
- js跨域交互之jsonp - 看完就能让你了解jsonp原理 (原)
跨域? 跨域的安全限制都是对浏览器端来说的,服务器端是不存在跨域安全限制的. 同源策略? 一般来说 a.com 的网页无法直接与 b.com的服务器沟通, 浏览器的同源策略限制从一个源加载的文档或脚本 ...
- Eclipse安装fatjar(不用自己下载fatjar包)
.安装Eclipse-jee-luna-SR2-win32-x86_64版本的插件支持 方法如下: Help -> Install New Software... -> Work with ...
- windows程序设计 加载位图图片
现在网上随便下个jpg图片,用windows自带的画图工具打开,点击画图工具左上角,文件->另存为->选择bmp,点击保存,保存好后,就得到一张位图了. 得到的位图,位图的内存比原图片jp ...
- windows安装composer总结
1.直接去网吧下载windows安装EXE程序,傻瓜式安装,so easy. 2.通过命令行安装,可以直接在php目录跑起来 php -r "readfile('https://getcom ...
- vue组件传值
组件的传值(组件之间的通讯) 1.父子通信 1)父传子 传递:当子组件在父组件中当做标签使用的时候,通过给子组件绑定一个自定义属性,值为需要传递的数据 接收:在子组件内部通过props进行接收 接收的 ...
- dubbo多网卡时,服务提供者的错误IP注册到注册中心导致消费端连接不上
使用了虚拟机之后,启动了dubbo服务提供者应用,又连了正式环境的注册中心: 一旦dubbo获取的ip错误后, 这种情况即使提供者服务停掉,目前dubbo没有能力清除这类错误的提供者: (需要修改源码 ...
- 详解 Nginx如何配置Web服务器
概述 在高层次上,将NGINX配置作为Web服务器有一些问题需要了解,定义它处理哪些URL以及如何处理这些URL上的资源的HTTP请求. 在较低层次上,配置定义了一组控制对特定域或IP地址的请求的处理 ...
- echarts2.0tooltip边框限制导致tooltip显示不全解决办法
1.显示常数位置x和y; 2.根据鼠标移动显示:tooltip : { trigger: 'axis', position:function(p){ //其中p为当前鼠标的位置 return [p[0 ...