器→工具, 编程语言

Python学习之面向对象基础

钱魏Way · · 7 次浏览

当我们谈论面向对象时,就像在搭积木一样,每个积木都是一个特殊的东西,而整个搭建起来就是一个大作品。在编程中,面向对象是一种方法,你可以把不同的东西(比如动物、车辆、游戏角色)看作是特殊的“积木”,每个“积木”都有自己的特征和能做的事情。这样,我们可以更轻松地组织和管理我们的代码,就像组织积木一样。以下为个人整理的一些知识点。

类和对象

理解类和对象

理解类和对象是面向对象编程(OOP)中的基础概念。理解类和对象是理解面向对象编程的关键。类定义了问题领域中的实体和它们之间的关系,而对象是这些实体的具体实例。通过类和对象,可以更好地组织和抽象问题,使得代码更具可读性和可维护性。

类(Class)

  • 定义: 类是一种数据结构,用于封装数据(属性)和行为(方法)。它是一种用户定义的数据类型,用于创建对象。
  • 特点:
    • 属性(成员变量): 类具有属性,用于描述类的特征或状态。这些属性存储在类的对象中。
    • 方法(成员函数): 类具有方法,用于定义类的行为或功能。这些方法是在类的对象上执行的操作。

对象(Object)

  • 定义: 对象是类的实例。它是类的具体实体,具有类定义的属性和行为。
  • 特点:
    • 属性: 对象具有类定义的属性,这些属性存储在对象中。
    • 方法: 对象可以调用类定义的方法,这些方法在对象上执行特定的操作。

总结理解:

  • 类是模板: 类是一个抽象的概念,定义了一类对象应该有的属性和行为。
  • 对象是实体: 对象是类的具体实例,具有特定的属性值和可以执行的方法。
  • 封装: 类封装了数据和行为,对象是这个封装的具体实例。
  • 实例化: 创建对象的过程称为实例化,通过实例化可以得到类的一个具体实例。

创建类和实例化对象

在Python中,你可以使用class关键字创建类,然后使用该类来实例化对象。以下是一个简单的示例:

# 创建一个简单的类
class Dog:
    # 构造函数,初始化对象的属性
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 类的方法
    def bark(self):
        print(f"{self.name} is barking!")

# 实例化对象
my_dog = Dog(name="Buddy", age=3)

# 访问对象的属性和调用方法
print(f"{my_dog.name} is {my_dog.age} years old.")
my_dog.bark()

在这个例子中,我们创建了一个Dog类,具有name和age属性以及一个bark方法。然后,通过使用Dog类,我们创建了一个名为my_dog的实例,并访问了它的属性和调用了它的方法。

记住,__init__方法是特殊的构造函数,用于初始化对象的属性。在实例化对象时,这个方法会自动调用。

类的属性和方法

在面向对象编程中,类包含属性和方法,它们定义了类的状态和行为。以下是对类属性和方法的详细说明:

类属性(Class Attributes)

  • 定义: 类属性是属于类而不是类的实例的属性。它们通常在类的顶层定义,属于整个类,而不是类的任何特定实例。
  • 访问: 可以通过类名或类的实例访问类属性。当实例访问类属性时,如果实例没有该属性,将会从类中查找。
  • 修改: 类属性可以被修改,修改将影响到所有实例。
class MyClass:
    class_attribute = "I am a class attribute"

# 访问类属性
print(MyClass.class_attribute)

# 创建类的实例
obj = MyClass()
# 实例访问类属性
print(obj.class_attribute)

MyClass.class_attribute = "Modified class attribute"
print(MyClass.class_attribute)  # 输出 "Modified class attribute"

实例属性(Instance Attributes):

  • 定义: 实例属性是属于类的特定实例的属性。它们通常在构造函数 __init__ 中初始化,并通过 self 关键字与实例关联。
  • 访问: 实例属性只能通过实例访问。
  • 修改: 每个实例的实例属性是独立的,可以独立修改。
class MyClass:
    def __init__(self, instance_attribute):
        self.instance_attribute = instance_attribute

# 创建类的实例并传入参数
obj = MyClass(instance_attribute="I am an instance attribute")

# 访问实例属性
print(obj.instance_attribute)

obj1 = MyClass(instance_attribute="Instance 1")
obj2 = MyClass(instance_attribute="Instance 2")

print(obj1.instance_attribute)  # 输出 "Instance 1"
print(obj2.instance_attribute)  # 输出 "Instance 2"

# 修改实例属性
obj1.instance_attribute = "New Instance 1"
print(obj1.instance_attribute)  # 输出 "New Instance 1"

类方法(Class Methods)

类方法使用 @classmethod 装饰器定义,第一个参数通常是 cls,表示对类本身的引用。类方法可以访问和修改类属性。

class MyClass:
    class_variable = "Class Variable"

    @classmethod
    def class_method(cls):
        print(f"This is a class method. Class variable: {cls.class_variable}")

# 调用类方法
MyClass.class_method()

实例方法(Instance Methods):

实例方法是最常见的方法类型,它们使用 self 参数引用实例本身,可以访问和修改实例属性。

class MyClass:
    def __init__(self, instance_variable):
        self.instance_variable = instance_variable

    def instance_method(self):
        print(f"This is an instance method. Instance variable: {self.instance_variable}")

