Bootstrap

编程新手必看,Python3中继承知识点及语法学习总结(28)

1、继承

在Python 3中,继承是面向对象编程的一个核心概念,它允许我们创建一个新的类(称为子类或派生类),该类继承另一个类(称为父类或基类)的属性和方法。通过这种方式,子类可以重用父类的代码,同时添加或覆盖自己的特定功能。

下面是一个简单的Python 3继承示例:

# 定义一个父类  
class Animal:  
    def __init__(self, name):  
        self.name = name  
      
    def speak(self):  
        print(f"{self.name} makes a sound")  
  
# 定义一个子类,继承自Animal类  
class Dog(Animal):  
    def __init__(self, name, breed):  
        # 调用父类的构造函数  
        super().__init__(name)  
        self.breed = breed  
      
    def speak(self):  
        # 覆盖父类的speak方法  
        print(f"{self.name} barks")  

# 创建一个Dog对象  
my_dog = Dog("Buddy", "Labrador")  
# 调用继承自父类的方法  
print(my_dog.name)  # 输出: Buddy  
# 调用子类覆盖的方法  
my_dog.speak()  # 输出: Buddy barks

在这个例子中,我们定义了一个Animal类作为父类,它有一个name属性和一个speak方法。然后,我们定义了一个Dog类作为子类,它继承了Animal类。在Dog类的构造函数中,我们使用super().init(name)来调用父类的构造函数,并初始化name属性。然后,我们添加了一个breed属性,并覆盖了speak方法,使其更适合狗的行为。
通过继承,我们可以轻松地在子类中重用父类的代码,并在需要时添加或修改功能。这使得代码更加模块化和可维护。

2、继承的优缺点

继承是面向对象编程(OOP)的一个核心特性,它允许我们创建基于现有类的新类,从而实现代码的重用和扩展。然而,就像其他编程技术一样,继承也有其优点和缺点。
继承的优点:
代码重用:继承允许子类直接使用父类的属性和方法,从而避免了重复编写相同的代码。这有助于减少代码的冗余和提高代码的可维护性。
扩展性:子类可以在继承父类的基础上添加新的属性和方法,从而扩展父类的功能。这使得我们能够构建更加复杂和灵活的软件系统。
多态性:通过继承,我们可以实现多态性,即使用父类类型的引用指向子类对象,并调用子类实现的方法。这增强了代码的灵活性和可扩展性。
易于理解:继承关系通常反映了现实世界中的“is-a”关系,例如“狗是动物”这样的关系。这种关系使得代码更加直观和易于理解。
继承的缺点:
破坏封装性:继承可能会破坏封装性,因为子类可以访问和修改父类的内部状态。这可能导致父类内部的实现细节被暴露给子类,增加了代码的耦合度。
层次结构僵化:过度使用继承可能导致类的层次结构变得僵化。一旦一个类被设计为继承自另一个类,就很难改变这种关系。这可能会限制代码的灵活性和可维护性。
继承爆炸:当类层次结构过于复杂时,可能会导致继承爆炸的问题。这意味着一个子类可能继承了许多不必要的属性和方法,使得代码难以管理和维护。
多重继承的复杂性:虽然Python支持多重继承,但多重继承可能导致复杂的类关系,使得代码难以理解和调试。在处理多重继承时,需要特别小心以避免出现冲突和不可预测的行为。
为了克服继承的缺点,可以考虑使用其他面向对象编程技术,如组合(composition)、接口(interface)和抽象类(abstract class)等。这些技术可以在一定程度上替代继承,提供更加灵活和可维护的代码结构。在实际编程中,应根据具体情况权衡利弊,选择合适的面向对象编程技术。

3、多继承

在Python中,多继承允许一个类继承自多个父类,从而可以获取多个父类的属性和方法。这提供了更大的灵活性和代码重用,但也可能导致一些复杂性和潜在的问题,比如方法解析顺序(Method Resolution Order,MRO)和潜在的命名冲突。
下面是一个Python中多继承的简单示例:

