MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python子类属性与方法的定义方式

2021-11-296.7k 阅读

Python 子类属性与方法的定义方式

Python 类继承基础回顾

在深入探讨子类属性与方法的定义方式之前,我们先来简要回顾一下 Python 中类继承的基本概念。继承是面向对象编程中的一个重要特性,它允许我们创建一个新类(子类),这个新类基于一个已有的类(父类),并自动获得父类的属性和方法。通过继承,我们可以实现代码的复用,减少重复代码,并且能够在子类中根据需求对父类的行为进行扩展或修改。

在 Python 中,定义一个子类继承自父类的语法如下:

class ParentClass:
    def __init__(self):
        self.parent_attribute = "I am a parent attribute"

    def parent_method(self):
        print("This is a parent method")


class ChildClass(ParentClass):
    pass


child = ChildClass()
print(child.parent_attribute)
child.parent_method()

在上述代码中,ChildClass 继承自 ParentClass。当我们创建 ChildClass 的实例 child 时,child 可以访问 ParentClass 中定义的属性 parent_attribute 和方法 parent_method

子类属性的定义方式

在子类 __init__ 方法中定义属性

在子类中,最常见的定义属性的方式是在子类的 __init__ 方法中进行。通过调用 super().__init__(),我们可以先初始化父类的属性,然后再定义子类特有的属性。

class Animal:
    def __init__(self, name):
        self.name = name


class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed


my_dog = Dog("Buddy", "Golden Retriever")
print(my_dog.name)
print(my_dog.breed)

在这段代码中,Dog 类继承自 Animal 类。在 Dog 类的 __init__ 方法中,首先通过 super().__init__(name) 调用了父类 Animal__init__ 方法,初始化了 name 属性。然后,定义了子类特有的属性 breed。这样,my_dog 实例既有从父类继承的 name 属性,也有子类特有的 breed 属性。

直接在子类中定义类属性

除了在 __init__ 方法中定义实例属性,我们还可以在子类中直接定义类属性。类属性是属于类本身的属性,而不是属于类的实例。所有实例共享这些类属性。

class Shape:
    def __init__(self, color):
        self.color = color


class Rectangle(Shape):
    sides = 4

    def __init__(self, color, width, height):
        super().__init__(color)
        self.width = width
        self.height = height


rect = Rectangle("red", 5, 10)
print(rect.color)
print(rect.width)
print(rect.height)
print(rect.sides)

在上述代码中,Rectangle 类继承自 Shape 类。Rectangle 类定义了一个类属性 sides,表示矩形的边数。每个 Rectangle 实例都可以访问这个类属性。需要注意的是,如果在实例中修改类属性,并不会真正修改类属性,而是会在实例中创建一个同名的实例属性。

rect2 = Rectangle("blue", 3, 7)
print(Rectangle.sides)
rect2.sides = 5
print(rect2.sides)
print(Rectangle.sides)

在这段代码中,rect2.sides = 5 并没有改变 Rectangle 类的 sides 属性,而是在 rect2 实例中创建了一个新的 sides 实例属性。所以,print(rect2.sides) 输出 5,而 print(Rectangle.sides) 仍然输出 4

使用 property 装饰器定义属性

property 装饰器在 Python 中提供了一种优雅的方式来定义属性,它可以将方法转换为只读属性,或者提供更灵活的属性访问控制。在子类中,我们同样可以使用 property 装饰器来定义属性。

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @property
    def full_name(self):
        return f"{self.first_name} {self.last_name}"


class Employee(Person):
    def __init__(self, first_name, last_name, employee_id):
        super().__init__(first_name, last_name)
        self.employee_id = employee_id

    @property
    def employee_info(self):
        return f"Employee ID: {self.employee_id}, Name: {self.full_name}"


emp = Employee("John", "Doe", 12345)
print(emp.full_name)
print(emp.employee_info)

在上述代码中,Person 类使用 property 装饰器定义了 full_name 属性。Employee 类继承自 Person 类,并定义了自己的 employee_id 属性和 employee_info 属性。employee_info 属性依赖于从父类继承的 full_name 属性,展示了子类如何在继承父类属性定义方式的基础上,进一步扩展自己的属性定义。

子类方法的定义方式

重写父类方法

重写是指在子类中定义一个与父类方法同名的方法,以改变父类方法的行为。这是子类对父类行为进行定制的重要方式。

class Vehicle:
    def move(self):
        print("The vehicle moves")


class Car(Vehicle):
    def move(self):
        print("The car drives")


class Boat(Vehicle):
    def move(self):
        print("The boat sails")


car = Car()
boat = Boat()

car.move()
boat.move()

在这段代码中,Car 类和 Boat 类都继承自 Vehicle 类,并分别重写了 move 方法。当调用 car.move() 时,输出 The car drives,而调用 boat.move() 时,输出 The boat sails。这体现了子类通过重写父类方法来实现不同的行为。

在子类方法中调用父类方法

有时候,我们在子类方法中不仅要改变父类方法的行为,还需要保留父类方法的部分功能。这时,可以在子类方法中通过 super() 来调用父类方法。

class Animal:
    def make_sound(self):
        print("Some generic animal sound")


class Cat(Animal):
    def make_sound(self):
        super().make_sound()
        print("Meow")


cat = Cat()
cat.make_sound()

在上述代码中,Cat 类继承自 Animal 类并重写了 make_sound 方法。在 Cat 类的 make_sound 方法中,首先通过 super().make_sound() 调用了父类的 make_sound 方法,输出 Some generic animal sound,然后再输出 Meow。这样既保留了父类方法的部分功能,又增加了子类特有的行为。

定义子类特有的方法