# 创建类的实例
obj = MyClass(instance_variable="Instance Variable")

# 调用实例方法
obj.instance_method()

类属性和实例属性以及类方法和实例方法为面向对象编程提供了一种组织数据和行为的方式。类属性是属于整个类的属性,而实例属性是属于特定实例的属性。类方法是可以访问类属性的方法,而实例方法是可以访问实例属性的方法。这种组织结构可以帮助更好地模型现实世界中的问题。

类变量和实例变量

在面向对象编程中,类变量和实例变量是两种不同类型的变量,它们存储在类和类的实例中,有着不同的作用和生命周期。

类变量(Class Variables):

  • 类变量是定义在类中,但在方法之外的变量。
  • 它是类的所有实例共享的,即所有实例都可以访问和修改相同的类变量。
  • 通常用于存储类的特有属性或在所有实例之间共享的信息。
class MyClass:
    class_variable = 0  # 类变量

    def __init__(self, instance_variable):
        self.instance_variable = instance_variable  # 实例变量

# 访问类变量
print(MyClass.class_variable)  # 输出 0

# 修改类变量
MyClass.class_variable = 1

# 创建实例
obj1 = MyClass(10)
obj2 = MyClass(20)

# 访问实例变量
print(obj1.instance_variable)  # 输出 10
print(obj2.instance_variable)  # 输出 20

实例变量(Instance Variables)

  • 实例变量是定义在方法中(通常是在 __init__ 构造函数中)的变量。
  • 每个类的实例都拥有独立的实例变量,它们在不同的实例中互不影响。
  • 用于存储对象的特定状态或属性。
class MyClass:
    class_variable = 0  # 类变量

    def __init__(self, instance_variable):
        self.instance_variable = instance_variable  # 实例变量

# 创建实例
obj1 = MyClass(10)
obj2 = MyClass(20)

# 访问实例变量
print(obj1.instance_variable)  # 输出 10
print(obj2.instance_variable)  # 输出 20

总的来说,类变量在整个类中共享,而实例变量是每个实例独有的。类变量用于存储类级别的信息,实例变量用于存储每个实例的特定信息。

使用类变量在实例中共享数据

使用类变量在实例中共享数据可以通过以下步骤完成:

在类中定义类变量:在类定义中声明一个类变量,并赋予其初始值。

class MyClass:
    class_variable = 0  # 类变量
在实例化对象时访问和修改类变量:在类的实例方法中可以通过 self 关键字访问和修改类变量。当实例化对象时,可以通过对象实例来访问和修改。
class MyClass:
    class_variable = 0  # 类变量

    def __init__(self, instance_variable):
        self.instance_variable = instance_variable  # 实例变量

    def update_class_variable(self, value):
        MyClass.class_variable = value

示例:
# 创建两个对象
obj1 = MyClass(10)
obj2 = MyClass(20)

# 访问和修改类变量
print(obj1.class_variable)  # 输出 0
print(obj2.class_variable)  # 输出 0

obj1.update_class_variable(5)

print(obj1.class_variable)  # 输出 5
print(obj2.class_variable)  # 输出 5

在这个例子中,class_variable 是一个类变量,它被两个不同的对象实例所共享。通过实例方法 update_class_variable,我们可以在一个对象上更新这个类变量,然后通过另一个对象访问时,变量的值也会发生变化。

请注意,如果在某个实例中修改了类变量,这将影响所有的实例,因为它们共享相同的类变量。

类方法和静态方法区别

类方法(class method)和静态方法(static method)都是在类级别定义的方法,但它们在用法和行为上有一些重要的区别:

类方法(Class Method):

  • 类方法使用 @classmethod 装饰器来标识,第一个参数通常被命名为 cls,表示对类本身的引用。
  • 类方法可以访问类级别的属性,并且可以通过类名或实例调用。
  • 通常用于执行与类整体相关的操作。
class MyClass:
    class_variable = "Class Variable"

    @classmethod
    def class_method(cls):
        print(f"Class method called. Class variable: {cls.class_variable}")

# 调用类方法
MyClass.class_method()

静态方法(Static Method):

  • 静态方法使用 @staticmethod 装饰器来标识,不需要传递类实例或类引用作为第一个参数。静态方法与类和实例无关。
  • 通常用于执行与类或实例无关的操作,与类的状态无关。
class MyClass:
    @staticmethod
    def static_method():
        print("Static method called.")

# 调用静态方法
MyClass.static_method()

区别总结:

  • 参数传递:
    • 类方法的第一个参数是类本身(通常命名为 cls),可以访问和修改类级别的属性。
    • 静态方法没有隐式的第一个参数,不传递类或实例引用,只能访问方法内的本地变量。
  • 调用方式:
    • 类方法可以通过类名或实例调用,因为它可以访问类属性。
    • 静态方法可以通过类名或实例调用,但不能访问类属性,因为它与类或实例无关。
  • 使用场景:
    • 类方法通常用于在类级别执行操作,可能涉及到类的属性。
    • 静态方法通常用于执行与类或实例无关的操作,不依赖于类的状态。
class MyClass:
    class_variable = "Class Variable"

    @classmethod
    def class_method(cls):
        print(f"Class method called. Class variable: {cls.class_variable}")

    @staticmethod
    def static_method():
        print("Static method called.")