# 定义两个父类  
class ParentA:  
    def feature_a(self):  
        print("Feature from ParentA")  
  
class ParentB:  
    def feature_b(self):  
        print("Feature from ParentB")  
  
# 定义一个子类,继承自ParentA和ParentB  
class Child(ParentA, ParentB):  
    def child_feature(self):  
        print("Feature from Child")  
# 创建一个Child对象  
my_child = Child()  
# 调用从ParentA继承的方法  
my_child.feature_a()  # 输出: Feature from ParentA  
# 调用从ParentB继承的方法  
my_child.feature_b()  # 输出: Feature from ParentB  
# 调用子类自己的方法  
my_child.child_feature()  # 输出: Feature from Child

在这个例子中,Child类继承自ParentA和ParentB。因此,Child类的对象可以访问ParentA和ParentB中的方法,以及它自己的方法。
然而,当子类需要覆盖父类中的方法时,或者在多个父类中存在相同名称的方法时,就可能出现一些问题。Python使用一种称为方法解析顺序(MRO)的算法来确定在调用方法时应该使用哪个父类中的版本。MRO是动态计算的,通常基于类的继承层次结构来决定。
此外,命名冲突也是多继承中需要注意的问题。如果两个父类中有相同名称的属性或方法,并且子类没有覆盖它们,那么子类将继承第一个父类中的版本(根据MRO的顺序)。如果需要从另一个父类访问该方法,可以通过调用super()或者使用类名直接访问。
为了避免多继承带来的复杂性,通常建议:
尽可能使用单继承,只在必要时使用多继承。
如果使用多继承,确保对MRO有深入的理解,并仔细考虑类之间的关系。
使用接口或抽象基类来定义类的行为,而不是直接继承多个实现类。
当需要覆盖父类方法时,确保明确指定要覆盖哪个父类的方法,并考虑使用super()来调用父类的方法。

4、Python 子类继承父类构造函数说明

如果在子类中需要父类的构造方法就需要显式地调用父类的构造方法,或者不重写父类的构造方法。
子类不重写 init,实例化子类时,会自动调用父类定义的 init

class Father(object):
    def __init__(self, name):
        self.name=name
        print ( "name: %s" %( self.name) )
    def getName(self):
        return 'Father ' + self.name
 
class Son(Father):
    def getName(self):
        return 'Son '+self.name
 
if __name__=='__main__':
    son=Son('python')
    print ( son.getName() )

输出结果为:

name: python
Son python

如果重写了__init__ 时,实例化子类,就不会调用父类已经定义的 init,语法格式如下:

class Father(object):
    def __init__(self, name):
        self.name=name
        print ( "name: %s" %( self.name) )
    def getName(self):
        return 'Father ' + self.name
 
class Son(Father):
    def __init__(self, name):
        print ( "hi" )
        self.name =  name
    def getName(self):
        return 'Son '+self.name
 
if __name__=='__main__':
    son=Son('python')
    print ( son.getName() )

运行结果

hi
Son Python

如果重写了__init__ 时,要继承父类的构造方法,可以使用 super 关键字:

super(子类,self).__init__(参数1,参数2....)

还有一种经典写法:

父类名称.__init__(self,参数1,参数2...)
class Father(object):
    def __init__(self, name):
        self.name=name
        print ( "name: %s" %( self.name))
    def getName(self):
        return 'Father ' + self.name
 
class Son(Father):
    def __init__(self, name):
        super(Son, self).__init__(name)
        print ("hi")
        self.name =  name
    def getName(self):
        return 'Son '+self.name
 
if __name__=='__main__':
    son=Son('Python')
    print ( son.getName() )

输出结果为:

name: Python
hi
Son Python

4、多个继承和单继承之间区别、

