python代码学习——类与对象提升(继承、超继承,类的例题,魔术方法、多态)

面向对象的特征

  • 面向对象编程有三大特征:封装,继承和多态
  • 封装:将数据和方法都放在一个类中,就构成了封装
  • 继承:python中一个类可继承另外一个类,也可以继承多个类,被继承的类叫做父类或者基类,继承的类叫做子类
  • 多态:指的是一类事物有多种状态,一个抽象类有多个子类(因而多态的概念依赖于继承),不同的子类对象调用相同的方法,产生不同执行结果,多态可以增加代码的灵活度

继承

特点或者作用:

  • 继承后的类,拥有父类的所有方法和属性,包括初始化函数
  • 若初始化函数中存在参数,调用继承的类时,需要传入相应的参数
  • 结构:
 class 类名(父类名):
 		方法
 		属性
  • 继承后,子类可以直接调用父类中的任意方法和属性,这样就可以减少代码的冗余,增强其复用的能力(继承的作用一)
  • 子类可以定义自己的属性和方法
  • 子类中父类不存在的方法叫拓展,子类中与父类名称一致的方法叫重写
  • 调用重写的方法时,执行的是子类中的方法,初始化函数、方法和属性都可以被重写,重写后调用的就是子类中的新方法
  • 如果在类定义时,没有父类列表,等同于继承自object
  • 在python3中,object是所有对象的根父类

继承的特殊属性:

    • 子类名.__base__ :显示类的父类
    • 子类名.__bases__:显示类的父类元组
    • 子类名.__mro__:显示方法查找的顺序,父类的元组
    • 子类名.mro():同上
    • 父类名.__subclasses__():显示类的子类列表

代码示例

class FatherObject:

    #父类的初始化函数
    def __init__(self,number_people,name):
        self.number_people = number_people
        self.name = name

    def people_eat(self,a):
        print("我喜欢吃{0}菜".format(a))

    def people_drink(self,a):
        print('我喜欢喝{0}'.format(a))

    def people_live(self,a):
        print('我喜欢住在{0}'.format(a))

    @staticmethod
    def people_travel(a):
        print('我喜欢{0}出门'.format(a))

class SonObject(FatherObject):#继承父类

    @staticmethod
    def people_play(a):
        print('我喜欢去{0}玩儿'.format(a))

    def people_study(self,a):
        print('我的名字是{0},一家{1}口人'.format(
            self.name,self.number_people))
        # format括号中的信息是父类的初始化属性,子类中没有初始化
        print('我想通过{0}的方式学习'.format(a))

    @staticmethod
    def people_live(a,b):
        print('我住在{0},离{1}很近'.format(a,b))

print("查看类的父类信息:",SonObject.__base__)
print("查看类的父类的元组:",SonObject.__bases__)
print("显示方法的查找顺序:",SonObject.__mro__)
print("显示方法的查找顺序:",SonObject.mro())
print("类的子类列表:",FatherObject.__subclasses__())
print("=========创建子类实例和调用属性=======")
cla = SonObject('4','jjj')# 因为父类存在初始化,必须传入参数
cla.people_study('不断练习')
#继承属性值后,对象方法必须传入属性值,拓展调用新函数
print("========对象和类名,调用父类静态方法====")
SonObject.people_travel('坐飞机')
cla.people_travel('坐轮船')
print("======对象调用父类的对象方法======")
cla.people_drink("奶茶")
print("=======类名和对象,调用子类的静态方法======")
SonObject.people_play('海边')
cla.people_play('沙漠')
print("======方法重写,名称一致,调用的是子类的新方法=========")
SonObject.people_live('深圳','海边')

*****************run_result****************
查看类的父类信息: <class '__main__.FatherObject'>
查看类的父类的元组: (<class '__main__.FatherObject'>,)
显示方法的查找顺序: (<class '__main__.SonObject'>, <class '__main__.FatherObject'>, <class 'object'>)
显示方法的查找顺序: [<class '__main__.SonObject'>, <class '__main__.FatherObject'>, <class 'object'>]
类的子类列表: [<class '__main__.SonObject'>]
=========创建子类实例和调用属性=======
我的名字是jjj,一家4口人
我想通过不断练习的方式学习
========对象和类名,调用父类静态方法====
我喜欢坐飞机出门
我喜欢坐轮船出门
======对象调用父类的对象方法======
我喜欢喝奶茶
=======类名和对象,调用子类的静态方法======
我喜欢去海边玩儿
我喜欢去沙漠玩儿
======方法重写,名称一致,调用的是子类的新方法=========
我住在深圳,离海边很近

方法的重写和覆盖(overrrid)

  • 在子类中编写和父类一样的初始化函数,方法和属性,这就是重写
  • 重写后,调用的就是子类中的函数
  • 如果在重写之后,还需要调用父类中的方法或者属性,需要使用超继承

总结

  • 子类从父类中继承的,调用时自己没有的,就可以到父类中进行寻找
  • 继承后,共有的,子类和实例都可以随意访问
  • 父类中私有的属性和方法(__开头)被隐藏,子类和实例不可以直接被访问,但私有方法或属性所在的类内的方法可以访问这个私有变量
  • 但是本质上依然是改了名字放在这个属性所在类的__dict__
  • 知道这个新名称就可以直接找到这个隐藏的变量,不过这种方式慎用
  • 属性的查找顺序:实例的__dict__——>类__dict__,如果有继承,还要查询父类的__dict__
  • 如果在这些地方都没有找到就会抛出异常,找到就直接返回了
  • 属性、初始化方法和方法重写后,调用的是子类中的新属性、方法,而不是父类的属性或者方法

一个类继承多个父类

  • 子类继承多个父类,需要在括号中使用逗号隔开
  • 子类所继承的父类,被继承的父类必须在子类的上面
  • 父类被继承之后,子类可以任意使用父类中的属性和方法

超继承

  • 超继承什么时候用?
    答:如果子类中存在某个已经重写的函数,但是某种情况下,想同时使用子类和父类中重写函数的特性,则需要使用超继承来实现
  • 也就是说相当于重写了类中的方法,但是没有将父类中的方法丢掉
  • 关键字:super()
  • super可以访问到父类中的属性
  • 使用方法:super(子类名,self).父类的方法名(),也可以省略super括号中的参数
  • 代码示例:
class FatherObject:

    #父类的初始化函数
    def __init__(self,number_people,name):
        self.number_people = number_people
        self.name = name

    def people_eat(self,a):
        print("我喜欢吃{0}".format(a))

    def people_basic(self):
        print("我的名字是{1},家里有{0}口人".format(self.name,self.number_people))

class SonObject(FatherObject):#继承父类

    def people_eat(self,a,*args):
        self.people_basic()#继承
        print('我也喜欢吃{0}'.format(a))
        super(SonObject,self).people_eat(args)#超继承
        super().people_eat(args)#可以省略括号中的参数

print("========实例化===========")
cla = SonObject('gbk','18')
cla.people_eat('北京菜','川菜','粤菜','鲁菜','豫菜')
#继承调用父类的方法

*************run_result**********
========实例化===========
我的名字是gbk,家里有18口人
我也喜欢吃北京菜
我喜欢吃('川菜', '粤菜', '鲁菜', '豫菜')
我喜欢吃('川菜', '粤菜', '鲁菜', '豫菜')

继承中的初始化

  • 继承中使用父类的初始化函数,有两种方法
  • 第一,直接使用父类的域名调用初始化函数,init中要传self
  • 第二,使用super,不要传self