总体而言,类方法和静态方法在设计类时提供了更灵活的选择,允许开发者根据需要选择适当的方法类型。

构造函数和析构函数

构造函数

在Python中,构造函数是一个特殊的方法,用于在创建类的实例时进行初始化操作。构造函数的名称是__init__,它在对象被创建后自动调用。以下是构造函数的详细介绍:

  • 定义构造函数: 在类中定义__init__方法,它的第一个参数通常是self,表示对象实例本身。其他参数用于接收在创建对象时传递的初始值。
  • 自动调用: 当你创建类的实例时,__init__方法会自动调用,可以在这个方法里进行对象的初始化工作。
  • self参数: self参数表示对象实例本身,它是一个约定俗成的名称,但实际上你可以使用任何名称。在构造函数内部,通过self可以访问对象的属性。
  • 初始化对象属性: 在构造函数中,你可以通过self关键字为对象设置初始值,这些值通常是对象的属性。
  • 传递参数: 当创建类的实例时,可以传递相应数量的参数给构造函数,这些参数会被传递给__init__方法。
class MyClass:
    def __init__(self, param1, param2):
        # 初始化操作,可以使用传递进来的参数
        self.param1 = param1
        self.param2 = param2

my_instance = MyClass("value1", "value2")

构造函数的作用是确保在创建对象时进行必要的初始化工作,为对象的属性设置合适的初始值。这有助于保持对象的一致性和正确性。

析构函数

在Python中,析构函数是一个特殊的方法,用于在对象被销毁(即释放内存)之前执行一些清理工作。析构函数的名称是__del__,它在对象被销毁时自动调用。以下是析构函数的详细介绍:

  • 定义析构函数:在类中定义__del__方法,它的第一个参数通常也是self,表示对象实例本身。与构造函数不同,析构函数不需要额外的参数。
  • 自动调用:当对象被销毁时,Python解释器会自动调用析构函数。对象的销毁通常发生在对象不再被引用时,即没有任何变量指向它时。
  • 清理工作:在析构函数中,你可以执行任何必要的清理工作,比如关闭文件、释放资源或记录对象销毁的信息。
  • 不是必需的:在大多数情况下,析构函数并不是必需的,因为Python会自动处理内存的释放。只有当你的类需要在对象销毁时执行一些额外的操作时才需要定义析构函数。
  • 注意事项:
    • 不要过度依赖析构函数执行清理工作。最好显式地调用一个专门的方法来进行清理,以确保及时执行。
    • 不要在析构函数中触发可能引起异常的操作,因为析构函数的调用是由解释器自动管理的,异常可能导致不确定的行为。
    • 析构函数通常在需要进行资源清理或收尾工作时使用,但在一般情况下,Python的垃圾回收机制会自动处理对象的内存释放。
class MyClass:
    def __init__(self, param):
        self.param = param

    def __del__(self):
        # 析构函数的清理工作
        print(f"Object with param {self.param} is being destroyed.")

my_instance = MyClass("value")
del my_instance  # 此时 __del__ 方法被调用

class FileHandler:
    def __init__(self, file_path):
        self.file = open(file_path, 'w')

    def __del__(self):
        self.file.close()
        print("File handler is closed.")

类的特殊方法

类的特殊方法,也被称为魔术方法或双下划线方法,是在类定义中以双下划线开头和结尾的方法。这些方法用于定义类的行为,控制类的实例化、字符串表示、属性访问等等。以下是一些常见的类特殊方法:

  • __init__(self, …):初始化方法,用于创建对象并设置对象的初始状态。
  • __str__(self), __repr__(self):
    • __str__ 用于返回对象的用户可读的字符串表示。
    • __repr__ 用于返回对象的“官方”字符串表示,通常是一个可以用于重新创建对象的字符串。
  • __len__(self): 用于返回对象的长度,通常配合内置函数 len() 使用。
  • __getitem__(self, key), __setitem__(self, key, value), __delitem__(self, key): 用于实现索引访问,即 obj[key] 的行为。
  • __iter__(self), __next__(self): 用于实现迭代器协议,即对象可以被迭代。
  • __contains__(self, item): 用于实现成员关系测试,即 item in obj 的行为。
  • __call__(self, …): 允许一个对象像函数一样被调用,即 obj() 的行为。
  • __eq__(self, other), __ne__(self, other), __lt__(self, other), __le__(self, other), __gt__(self, other), __ge__(self, other): 用于实现对象之间的比较运算。
  • __add__(self, other), __sub__(self, other), __mul__(self, other), __truediv__(self, other), 等等: 用于实现对象的算术运算。
  • __enter__(self), __exit__(self, exc_type, exc_value, traceback): 用于实现上下文管理协议,通常在使用 with 语句时调用。

这些特殊方法使得你可以控制你的类的行为,并与 Python 的语法和内置函数进行交互。可以根据实际需要选择性地实现这些方法。

__str__ 和 __repr__ 是 Python 中两个用于返回对象字符串表示的特殊方法。它们在不同的情况下使用,有一些区别:

__str__(self):

  • 用于返回对象的“用户可读”字符串表示。
  • 当调用内置函数 str(obj) 或 print(obj) 时,如果 __str__ 被定义,将调用该方法。
  • 主要用于提供友好的输出,适合最终用户阅读。
