一、了解super()

  • 在继承问题之前先来了解下:super是什么?(后面继承代码会用到)
    • 严格来说:super () 不是方法,它是一个!是用来创建 代理对象 的内置类!
    • super() 是 Python 内置的 类(class),调用 super() 其实是在 创建一个 super 对象
      # 来看下这个是什么意思?
      super().__init__()
      
      # 1.super() 创建一个代理对象
      # 2.这个对象自动帮你找到父类
      # 3.然后调用父类的 __init__ 方法
      

二、发现与定位问题

接下来进入正题,看下面代码为什么会报错?

# 爷爷类:基础用户(所有用户都有的属性)
class User:
    def __init__(self, username, password):
        self.username = username
        self.password = password

# 父类1:会员用户
class MemberUser(User):
    def __init__(self, username, password, vip_level):
        # 注意这里的 super() 会自动按 MRO 顺序调用
        super().__init__(username, password)
        self.vip_level = vip_level

# 父类2:管理员用户
class AdminUser(User):
    def __init__(self, username, password, role):
        # 注意这里的 super() 会自动按 MRO 顺序调用
        super().__init__(username, password)
        self.role = role

# 子类:超级用户(多继承 → 既是会员,又是管理员)
class SuperUser(MemberUser, AdminUser):
    def __init__(self, username, password, vip_level, role):
        # 手动初始化两个父类
        MemberUser.__init__(self, username, password, vip_level)
        AdminUser.__init__(self, username, password, role)

super_user = SuperUser('test', '123456', '3', 'test_user')

报错信息:TypeError: AdminUser.init() missing 1 required positional argument: ‘role’。 AdminUser类 为什么会缺少一个位置参数呢?

  • 第一步:我们从头开始捋
    • 代码从实例化 SuperUser 子类开始
    • SuperUser 又是多继承
    • 那么我们来看 SuperUser 子类的mro顺序,也就是继承的顺序,也就是 super() 代理对象 的顺序,这个很关键!
    # 以下两种打印方式都可以
    # print(SuperUser.__mro__)  # 这种打印出来是元组
    print(SuperUser.mro())  # 这种打印出来是列表,这里用这个了
    # 打印结果:
    # [<class '__main__.SuperUser'>, <class '__main__.MemberUser'>, <class '__main__.AdminUser'>, <class '__main__.User'>, <class 'object'>]
    # 这里继承的顺序很关键:SuperUser -- MemberUser -- AdminUser -- User -- object
    
  • 第二步:我们知道了继承的顺序,先是 SuperUser 本身,这里没什么问题,然后是MemberUser ,再然后是 AdminUser … 此时我们来看下 MemberUser 中的代码!
    # 父类1:会员用户
    class MemberUser(User):
        def __init__(self, username, password, vip_level):
            # 注意这里的 super() 会自动按 MRO 顺序调用
            super().__init__(username, password)
            self.vip_level = vip_level
            
    # mro继承顺序很关键:SuperUser -- MemberUser -- AdminUser -- User -- object
    
    • 问题关键:
      • 来看下上面代码的super()
      • 我们的本意是要让他调用 User 爷爷类的初始化方法
      • 但是现在按照mro顺序实际找的是 MemberUser 下一个类,也就是 AdminUser
      • 相当于这里的 super() 调用的是 AdminUser 类中的 初始化方法
      • AdminUser 中的__init__需要三个参数,但是 MemberUser 中 super() 调用的时候传了两个,所以缺少了一个参数,所以报错了
        # 父类2:管理员用户
        class AdminUser(User):
            def __init__(self, username, password, role):
                # 注意这里的 super() 会自动按 MRO 顺序调用
                super().__init__(username, password)
                self.role = role
        

三、解决方式之硬编码方式(不推荐❌)

  • 直接硬编码父类名
    • 通过父类名方式调用父类方法,让 MemberUser 和 AdminUser 中的 super() 去找 User
  • 缺点
    • 不统一使用 super ()(断裂 MRO 链),硬编码类名会破坏 MRO 链条,不会沿着统一路线流转
  • 解决后的完整代码
    # 爷爷类:基础用户(所有用户都有的属性)
    class User:
        def __init__(self, username, password):
            self.username = username
            self.password = password
    
    # 父类1:会员用户
    class MemberUser(User):
        def __init__(self, username, password, vip_level):
            # 注意这里的 super() 会自动按 MRO 顺序调用
            # super().__init__(username, password)
            # 将这里的 super() 改为 User, 注意改成这种方式后,要传递self
            User.__init__(self, username, password)
            self.vip_level = vip_level
            
    # 父类2:管理员用户
    class AdminUser(User):
        def __init__(self, username, password, role):
            # 注意这里的 super() 会自动按 MRO 顺序调用
            # super().__init__(username, password)
            # 将这里的 super() 改为 User, 注意改成这种方式后,要传递self
            User.__init__(self, username, password)
            self.role = role
    
    # 子类:超级用户(多继承 → 既是会员,又是管理员)
    class SuperUser(MemberUser, AdminUser):
        def __init__(self, username, password, vip_level, role):
            # 手动初始化两个父类
            MemberUser.__init__(self, username, password, vip_level)
            AdminUser.__init__(self, username, password, role)
    
    super_user = SuperUser('test', '123456', '3', 'test_user')
    

