继承顺序


'''
一点需要注意
'''
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类的定义来看,外部代码还是可以自由地修改一个实例的namescore属性:

>>> 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_nameget_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() #对象也可以调用,但是默认传的第一个参数仍然是类

最新文章

  1. SQL笔记 - CTE递归实例(续):显示指定部门的全称
  2. [ 学习路线 ] 2015 前端(JS)工程师必知必会 (2)
  3. PHP字符处理基础知识
  4. css3 2D变换 transform
  5. web标准(复习)--3 二列和三列布局
  6. Backbone案例的初略理解
  7. BZOJ 2716 Violet 3 天使玩偶 CDQ分治
  8. hdu 1548 简单BFS
  9. 潭州课堂25班:Ph201805201 tornado 项目 第十课 深入应用异步和协程(课堂笔记)
  10. js 两数组去除重复数值
  11. 第四周作业&amp;&amp;结对编程
  12. [Harbor]Docker登录Harbor仓库(HTTP方式)
  13. C#检测本机是否联网
  14. html5页面与android页面之间通过url传递参数
  15. 微信小程序:wx.navigateBack页面返回传参
  16. Spring Boot学习笔记:kafka应用
  17. FastDFS+nginx+keepalived集群搭建
  18. Cannot find config.m4. Make sure that you run &#39;/usr/local/php/bin/phpize&#39; in the top level source directory of the module的 解决方法
  19. DXDBGrid使用方法
  20. centos7.2下caffe的安装及编译

热门文章

  1. UVA 12034 Race (递推神马的)
  2. 通过某个进程号显示该进行打开的文件 lsof -p 1 11. 列出多个进程号对应的文件信息 lsof -p 123,456,789 5. 列出某个用户打开的文件信息 lsof -u username
  3. 【BZOJ】3668: [Noi2014]起床困难综合症(暴力)
  4. Linux经常使用命令(十八) - find概述
  5. windows 中 Eclipse 打开当前文件所在文件夹
  6. hdu5861(Road)
  7. Leetcode-Convert Sorted Array to BST
  8. MySQL 索引设计概要
  9. Java 之内部类
  10. 【查看版本】查看linux版本/查看32还是64