class FatherObject:

    #父类的初始化函数
    def __init__(self,number_people,name):
        self.number_people = number_people
        self.name = name

    def people_eat(self,a):
        print("我喜欢吃{0}".format(a))

    def people_basic(self):
        print("我的名字是{1},家里有{0}口人".format(self.name,self.number_people))

class SonObject(FatherObject):#继承父类

    def __init__(self,k,v,item):
        FatherObject.__init__(self,k,v)# 继承了父类中的初始化函数
        self.item = item
    
    # 也可以使用super方式写,不过init中不要传self
    # def __init__(self,k,v,item):
    #     super().__init__(k,v)# 继承了父类中的初始化函数
    #     self.item = item

    def people_eat(self,*args):
        self.people_basic()#继承
        print('我也喜欢吃{0}'.format(self.item))
        super(SonObject,self).people_eat(args)#超继承
        super().people_eat(args)#可以省略括号中的参数

print("========实例化===========")
cla = SonObject('gbk','18','北京菜')
cla.people_eat('川菜','粤菜','鲁菜','豫菜')
#继承调用父类的方法

************run_result***********
========实例化===========
我的名字是gbk,家里有18口人
我也喜欢吃北京菜
我喜欢吃('川菜', '粤菜', '鲁菜', '豫菜')
我喜欢吃('川菜', '粤菜', '鲁菜', '豫菜')
  • 类的私有属性的基本原则:
    • 自己的私有属性,就应该使用自己的方法读取或者修改
    • 不要接住其他类的方法,即使是父类或者自己的子类

多继承

  • OCP原则:多继承,少修改
  • 继承的用途:增强父类,实现多态
  • 多态:在面向对象中,父类,子类通过继承联系在一起,如果可以通过一套方法,就可以实现不同表现,这就是多态的定义
  • 一个类继承自多个类就是多继承,他具有多个类的特征
  • 多继承的弊端:
    • 多继承的实现会导致编译器设计的复杂度增加,因此现在许多语言都舍弃了类的继承
    • 因为多继承会带来二义性,可能存在一个属性,多个父类中都存在的情况
    • 实现多继承语言,首先就要先解决二义性,确定意义的方式是以深度优先或者广度优先
  • 多继承的语法:
class ClassName(父类列表):
	类体
  • python使用MRP解决父类搜索顺序的问题(解决多继承的二义性)
  • python3 当前使用的是C3算法,在类被创建出来的时候,就计算出了一个MRO有序列表
  • 当类很多,继承很复杂的情况下,python解释器是在只有执行到的时候,才发现错误
  • 团队协作开发,如果引入多继承,那么代码将不可控
  • 因此,不论编程语言是否支持多继承,都应该避免多继承
  • 查看MRO,代码如下:
class FatherObject:

    #父类的初始化函数
    def __init__(self,number_people,name):
        self.number_people = number_people
        self.name = name

    def people_eat(self,a):
        print("我喜欢吃{0}".format(a))

    def people_basic(self):
        print("我的名字是{1},家里有{0}口人".format(self.name,self.number_people))

class SonObject(FatherObject):#继承父类

    def __init__(self,k,v,item):
        super().__init__(k,v)# 继承了父类中的初始化函数
        self.item = item

    def people_eat(self,*args):
        self.people_basic()#继承
        print('我也喜欢吃{0}'.format(self.item))
        super(SonObject,self).people_eat(args)#超继承
        super().people_eat(args)#可以省略括号中的参数

print(SonObject.mro())

***************run_result************
[<class '__main__.SonObject'>, <class '__main__.FatherObject'>, <class 'object'>]

例题

1.人和机器猜拳游戏写成一个类,有如下几个函数:

  • 函数1:选择角色1 曹操 2张飞 3 刘备
  • 函数2:角色猜拳1剪刀 2石头 3布 玩家输入一个1-3的数字
  • 函数3:电脑出拳 随机产生1个1-3的数字,提示电脑出拳结果
  • 函数4:角色和机器出拳对战,对战结束后,最后出示本局对战结果…赢…输,然后提示用户是否继续?按y继续,按n退出
  • 最后结束的时候输出结果 角色赢几局 电脑赢几局,平局几次 游戏结束
class PeopleVmCom:

    def select_ppeople(self):
        people = int(input("请选择角色:1 曹操 2张飞 3 刘备"))
        dict_people = {1:'曹操', 2:'张飞', 3:'刘备'}
        try:
            people_role = dict_people[people]
        except Exception :
            print('您选择的角色不存在,默认指定角色刘备')
            people_role = '刘备'
        print("您选择的角色是{0}".format(people_role))
        return people

    def people_result(self):
        people_result = int(input('请出拳(数字):1剪刀 2石头 3布'))
        dict_result = {1:'剪刀', 2:'石头', 3:'布'}
        try:
            people_role = dict_result[people_result]
            print("您选择的角色是{0}".format(people_role))
        except Exception :
            print('您选择的角色不存在,默认指定角色剪刀')
            people_result = 2
            print("您选择的角色是{0}".format(dict_result[people_result]))
        return people_result

    def computer_result(self):
        import random
        dict_role = {1:'剪刀', 2:'石头', 3:'布'}
        computer_result = random.randint(1,3)
        print("电脑输出的数字是{0}".format(dict_role[computer_result]))
        return computer_result

    def vs_result(self):
        people = self.select_ppeople()
        people_result = self.people_result()
        computer_result = self.computer_result()
        if people_result == 2 and computer_result ==1:
            result = '角色赢啦!'
            print(result)
        elif people_result == 1 and computer_result ==3:
            result = '角色赢啦!'
            print(result)
        elif people_result == 3 and computer_result ==2:
            result = '角色赢啦!'
            print(result)
        elif people_result == computer_result :
            result = '平局'
            print(result)
        else:
            result = '很抱歉,电脑赢啦!'
            print(result)
        return result
    def count_result(self):
        count_ping = 0
        couunt_computer = 0
        count_people = 0
        while 1:
            result =self.vs_result()
            if result == '平局':
                count_ping += 1
            elif result == '很抱歉,电脑赢啦!':
                couunt_computer += 1
            else:
                count_people +=1
            mes = input('是否继续?y 继续 n 终止')
            if mes == 'y':
                continue
            else:
                print('电脑胜利{0}轮,您胜利{1}轮,平局{2}轮'.format(couunt_computer,count_people,count_ping))
                break
PeopleVmCom().count_result()

2.按照以下要求定义一个游乐园门票类,并创建实例调用函数,完成儿童和大人的总票价统计(人数不定,由你输入的人数个数来决定)

  • 平日票价100元
  • 周末票价为平日票价120%
  • 儿童半价
class TicketPrice:

    def __init__(self):
        self.people_ticket = 100
        self.child_ticket = 50

    def ticket_price(self):
        child_num = int(input('请输入小孩人数:'))
        people_num = int(input('请输入大人人数:'))
        today_datetime = int(input('请输入今天(1-7代表周一到周日):'))
        if today_datetime  in range(1,6):
            people_money = self.people_ticket * people_num
            child_money = self.child_ticket * child_num
            total_money = people_money + child_money
        else:
            people_money = self.people_ticket * people_num
            child_money = self.child_ticket * child_num
            total_money = (people_money + child_money) * 1.2
        print('儿童花费{}元,大人花费{}元,总计{}元'.format(child_money, people_money, total_money))