class MyClass:
    def __str__(self):
        return "This is a MyClass object."

obj = MyClass()
print(obj)  # 调用 __str__ 方法输出 "This is a MyClass object."

__repr__(self):

  • 用于返回对象的“官方”字符串表示,通常是一个可以用于重新创建对象的字符串。
  • 当调用内置函数 repr(obj) 时,如果 __repr__ 被定义,将调用该方法。
  • 在交互式环境中,直接输入对象名并回车时也会调用 __repr__。
class MyClass:
    def __repr__(self):
        return "MyClass()"

obj = MyClass()
print(repr(obj))  # 调用 __repr__ 方法输出 "MyClass()"

区别:

  • 如果只定义了一个方法,而另一个没有定义,Python 将在需要时使用已定义的那一个。
  • 如果两者都定义了,__str__ 会覆盖 __repr__,因为在需要 __str__ 的地方,Python 会首先尝试调用它。
  • __repr__ 应该返回一个准确的、可用于重新创建对象的字符串,而 __str__ 可以提供更友好的、用于显示的字符串。
class MyClass:
    def __str__(self):
        return "This is a MyClass object."

    def __repr__(self):
        return "MyClass()"

obj = MyClass()
print(str(obj))   # 调用 __str__ 方法输出 "This is a MyClass object."
print(repr(obj))  # 调用 __repr__ 方法输出 "MyClass()"

总的来说,__str__ 适合给最终用户查看,而 __repr__ 更适合开发者和调试,提供更具有重建性的信息。

使用装饰器来扩展类或方法的功能

使用装饰器可以方便地扩展类或方法的功能,而无需修改其原始代码。以下是一些示例说明如何使用装饰器来实现功能扩展:

装饰类方法:

# 装饰器函数
def log_method(func):
    def wrapper(self, *args, **kwargs):
        print(f"Calling method: {func.__name__}")
        result = func(self, *args, **kwargs)
        print(f"Method {func.__name__} finished.")
        return result
    return wrapper

# 被装饰的类
class MyClass:
    @log_method
    def my_method(self):
        print("Executing my_method")

# 使用装饰后的类
obj = MyClass()
obj.my_method()

装饰类:

# 装饰器函数
def log_class(cls):
    class WrappedClass(cls):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            print(f"Initializing instance of {cls.__name__}")

    return WrappedClass

# 被装饰的类
@log_class
class MyClass:
    def __init__(self, value):
        self.value = value

# 使用装饰后的类
obj = MyClass(42)

参数化装饰器:

# 参数化装饰器函数
def log_method_with_prefix(prefix):
    def decorator(func):
        def wrapper(self, *args, **kwargs):
            print(f"{prefix}: Calling method: {func.__name__}")
            result = func(self, *args, **kwargs)
            print(f"{prefix}: Method {func.__name__} finished.")
            return result
        return wrapper
    return decorator

# 被装饰的类
class MyClass:
    @log_method_with_prefix("INFO")
    def my_method(self):
        print("Executing my_method")

# 使用参数化装饰器
obj = MyClass()
obj.my_method()

这些例子演示了如何使用装饰器来添加日志、计时、验证等功能,而不需要直接修改原始类或方法的代码。装饰器提供了一种优雅而灵活的方式来修改或扩展现有的代码功能。

封装、继承与多态

当我们说到抽象时,可以想象成一张蓝图,它定义了一种东西的基本特征,但并没有具体的细节。就像画了一只猫的轮廓,但没有填充颜色一样。

  • 封装就像是把一些东西放进一个盒子里,其他人只需要知道盒子外部的信息,而不必了解盒子内部的复杂性。这样可以隐藏实现细节,使得代码更易维护。
  • 继承就像是从一张蓝图中创建新的蓝图,新的蓝图可以沿袭旧蓝图的特征,同时可以添加一些新的特征。比如,从“动物”蓝图可以继承出“狗”蓝图,保留了动物的基本特征,又增加了狗独有的特征。
  • 多态就像是一种灵活性,同样的动作对于不同的对象可以有不同的表现。就像玩具可以有不同的形状,但都可以被玩,这种多样性使得我们可以用相同的方式对待不同的事物。

封装

使用私有(private)和受保护(protected)成员来限制对类内部的访问

在Python中,对象的访问限制是通过属性的命名规则和特定的修饰符来实现的,主要包括私有属性和受保护属性。这种限制旨在防止外部直接访问对象的内部实现,从而提高封装性和安全性。

  • 私有属性(Private):在属性名前加上双下划线 __ 就可以将属性定义为私有属性。例如:__private_var = 42。“Private”:词根:”Priv-” 是一个词根,来源于拉丁语的 “privatus”,意味着个人的、私人的。词缀:”-ate” 是一个后缀,用于构建形容词,表示具有某种性质或状态。在这里,它强调了私有的属性。因此,”private” 这个单词的整体含义是指属于个人、私人的,表示不公开或只能被特定个体访问的状态。在编程领域,”private” 通常用于表示类中的成员(如属性或方法)具有最高的封装性,只能在声明它们的类内部访问,而在类的外部无法直接访问。这有助于隐藏对象的内部实现,提高封装性和安全性。
  • 受保护属性(Protected):在属性名前加上一个下划线 _ 也可以实现一定程度的访问限制,通常被称为受保护属性。例如:_protected_var = 10。“Protected”:词根:”Protect” 是一个词根,意味着保护、防护。词缀:”-ed” 是一个过去分词后缀,将动词转换为过去分词形式。在这里,它表示被保护的状态,即受到了保护的。因此,”protected” 这个单词的整体含义是指被保护的、受到防护的。在编程领域,”protected” 通常用于表示一种中间程度的访问权限,介于公共(public)和私有(private)之间,表示这个成员在类的内部及其子类中可访问。

