多继承以及MRO顺序
在重写父类方法的时候如果在重写方法中需要调父类的方法,一共有三种方式(以__init__()方法为例):
- 父类名.__init__(self, 形参)
- super().__init__(形参)
- super(父类名, self).__init__(形参)
单独调用父类的方法
# coding=utf-8
print("******多继承使用类名.__init__ 发生的状态******")
class Parent(object):
def __init__(self, name):
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age):
print('Son1的init开始被调用')
self.age = age
Parent.__init__(self, name)
print('Son1的init结束被调用')
class Son2(Parent):
def __init__(self, name, gender):
print('Son2的init开始被调用')
self.gender = gender
Parent.__init__(self, name)
print('Son2的init结束被调用')
class Grandson(Son1, Son2):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
Son1.__init__(self, name, age) # 单独调用父类的初始化方法
Son2.__init__(self, name, gender)
print('Grandson的init结束被调用')
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
print("******多继承使用类名.__init__ 发生的状态******\n\n")
运行结果:
******多继承使用类名.__init__ 发生的状态******
Grandson的init开始被调用
Son1的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son1的init结束被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
******多继承使用类名.__init__ 发生的状态******
通过上面的程序可以看到parent有两个子类,Grandson继承了这两个类,也就是说这种写法会调用两次parent的__init__()方法,这样的话会造成资源的浪费,如果有几十个子类,那么parent中的方法就会被调用几十次。当然这种方法的优点是简洁明了,但是总的来说弊大于利
注:
- 如果是多继承,多个父类中都有相同的方法名,那么子类在没有重写该方法的时候应该根据MRO顺序,调用紧跟在子类名后面的类的那个父类的该方法
示例如下:
class A(object):
def test(self):
print('---A----')
class B(object):
def test(self):
print('----B----')
class C(A,B):
pass
c = C()
c.test()
print(C.__mro__)
可以看到默认调用的是A类中的test方法,mro顺序里在C类之后就是A类,所以优先调用的是A类中的test方法
多继承中super调用所有父类的被重写的方法
print("******多继承使用super().__init__ 发生的状态******")
class Parent(object):
def __init__(self, name, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('Son1的init开始被调用')
self.age = age
super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
print('Son1的init结束被调用')
class Son2(Parent):
def __init__(self, name, gender, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('Son2的init开始被调用')
self.gender = gender
super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
print('Son2的init结束被调用')
class Grandson(Son1, Son2):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
# 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
# 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
# super(Grandson, self).__init__(name, age, gender)
super().__init__(name, age, gender)
print('Grandson的init结束被调用')
print(Grandson.__mro__)
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
print("******多继承使用super().__init__ 发生的状态******\n\n")
运行结果:
******多继承使用super().__init__ 发生的状态******
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
Grandson的init开始被调用
Son1的init开始被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
******多继承使用super().__init__ 发生的状态******
通过这个运行的流程来看super函数的作用:首先Grandson调用super方法,根据MRO顺序下一个调用的就是Son1中的__init__方法,在__init__方法中调用super方法之后根据MRO顺序调用Son2的__init__方法,然后调用Parent的初始化方法
注意:
- 以上2个代码执行的结果不同
- 如果2个子类中都继承了父类,当在子类中通过父类名调用时,parent被执行了2次
- 如果2个子类中都继承了父类,当在子类中通过super调用时,parent被执行了1次
通过super方法可以看到Parent的初始化方法只被调用了一次,这样做可以节省资源
除了super().__init__()这种方法之外,也可以使用super(类名,self).__init__来实现上述的功能,只不过这里的类名如果放的就是当前类那么和super()没有任何区别,如果是其他类名,那么就按照MRO顺序查找该类的下一个类名,调用该类名的初始化方法
单继承中的super
print("******单继承使用super().__init__ 发生的状态******")
class Parent(object):
def __init__(self, name):
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age):
print('Son1的init开始被调用')
self.age = age
super().__init__(name) # 单继承不能提供全部参数
print('Son1的init结束被调用')
class Grandson(Son1):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
super().__init__(name, age) # 单继承不能提供全部参数
print('Grandson的init结束被调用')
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
#print('性别:', gs.gender)
print("******单继承使用super().__init__ 发生的状态******\n\n")
总结
- super().__init__相对于类名.__init__,在单继承上用法基本无差
- 但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次,具体看前面的输出结果
- 多继承时,使用super方法,对父类的传参数,应该是由于python中super的算法导致的原因,必须把参数全部传递,否则会报错
- 单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
- 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍, 而使用super方法,只需写一句话便执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
补充:*args和**kwargs
*args和**kwargs是不定长参数,其中*args是以元组进行传递,**kwargs是以字典方式进行传递,也就是说我们在写函数的过程中如果不确定参数的个数,那么就可以在形参上加上这两个。其中多出来的非关键字参数会存储到args中去,关键字参数会传递到kwargs中去:
def test(name,age,*args,**kwargs):
print(name)
print(age)
print(args)
print(kwargs)
test('laowang',20,30,(11,22),a='laowang',b=20)
test('laowang',20)
运行结果:
laowang
20
(30,(11,22))
{'a':'laowang','b':20}
laowang
20
()
{}
1 def test(name,age,*args,**kwargs):
2 print(args) # args=(30,(11,40))
3 print(kwargs) # kwargs={'b':20,'temp':'laowang'}
4 print('---1---')
5 test1(name,age,args,kwargs) # 相当于传入的全部都是值
6 print('----2----')
7 test1(name,age,*args,kwargs) # args被解包,*args是30,(11,40)
8 print('----3----')
9 test1(name,age,*args,*kwargs) #kwargs输出的是键名,没有值
10 print('---4-----')
11 test1(name,age,*args,**kwargs) # 二者均被解包,也就是**kwargs是temp='laowang',b=20
12
13 def test1(name,age,*args,**kwargs):
14 print(args)
15 print(kwargs)
16
17 test('zhangsan',20,30,(11,40),temp='laowang',b=20)
运行结果:
小试牛刀
以下代码能输出什么?
class Parent(object):
x = 1
class Child1(Parent):
pass
class Child2(Parent):
pass
print(Parent.x, Child1.x, Child2.x)
Child1.x = 2 # 相当于创建一个类属性
print(Parent.x, Child1.x, Child2.x)
Parent.x = 3 # 对类属性进行修改
print(Parent.x, Child1.x, Child2.x)
输出结果如下:
1 1 1
1 2 1
3 2 3
这里要明白一个道理,继承并不是复制一份拿过来,而是复制一个引用到子类中去,如果在调用的时候发现在本类中没有该属性,那么就到父类中去找,这也就是为什么最后一个使323而不是321的原因所在
再论静态方法和类方法
类属性和实例属性
他们在定义和使用中有所区别,而最本质的区别是在内存中保存的位置不同
- 实例属性属于对象,实例属性在每个对象中都有一份;实例属性需要通过对象来访问
- 类属性属于类,类属性在内存中只保存一份;类属性通过类访问,也可以通过实例对象访问,但是无法通过实例对象修改
class Province(object):
# 类属性
country = '中国'
def __init__(self, name):
# 实例属性
self.name = name
# 创建一个实例对象
# 这一步包含两步:首先调用__new__方法,创建对象也就是分配一个内存空间,然后调用__init__方法,对刚刚申请的空间进行初始化
obj = Province('山东省')
# 直接访问实例属性
print(obj.name)
# 直接访问类属性
Province.country
实例方法、静态方法、类方法
三种方法在内存中都属于类,区别在于调用方式不同
- 实例方法:由对象调用,至少有一个self参数;执行实例方法时,自动将调用该方法的对象赋值给self
- 类方法:由类调用,至少有一个cls参数,执行类方法时,自动将调用该方法的类赋值给cls
- 静态方法:由类调用;无默认参数
class Foo(object):
def __init__(self, name):
self.name = name
def ord_func(self):
""" 定义实例方法,至少有一个self参数 """
# print(self.name)
print('实例方法')
# 可以修改类属性,通过实例方法是修改不了的
@classmethod
def class_func(cls):
""" 定义类方法,至少有一个cls参数 """
print('类方法')
@staticmethod
def static_func():
""" 定义静态方法 ,无默认参数"""
print('静态方法')
f = Foo("中国")
# 调用实例方法
f.ord_func()
# 调用类方法
Foo.class_func()
# 调用静态方法
Foo.static_func()
实例对象可以调用这三种方法,类对象只可以调用类方法和静态方法
property属性
property属性类似于实例属性的一种特殊属性,可以对应于某方法
class Foo:
def func(self):
pass
# 定义property属性
@property
def prop(self):
return 100
foo_obj = Foo()
foo_obj.func() # 调用实例
print(foo_obj.prop) # 调用property属性,输出100
property属性在定义过程中调用要注意一下:
- 定义时,在实例方法的基础上添加@property装饰器;并且只有一个属性,就是self
- 调用时,无需括号
简单的实例
对于京东商城中显示电脑主机的列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据 这个分页的功能包括:
-
根据用户请求的当前页和总数据条数计算出 m 和 n
-
根据m 和 n 去数据库中请求数据
class Pager:
def __init__(self, current_page):
# 用户当前请求的页码(第一页、第二页...)
self.current_page = current_page
# 每页默认显示10条数据
self.per_items = 10
@property
def start(self):
val = (self.current_page - 1) * self.per_items
return val
@property
def end(self):
val = self.current_page * self.per_items
return val
# ############### 调用 ###############
p = Pager(1)
p.start # 就是起始值,即:m
p.end # 就是结束值,即:n
python的property属性的功能就是:Property属性内部进行一系列的逻辑运算,最终将结果进行返回,这是封装的一个体现,而且从外面看就相当于是调用了一个属性,更简洁。如果是一个函数在用的时候还需要看看源码查看是否需要传参,使用property属性根本不需要想就可以直接使用
property创建的两种方式
- 装饰器:在方法上安装装饰器
- 类属性:在类中定义值为property对象的类属性
装饰器方式
在类的实例方法上应用@property装饰器
Python中类有经典类和新式类,新式类的属性比经典类的属性丰富
经典类,只有一种@property装饰器
新式类,具有三种@property装饰器
class Goods(object):
def __init__(self):
# 原价
self.original_price = 100
# 折扣
self.discount = 0.8
@property
def price(self):
# 实际价格 = 原价 * 折扣
new_price = self.original_price * self.discount
return new_price
@price.setter
def price(self, value):
self.original_price = value
@price.deleter
def price(self):
del self.original_price
obj = Goods()
obj.price # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
obj.price = 123 # 自动执行 @price.setter 修饰的 price 方法,并将 123 赋值给方法的参数
del obj.price # 自动执行 @price.deleter 修饰的 price 方法
- 经典类中的属性只有一种访问方式,其对应被@property修饰的方法
- 新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法
- 由于新式类具有三种访问方式,我们可以根据它们结果属性的特点,分别将三个方法对应于同一个属性:获取、修改、删除,代码如上
类属性方式,创建值为property对象的类属性
当使用类属性的方式创建property属性时,经典类和新式类没有区别
property方法中有四个参数:
- 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
- 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
- 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
- 第四个参数是字符串,调用 对象.属性.__doc__ ,此参数是该属性的描述信息
#coding=utf-8
class Foo(object):
def get_bar(self):
print("getter...")
return 'laowang'
def set_bar(self, value):
"""必须两个参数"""
print("setter...")
return 'set value' + value
def del_bar(self):
print("deleter...")
return 'laowang'
BAR = property(get_bar, set_bar, del_bar, "description...")
obj = Foo()
obj.BAR # 自动调用第一个参数中定义的方法:get_bar
obj.BAR = "alex" # 自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入
desc = Foo.BAR.__doc__ # 自动获取第四个参数中设置的值:description...
print(desc)
del obj.BAR # 自动调用第三个参数中定义的方法:del_bar方法
同样的根据类属性的方式创建property属性具有三种不同的访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为同一属性:获取、修改、删除
通过使用property属性,能够简化调用者在获取数据的流程
property属性-应用
私有属性添加getter和setter方法
class Money(object):
def __init__(self):
self.__money = 0
def getMoney(self):
return self.__money
def setMoney(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
使用property升级getter和setter方法
class Money(object):
def __init__(self):
self.__money = 0
def getMoney(self):
return self.__money
def setMoney(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
# 定义一个属性,当对这个money设置值时调用setMoney,当获取值时调用getMoney
money = property(getMoney, setMoney)
a = Money()
a.money = 100 # 调用setMoney方法
print(a.money) # 调用getMoney方法
使用property取代getter和setter方法
class Money(object):
def __init__(self):
self.__money = 0
# 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,调用装饰的方法
@property
def money(self):
return self.__money
# 使用装饰器对money进行装饰,当对money设置值时,调用装饰的方法
@money.setter
def money(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
a = Money()
a.money = 100
print(a.money)
魔法属性
私有属性名字重整:
class A(object):
def __init__(self,name):
self.__name = name
a = A('laowang')
a.__name # 报错
a._A__name # 输出laowang,这就是私有属性的名字重整
__doc__
- 表示类的描述信息
class Foo:
""" 描述类信息,这是用于看片的神器"""
def func(self):
pass
print(Foo.__doc__)
# 输出:‘类的描述信息,这是用于看片的神器’
__module__和__class__
- __module__表示当前操作的对象在那个模块
- __class__表示当前操作的对象的类是什么
# test.py
class Person(object):
def __init__(self):
self.name = 'laowang'
# main.py
from test import Person
obj = Person()
print(obj.__module__) # 输出 test 即:输出模块
print(obj.__class__) # 输出 test.Person 即:输出类
__init__
- 初始化方法,通过类创建对象时,自动触发执行
__str__
重写__str__方法,当print(实例对象名)的时候会输出__str__方法返回的内容
class Cat(object):
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
return '%s的年龄是%d'%(self.name,self.age)
tom = Cat('tom',10)
print(tom)
# 输出的是:tom的年龄是10
__new__
在创建对象的时候系统最先做的事就是调用__new__方法,调用这个方法来创建一个对象,然后找一个变量也就是对象名接受__new__函数返回的值,这个值表示创建对象的引用,然后才是初始化
__del__
- 当对象在内存中释放的时候,自动触发执行
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,__del__的调用是由解释器在进行垃圾回收时自动触发执行的。
__call__
- 对象后面加括号,触发执行
注:__init___方法的执行是由创建对象触发的,即对象=类名();而对于__call__方法的执行是由对象后加括号触发的:即:对象()或者类()
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
# 输出__call__
__dict__
- 类或者对象中的所有属性
类的实例属性属于对象;类的类属性和方法属于类;即
class Province(object):
country = 'China'
def __init__(self, name, count):
self.name = name
self.count = count
def func(self, *args, **kwargs):
print('func')
# 获取类的属性,即:类属性、方法、
print(Province.__dict__)
# 输出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}
obj1 = Province('山东', 10000)
print(obj1.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 10000, 'name': '山东'}
obj2 = Province('山西', 20000)
print(obj2.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 20000, 'name': '山西'}
__getitem__、__setitem__、__delitem__
- 用于索引操作,如字典,以上分别表示获取、设置、删除数据
class Foo(object):
def __getitem__(self, key):
print('__getitem__', key)
def __setitem__(self, key, value):
print('__setitem__', key, value)
def __delitem__(self, key):
print('__delitem__', key)
obj = Foo()
result = obj['k1'] # 自动触发执行 __getitem__
obj['k2'] = 'laowang' # 自动触发执行 __setitem__
del obj['k1'] # 自动触发执行 __delitem__
__getslice__、__setslice__、__delslice__
- 该三个方法用于分片操作
class Foo(object):
def __getslice__(self, i, j):
print('__getslice__', i, j)
def __setslice__(self, i, j, sequence):
print('__setslice__', i, j)
def __delslice__(self, i, j):
print('__delslice__', i, j)
obj = Foo()
obj[-1:1] # 自动触发执行 __getslice__
obj[0:1] = [11,22,33,44] # 自动触发执行 __setslice__
del obj[0:2] # 自动触发执行 __delslice__