3.Shape父类,要求所有子类都必须提供面积计算,子类有三角形,圆形和矩形

import math

class Shape:

    @property
    def area(self):
        raise  NotImplementedError("父类未实现")
        # 父类中定义了area属性,子类可以继承
        # 子类中调用area属性,area未实现,就不会覆盖父类的area
        # 继承之后,子类调用未被覆盖的area,就会抛出属性错误的异常
        # 因为父类的area,在没有被覆盖之前,写的就是直接抛出异常
        # 常见写法:
        # 在父类中定义一个统一的属性方法,子类可以继承
        # 防止子类直接调用父类的属性的方式是:方法直接抛出异常,属性直接给None

# 三角形
class Triangle(Shape):

    def __init__(self,a,b,c):
        self.a = a
        self.b = b
        self.c = c

    @property
    def area(self):
        p = (self.a + self.b +self.c)/2
        return  math.sqrt(p*(p-self.a)*(p-self.b)*(p-self.c))

# 长方形
class Rectangle(Shape):

    def __init__(self,width,heigth):
        self.width = width
        self.heigth = heigth

    @property
    def area(self):
        return self.width*self.heigth

# 圆
class Circle(Shape):

    def __init__(self,radius):
        self.d = radius*2

    @property
    def area(self):
        return math.pi * self.d *self.d *0.25

shapes = [Triangle(3,4,5),Rectangle(3,4),Circle(4)]
for s in shapes:
    print("The area of {} = {}".format(s.__class__.__name__,s.area))
    print(s.__dict__)
print(Shape.__dict__)

*************run_result*********************
The area of Triangle = 6.0
{'a': 3, 'b': 4, 'c': 5}
The area of Rectangle = 12
{'width': 3, 'heigth': 4}
The area of Circle = 50.26548245743669
{'d': 8}
{'__module__': '__main__', 
'area': <property object at 0x0000023A4E073E00>, 
'__dict__': <attribute '__dict__' of 'Shape' objects>, 
'__weakref__': <attribute '__weakref__' of 'Shape' objects>, 
'__doc__': None}

类的魔术方法

  • 魔术方法是python中提供的一种方法,特点是使用双下划线开头,双下划线结尾
  • 魔术方法会在恰当时候激活,自动执行
  • 魔术方法有两个特点:两侧都有下划线;其是关键字,不能随便使用
  • __init__函数和__del__函数就是魔术方法,他们对应类的创建于销毁
  • 《python魔术方法触发时机大全》
  • 下面讲解集集中常常使用的魔术方法

__new__

  • new方法在__init__方法之前执行
  • 定义类的时候没有设置new方法,因为他在object类中,可继承
  • new方法是一个静态方法,第一个参数是cls,也就是说使用new的时候,必须要传入的一个参数是类名,其会根据传入的类名,创建一个新的对象
  • new方法默认的返回值是None,没有返回值的情况下,不会调用类中的初始化函数,因为没有对象返回
  • 如果new返回的不是自己的类对象,也不能调用初始化函数,因为就不是这个类
  • 解决这个问题的方式是,使用obnject调用,或者使用super解决
class Myclass:

    def __init__(self,name):
        self.name = name # 如果new方法没有return,这个方法不会执行

    def __new__(cls, *args, **kwargs):
        print("这一个new方法")
        # return  super().__new__(cls) # 使用super调用父类的方法
        return object.__new__(cls)# 使用父类的名称调用


    def print_g(self):
        print(self.name)

a = Myclass("yiyi")
a.print_g()

new方法的使用场景:单例模式

  • 类每一次实例化的时候,都会创建一个新的对象,如果要求类只能被实例化一次,这就是单例模式
  • 例如:系统中公用的配置信息,首次创建的时候已经创建完成了,其他系统模块也要用到这些配置,就不需要再去创建一次,会浪费模块资源
  • 单例模式的好处:不论实例化多少次,始终都只会返回第一次创建的实例
  • 单例模式的实现思路
    在这里插入图片描述
# 如果类属性__insatance 为None,
# 那怎么就创建一个对象,并赋值为这个对象的引用,
# 保证下次调用这个方法时能够知道之前已经创建过对象了,
# 这样就保证了只有一个对象

class Test():

    __instance = None

    def __new__(cls, *args, **kwargs):
        if not cls.__instance:
            cls.__instance = super().__new__(cls)
            return cls.__instance
        return cls.__instance


a= Test()
a.name = "yiyi"
b= Test()
print(b.name) # a创建的属性,b实例可以访问
************run_result***********
yiyi

哈希(hash)和eq方法

  • 方法名:__hash____eq__
  • 哈希方法:
    • 内建函数hash()调用的返回值,返回一个整数
    • 如果定义这个方法,该类的实例就可hash
    • 所有的类都继承object,而这个类是具有__hash__()方法的,如果一个类不能被hash,就是重写了__hash__方法,并将其返回值设置成了None
    • 可hash,并不代表可去重
  • eq方法:
    • 对应==操作符,判断两个类的对象是否相等,返回bool值
  • hash方法只是返回一个hash值作为set的key,但是去重,就需要eq方法来判断两个对象是否相等
  • hash相等(计算后的key相等),只是hash冲突,不能说明两个对象是相等的
  • 因此,一般都提供hash方法是为了作为set或者dict的key,所以去重要同时提供eq方法
  • 可hash对象一定提供__hash__方法,没有提供的话,isinstance(类名/对象, collections.Hashable)的结果肯定是false
  • 判断某个对象、方法是否可hash,使用collections.Hashable,则要导入模块,语句:from collections import Hashable
  • 去重需要提供eq方法
class A:

    X = 1234

    def __init__(self):
        self.a = 5
        self.b = 6

    def __hash__(self):
        return  1

print("求hash值,返回的是A类中自定义的hash:",hash(A()))
print((A(),A()))
s = {A(),A()}
print("==没有去重的原因:hash的两个值并不相等,解决方式,使用eq方法==")
print(s)

*********** run_result**************hash值,返回的是A类中自定义的hash1
(<__main__.A object at 0x100dfb910>, <__main__.A object at 0x100dfb8b0>)
==没有去重的原因:hash的两个值并不相等,解决方式,使用eq方法==
{<__main__.A object at 0x100dfb910>, <__main__.A object at 0x100dfb8b0>}

# 解决方式:
class A:

    X = 1234

    def __init__(self):
        self.a = 5
        self.b = 6

    def __hash__(self):
        return  1

    def __eq__(self, other):
        return True # 使用eq函数,默认函数中的属性相等
        # return self.a == self.b 这里的==用的是object中的eq方法,
        # 而不是A类中自定义的方法

print("求hash值,返回的是A类中自定义的hash:",hash(A()))
print((A(),A()))
s = {A(),A()}
print("==使用eq函数,默认属性相等,返回True,set去重成功==")
print(s)
**************run_result*************hash值,返回的是A类中自定义的hash1
(<__main__.A object at 0x10522ba30>, <__main__.A object at 0x10522b9d0>)
==使用eq函数,默认属性相等,返回Trueset去重成功==
{<__main__.A object at 0x10522ba30>}

bool (布尔)

  • 内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值
  • 没有定义__bool__(),就找__len__()返回长度,非0位为真
  • 如果__len__()也没有定义,那么所有的示例都返回为真
