多继承以及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__)

Python提高---2

可以看到默认调用的是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的初始化方法

注意:

  1. 以上2个代码执行的结果不同
  2. 如果2个子类中都继承了父类,当在子类中通过父类名调用时,parent被执行了2次
  3. 如果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")

总结

  1. super().__init__相对于类名.__init__,在单继承上用法基本无差
  2. 但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次,具体看前面的输出结果
  3. 多继承时,使用super方法,对父类的传参数,应该是由于python中super的算法导致的原因,必须把参数全部传递,否则会报错
  4. 单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
  5. 多继承时,相对于使用类名.__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)

运行结果:

Python提高---2

小试牛刀

以下代码能输出什么?

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__

 

 

 

相关文章:

  • 2021-09-21
  • 2022-02-22
  • 2021-09-10
  • 2021-04-15
  • 2021-09-30
  • 2021-11-22
  • 2021-06-17
  • 2021-12-18
猜你喜欢
  • 2022-12-23
  • 2021-07-27
  • 2021-09-05
  • 2021-05-22
  • 2022-12-23
  • 2021-06-18
  • 2021-08-09
相关资源
相似解决方案