在Python中,尽管私有属性和受保护属性的目的是限制外部访问,但实际上是可以通过一些方法来访问的。这是因为在 Python 中,封装是基于约定而不是强制执行的。

访问私有属性:

  • 私有属性在其名字前加上双下划线 __,例如__private_var。
  • 尽管不能直接使用__private_var 的方式从外部访问,但在 Python 中并没有真正的私有性。
  • 通过名称修饰可以访问,例如 _ClassName__private_var。
class MyClass:
    def __init__(self):
        self.__private_var = 42

obj = MyClass()
# print(obj.__private_var)  # 无法直接访问
print(obj._MyClass__private_var)  # 通过名称修饰可以访问

访问受保护属性:

  • 受保护属性在其名字前加上一个下划线 _,例如_protected_var。
  • 同样,虽然不鼓励从外部直接访问,但你仍然可以通过名称修饰来访问。
class MyClass:
    def __init__(self):
        self._protected_var = 10

obj = MyClass()
print(obj._protected_var)  # 可以直接访问,但不鼓励

需要注意的是,这些方式并不是 Python 的正式特性,而是一种绕过私有性和受保护性的方法。在实际编码中,尊重类的设计意图,不直接访问私有和受保护的属性,通过提供公共接口来操作对象。

使用@property 装饰器创建属性的 getter 和 setter 方法。

在编程领域,特别是在面向对象编程中,”property” 通常指的是一个类的特殊属性,其访问和修改通过类的方法而不是直接通过属性名进行。在面向对象编程中,类的属性是描述类对象特征的变量。而 “property” 这个词经常用于强调类的属性的特殊性,尤其是通过特殊方法(getter、setter、deleter)来控制访问和修改。

 

在面向对象编程中,getter 和 setter 是用于访问和修改对象属性的方法,它们允许你以一种更灵活、可控的方式操作对象的状态。下面是对 getter 和 setter 的详细解释:

Getter(获取器):

  • Getter 方法用于获取对象的属性值。
  • 命名通常为 get_property(),其中 property 是属性的名称。
  • Getter 方法让你能够通过调用一个方法而不是直接访问属性来获取属性的值。
  • 通过 getter,你可以在获取属性值时执行一些额外的逻辑,比如格式化输出或验证值的有效性。

Setter(设置器):

  • Setter 方法用于设置对象的属性值。
  • 命名通常为 set_property(value),其中 property 是属性的名称,value 是要设置的值。
  • Setter 方法让你能够通过调用一个方法而不是直接赋值语句来设置属性的值。
  • 通过 setter,你可以在设置属性值时执行一些额外的逻辑,比如验证值的有效性或触发其他操作。
class MyClass:
    def __init__(self):
        self._value = 0

    def get_value(self):
        return self._value

    def set_value(self, new_value):
        if new_value >= 0:
            self._value = new_value
        else:
            print("Value must be non-negative.")

obj = MyClass()
print(obj.get_value())  # 使用 getter 方法获取属性值
obj.set_value(42)  # 使用 setter 方法设置属性值

property 装饰器在 Python 中用于将类的方法转化为属性,使得可以像访问属性一样调用方法。这通常用于创建 getter 和 setter 方法,使得属性的访问和修改更具控制性。以下是对 property 装饰器的详细介绍:

基本使用:property 装饰器可以用来创建只读属性(getter)。

class MyClass:
    def __init__(self):
        self._value = 0

    @property
    def value(self):
        return self._value

obj = MyClass()
print(obj.value)  # 使用属性方式访问方法

创建可读写属性:可以使用 @property 装饰器创建 getter 方法,同时使用 @<property_name>.setter 装饰器创建 setter 方法。

class MyClass:
    def __init__(self):
        self._value = 0

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        if new_value >= 0:
            self._value = new_value
        else:
            print("Value must be non-negative.")

obj = MyClass()
obj.value = 42  # 使用属性方式调用 setter 方法
print(obj.value)  # 使用属性方式调用 getter 方法

删除属性:使用 @<property_name>.deleter 装饰器可以定义删除属性的方法。

class MyClass:
    def __init__(self):
        self._value = 0

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        if new_value >= 0:
            self._value = new_value
        else:
            print("Value must be non-negative.")

    @value.deleter
    def value(self):
        print("Deleting value.")
        del self._value

obj = MyClass()
del obj.value  # 使用属性方式调用 deleter 方法

property 装饰器使得对类的属性进行操作更加直观,同时可以在访问或修改属性时执行一些自定义的逻辑。

继承

创建子类