多个继承和单继承之间的主要区别在于它们如何处理类之间的层次关系和代码重用。下面是它们之间的一些主要区别:
单继承
定义:单继承是指一个子类只能继承自一个父类。
优点:
简单直观:类的层次结构清晰明了,易于理解。
较少的冲突:由于子类只继承自一个父类,所以发生方法名冲突的可能性较低。
更易于管理:代码结构相对简单,维护起来更为容易。
缺点:
代码重用限制:当多个类有相似的功能时,每个类都需要单独实现这些功能,无法直接共享代码。
多个继承
定义:多个继承允许一个子类继承自多个父类。
优点:
代码重用增强:子类可以继承多个父类的属性和方法,从而减少了重复代码的需要。
灵活性提高:子类可以组合多个父类的功能,创建出更复杂的类。
缺点:
复杂性增加:类的层次结构变得更加复杂,难以理解和管理。
方法名冲突:当多个父类中存在相同名称的方法时,子类必须明确决定使用哪个父类的方法,或者覆盖这些方法。

钻石问题(Diamond Problem):在某些情况下,当多个父类继承自同一个更远的祖先类,并且这些父类都覆盖了祖先类中的某个方法时,子类可能会遇到选择哪个父类版本的问题。虽然Python通过方法解析顺序(MRO)解决了这个问题,但它仍然增加了理解和维护的复杂性。
总结
单继承和多继承各有其优缺点,选择使用哪种方式取决于具体的应用场景和需求。在大多数情况下,单继承由于其简单性和易于管理的特点而被广泛使用。然而,在某些情况下,当需要组合多个类的功能时,多个继承可能会更加合适。无论使用哪种方式,都需要仔细考虑类的设计和继承关系,以确保代码的可读性、可维护性和可扩展性。

5、方法重写

如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法,实例如下:

class Parent:        # 定义父类
   def myMethod(self):
      print ('调用父类方法')
 
class Child(Parent): # 定义子类
   def myMethod(self):
      print ('调用子类方法')
 
c = Child()          # 子类实例
c.myMethod()         # 子类调用重写方法
super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法

super() 函数是用于调用父类(超类)的一个方法。
输出结果为:

调用子类方法
调用父类方法

6、类属性与方法

类的私有属性
__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。
类的方法
在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数,self 代表的是类的实例。
self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定使用 self。
类的私有方法
__private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。self.__private_methods。
实例
类的私有属性实例如下:

lass JustCounter:
    __secretCount = 0  # 私有变量
    publicCount = 0    # 公开变量
 
    def count(self):
        self.__secretCount += 1
        self.publicCount += 1
        print (self.__secretCount)
 
counter = JustCounter()
counter.count()
counter.count()
print (counter.publicCount)
print (counter.__secretCount)  # 报错,实例不能访问私有变量

输出结果为:

1
2
2
Traceback (most recent call last):
  File "test.py", line 16, in <module>
    print (counter.__secretCount)  # 报错,实例不能访问私有变量
AttributeError: 'JustCounter' object has no attribute '__secretCount'

类的私有方法实例如下:

class Site:
    def __init__(self, name, url):
        self.name = name       # public
        self.__url = url   # private
 
    def who(self):
        print('name  : ', self.name)
        print('url : ', self.__url)
 
    def __foo(self):          # 私有方法
        print('这是私有方法')
 
    def foo(self):            # 公共方法
        print('这是公共方法')
        self.__foo()
 
x = Site('菜鸟教程', 'www.runoob.com')
x.who()        # 正常输出
x.foo()        # 正常输出
x.__foo()      # 报错

以上实例执行结果:
在这里插入图片描述
类的专有方法:
init : 构造函数,在生成对象时调用
del : 析构函数,释放对象时使用
repr : 打印,转换
setitem : 按照索引赋值
getitem: 按照索引获取值
len: 获得长度
cmp: 比较运算
call: 函数调用
add: 加运算
sub: 减运算
mul: 乘运算
truediv: 除运算
mod: 求余运算
pow: 乘方

7、运算符重载

Python同样支持运算符重载,我们可以对类的专有方法进行重载,实例如下:

class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b
 
   def __str__(self):
      return 'Vector (%d, %d)' % (self.a, self.b)
   
   def __add__(self,other):
      return Vector(self.a + other.a, self.b + other.b)
 
v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)

运行结果

Vector(7,8)
;