class Point:
    '''bool和len方法都不存在,所有调用返回为真'''

    def __init__(self,x,y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        if self is other:
            return True
        return self.x == other.x and self.y == other.y

    def __len__(self):# 没有bool,看这个方法,大于0为True
        return 0
        #return False

    def __bool__(self):# 优先看这个方法,返回结果为True
        return True

print(bool(Point(4,5)))

可视化

  • 可视化的方法有三个,详见下面列表
方法意义
__repr__内建函数repr()对一个对象获取字符串表达
如果一个类定义了__repr__() ,但是没有定义__str__
那么在请求该类的实例的非正式的字符串表示时也将调用__repr__()
__str__str()函数、内建函数format、print()函数调用,需要返回对象的字符串表达
__bytes__bytes的时候,返回一个对象的bytes表达,即返回bytes()对象
  • 使用str函数或者print函数打印,首先会触发str个方法,如果没有,会触发pepr方法,如果都没有,回去父类找str方法
  • 使用repr方法或者交互环境下输入变量,会先找自身的repe方法,自身没有repr方法,回去找父类的repe方法
class Point:

    def __init__(self,x,y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        if self is other:
            return True
        return self.x == other.x and self.y == other.y

    def __repr__(self):
        return "答案:{0}".format("abc")

    def __str__(self):
        return str(123)

P = Point(4,5)
p = Point(3,4)

# 直接作用于对象,强行调用对象的__str__方法
print("单个打印:",P)

k = [P,p]
for _ in k:
    print("循环列表元素:",_)#_为列表中的元素,也是字符串

# 如果print的对象不是字符串类型,
# 打印的时候,调用的是__repr__方法
print("列表打印:",k)
***********run_result**************
单个打印: 123
循环列表元素: 123
循环列表元素: 123
列表打印: [答案:abc, 答案:abc]

运算符重载

  • operator 提供以下特殊方法,可以将类的实例使用下面的操作符来操作
运算符特殊方法含义
<<===>>=!=__lt____le____eq__
__gt____ge____ne__
比较运算符
+-*/%//divmod__add____sub__
__mul____truediv__
__mod____floordiv__
__pow____divmod__
算数运算符,移位或者(#`O′)运算也有相应的方法
+=-=*=/=%=//=++=__iadd____isub__
__imul____itruediv__
__imod____ifloordiv__
__ipow__
class A :

    def __init__(self,x):
        self.x = x

    # 减法,other是个对象(实例)
    def __sub__(self, other):
        return self.x - other.x

    #加法
    def __add__(self, other):
        return self.x + other.x

    def __lt__(self, other):
        return self.x <= other.x

    def __ne__(self, other):
        return self.x != other.x

    def __eq__(self, other):
        return self.x == other.x

    def __iadd__(self, other):
        self.x += other.x
        return self  #  这里,如果直接返回self,则是对数据就地修改
    #否则就是返回新的对象

    def __repr__(self): # 存在这个方法的情况下,返回的就不是地址
        #  这个方法自动执行,不需要调用
        return str(self.x)


a =  A(4)
b = A(10)
print(a-b)
print(a.__sub__(b))

print(a+b)
print(a.__add__(b))

lst = [a,b]
print(list(sorted(lst)))
# 如果存在大于等于,就可以对对象进行排序

print(a==b)
print(a.__eq__(b))

print(a!=b)
print(a.__ne__(b))

a+=b
print(a)
  • 例题:完成Point类的设计,实现判断点相等的方法,并完成向量的加法
class Point:

    # 初始化一个点,有x和y两个元素组成
    def __init__(self,x,y):
        self.x = x
        self.y = y

    # 判断设置的实例,是不是一个点
    def __eq__(self, other):
        if self is other:
            return True
        return self.x == other.x and self.y == other.y

    # 实现实例的加法
    def __add__(self, other):
        add_x = self.x + other.y
        add_y = self.y + other.y
        return (add_x,add_y)

    def add(self,other):
        return Point(other.x+self.x,other.y+self.y)
        # 返回的是一个对象,可以在后面直接添加数值

    def __repr__(self):
        return "Point:{},{}".format(self.x,self.y)

P = Point(11,12)
p = Point(10,11)
point = (P,p)
print("打印两个实例的元祖:",point)
print("add方法的调用:",point[0].add(point[1]))# add方法,返回的是一个对象
print("元祖中的两个点相加",point[0]+point[1])
print("两个向量的和:",p+P)
print("构建实例之后,解构:",Point(*(point[0]+point[1])))
print("是不是一个点:",p==P)
  • 运算符重载的场景:
    • 往往是面向对象实现的类,需要做大量的运算,而运算符是这种运算在数学上最常见的表达方式
    • 提供运算符重载,比直接提供加法更加适合该领域内使用者的习惯,int类,几乎实现了所有操作符

容器相关的方法

  • __len__:内建函数len(),返回对象的长度(>=0的整数)
    • 其实即使将对象当做容器类型来看,就如同list或者dict
    • bool()函数调用的时候,如果没有__bool__()方法,会看__len__()方法是否存在,存在返回非0 的数值
  • __iter__:迭代容器时,钓鱼哦那个,返回一个新的迭代器对象
    • 类中只要有这个方法,就可以进行for循环遍历
  • __contains__:in成员运算符,没有实现就调用__iter__()方法遍历
  • __getitem__:实现self[key]访问
    • 序列对象,key接受证书为索引,或者切片
    • 对set和dict,key作为hashable,key不存在就引发keyError异常
  • __setitem__:和__getitem__的访问类似,是设置值的方法
  • __missing__:字典使用__getitem__()调用的时候,key不存在执行该方法
class Item:

    def __init__(self,name,**kwargs):
        self.name = name
        self._spec = kwargs # 字典
        self.data_d = {self.name:self._spec}

    def __repr__(self):
        return "{}:{}".format(self.name,self._spec.values())

class Cart(Item):

    def __init__(self,name,**kwargs):
        super().__init__(name,**kwargs)
        self.iterms = []#进来就是空的购物车

    # 重写时设置长度方法,否则不能对实例求长度
    def __len__(self):
        return len(self.iterms)

    # 在购物车中添加商品,是否需要返回值,根据业务的实际情况设置
    def additem(self,item):
        self.iterms.append(item)

    #这个方法解决持续添加商品的问题
    # others可以不是实例,可以是任何类型的数据
    # 不过既然是购物车,要加入的东西就是商品的信息
    def __add__(self, other):
        if isinstance(other,Item) or isinstance(other,dict):
            self.iterms.append(other)
            return self

    def __getitem__(self, index):
        return self.iterms[index]

    def __setitem__(self, key, value):
        print(key,value)
        self.iterms[key] = value

    def __iter__(self):
        return iter(self.iterms)

    def __repr__(self):
        return str(self.iterms)

    def __missing__(self, key):
        # 特指dict或者setkey不存在的时候,生效
        return key


a = Cart("電子產品")
print("添加前:",len(a))
print("*********商品实例化************")
good_1 = Item("电脑",id = "001")
good_2 = Item("手机",id = "002")
good_3 = Item("食品",id = "003")
good_4 = Item("玩具",id = "004")
good_5 = Item("汽车",id = "005")
good_6 = Item("房产", id = "006")
#这里可以使用,自动调用的是__add__ 方法,因为返回的是self对象
#所以可以在后面直接添加方法,即:
# 等价于:cart.__add__(good_3).__add__(good_2)
print("************连续调用__add__添加商品**********")
print(a+good_3+good_5+good_4+good_1+good_2)
print("************调用additem单个添加商品**********")
a.additem(good_6)
print(a.iterms)
print("添加后:",len(a))
print("*************存在__iter__,可以对实例进行遍历************")
for x in a:
    print(x)

# 存在__getitem__和__setitem__,可以直接对实例进行索引赋值和取值
good_7 = Item("纸巾", id = "001")
print("修改前取值:",a[3])
a[3] = good_7
print("修改后取值:",a[3])

good_8 = Item("键盘",id = "009")
a + good_8.data_d
print("************确认missing是否生效********")
print(a[6]["火箭"])

**************************run_result********************
添加前: 0
************连续调用__add__添加商品**********
[食品:dict_values(['003']), 汽车:dict_values(['005']), 
玩具:dict_values(['004']), 电脑:dict_values(['001']), 
手机:dict_values(['002'])]
************调用additem单个添加商品**********
[食品:dict_values(['003']), 汽车:dict_values(['005']), 
玩具:dict_values(['004']), 电脑:dict_values(['001']),
 手机:dict_values(['002']), 房产:dict_values(['006'])]
添加后: 6
*************存在__iter__,可以对实例进行遍历************
食品:dict_values(['003'])
汽车:dict_values(['005'])
玩具:dict_values(['004'])
电脑:dict_values(['001'])
手机:dict_values(['002'])
房产:dict_values(['006'])
修改前取值: 电脑:dict_values(['001'])
3 纸巾:dict_values(['001'])
修改后取值: 纸巾:dict_values(['001'])
**********修改后的iterms**********
[食品:dict_values(['003']), 汽车:dict_values(['005']), 
玩具:dict_values(['004']), 纸巾:dict_values(['001']),
 手机:dict_values(['002']), 房产:dict_values(['006'])]
************确认missing是否生效********
没有报错,就生效了

可调用对象

  • python中一切皆对象,函数也不例外
  • 触发条件:对象像函数一样被调用的时候,触发
#函数
def foo():
    print(foo.__module__,foo.__name__)

foo()#  等价于下面的代码
foo.__call__()
  • 函数就是对象,对象foo加上(),就是调用对象的__call__()
  • 如果一个对象可调用,那么他一定具有魔术方法:__call__
  • __call__:类的第一个方法,实例就可以像函数一样调用
  • 可调用对象:定义一个类,并实例化得到其实例,将实例像函数一样调用
class  Point:

    def __init__(self,x,y):
        self.x = x
        self.y = y

    def __call__(self, *args, **kwargs):
        return "Point({0},{1})".format(self.x,self.y)

p = Point(4,5)
print(p) # p表示一个实例
print(p())#p() ,表示p.__call__()
print(p.__call__())

#类的封装
class  Adder:

    def __call__(self, *args, **kwargs):
        ret = 0
        print(*args)#  非=的数据,全部由args接收,放在一个迭代器中
        for x in args:#遍历相加
            ret +=x
        self.ret = ret#给adder添加ret的属性
        return  ret #  返回

adder = Adder()# 创建实例
print(adder(1,2,3,4,5,6,7,8,9,10))
print(adder.ret)
print(adder.__dict__)

定义一个斐波那契数列的类,方便调用,计算第n项的值

class Fib:

    def __init__(self):
        self.lst = [0,1,1]

    def __call__(self, x):

        if x <len(self.lst):
            return self.lst

        for i in range(2,x):
            self.lst.append((self.lst[i-1]+self.lst[i]))
        return self.lst
f = Fib()
print(f(60))
# 打印斐波那契数列中,第n项的值
class Fib:

    def __init__(self):
        self.lst = [0,1,1]

    def __call__(self, index):
        if index < 0:
            return IndexError("Error")

        if index <len(self.lst):
            return self.lst[index]

        for i in range(3,index+1):
            self.lst.append((self.lst[i-1]+self.lst[i-2]))
        return self.lst[index]

    def __iter__(self):
        return iter(self.lst)

    def __len__(self):
        return len(self.lst)

    def __str__(self):
        return str(self.lst)

f = Fib()
print(f(60),len(f))
for x in f:
    print(x)

上下文管理

  • 当一个对象同时实现了__enter__()__exit__()方法,他就属于上下文管理的对象
  • __enter__,进入对象相关的上下文
    • 如果存在该方法,with语法会将该方法返回值作为绑定到as子句中制定的变量上,返回对象本身,就可以调用类中的方法或者属性
  • __exit__,退出与此对象相关的上下文,返回值为正数或者True,可以不抛出异常(压制异常),返回值为0或者False,会抛出异常
  • __enter__方法没有其他参数
  • __exit__方法有三个参数,分别是:exc_type, exc_val, exc_tb,这三个参数都和异常有关,抛出异常的后会打印相关的信息,无异常都返回None
    • exc_type是异常的类型
    • exc_val是异常的值
    • exc_tb是异常的追踪信息
class Point:

    def __init__(self):
        print("初始化:int")

    def __enter__(self):
        print("打开文本:enter")

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("关闭:exit")

p = Point()
with p as f:
    #sys.exit() # 在这里6添加结束运行的语句之后,下面的就不执行了
    print("执行代码:do")
    print("比较:",p==f,p)
    # 因为是将enter的返回值给f,其返回值为None,所以f就是None
    print("result:",p is f,f)
 
************run_result***********
初始化:int
打开文本:enter
执行代码:do
比较: False <__main__.Point object at 0x102860d30>
result: False None
关闭:exit
  • 实例化对象的时候,并不会调用rnter,进入with语句块调用enter方法,然后执行语句体,最后离开with语句块的时候,调用exit方法
  • wirh可以开启一个上下文运行环境,在执行一些准备工作,执行后的一些收尾工作
  • 从上面的例子可以看出,enter和exit照样执行,因此上下文管理器是安全的
  • 极端的例子,直接调用sys.exit(),他就会退出当前解释器,python运行环境就自动退出

例题:使用魔术方法实现文件的上下文管理器

class MyFile(object):

    def __init__(self,file_name,open_method):
        self.name = file_name
        self.method = open_method

    def __enter__(self):
        self.f = open(self.name,self.method)
        return self.f

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()

with MyFile("user.txt","r") as f:
    content = f.read()
    print(content)
*********************run——result************
{"user":"python12","pwd":"python","token":True}

例题:为加法函数计时,要求分别使用上下文管理器和装饰器统计函数的执行时长

  • 简单的实现方式:
import time
import datetime
from functools import  wraps

def timeir(fn):
    @wraps(fn) #等价于wraps(fn)(wrapper)
    # 或者理解为a = wraps(fn); a(wrapper)
    def wrapper(*args,**kwargs):
        start = datetime.datetime.now()
        ret = fn(*args,**kwargs)
        delta = (datetime.datetime.now()-start).total_seconds()
        print("dec {} took {}s".format(fn.__name__,delta))
        return ret
    return wrapper

class TimeIt:

    def __init__(self,fn):
        self._fn = fn

    def  __enter__(self):
        print("enter")
        self.start = datetime.datetime.now()
        return self # 返回对象本身,在上下文管理中,可以调用类中的方法

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit")
        data = (datetime.datetime.now() - self.start).total_seconds()
        # 此方法是最后执行的一个方法,时间直接在这个方法中计算比较好
        print("context {} took {}s".format(self._fn.__name__,data))
        return True # 解决异常情况的抛出,如果为False,就会抛出

    def print_d(self):
        print("end")

#函数
@timeir
def add(x,y):
    time.sleep(5)
    return x+y

with TimeIt(add) as f:
    k = add(5,6)
    print("add`s result:",k)
    f.print_d()
    #  f接受TimeIt()在enter中的返回值,就相当于TimeIt()
  • 优化后的代码:将类改成可调用的类
import time
import datetime
from functools import  wraps

def timeir(fn):
    @wraps(fn)
    def wrapper(*args,**kwargs):
        start = datetime.datetime.now()
        ret = fn(*args,**kwargs)
        delta = (datetime.datetime.now()-start).total_seconds()
        print("dec {} took {}s".format(fn.__name__,delta))
        return ret
    return wrapper

class TimeIt:

    def __init__(self,fn):
        self._fn = fn

    def  __enter__(self):
        print("enter")
        self.start = datetime.datetime.now()
        print(self._fn,self)
        return self # 返回对象本身,在上下文管理中,可以调用类中的方法
        
	#添加可调用函数
    def __call__(self, *args, **kwargs):
        return self._fn(*args,**kwargs)

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit")
        data = (datetime.datetime.now() - self.start).total_seconds()
        # 此方法是最后执行的一个方法,时间直接在这个方法中计算比较好
        print("context {} took {}s".format(self._fn.__name__,data))
        return True # 解决异常情况的抛出,如果为False,就会抛出

    def print_d(self):
        print("end")

#函数
@timeir
def add(x,y):
    time.sleep(5)
    return x+y

with TimeIt(add) as f:
    print(f(5,6))
    k = add(5,6)
    print("add`s result:",k)
    f.print_d()
    print(f)
    #  f接受TimeIt()在enter中的返回值,就相当于TimeIt()
    # 可直接调用类中的方法

如何编写类装饰器

  • 将类改成类装饰器,将类改为装饰器之后,他只会调用__call__方法
import time
import datetime
from functools import  wraps

class TimeIt:

    def __init__(self,fn):
        self._fn = fn
        wraps(fn)(self) # 这里,其实是用wraps将类的属性全部添加到实例的__dict__中
        #这样就可以调用doc和name属性

    def __call__(self, *args, **kwargs):
        print("__call__")
        start = datetime.datetime.now()
        ret = self._fn(*args,**kwargs)
        data = (datetime.datetime.now()-start).total_seconds()
        print("dec {} took {}s".format(self._fn.__name__,data))
        return ret

#函数
@TimeIt  #等价于TimeIt(add)
def add(x,y):
    '''this is add def'''
    time.sleep(5)
    return x+y

print(add(5,6))
print("装饰器,装饰后的函数,实际上是实例:",add)
print("doc:{0},name:{1}".format(add.__doc__,add.__name__))
***********************run_result*************************
__call__
dec add took 5.013974s
11
装饰器,装饰后的函数,实际上是实例: <__main__.TimeIt object at 0x00000225947A3FD0>
doc:this is add def,name:add

上下文的应用场景

  • 1,增强功能,在代码执行的前后增加代码,以增强其功能,类似于装饰器
  • 2.资源管理,打开资源需要关闭,例如文件对象,网络连接,数据库连接等
  • 3.权限验证,在代码执行之前,做权限验证,在__enter__中做处理

contextlib.contextmanager

  • contextlib.contextmanager是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现__enter____exit__之后再
  • 对下面的函数有要求,必须要有yield,也就是这个函数必须返回一个生成器,而且只有yield一个值
  • 如果业务逻辑简单,可以使用函数加商时期的形式;如果业务逻辑复杂,用类的方式加__enter__()__exit__()比较方便
import contextlib
import datetime
import time

@contextlib.contextmanager
def add(x,y): # 为生成器增加了上下文管理
    start = datetime.datetime.now()
    try:
        yield x+y
        #yield的值只能有一个,作为__enter__()方法的返回值
    finally:
        delta = (datetime.datetime.now()-start).total_seconds()
        print("run time:{0}".format(delta))

with add(5,6) as a:
    time.sleep(3)
    print(a)
************run_result***********
11
run time:3.005129

functools.total_ordering装饰器

  • 实现类中的__lt__等比较方法全部写完比较麻烦,因此使用这个装饰器简化代码
from functools import  total_ordering

@total_ordering
class Order:
    
    def __init__(self,x):
        self.x = x

    def __lt__(self, other):
        return self.x < other.x # 比较方法至少实现一个

    def __eq__(self, other): # eq方法必须实现
        return self.x == other.x

O= Order(5)
o= Order(6)
print(O >= o)
print(O <= o)
print(O==o)

反射

  • 反射,reflection,指的是运行时获取类型定义的信息
  • 一个对象能够在运行时,像照镜子一样,反射出其类型的信息
  • 简单的说,就是在python中,能够通过一个对象分,找出其type、class、attribute或者method的能力,能够反射或者自省
  • 具有反射的函数有:type() 、is instance()、callable()、dir()、getattr()
  • 反射的内建函数,详见《python拓展学习——反射》

通过命令分发器,通过名称找对应的函数执行

class Dispatcher:
    pass

class Dispatcher:
    #命令和函数存储的地方
    # 解决函数注册的问题,直接将函数名和对应的函数写入字典
    def run_def(self,cmd,fn):
        if isinstance(cmd,str):
            setattr(self.__class__,cmd,fn)
        else:
            print("error")

    #缺省函数,不知道的函数,返回不知道
    def default_def(self):
        print("Unknown comnmand")


    #调度函数
    def dispatcher(self):
        while True:
            cmd = input(">>")
            if cmd.strip() == "quit":
                return
            # print(command,cmd)
            getattr(self,cmd.strip(),self.default_def)()

            #self.command.get(cmd,self.default_def)()
            #command是一个字典,cmd存在,就去执行fn,cmd不存在,执行默认函数default_def
            #command是一个字典,使用get获取cmd对应的value,value是一个函数对象

d = Dispatcher()
d.run_def("yiyi",lambda self:print("my name is yiyi"))
d.run_def("age",lambda  self:print("i am 18 years old"))
d.run_def(100,lambda  self:print("i am 18 years old"))
d.dispatcher()
*************run_result************
error
>>yiyi
my name is yiyi
>>age
i am 18 years old
>>prror
Unknown comnmand
>>quit

反射相关的魔术方法

  • __getarrt__()方法触发条件:访问类中的属性的时候,如果属性不存在,就会触发该方法
    • 访问属性触发语句:print(实例.属性)
  • 如果属性不存在,又想访问值,就将返回的值放在__getarrt__()方法的return语句中
  • 一个类的属性首先会按照继承查找,如果找不到,就会执行__getarrt__()方法,如果没有这个方法,就会抛出AttributeEooror的异常,表示找不到该属性
  • 即顺序为:实例的dict、类的dict、祖先类的dict(直到object),如果都没有,调用getattr()
  • 实例通过.设置属性,如同self.x = x,就会调用__setattr__(),属性要加到实例的__dict__中,就需要自己完成
    • __setattr__()方法,可以拦截对实例属性的增加,修改操作,如果要设置生效,需要自己操作实例的__dict__
      • 这个方法是在给对象设置属性的时候触发,设置属性的方法:对象/实例.属性=新值
  • __delattr__() 可以阻止通过实例删除属性的操作,但是通过类可以删除属性;通过属性删除,执行__delattr__魔术方法下的代码
      • 这个方法是在删除属性的时候触发,删除属性的语句:del 实例.属性名
  • 实例的所有属性访问,第一个都会调用__getattribute__(),他阻止了属性的查找
    • 该方法返回计算后的值或者抛出一个AttributeError的异常分
    • 他的return值将作为属性查找的结果,抛出AttributeError后,会调用__getarrt__()方法,表示属性没有找到
    • __getattribute__()方法中为了避免在该方法中无限的递归,他的实现应该永远调用父类的同名方法以访问需要的任何属性
    • 注意,除非明确知道__getattribute__()方法用来做什么,否则不要用它
    • 在查找属性的时候,第一时间触发该方法去查找属性
class Base:
    n=0

class Point(Base):
    z = 6
    def __init__(self,x,y):
        self.x = x
        self.y = y

    def show(self):
        print(self.x,self.y)

    def __setattr__(self, key, value):
        print(key,value)
        self.__dict__[key] = value # 自己操作实例的字典赋值

    def __getattr__(self, item):
        # 按照实例属性,类属性,祖先类的属性和getattr方法找寻函数
        # 如果没有找到,执行这个方法下的方法
        print("__getattr__:",item)
        return "missing :{0}".format(item)

    def __delattr__(self, item):
        print("__delattr__")

    def __getattribute__(self, item):
        print("__getattribute__:",item)
        # return self.__dict__[item] # 进入递归调用的死死循环,不能这样做
        #raise AttributeError("Not Found") # 不行,执行下一步赋值的时候,报错
        return object.__getattribute__(self,item)
    
    
p = Point(4,5)
print("赋值前实例的字典:",p.__dict__)
p.a = 100
p.y = 200
print("赋值后实例的字典:",p.__dict__)
# 查找顺序:实例的初始化,类的属性,父类的属性
print("子类初始化:",p.x,p.y)
print("子类属性:",p.z)
print("父类属性:",p.n)
print("----------无属性,执行获取的魔术方法---------")
print("无属性:",p.d)
print("---------删除魔术方法生效----------")
del p.x
print("删除实例属性后的字典:",p.__dict__)

************没有__getattribute__之前的run_result**********
x 4
y 5
赋值前实例的字典: {'x': 4, 'y': 5}
a 100
y 200
赋值后实例的字典: {'x': 4, 'y': 200, 'a': 100}
子类初始化: 4 200
子类属性: 6
父类属性: 0
----------无属性,执行获取的魔术方法---------
__getattr__: d
无属性: missing :d
---------删除魔术方法生效----------
__delattr__
删除实例属性后的字典: {'x': 4, 'y': 200, 'a': 100}

************__getattribute__之后的run_result**********
x 4
__getattribute__: __dict__
y 5
__getattribute__: __dict__
__getattribute__: __dict__
赋值前实例的字典: {'x': 4, 'y': 5}
a 100
__getattribute__: __dict__
y 200
__getattribute__: __dict__
__getattribute__: __dict__
赋值后实例的字典: {'x': 4, 'y': 200, 'a': 100}
__getattribute__: x
__getattribute__: y
子类初始化: 4 200
__getattribute__: z
子类属性: 6
__getattribute__: n
父类属性: 0
----------无属性,执行获取的魔术方法---------
__getattribute__: d
__getattr__: d
无属性: missing :d
---------删除魔术方法生效----------
__delattr__
__getattribute__: __dict__
删除实例属性后的字典: {'x': 4, 'y': 200, 'a': 100}

描述器

  • 只要以下三个方法中的一个的使用了,这个类就是一个描述器
  • 描述器用到了三个魔术方法:__get__()__set__()__delete__()
  • __get__(self, instance, owner)__set__(self, instance, value)__delete__(self, instance)
    • self 指的是实例本身,调用者
    • instance是owner的实例
    • owner是属性所属的类
    • 当通过类B的一个属性a去访问,刚好这个属性a是另外一个类 A的实例,如果类A中存在描述器的三方法之一,就会自动执行描述起方法
    • 也就是说通过类属性去访问存在描述器的类的实例,就会触发描述器方法,如果通过实例去访问,就不会触发
  • 如果一个类中仅仅存在__get__(),就是非数据描述器
  • 如果同时实现了__get__()__set__()或者__delete__()中的一个,就是数据描述器
  • 如果一个类的类属性设置为描述器,那么他就是owner的属主
class A :

    def __init__(self):
        print("A init")
        self.a = "a1"
    def __get__(self, instance, owner):
        print("A实例:{},instance参数:{},谁调用:{}".format(self,instance,owner))


class B :

    x = A() # x存储的是一个描述器对象
    # 描述器会肤隔离类属性的相关操作
    # 直接触发的是描述器中的get、set和delete方法

    def __init__(self):
        print("B init")
        self.x = 100


print("B类中x的属性,就是A():",B.x)
# 在没有__get__方法的情况下,下面这句代码可执行,返回结果为A初始化函数中a的值
# print("B类中x的属性,就是A():",B.x.a) 
# 存在描述起,B.x的返回值就是None,没有a属性

# B类没有实例化,所以在此B中初始化函数没有执行,
# 因此没有打印B初始化函数中的字符串
# 这里的结果是None,因为初始化函数的返回值就是None
b = B()
print("B实例中x的属性,为init中的数据",b.x)

**********run——result*************
A init
A实例:<__main__.A object at 0x1044fba30>,instance参数:None,
谁调用:<class '__main__.B'>

B类中x的属性,就是A()None
B init
B实例中x的属性,为init中的数据 100
  • 【解析】
    • 类加载的时候,类变量需要先生成,而类B的x属性就是类A得实例
    • 因此,类A先初始化,打印A init,自动调用__get__方法
    • B 中x的实例为None,是因为初始化返回值为None
    • 然后执行到打印B init,实例并初始化B的实例b
    • 在没有__get__魔术方法的情况下,b.x会查找类下的x属性,指向A的实例,可调用b.x.a
    • 定义了__get__的魔术方法,类A就是一个描述起,对类B或者类B的实例的x属性读取,成为了A的实例的访问,就会调用__get__方法
  • B类的初始化代码变形,没有x属性:
class A :

    def __init__(self):
        print("A init")
        self.a = "a1"
    def __get__(self, instance, owner):
        print("A实例:{},instance参数:{},谁调用:{}".format(self,instance,owner))


class B :

    x = A()


    def __init__(self):
        print("B init")
        # B实例的初始化中没有x,就调用类属性的x
        # 也就是A(),这样A又重新执行初始化函数


print("B类中x的属性,就是A():",B.x)
b = B()
print("B实例中x的属性,为init中的数据",b.x)
*************run_result***********
A init
A实例:<__main__.A object at 0x10510b9a0>,
instance参数:None,谁调用:<class '__main__.B'>

B类中x的属性,就是A()None
B init
A实例:<__main__.A object at 0x10510b9a0>,
instance参数:<__main__.B object at 0x10510b8e0>,谁调用:<class '__main__.B'>
B实例中x的属性,为init中的数据 None
  • 【解析】
    • 类加载的时候,类变量需要先生成,而类B的x属性就是类A得实例
    • 因此,类A先初始化,打印A init,自动调用__get__方法
    • 打印信息中,第一个参数是A的实例,第三个参数是是x属性所属的类,第三个参数是x属性所属类的实例,因为调用方式为B.x,B没有实例化,因此为None
    • 调用__get__方法后,其没有返回值,因此返回值为None,因此B类的属性A()的返回值就是None,故无法调用A类中的a属性
    • 然后实例化B,调用B类的初始化函数,打印 init B
    • b实例调用x,实例中没有x属性,就调用B的类属性x,即A()
    • 因此A又重新实例化,打印A init,自动执行__get__,打印相关信息,
    • 信息中instance表示x属性所属类的实例,也就是B类的实例,因此调用方式为B().x

解决拿不到A类初始化中的属性

  • 在get中,将self直接返回即可
class A :

    def __init__(self):
        print("A init")
        self.a = "a1"
        
    def __get__(self, instance, owner):
        return self

class B :

    x = A()

    def __init__(self):
        print("B init")

print("B类中x的a属性,就是A():",B.x.a)

b = B()
print("B实例中x的属性,为init中的数据",b.x)
***********run_rerult*******
A init
B类中x的a属性,就是A(): a1

B init
B实例中x的属性,为init中的数据 <__main__.A object at 0x10474f970>
  • 如果直接将A实例写在B类的初始化函数中,调用的时候就不会触发描述器的机制,初始化函数会正常执行,但是不会执行__get__
class A :

    def __init__(self):
        print("A init")
        self.a = "a1"
    def __get__(self, instance, owner):
        print("A实例:{},instance参数:{},谁调用:{}".format(self,instance,owner))
        return self

class B :

    x = A()

    def __init__(self):
        print("B init")
        self.x = A() # 实例属性指向A的实例

print("B类中x的a属性,就是A():",B.x.a)

b = B()
print("B实例中x的属性,为init中的数据",b.x)
************run_result*************
A init
B类中x的a属性,就是A(): a1
B init

A init ——>B类中调用A的实例,A执行初始化函数,但是没有执行描述器方法
B实例中x的属性,为init中的数据 <__main__.A object at 0x104537910>

__set____delete__

  • 一个类B的类属性(x),是一个数据描述器(A的实例,存在get和set/delete的魔术方法),其对相同名字实例(self.x/B() .x)的操作,相当于在操作类属性(B.x)
class A :

    def __init__(self):
        print("A init")
        self.a = "a1"
    def __get__(self, instance, owner):
        print("A实例:{},instance参数:{},谁调用:{}".format(self,instance,owner))
        return self
    def __set__(self, instance, value):
        print("__set__的self:{},instance:{}" .format(self,instance))
        print("__set__的value信息,接收B实例中同名函数的值:",value)

class B :

    x = A()

    def __init__(self):
        print("B init")
        self.x = 100 # 实例属性指向A的实例

b = B()#B实例化后,首先自动执行类属性x,即B.x ;
#B实例化,自动执行B的初始化函数;然后自动执行set函数
print("B实例中x的属性,为init中的数据",b.x) 
# 实例中的x和属性同名,调用的是属性中的x,因此返回A的实例
*************************run_result*************************
A init
B init
__set__的self:<__main__.A object at 0x0000026CC7614FD0>,
instance:<__main__.B object at 0x0000026CC7614E80>
__set__的value信息,接收B实例中同名函数的值: 100
A实例:<__main__.A object at 0x0000026CC7614FD0>,instance参
数:<__main__.B object at 0x0000026CC7614E80>,谁调用:<class
 '__main__.B'>
B实例中x的属性,为init中的数据
 <__main__.A object at 0x0000026CC7614FD0>

多态

  • 严格来说,python是没有多态的,但是python可以实现伪多态
  • 多态建立在继承的基础上,一个类有多个形态,换一个方法说:一个父类会有多个子类

实现多态的步骤

  • 定义一个父类(Base),实现某个方法,例如:run
  • 定义多个子类,在子类中重写父类的方法(run),每个子类run方法实现不同的功能
  • 假设我们定义了一个函数,需要一个base类型的对象的参数,那么调用函数的时候,传入Base类不同的子类对象,那么每个函数都会执行不同的功能,这就是多态的体现
  • 多态的好处是:
    • 当我们要传入子类的对象时,我们只要接收Bases的类型即可,因为他们都是Bases类型
    • 然后按照Bases的类型操作,由于父类中存在count方法,因此传入任何的类对象,只要是Bases及其子类,就会自动调用实际的count方法,这就实现了伪多态
class Bases:

    def __init__(self,a,b):
        self.a = a
        self.b = b

    def count(self):
        print("Bases:这是一个加法运算")
        return "a+b={0}".format(self.a+self.b)

class SonOne(Bases):

    def __init__(sel,a,b):
        super().__init__(a,b)

    def count(self):
        print("SonOne:这是一个减法运算")
        return"a-b={0}".format(self.a - self.b)


class SonTwo(Bases):

    def __init__(self, a, b):
        super().__init__(a, b)

    def count(self):
        print("SonTwo:这是一个乘法运算")
        return "a*b={0}".format(self.a*self.b)

class SonThree(Bases):
    pass


b_obj = Bases(4,5)
s1_ogj = SonOne(4,5)
s2_ogj = SonTwo(4,5)
s3_ogj = SonThree(4,5)

# 传入的参数必须是类的对象
def func(base_obj):
    res = base_obj.count()
    return res


print(func(b_obj))
print(func(s1_ogj))
print(func(s2_ogj))
print(func(s3_ogj))

*******************run_result*********************
Bases:这是一个加法运算
a+b=9
SonOne:这是一个减法运算
a-b=-1
SonTwo:这是一个乘法运算
a*b=20
Bases:这是一个加法运算
a+b=9
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python中的类继承和多是面向对象编程的重要概念。 类继承是指一个类可以继承另一个类的属性和方法。被继承的类称为父类或基类,继承父类的类称为子类或派生类。子类可以继承父类的属性和方法,并且可以添加自己的属性和方法。通过继承,子类可以重用父类的代码,实现代码的复用和扩展。 在Python中,可以使用以下语法实现类的继承: ```python class ParentClass: pass class ChildClass(ParentClass): pass ``` 在上面的示例中,`ChildClass` 继承了 `ParentClass`,它可以访问和使用 `ParentClass` 中定义的属性和方法。 多是指同一个方法名可以在不同的对象上产生不同的行为。在面向对象编程中,多通过方法重写和方法重载来实现。 方法重写是指子类定义一个与父类同名的方法,从而覆盖父类中的方法。当调用该方法时,会执行子类中的方法而不是父类中的方法。 ```python class ParentClass: def greet(self): print("Hello, I am the parent.") class ChildClass(ParentClass): def greet(self): print("Hello, I am the child.") parent = ParentClass() child = ChildClass() parent.greet() # 输出:Hello, I am the parent. child.greet() # 输出:Hello, I am the child. ``` 在上面的示例中,`ChildClass` 重写了 `ParentClass` 中的 `greet` 方法,所以在子类对象上调用 `greet` 方法时会执行子类中的方法方法重载是指一个类中可以定义多个同名的方法,但这些方法的参数类型或个数不同。根据不同的参数类型或个数,会自动选择合适的方法进行调用。 ```python class Calculator: def add(self, a, b): return a + b def add(self, a, b, c): return a + b + c calculator = Calculator() print(calculator.add(1, 2)) # 输出:3 print(calculator.add(1, 2, 3)) # 输出:6 ``` 在上面的示例中,`Calculator` 类中定义了两个同名的 `add` 方法,但参数个数不同。根据传入的参数个数,会自动选择合适的方法进行调用。 这就是Python中类继承和多的基本概念和用法。它们可以让我们更好地组织和重用代码,提高代码的可维护性和扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值