在 Python 中,你可以通过创建一个新类并在类定义时指定基类(父类)来创建一个子类。子类继承了基类的属性和方法,并可以添加自己的属性和方法。以下是创建子类的基本语法:

class ParentClass:
    # 父类的属性和方法

class ChildClass(ParentClass):
    # 子类的属性和方法

在这里,ChildClass 是 ParentClass 的子类。以下是一个简单的例子:

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

    def speak(self):
        print(f"{self.name} says something.")

class Dog(Animal):  # Dog 是 Animal 的子类
    def bark(self):
        print(f"{self.name} barks loudly.")

class Cat(Animal):  # Cat 是 Animal 的子类
    def meow(self):
        print(f"{self.name} meows softly.")

# 创建 Dog 对象
my_dog = Dog("Buddy")
my_dog.speak()  # 调用继承自父类的方法
my_dog.bark()   # 调用子类自己的方法

# 创建 Cat 对象
my_cat = Cat("Whiskers")
my_cat.speak()  # 调用继承自父类的方法
my_cat.meow()   # 调用子类自己的方法

在这个例子中,Dog 和 Cat 类都是 Animal 类的子类,它们继承了 Animal 类的属性和方法,同时可以拥有自己的属性和方法。

调用父类的方法

在 Python 中,你可以使用 super() 函数来调用父类的方法。super() 返回一个临时对象,该对象允许你调用父类的方法。以下是一个简单的例子:

class ParentClass:
    def show_message(self):
        print("Message from the parent class.")

class ChildClass(ParentClass):
    def show_message(self):
        # 调用父类的 show_message 方法
        super().show_message()
        print("Message from the child class.")

# 创建子类对象
child_obj = ChildClass()

# 调用子类的 show_message 方法
child_obj.show_message()

在这个例子中,ChildClass 继承了 ParentClass。在 ChildClass 的 show_message 方法中,通过 super().show_message() 调用了父类 ParentClass 的 show_message 方法,然后在子类方法中添加了一些额外的逻辑。

super() 函数返回一个临时对象,使得可以调用父类的方法。它的使用通常在子类覆盖了父类方法后,需要在子类方法中调用父类方法时非常有用。

多重继承的概念

在 Python 中,多重继承是指一个类可以同时继承自多个父类。这允许子类继承多个父类的属性和方法。多重继承的概念可以通过以下几个方面来理解:

语法:

  • 在类定义时,可以在类名后的圆括号中列出多个父类,使用逗号分隔。
  • class SubClass(BaseClass1, BaseClass2, …):
class Parent1:
    pass

class Parent2:
    pass

class Child(Parent1, Parent2):
    pass

继承顺序:

  • 多重继承中,类的方法和属性按照其在类定义时列出的父类的顺序进行搜索。
  • 如果两个或多个父类中有相同名字的属性或方法,Python 将按照继承列表中的顺序选择第一个找到的。

方法解析顺序(MRO):

  • Python 使用 C3 线性化算法来确定类的方法解析顺序(Method Resolution Order)。
  • MRO 定义了类的属性和方法查找顺序,保证了按照类的继承关系正确地找到属性和方法。
class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

# D 的 MRO: D -> B -> C -> A
print(D.mro())

Diamond Problem:

  • 多重继承可能引发 Diamond Problem,即当一个类同时继承自两个有公共父类的类时,可能会导致属性和方法冲突。
  • Python 的 MRO 确保了类的搜索路径,但程序员需要小心设计类的层次结构以避免潜在的问题。
class A:
    def method(self):
        print("Method in A")

class B(A):
    def method(self):
        print("Method in B")

class C(A):
    def method(self):
        print("Method in C")

class D(B, C):
    pass

obj = D()
obj.method()  # 输出 "Method in B"

多重继承在一些场景中非常有用,但也需要小心使用,以避免潜在的设计复杂性和冲突。理解 MRO 可以帮助更好地组织和设计具有多重继承的类。

深入理解 super 关键字

super 关键字是在面向对象编程中用于调用父类方法的工具。它提供了一种访问父类的途径,以便在子类中调用父类的方法。下面是一些深入了解 super 关键字的要点:

调用父类方法:super 主要用于在子类中调用父类的方法。通过 super().method() 可以调用父类中名为 method 的方法。

class Parent:
    def method(self):
        print("Parent method")

class Child(Parent):
    def method(self):
        super().method()
        print("Child method")

obj = Child()
obj.method()

输出:

sql
Copy code
Parent method
Child method

super 与多继承:在多继承的情况下,super 使用方法是基于 C3 线性化算法,确保按照类的继承顺序调用方法。

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

class B(A):
    def method(self):
        print("B method")
        super().method()

class C(A):
    def method(self):
        print("C method")
        super().method()

class D(B, C):
    pass

obj = D()
obj.method()

输出:

sql
Copy code
B method
C method
A method

super 的工作原理:super 并不是简单地调用父类的方法,而是使用 MRO(Method Resolution Order)算法找到下一个应该调用的方法。这确保了在多继承情况下的正确方法解析。

super 与 __init__ 方法:在子类的 __init__ 方法中使用 super().__init__() 可以调用父类的构造函数,确保子类对象包含父类的初始化。

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

class Child(Parent):
    def __init__(self, name, age):
        super().__init__(name)
        self.age = age

