Python继承
在继承关系中,已有的、设计好的类称为父类或基类,新设计的类称为子类或派生类。派生类可以继承父类的公有成员,但是不能继承其私有成员。如果需要在派生类中调用基类的方法,可以使用内置函数super()或者通过“基类名.方法名()”的方式来实现这一目的。
在Python中,如果一个类需要继承另一个类的成员,可以使用以下方式进行定义:
class BaseClass:def __init__(self):self.public_member = "I am a public member"self.__private_member = "I am a private member" # 双下划线开头的成员变量为私有成员def public_method(self):print("This is a public method")def __private_method(self):print("This is a private method")class DerivedClass(BaseClass):def derived_method(self):# 调用从父类继承的公共成员print(self.public_member)# 无法调用父类的私有成员# print(self.__private_member)# 调用从父类继承的公共方法self.public_method()# 通过super()函数调用父类的方法super().public_method()# 无法直接调用基类的私有方法# self.__private_method()
在以上代码中,BaseClass
是一个基类,DerivedClass
是一个派生类。在 BaseClass
中,定义了一个公有成员 public_member
和一个私有成员 __private_member
,以及一个公有方法 public_method
和一个私有方法 __private_method
。在 DerivedClass
中,继承了 BaseClass
的公有成员 public_member
和公有方法 public_method
,并且定义了一个派生类方法 derived_method
。
在派生类中,我们可以通过继承来使用从基类继承的公共成员和方法,但是无法直接调用基类中的私有成员或方法。如果需要在派生类中调用基类的方法,可以使用内置函数 super()
或者通过“基类名.方法名()
”的方式来实现这一目的。在 derived_method
中,我们展示了如何使用 super().public_method()
来调用基类的 public_method
方法。
Python支持多继承,如果父类中有相同的方法名,而在子类中使用时没有指定父类名,则Python解释器将从左向右按顺序进行搜索。
以下是一个简单的示例代码,用于说明Python支持多继承时的方法解析顺序(MRO)问题:
class A:def hello(self):print("Hello from A")class B:def hello(self):print("Hello from B")class C(A, B):passclass D(B, A):pass# 创建子类C的实例
obj_c = C()
obj_c.hello() # 输出 "Hello from A"# 创建子类D的实例
obj_d = D()
obj_d.hello() # 输出 "Hello from B"
在上面的示例代码中,我们定义了两个父类A
和B
,分别有相同的方法名hello
,同时我们分别创建了两个子类C
和D
,并使用多继承方式分别继承了A
和B
。
在子类C
中,我们没有定义hello
方法,因此当我们创建obj_c
实例后,调用hello
方法时,Python解释器按照继承顺序从左到右进行搜索,首先找到A
类中的hello
方法,并执行。
而在子类D
中,我们同样也没定义hello
方法,但我们将B
类放在A
类前面来继承,因此当我们创建obj_d
实例后,调用hello
方法时,Python解释器按照继承顺序从左到右进行搜索,首先找到B
类中的hello
方法,因此执行结果输出"Hello from B"。
这就是Python多继承时的方法解析顺序(MRO)问题,如果父类中有相同的方法名,而在子类中没有指定父类名,则Python解释器会按照子类继承父类的先后顺序从左到右进行搜索,找到第一个匹配的方法并执行。当然,我们也可以通过显式地指定父类名来调用指定的父类中的方法,避免方法名冲突问题。
在Python中,私有变量是可以继承的。但是,子类无法直接访问父类的私有属性,因为私有属性在父类实例化之后会被转成一个新的名称,子类中也无法访问这个名称。但如果需要子类能够访问父类的私有变量,可以通过调用父类的get和set方法来实现。
以下是一个简单的示例代码,用于说明在Python中,子类无法直接访问父类的私有属性,但可以通过调用父类的get和set方法来实现:
class Parent:def __init__(self):self.__private_var = "I am a private variable in parent class"def get_private_var(self):return self.__private_vardef set_private_var(self, value):self.__private_var = valueclass Child(Parent):def __init__(self):super().__init__()self.__private_var_in_child = "I am a private variable in child class"def print_private_var(self):# 子类无法直接访问父类的私有属性# print(self.__private_var) # 这里会出错# 如果需要访问父类的私有属性,可以通过调用父类的get方法来实现print(self.get_private_var())# 子类可以访问自己的私有属性print(self.__private_var_in_child)# 创建子类的实例
child_obj = Child()# 调用子类的方法来访问私有变量
child_obj.print_private_var()# 调用父类的方法来修改私有变量的值
child_obj.set_private_var("Modified private var in parent class")# 再次调用子类的方法来访问私有变量
child_obj.print_private_var()
运行上面的代码,输出结果如下:
I am a private variable in parent class
I am a private variable in child class
Modified private var in parent class
I am a private variable in child class
从输出结果可以看出,在子类中无法直接访问父类的私有变量__private_var
,但可以通过调用父类的公共方法get_private_var()
来获取私有变量的值,并且可以通过调用父类的公共方法set_private_var()
来修改私有变量的值。同时,子类也可以定义自己的私有变量,对于这些私有变量,则可以直接在子类中访问和修改。
dir()函数
dir()
函数是Python内置函数之一,它返回一个列表,包含当前作用域内的所有可用属性和方法的名称。它可以接受一个参数,表示要查询的对象或模块,也可以不传递参数,此时会返回当前作用域内的所有全局名称。
以下是一个示例:
class MyClass:def __init__(self):self.my_attribute = 42def my_method(self):passmy_object = MyClass()print(dir(my_object))
输出结果:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'my_attribute', 'my_method']
此处我们定义了一个 MyClass
类和一个 my_object
实例,并使用 dir()
函数来探索 my_object
的属性和方法。可以看到,除了我们在 MyClass
中定义的 "my_attribute"
和 "my_method"
之外,还有许多其他属性和方法在这个对象里面,例如 "__class__"
, "__dict__"
, "__doc__"
, "__hash__"
, 等等。
值得注意的是,这些以双下划线开头和结尾的名称都是对象所继承的一些特殊属性和方法,它们在Python中被称为“魔术方法”或“特殊方法”。它们在Python中有特殊的语法和行为,可用于实现一些特殊的功能,比如重载操作符。
另外,还有一些内置模块和函数也可以通过 dir()
函数进行探索。例如:
import mathprint(dir(math))
输出结果:
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'comb', 'copysign', 'cos', 'cosh', 'degrees', 'dist', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'isqrt', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'nextafter', 'perm', 'pi', 'pow', 'prod', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc', 'ulp']
可以看到,在数学模块中,有许多不同的常量、函数和方法可供使用,例如符号常数 pi 或 tau,三角函数 sin 和 cos,还有一些数学运算和工具函数,例如 factorial 和 gcd 等,它们都可以通过 dir()
函数进行探索。
dir()
函数是一个十分强大的工具,它可以帮助我们快速了解和理解Python中可用的函数、模块、类、对象、方法等,并对我们在开发和调试代码时带来很多便利。
多态
在Python中,多态(polymorphism)是面向对象编程的一个重要概念,它指的是基类的同一方法在不同派生类对象中具有不同的表现和行为。也就是说,同样的方法名可以被派生类重写,从而实现不同的行为或逻辑。
下面我们来举一个简单的例子:
class Animal:def sound(self):print("The animal makes a sound.")class Dog(Animal):def sound(self):print("The dog barks.")class Cat(Animal):def sound(self):print("The cat meows.")def do_sound(animal):animal.sound()dog = Dog()
cat = Cat()do_sound(dog)
do_sound(cat)
在这个例子中,我们定义了一个基类 Animal
,它包含一个 sound()
方法,并打印出一条通用的动物发声信息。然后我们定义了两个派生类 Dog
和 Cat
,它们都继承了 Animal
的 sound()
方法,但改写了此方法以打印自己的声音信息。最后,我们编写了一个函数 do_sound()
,它接受一个 Animal
类型的参数,并调用它的 sound()
方法。
当我们创建一个实例 dog
或 cat
时,它们分别是 Dog
和 Cat
类型的对象,同时也都是 Animal
类型的对象。当我们调用 do_sound(dog)
或 do_sound(cat)
时,它们都会被传递给 do_sound()
函数,该函数会调用它们各自的 sound()
方法。由于派生类重写了基类方法,因此每个对象的 sound()
方法的行为都是不同的。
另外,需要注意的是,多态还可以用于参数和返回值的类型注解。例如:
from typing import Unionclass Shape:def area(self) -> float:passclass Rectangle(Shape):def __init__(self, width: float, height: float):self.width = widthself.height = heightdef area(self) -> float:return self.width * self.heightclass Circle(Shape):def __init__(self, radius: float):self.radius = radiusdef area(self) -> float:return 3.14 * self.radius ** 2def get_shape_area(shape: Union[Shape, Rectangle, Circle]) -> float:return shape.area()rectangle = Rectangle(5, 10)
circle = Circle(4)print(get_shape_area(rectangle))
print(get_shape_area(circle))
在这个例子中,我们定义了一个 Shape
基类,它包含一个 area()
方法,但没有实现任何具体逻辑。然后我们定义了两个派生类 Rectangle
和 Circle
,它们分别重写了 area()
方法以计算自己的面积。最后,我们编写了一个函数 get_shape_area()
,它接受一个 Shape
或其子类类型的参数,并调用它们的 area()
方法来获取它们的面积。
在这个例子中,我们使用了 Union
类型注解来指定函数参数的类型,表示它可以是 Shape
或其子类类型。这样,我们就可以在函数中以相同的方式处理不同类型的对象,并获得不同的结果。
需要注意的是,多态最大的好处之一是提高了代码的重用性和可扩展性。通过继承和多态,我们可以在不修改原有代码的情况下,轻松地添加新的行为和属性,或者修改现有的行为和属性,从而达到更好的代码复用和更好的系统扩展。
Python的运算符重载功能,是通过实现特殊方法(也称为魔术方法)来支持。这些特殊方法是以双下划线开头和结尾的,例如 __add__
是用来重载加法操作符 +
的。
在多态方面,Python中大多数运算符可以作用于多种不同类型的操作数,并且对于不同类型的操作数往往有不同的表现,这本身就是多态的体现。比如,在数字中,加号运算符用于执行加法操作,而在字符串中,加号运算符则用于字符串连接。
举个例子,我们定义一个自定义的类 Vector
,它代表二维向量,并希望支持向量的加法操作。为了支持加法操作符 +
,我们需要实现特殊方法 __add__
,并在其中定义加法操作的行为。
class Vector:def __init__(self, x, y):self.x = xself.y = y# 重载加法操作符def __add__(self, other):return Vector(self.x + other.x, self.y + other.y)
现在我们可以创建两个 Vector
对象,然后使用 +
运算符将它们相加:
v1 = Vector(2, 3)
v2 = Vector(4, 5)
v3 = v1 + v2
print(v3.x, v3.y) # 输出 6 8
此时我们可以看到,不同类型的操作数 v1
和 v2
都支持加法操作符,而且在 Vector
类中,加法的行为也是自定义的。这正是多态的体现。
除了上述例子中的加法操作符,Python还支持许多其他运算符的重载,例如减法、乘法、除法、位运算等等。在自定义类的操作中,运算符的重载让代码变得更具可读性和灵活性。
另外,在Python中,运算符重载也可以应用于内置的数据类型,比如数字、字符串、列表等,这使得开发者可以更自由地使用这些数据类型。