Python(面向对象编程4——继承顺序、封装)
继承顺序
'''
一点需要注意
'''
class Father:
def f1(self):
print("test func followed ==>")
self.test()
def test(self):
print("from Father test")
class Son(Father):
def test(self):
print("from son test")
res=Son()
res.f1()
结果 >>>:
test func followed ==>
from son test
'''
子类调用 self.test() 的时候任然要重新从自己开始找 .test()方法。
子类调用父类的属性,每次调用,都是优先从自己这里开始找,按照mro算法的顺序依次找下去,知道找到第一个符合的,所以不能只看定义的时候的单个类的情况。
'''
新式类继承:广度优先。
经典类继承:深度优先。
继承了object的类以及其子类,都是新式类
没有继承object的类以及其子类,都是经典类
Python3中默认继承object,所以Python3中都是新式类
Python2中不会默认继承object
class A(object):
def test(self):
print('from A') class B(A):
def test(self):
print('from B') class C(A):
def test(self):
print('from C') class D(B):
def test(self):
print('from D') class E(C):
def test(self):
print('from E') class F(D,E):
# def test(self):
# print('from F')
pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性 #新式类继承顺序:F->D->B->E->C->A
#经典类继承顺序:F->D->B->A->E->C
#python3中统一都是新式类
#pyhon2中才分新式类与经典类 继承顺序
继承原理(python如何实现的继承)
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
>>> F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
子类调用父类的方法(内置函数super)
low版调用方法,还是那个teacher还是那个people:
1 class People:
2 def __init__(self,name,age,sex):
3 self.name=name
4 self.age=age
5 self.sex=sex
6 def foo(self):
7 print('from parent')
8
9 class Teacher(People):
10 def __init__(self,name,age,sex,salary,level):
11 People.__init__(self,name,age,sex) #指名道姓地调用People类的__init__函数
12 self.salary=salary
13 self.level=level
14 def foo(self):
15 print('from child')
16
17 t=Teacher('bob',18,'male',3000,10)
18 print(t.name,t.age,t.sex,t.salary,t.level)
19 t.foo()
low版调用方法,在更改父类的名字之后,需要改动的地方除了子类继承的父类名字,还要改子类里面调用的父类名,比较麻烦
高端大气调用方式:只需要改动子类继承的父类名,即括号里的父类名字
1 class People:
2 def __init__(self,name,age,sex):
3 self.name=name
4 self.age=age
5 self.sex=sex
6 def foo(self):
7 print('from parent')
8
9 class Teacher(People):
10 def __init__(self,name,age,sex,salary,level):
11 #在python3中
12 super().__init__(name,age,sex) #调用父类的__init__的功能,实际上用的是绑定方法,用到了mro表查询继承顺序,只能调用一个父类的功能
13 #在python2中
14 # super(Teacher,self).__init__(name,age,sex) #super(Teacher,self)是一个死格式
15 self.salary=salary
16 self.level=level
17 def foo(self):
18 super().foo()
19 print('from child')
20
21 t=Teacher('bob',18,'male',3000,10)
22 print(t.name,t.age,t.sex,t.salary,t.level)
23 t.foo()
但是这种方式也有一个缺点,就是当一个子类继承了多个父类的时候,如果多个父类都包含了相同的属性名,当要调用该功能的时候,只能调用第一个父类的功能,无法实现多个父类同时调用。多个父类同时调用还是要用low版方法。
访问限制
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。
但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name
、score
属性:
>>> bart = Student('Bart Simpson', 98)
>>> bart.score
98
>>> bart.score = 59
>>> bart.score
59
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name
和实例变量.__score
了:
>>> bart = Student('Bart Simpson', 98)
>>> bart.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
需要注意的是,在Python中,变量名类似__xxx__
的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__
、__score__
这样的变量名。
有些时候,你会看到以一个下划线开头的实例变量名,比如_name
,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
查看__dict__可以看出,Python实际上是在类、对象,在定义的时候,将这类变量转换成了类似 _Student__name 的格式
不能直接访问__name
是因为Python解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
变量:
>>> bart._Student__name
'Bart Simpson'
但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name
改成不同的变量名。
总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。
最后注意下面的这种错误写法:
>>> bart = Student('Bart Simpson', 98)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 设置__name变量!
>>> bart.__name
'New Name'
表面上看,外部代码“成功”地设置了__name
变量,但实际上这个__name
变量和class内部的__name
变量不是一个变量!内部的__name
变量已经被Python解释器自动改成了_Student__name
,而外部代码给bart
新增了一个__name
变量。不信试试:
>>> bart.get_name() # get_name()内部返回self.__name
'Bart Simpson'
但是如果外部代码要获取name和score怎么办?可以给Student类增加get_name
和get_score
这样的方法:
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
如果又要允许外部代码修改score怎么办?可以再给Student类增加set_score
方法:
class Student(object):
...
def set_score(self, score):
self.__score = score
你也许会问,原先那种直接通过bart.score = 59
也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:
class Student(object):
...
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
但是这样来更改一个属性还是显得比较诡异,因为一个原本 self.name=[名字] 的操作,变成了一个函数来操作。
但是你任然可以将隐藏的属性伪装成一个正常属性,类的内部对这个变量的操作进行限制,或者参数检查等功能。
class People:
def __init__(self,name,permission=False):
self.__name=name
self.permission=permission @property #将 self.name() 变成了 self.name
def name(self):
return self.__name @name.setter #让 self.name 可以像正常属性一样可以用‘=’操作设置属性的新值 self.name='[新值]'
def name(self,val):
if not isinstance(val,str):
raise TypeError('must be str') #raise 定义错误信息
self.__name=val @name.deleter #可以 del self.name 删除属性
def name(self):
if not self.permission:
raise PermissionError('不让删')
del self.__name snow=People('***snow***')
print(snow.name)
print(snow.permission)
snow.permission=True
del snow.name
print(snow.name)
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) #不如装饰器的方式清晰 一种property的古老用法
一种property的古老用法
绑定方法与非绑定方法
类中定义的函数分成两大类:
一:绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):
1. 绑定到类的方法:用classmethod装饰器装饰的方法。
为类量身定制
类.boud_method(),自动将类当作第一个参数传入
(其实对象也可调用,但仍将类当作第一个参数传入)
2. 绑定到对象的方法:没有被任何装饰器装饰的方法。
为对象量身定制
对象.boud_method(),自动将对象当作第一个参数传入
(属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)
二:非绑定方法:用staticmethod装饰器装饰的方法
1. 不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
1 staticmethod
statimethod不与类或对象绑定,谁都可以调用,没有自动传值效果,python为我们内置了函数staticmethod来把类中的函数定义成静态方法
import hashlib
import time
class MySQL:
def __init__(self,host,port):
self.id=self.create_id()
self.host=host
self.port=port
@staticmethod
def create_id(): #就是一个普通工具
m=hashlib.md5(str(time.clock()).encode('utf-8'))
return m.hexdigest() print(MySQL.create_id) #<function MySQL.create_id at 0x0000000001E6B9D8> #查看结果为普通函数
conn=MySQL('127.0.0.1',3306)
print(conn.create_id) #<function MySQL.create_id at 0x00000000026FB9D8> #查看结果为普通函数
2 classmethod
classmehtod是给类用的,即绑定到类,类在使用时会将类本身当做参数传给类方法的第一个参数(即便是对象来调用也会将类当作第一个参数传入),python为我们内置了函数classmethod来把类中的函数定义成类方法
import settings
import hashlib
import time
class MySQL:
def __init__(self,host,port):
self.host=host
self.port=port @classmethod
def from_conf(cls):
print(cls)
return cls(settings.HOST,settings.PORT) print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>>
conn=MySQL.from_conf() print(conn.host,conn.port)
conn.from_conf() #对象也可以调用,但是默认传的第一个参数仍然是类
Python(面向对象编程4——继承顺序、封装)的更多相关文章
- Python面向对象编程-类的封装,继承、多态
面向对象是一种程序设计思想,对象作为程序基本单元,包含了数据和操作数据的函数. 面向对象的三大特点--数据封装.多态和继承. #类的创建,class关键字,类名大写,object表示从哪个类继承而来, ...
- Python面向对象编程(下)
本文主要通过几个实例介绍Python面向对象编程中的封装.继承.多态三大特性. 封装性 我们还是继续来看下上文中的例子,使用Student类创建一个对象,并修改对象的属性.代码如下: #-*- cod ...
- python 面向对象编程(一)
一.如何定义一个类 在进行python面向对象编程之前,先来了解几个术语:类,类对象,实例对象,属性,函数和方法. 类是对现实世界中一些事物的封装,定义一个类可以采用下面的方式来定义: class c ...
- python面向对象编程进阶
python面向对象编程进阶 一.isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 1 ...
- python面向对象编程学习
python面向对象编程 基本概念理解 面向对象编程--Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作 ...
- Python面向对象编程——继承与派生
Python面向对象编程--继承与派生 一.初始继承 1.什么是继承 继承指的是类与类之间的关系,是一种什么"是"什么的关系,继承的功能之一就是用来解决代码重用问题. 继承是一种创 ...
- python 面向对象编程学习
1. 问题:将所有代码放入一个py文件:无法维护 方案:如果将代码才分放到多个py文件,好处: 1. 同一个名字的变量互相不影响 2.易于维护 3.引用模块: import module 2.包:解决 ...
- Python面向对象编程指南
Python面向对象编程指南(高清版)PDF 百度网盘 链接:https://pan.baidu.com/s/1SbD4gum4yGcUruH9icTPCQ 提取码:fzk5 复制这段内容后打开百度网 ...
- Python 面向对象编程——访问限制
<无访问限制的对象> 在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑.但是,从前面Student类的定义来看(见:Py ...
- Python 面向对象编程 继承 和多态
Python 面向对象编程 继承 和多态 一:多继承性 对于java我们熟悉的是一个类只能继承一个父类:但是对于C++ 一个子类可以有多个父亲,同样对于 Python一个类也可以有多个父亲 格式: c ...
随机推荐
- struts2中,Action通过什么方式获得用户从页面输入的数据,又是通过什么方式把其自身的数据传给视图的?
struts2中,Action通过什么方式获得用户从页面输入的数据,又是通过什么方式把其自身的数据传给视图的? 解答: 1)可以直接通过与表单元素相同名称的数据成员(需要存在符合命名规范set和get ...
- String, StringBuffer StringBuilder的区别。
解答:String的长度是不可变的: StringBuffer的长度是可变的,如果你对字符串中的内容经常进行操作,特别是内容要修改时,那么使用StringBuffer,如果最后需要String,那么使 ...
- Oracle安装完成后,如何用命令行启动和关闭数据库?
Oracle安装完成后,如何用命令行启动和关闭数据库? 解答: 打开:STARTUP [FORCE] [RESTRICT] [PFILE= filename] [OPEN [RECOVER][ dat ...
- 利用JQuery jsonp实现Ajax跨域请求 .Net 的*.handler 和 WebService,返回json数据
1:跨域请求handler一般处理程序 using System; using System.Collections.Generic; using System.Web; using System.W ...
- LigerTree的使用
效果图: 页面: <div id="divs" style="width: 310px; overflow-x: hidden; overflow-y: hidde ...
- Django admin 的 9 个技巧
Tip 1:Django admin 后台不限于用 Django 开发的网站 虽然 Django admin 管理界面可以非常友好的用在 Django 项目的其它部分,它同样可以很容易用于其它像传统的 ...
- 【译】.Net 垃圾回收机制原理(转)
上一篇文章介绍了.Net 垃圾回收的基本原理和垃圾回收执行Finalize方法的内部机制:这一篇我们看下弱引用对象,代,多线程垃圾回收,大对象处理以及和垃圾回收相关的性能计数器. 让我们从弱引用对象说 ...
- Linux下自动调整时间和时区与Internet时间同步
(原文链接) 调整linux系统时间和时区与Internet时间同步一.修改时区:# cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime修改为中国的 ...
- Pycharm 2017 12月最新激活码
激活的办法:这个必须的联网才可以使用(每次打开PyCharm都需要电脑联网才可以正常使用),要是没网的话,就不能激活使用啦,大家注意哈. http://idea.iteblog.com/key.php ...
- cocos2d-X学习之主要类介绍:布景:CCLayer
类继承图: CCLayer 是CCNode的子类 ,实现了TouchEventsDelegate接口,继承了CCNode所有的特性,并且附加了一些自己的特性,它能够接收iPhone的触摸事件,也能够 ...