obj = Child("Alice", 25)
print(obj.name)  # 输出 "Alice"
print(obj.age)   # 输出 25

super 与类方法和静态方法:super 同样适用于类方法和静态方法,但需要传递相应的类名或类对象。

class Parent:
    @classmethod
    def class_method(cls):
        print(f"Parent class method in {cls.__name__}")

class Child(Parent):
    @classmethod
    def class_method(cls):
        print(f"Child class method in {cls.__name__}")
        super(Child, cls).class_method()

Child.class_method()

输出:

kotlin
Copy code
Child class method in Child
Parent class method in Parent

深入了解 super 涉及到对 Python 中的方法解析、MRO 算法、多继承等概念的理解。通过使用 super 关键字,可以更清晰地管理类之间的继承关系和方法调用。

多态

使用多态,同一个方法名可以在不同类中有不同的实现。

多态(Polymorphism)是面向对象编程中的一个概念,指的是同一个方法名可以在不同的类中具有不同的实现。多态性允许你使用相同的方法名调用不同类的方法,根据对象的实际类型来执行相应的方法。以下是对多态的更详细解释:

方法重写:

  • 多态依赖于方法重写(Overriding)的概念,即子类可以重写父类的方法。
  • 子类重写的方法具有相同的名称和参数列表,但提供了新的实现。

动态绑定:

  • 多态性是通过动态绑定(Dynamic Binding)实现的,也称为运行时绑定。
  • 在运行时,系统根据对象的实际类型选择调用相应的方法。

示例:

假设有一个基类 Shape,有一个方法 area 计算形状的面积。然后有两个子类 Circle 和 Rectangle,它们分别重写了 area 方法。

class Shape:
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

使用多态:使用多态时,可以创建一个基类的引用,但在运行时指定它实际引用的子类的对象。

def print_area(shape):
    print(f"Area: {shape.area()}")

circle = Circle(5)
rectangle = Rectangle(4, 6)

print_area(circle)      # 动态调用 Circle 类的 area 方法
print_area(rectangle)   # 动态调用 Rectangle 类的 area 方法

在这个例子中,print_area 函数接受一个 Shape 类型的参数,但实际上可以传入任何继承自 Shape 的子类对象。在运行时,系统将动态选择调用正确的 area 方法。

多态性使得代码更加灵活和可扩展,允许你编写通用的代码,而无需事先知道实际对象的类型。

鸭子类型

鸭子类型是一种动态类型语言(例如 Python)中的编程风格,其核心思想是关注对象的行为而不是它的类型。这个概念来源于一句格言:“如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子”。

在鸭子类型中,一个对象的可用性不是由继承某个特定的类或实现某个特定的接口来定义的,而是由它的方法和属性的存在来确定的。如果一个对象具有特定的行为,那么它可以被用在期望这种行为的地方,无论其实际的类型是什么。

以下是一个简单的例子:

class Duck:
    def quack(self):
        print("Quack, quack!")

class Dog:
    def quack(self):
        print("Woof, woof!")

class Cat:
    def meow(self):
        print("Meow, meow!")

def make_sound(animal):
    animal.quack()

duck = Duck()
dog = Dog()
cat = Cat()

make_sound(duck)  # 输出 "Quack, quack!"
make_sound(dog)   # 输出 "Woof, woof!",因为它有 quack 方法
# make_sound(cat)  # 会引发 AttributeError,因为 Cat 没有 quack 方法

在这个例子中,make_sound 函数期望传入一个具有 quack 方法的对象。尽管 Duck 和 Dog 的实现不同,但它们都可以被传递给 make_sound 函数,因为它们都有一个名为 quack 的方法。

鸭子类型强调的是对象的行为,而不是对象的继承或实现。这种灵活性使得代码更具通用性,允许不同类型的对象在不显示指定接口的情况下协同工作。

使用ABC(Abstract Base Classes)模块创建抽象基类。

抽象基类(Abstract Base Classes,简称 ABCs)是 Python 中的一种机制,用于定义抽象类和接口。它们允许你明确地定义类应该具有的一组方法,从而提高代码的可读性和可维护性。在 Python 中,抽象基类是由 collections.abc 模块提供的。

以下是使用抽象基类的一般步骤和概念:

导入 ABC 和 abstractmethod:使用 from abc import ABC, abstractmethod 导入 ABC 类和 abstractmethod 装饰器。

from abc import ABC, abstractmethod

定义抽象基类:创建一个类并将其继承自 ABC。

class MyABC(ABC):
    pass

定义抽象方法:使用 @abstractmethod 装饰器定义抽象方法。这些方法在派生类中必须被实现。

class MyABC(ABC):
    @abstractmethod
    def my_method(self):
        pass

实现派生类:派生类必须实现抽象基类中定义的所有抽象方法。

class MyConcreteClass(MyABC):
    def my_method(self):
        print("Implemented method in MyConcreteClass.")

示例:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

# 使用抽象基类的对象
circle = Circle(5)
rectangle = Rectangle(4, 6)

print(circle.area())      # 输出圆的面积
print(rectangle.area())   # 输出矩形的面积

抽象基类在设计类层次结构时提供了一些规范,确保派生类实现了预期的接口。在上述例子中,Shape 是抽象基类,定义了一个抽象方法 area,要求所有的派生类都必须实现这个方法。这样有助于提高代码的可读性和可维护性。