四、解决方式之整条继承链全部统一使用super(推荐✅)

  • 多继承 super() 的参数传递灾难
    • 参数统一使用 关键字参数 **kwargs 接收、转发
    # 爷爷类:基础用户(所有用户都有的属性)
    class User:
        def __init__(self, username, password, **kwargs):
            super().__init__(**kwargs)  # 注意这里的 super() 会自动按 MRO 顺序调用 User -> object  这里的 kwargs 必须是 {}
            self.username = username
            self.password = password
    
    # 父类1:会员用户
    class MemberUser(User):
        def __init__(self, vip_level, **kwargs):
            # 注意这里的 super() 会自动按 MRO 顺序调用  MemberUser -> AdminUser -> User -> object
            super().__init__(**kwargs)
            self.vip_level = vip_level
    
    # 父类2:管理员用户
    class AdminUser(User):
        def __init__(self, role, **kwargs):
            # 注意这里的 super() 会自动按 MRO 顺序调用  # AdminUser -> User -> object
            super().__init__(**kwargs)
            self.role = role
    
    # 子类:超级用户(多继承 → 既是会员,又是管理员)
    class SuperUser(MemberUser, AdminUser):
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
    
    # 打印 MRO 顺序
    print(SuperUser.mro())  # [<class '__main__.SuperUser'>, <class '__main__.MemberUser'>, <class '__main__.AdminUser'>, <class '__main__.User'>, <class 'object'>]
    
    # 实例化
    super_user = SuperUser(username = 'test', password = '123456', vip_level = '3', role ='test_user')
    # 打印属性
    print(super_user.username)
    print(super_user.password)
    print(super_user.vip_level)
    print(super_user.role)
    

五、统一使用super()传参详解

  • 前置知识之实现每层函数只接收自己用的,剩余的通过不定长关键字传递下去
    • 认识关键字不定长参数
      def fun(**kwargs):  # 定义函数的时候是打包,调用函数的时候是拆包
          print(kwargs)    # {a=10, b=20, c=30}
      if __name__ == '__main__':
          fun(a=10, b=20, c=30)
      
    • 位置参数+关键字不定长参数
      # 注意这里的位置参数b  可以用位置参数来传,也可以使用关键字传
      def fun_b(b, **kwargs):
          print(kwargs)   # {a=10, c=30}  注意:这里b 关键字传参将b抽取出来了,剩下了 a, c
      
      def fun(**kwargs):  # 定义函数的时候是打包,调用函数的时候是拆包
          print(kwargs)    # {a=10, b=20, c=30}
          fun_b(**kwargs)  # fun_b(a=10, b=20, c=30)
      if __name__ == '__main__':
          fun(a=10, b=20, c=30)
      
    • 每个函数接收自己的参数,把剩余参数传递下去
      # demo 3
      def fun_a(a, **kwargs):
          print(kwargs)   # {}
      
      def fun_c(c, **kwargs):
          print(kwargs)  # {a=10}
          fun_a(**kwargs)  # fun_a(a=10)
      
      def fun_b(b, **kwargs):
          print(kwargs)   # {a=10, c=30}
          fun_c(**kwargs)  # fun_c(a=10, c=30)
      
      def fun(**kwargs):  # 定义函数的时候是打包,调用函数的时候是拆包
          print(kwargs)  # {a=10, b=20, c=30}
          fun_b(**kwargs)  # fun_b(a=10, b=20, c=30)
      
      if __name__ == '__main__':
          fun(a=10, b=20, c=30)
      
  • 整体理解
    	# 祖先类
    	class A:
    	    def __init__(self, a, **kwargs):
        		"""
        		祖先类中的super()按理说也可以不写,但是在多继承 + kwargs 流转标准范式中有规范:
    			  1.所有类的__init__内部,统一写上 super().__init__(** kwargs),不要中途省略。
    			  2.保证 MRO 链路闭环,统一标准,避免后续扩展踩坑。
    			  3.只有最顶层祖先 A 执行完 super 之后,kwargs 必须清空,防止传给 object 时报错。
    			"""
    	        super().__init__(**kwargs)   # 重点:object.__init__ 不接受任何关键字参数!   object kwargs 是个空对象 {}
    	        print(f"A a={a}")
    	
    	class B(A):
    	    def __init__(self, b, **kwargs):
    	        super().__init__(**kwargs)   # super()灵活的  代理对象可变   A.__init__(**kwargs)硬编码
    	        print(f"B b={b}")
    	
    	class C(A):
    	    def __init__(self, c, **kwargs):
    	        super().__init__(**kwargs)
    	        print(f"C c={c}")
    	
    	# 多继承
    	class D(B, C):
    	    def __init__(self, **kwargs):
    	        super().__init__(**kwargs)
    	        print("D done")
    	
    	# 调用时全部使用关键字传参
    	d = D(a=10, b=20, c=30)
    	
    	# 两种打印方式:
    	print(D.mro())   # 多继承顺序  __mro__ 列表  [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
    	# print(D.__mro__)  # 多继承顺序  __mro__ 元组 (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
    	
    	"""
    	问题:
    	按照MRO 规则,为什么不写成D(B, C, A)?
    	    1.运行会直接报错:TypeError: Cannot create a consistent method resolution order (MRO) for bases B, C, A
    	    2.定义多继承时,只需要写当前类的直接父类,祖先类会通过父类自身的继承关系自动纳入 MRO 链条,不用手动逐层往上补全所有祖先
    	"""
    	```
    
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