除了重写和调用父类方法,子类还可以定义自己特有的方法,这些方法只属于子类,父类和其他子类都没有。

class Bird:
    def fly(self):
        print("The bird is flying")


class Penguin(Bird):
    def swim(self):
        print("The penguin is swimming")


penguin = Penguin()
penguin.fly()
penguin.swim()

在这段代码中,Penguin 类继承自 Bird 类。Penguin 类除了继承 Bird 类的 fly 方法外,还定义了自己特有的 swim 方法,展示了企鹅会游泳这一独特行为。

使用类方法和静态方法

在子类中,我们同样可以定义类方法和静态方法。类方法可以访问类属性,而静态方法不依赖于类或实例的状态。

class MathUtils:
    @classmethod
    def add(cls, a, b):
        return a + b

    @staticmethod
    def multiply(a, b):
        return a * b


class AdvancedMathUtils(MathUtils):
    @classmethod
    def power(cls, a, b):
        return a ** b

    @staticmethod
    def factorial(n):
        if n == 0 or n == 1:
            return 1
        else:
            return n * AdvancedMathUtils.factorial(n - 1)


print(AdvancedMathUtils.add(3, 5))
print(AdvancedMathUtils.multiply(4, 6))
print(AdvancedMathUtils.power(2, 3))
print(AdvancedMathUtils.factorial(5))

在上述代码中,AdvancedMathUtils 类继承自 MathUtils 类。AdvancedMathUtils 类定义了自己的类方法 power 和静态方法 factorial,同时也可以使用从父类继承的类方法 add 和静态方法 multiply

多重继承与子类属性和方法的交互

Python 支持多重继承,即一个子类可以继承自多个父类。在多重继承的情况下,子类属性和方法的定义与查找会变得更加复杂。

class A:
    def method(self):
        print("Method from A")


class B:
    def method(self):
        print("Method from B")


class C(A, B):
    pass


c = C()
c.method()

在上述代码中,C 类继承自 A 类和 B 类。当调用 c.method() 时,由于 A 类在继承列表中排在前面,所以会调用 A 类的 method 方法,输出 Method from A。这遵循了 Python 的 MRO(方法解析顺序)规则,MRO 是一个确定在多重继承情况下方法和属性查找顺序的算法。

print(C.mro())

通过调用 C.mro(),我们可以查看 C 类的方法解析顺序。在这个例子中,输出可能类似于 [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>],这表明 Python 会按照这个顺序查找属性和方法。

当子类定义属性或方法时,同样需要考虑多重继承的影响。如果子类定义的属性或方法与多个父类中的某个属性或方法同名,那么会根据 MRO 规则来确定最终使用哪个定义。

class A:
    def __init__(self):
        self.attr = "Attribute from A"


class B:
    def __init__(self):
        self.attr = "Attribute from B"


class C(A, B):
    def __init__(self):
        super().__init__()


c = C()
print(c.attr)

在这段代码中,C 类继承自 A 类和 B 类,并且在 C 类的 __init__ 方法中通过 super().__init__() 调用了父类的 __init__ 方法。由于 A 类在继承列表中排在前面,所以 c.attr 输出 Attribute from A

元类与子类属性和方法定义

元类是 Python 中一个较为高级的概念,它定义了类的行为。在 Python 中,类也是对象,而元类就是创建这些类对象的类。通过元类,我们可以在类定义时动态地修改类的属性和方法。

def upper_attr(future_class_name, future_class_parents, future_class_attr):
    new_attr = {}
    for name, value in future_class_attr.items():
        if not name.startswith('__'):
            new_attr[name.upper()] = value
        else:
            new_attr[name] = value
    return type(future_class_name, future_class_parents, new_attr)


class MyClass(metaclass=upper_attr):
    x = 10
    y = 20


print(hasattr(MyClass, 'x'))
print(hasattr(MyClass, 'X'))
print(MyClass.X)

在上述代码中,upper_attr 是一个元类函数。当定义 MyClass 类时,指定 metaclass = upper_attr,这意味着 upper_attr 函数会在 MyClass 类定义时被调用。upper_attr 函数会将类属性名转换为大写,所以 MyClass 类实际上有属性 XY,而不是 xy

当涉及到子类时,元类同样会影响子类属性和方法的定义。子类会继承父类的元类,除非子类显式指定了自己的元类。

class SubMyClass(MyClass):
    z = 30


print(hasattr(SubMyClass, 'Z'))
print(SubMyClass.Z)

在这段代码中,SubMyClass 继承自 MyClass,由于 MyClass 使用了 upper_attr 元类,SubMyClass 也会受到影响,所以 SubMyClass 有属性 Z

总结与实践建议

通过以上对 Python 子类属性与方法定义方式的详细探讨,我们了解了多种在子类中定义属性和方法的途径,包括在 __init__ 方法中定义属性、直接定义类属性、使用 property 装饰器,以及重写、调用父类方法和定义子类特有的方法等。在多重继承场景下,要注意 MRO 规则对属性和方法查找的影响。元类则为我们提供了一种更高级的动态修改类定义的方式。

在实际编程中,合理使用这些特性可以让代码更加清晰、可维护且具有扩展性。在定义子类属性和方法时,应根据具体的业务需求和代码逻辑来选择合适的方式。例如,如果需要扩展父类的行为,重写父类方法并结合调用父类方法是一个不错的选择;如果子类有独特的行为,定义子类特有的方法则更为合适。同时,在使用多重继承时要谨慎,避免因复杂的继承关系导致代码难以理解和维护。对于元类,虽然它提供了强大的功能,但由于其复杂性,应在真正需要动态修改类定义时才使用。通过不断实践和总结,我们能够更好地掌握 Python 子类属性与方法的定义技巧,编写出高质量的 Python 代码。