让我们更详细地解释一下为什么使用抽象基类在这里是有意义的:

  • 明确接口:Shape 抽象基类定义了一个名为 area 的抽象方法,明确说明了任何派生类都应该实现这个方法。这相当于一份接口规范,告诉其他开发者:所有的图形形状都应该能够计算面积。
  • 提高可读性和可维护性:使用抽象基类使得类层次结构更加清晰和易于理解。通过查看 Shape 类,开发者能够立即知道所有派生类都应该实现 area 方法。这提高了代码的可读性,使得后续维护更加容易。
  • 预防错误:抽象基类可以在运行时检查是否所有的抽象方法都被正确实现。如果某个派生类没有实现必要的抽象方法,Python 将在实例化时引发 TypeError,从而提前发现错误。
  • 代码文档:抽象基类也可以作为代码文档的一部分。其他开发者通过查看抽象基类,可以了解该类层次结构的设计意图和期望的行为。

接口的概念,即定义一组方法而不提供具体实现。

在Python中,虽然没有明确的“接口”关键字,但接口的概念通过鸭子类型(Duck Typing)和抽象基类(Abstract Base Classes,ABCs)得到了支持。接口定义了一组规范,指定了类或对象应该具有的方法和属性。以下是关于接口的详细说明:

鸭子类型(Duck Typing):

  • 在Python中,接口的概念主要通过鸭子类型来体现。如果一个对象具有特定的方法或属性,它就被视为实现了某个“接口”,而无需显式声明。
  • 鸭子类型的核心思想是“如果它走起来像鸭子、叫起来像鸭子,那么它就是鸭子”。
  • 通过鸭子类型,Python强调对象的行为而不是其类型。

抽象基类(ABCs):

  • 抽象基类是Python中一种显式定义接口的方式,位于abc模块中。
  • 抽象基类通过继承ABC类和使用@abstractmethod装饰器来定义一组必须在子类中实现的抽象方法。
  • 这样,抽象基类就成为了一种接口定义,规范了子类应该具有的方法。
from abc import ABC, abstractmethod

class MyInterface(ABC):
    @abstractmethod
    def method1(self):
        pass

    @abstractmethod
    def method2(self):
        pass

实现接口:

  • 在Python中,实现接口的概念主要是指某个类继承了包含抽象方法的抽象基类,并在子类中实现了这些抽象方法。
  • 实际上,任何具有相同方法签名(方法名称和参数列表)的类都可以被视为实现了相同的接口。
class MyClass(MyInterface):
    def method1(self):
        print("Implementing method1")

    def method2(self):
        print("Implementing method2")

检查接口实现:可以使用isinstance函数检查对象是否实现了特定的接口。这样可以在运行时检查对象是否符合预期的接口规范。

obj = MyClass()

if isinstance(obj, MyInterface):
    print("obj implements MyInterface")
else:
    print("obj does not implement MyInterface")

接口的作用:

  • 提供一种规范和契约,明确了类或对象应该提供哪些方法和属性。
  • 支持代码的可读性和可维护性,帮助开发者理解和使用他人的代码。
  • 在一定程度上提供了运行时的类型检查,预防一些错误。

虽然Python中的接口是通过鸭子类型和抽象基类来实现的,而非像其他语言那样有明确的interface关键字,但它们为Python提供了一种灵活而强大的方式来定义和使用接口。

异常处理

在面向对象编程中,异常处理是通过使用 try, except, finally, 和 raise 等关键字来实现的。以下是处理异常的一些方法:

使用 try 和 except 块捕获异常

try:
    # 有可能抛出异常的代码
    result = 10 / 0
except ZeroDivisionError as e:
    # 处理特定类型的异常
    print(f"Error: {e}")
except Exception as e:
    # 处理其他类型的异常
    print(f"An error occurred: {e}")
else:
    # 如果没有异常发生时执行的代码块
    print("No exceptions occurred.")
finally:
    # 无论是否发生异常都会执行的代码块
    print("This is the finally block.")

抛出异常(raise):

class CustomError(Exception):
    pass

def my_function(value):
    if value < 0:
        raise CustomError("Value must be non-negative.")

try:
    my_function(-1)
except CustomError as e:
    print(f"Caught custom error: {e}")

自定义异常类:

class CustomError(Exception):
    def __init__(self, message="Default error message"):
        self.message = message
        super().__init__(self.message)

try:
    raise CustomError("This is a custom error.")
except CustomError as e:
    print(f"Caught custom error: {e}")

使用 assert 断言:

def divide(a, b):
    assert b != 0, "Cannot divide by zero."
    return a / b

try:
    result = divide(10, 0)
except AssertionError as e:
    print(f"Assertion error: {e}")

使用 with 语句和上下文管理器:

class FileHandler:
    def __enter__(self):
        # 打开文件或资源
        print("File opened.")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        # 关闭文件或资源
        print("File closed.")

with FileHandler() as file:
    # 对文件进行操作
    print("File in use.")
    # 有可能抛出异常的代码
    result = 10 / 0

这些方法可以根据实际需求和程序的结构选择合适的方式处理异常。重要的是要捕获和处理可能发生的异常,以确保程序的健壮性和可维护性。

参考链接:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注