• Python3.11教程5:类与对象


    十、python的类与对象

    参考:Python 3.11官方文档《类》《datawhale——PythonLanguage》《Python3 面向对象》

    10.1 面向对象编程

      面向对象编程(Object-Oriented Programming,OOP)是一种编程范式或编程方法,在许多编程语言中得到广泛应用,包括Python、Java、C++、C#等。它以对象为核心,将数据和操作数据的方法(属性和方法)封装在一起,以模拟现实世界中的事物和交互。

      OOP 的主要思想是将问题分解为对象,并通过这些对象之间的互动来解决问题,有助于构建更清晰、可维护和可扩展的代码。以下是面向对象编程的关键概念:

    • 对象(Object):对象是现实世界中的实体或事物的抽象表示,是类的实例。它包括属性(数据)和操作数据的方法。对象可以是物理实体(例如,汽车、手机)或概念实体(例如,用户、订单)。

    • 类(Class):类是对象的模板或结构,它定义了对象的属性和方法,它规定了对象应该有哪些特征和行为。类具有三大特性——封装、继承和多态。

      1. 封装(Encapsulation):封装是将数据(属性)和操作数据的方法(方法)捆绑在一起的概念。它隐藏了对象内部的细节,只提供了有限的接口供外部访问。封装使得对象的实现细节可以随时更改,而不会影响使用该对象的代码,这提高了数据的安全性和代码的可维护性

      2. 继承(Inheritance)继承允许一个子类继承另一个父类的属性和方法。子类可以重用父类的代码,并可以在其基础上进行扩展或修改。继承建立了类之间的层次关系。

      3. 多态(Polymorphism)

        • 同一个方法可以在不同的类中具有不同的实现,即子类可以重写父类的方法,来实现不同的功能
        • 多态实现了方法的动态绑定,提高了灵活性。你可以通过统一的接口来处理不同的对象,这样可以编写通用的代码,减少了代码的复杂性。

      以上这三个特性一起构成了面向对象编程的基础,它们提供了一种有效的方法来组织和管理复杂的代码,使得代码更易于理解、维护和扩展。通过封装、继承和多态,可以创建具有高内聚性和低耦合性的代码,提高了代码的质量和可重用性。

    	class Animal:
    	    def __init__(self, name):
    	        self.name = name
    	
    	    def speak(self):
    	        pass
    	
    	class Dog(Animal):
    	    def speak(self):
    	        return f"{self.name} says Woof!"
    	
    	class Cat(Animal):
    	    def speak(self):
    	        return f"{self.name} says Meow!"
    	
    	# 创建不同类型的动物对象
    		my_dog = Dog("Buddy")
    		my_cat = Cat("Whiskers")
    		
    	# 使用继承的方法
    	print(my_dog.speak())  					  	 # 输出: Buddy says Woof!
    	print(my_cat.speak()) 						 # 输出: Whiskers says Meow!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

      在上述示例中,我们定义了一个基类 Animal,它有一个 speak 方法,然后创建了两个子类 DogCat,它们继承了 Animal 类,并覆盖了 speak 方法以提供自己的实现。这展示了继承和多态的概念,子类可以继承父类的属性和方法,并可以在不同的子类中进行自定义。

    最后总结一下OOP 的优点:

    • 模块化:将问题分解为对象,使得代码更易于理解和维护。
    • 可重用性:可以创建通用的类和方法,以便在不同的项目中重复使用。
    • 扩展性:可以通过添加新的类和方法来扩展功能,而不必修改现有代码。
    • 抽象性:允许从现实世界中的概念中提取出抽象类和对象,使得问题的建模更接近实际情况。

    10.2 类与对象

      类与对象是面向对象编程的核心概念,类的三大特性就是封装、继承和多态。在Python中,可以使用关键字 class 来定义一个类。然后,使用构造函数 __init__ 来初始化对象的属性,例如:

    class Dog:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
    # 创建一个Dog类的对象
    my_dog = Dog("Buddy", 3)
    
    # 访问对象的属性
    print(f"My dog's name is {my_dog.name}.")
    print(f"My dog is {my_dog.age} years old.")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

      这段代码定义了一个名为Dog的类,该类具有nameage属性。我们创建了一个名为my_dog的对象,并访问了它的属性。

      请注意,self 是一个特殊的参数,它指代对象本身。在类的方法中,通常会看到 self,以便访问和操作对象的属性。

    10.2.1 类与对象的基本概念
    1. 属性: 在类的声明中,属性是用变量来表示的,这些属性用于存储对象的数据。

    2. 类属性(Class Attributes)或类变量(Class Variables):定义在类里面、方法外面的属性

    3. 实例属性(Instance Attributes)或实例变量(Instance Variables):定义在类的方法里面的属性

      class Student:
          school_name = "ABC School"  		 # 类属性
      
          def __init__(self, name, grade):
              self.name = name  				 # 实例属性
              self.grade = grade  		  	 # 实例属性
      
      # 创建两个学生对象
      student1 = Student("Alice", 10)
      student2 = Student("Bob", 8)
      
      # 访问类属性
      print(Student.school_name) 				 # 输出: ABC School
      
      # 访问实例属性
      print(student1.name)  					 # 输出: Alice
      print(student2.grade)  				     # 输出: 8
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    4. 方法(Methods):类当中定义的函数称之为方法,它们定义了对象可以执行的操作,并使用self作为第一个参数,以引用对象实例。

    5. 方法重写:当一个子类继承自一个父类,并且在子类中定义了与父类同名的方法或属性时,子类中的方法会自动覆盖(重写)父类的方法。这意味着在子类中调用该方法时,将使用子类中的方法实现,而不是父类中的方法。

    6. 特殊方法(Special Methods): 以双下划线开头和结尾的方法称之为特殊方法,如__init____str__,它们会在特定的情况下由Python调用。

    7. 构造函数(Constructor):构造函数是一个特殊的方法,通常称为__init__,它用于初始化对象的属性。

    8. 类方法(Class Methods)

      • 类方法是一个装饰器,通常称为@classmethod
      • 它们是属于类而不是对象实例的方法。
      • 类方法可以访问和修改类属性,但不能访问实例属性。
    9. 静态方法(Static Methods)

      • 静态方法是一个装饰器,通常称为@staticmethod
      • 它们是不属于类或对象实例的方法,与类无关。
      • 静态方法通常用于实现与类有关的功能,但不需要访问类或对象状态。
    10. 实例化:创建一个类的实例,即类的具体对象。

    10.2.2 类属性和实例属性区别及其访问方式
    1. 类属性(Class Attributes)或类变量(Class Variables)

      • 类属性是定义在类级的属性(不是实例级别),通常在类的顶部定义,即定义在类里面、方法外面的属性
      • 类属性类似全局变量,在整个类中都是共享的,对所有类的实例都相同
      • 类里面:通过类名self来调用类属性
      • 类外面:推荐通过类名来访问,而不是使用类实例来访问。另外通常不推荐在类外面修改类属性,因为这会影响所有对象实例。
      • 修改类属性时,如果使用实例对象进行修改,实际上是创建了一个与类属性同名的实例属性,而不是修改类属性本身。这会导致该实例对象有一个与类属性同名的实例属性,但不会影响其他实例或类属性。
        class MyClass:
            class_attr = 100  # 类属性
        
        # 创建一个类对象
        obj1= MyClass()
        
        # 通过实例对象修改类属性
        obj1.class_attr = 200
        
        # 访问类属性和实例属性
        print("类属性:", MyClass.class_attr) 		 # 输出: 100 (类属性未被修改)
        print("实例属性:", obj1.class_attr)    	 	 # 输出: 200 (实际上是一个实例属性)
        
        # 创建另一个对象
        obj2 = MyClass()
        
        # 访问类属性和实例属性
        print("新对象的类属性:", obj2.class_attr) 		 # 输出: 100 (类属性未被修改)
        
        MyClass.class_attr=300
        print("新对象的类属性:", obj2.class_attr) 
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        类属性: 100
        实例属性: 200
        新对象的类属性: 100
        新对象的类属性: 300
        
        • 1
        • 2
        • 3
        • 4
    2. 实例属性(Instance Attributes)或实例变量(Instance Variables)

      • 定义在类的方法里面的属性,是对象实例的属性
      • 每个对象实例都可以具有不同的实例属性值。
      • 类里面:只能通过self调用实例属性,因为self是谁调用,它的值就属于该对象
      • 类外面:只能通过实例对象来访问和修改实例属性

      总的来说,类属性应该通过类名来访问或修改,实例属性只能通过实例对象。另外要注意的是,属性与方法名相同,属性会覆盖方法,因此在编写代码时需要小心使用相同的名称,这会导致冲突。

    class MyClass:
        def __init__(self):
            self.my_attribute = "This is an attribute."
    
        def my_method(self):
            return "This is a method."
    
    # 创建一个MyClass对象
    my_object = MyClass()
    
    # 访问属性和方法
    print(my_object.my_attribute)  			# 访问属性
    print(my_object.my_method())   			# 调用方法
    
    # 覆盖方法
    my_object.my_method = "This is now an attribute, not a method."
    
    # 再次尝试访问属性和方法
    print(my_object.my_attribute)  			# 访问属性
    print(my_object.my_method)     			# 访问覆盖后的属性
    print(my_object.my_method()) 			# TypeError: 'str' object is not callable
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    This is an attribute.
    This is a method.
    This is an attribute.
    This is now an attribute, not a method.
    TypeError: 'str' object is not callable
    
    • 1
    • 2
    • 3
    • 4
    • 5

      在这个示例中,我们创建了一个名为MyClass的类,其中包含一个属性my_attribute和一个方法my_method。然后,我们创建了一个my_object对象,并访问了属性和方法。

      但接下来,我们将my_method属性设置为字符串,覆盖了原来的方法。因此,当我们再次访问my_method时,它实际上是一个字符串属性,而不再是一个方法。这导致属性覆盖了方法,使得原来的方法不再可用。

    10.2.3 self 代表类的示例

      类的方法与普通的函数只有一个区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。

    class Test:
        def prt(self):
            print(self)
            print(self.__class__)
     
    t = Test()
    t.prt()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    <__main__.Test instance at 0x100771878>		 
    __main__.Test
    
    • 1
    • 2

      上面第一行结果是对象 t 的字符串表示形式,其在内存中的地址为 0x100771878,即self代表的是类的实例。第二行显示了对象 t 所属的类的名称,即 Test 类。

      从这个例子可以看出,self 在类中的作用是指代当前对象实例,实现封装和面向对象编程的基本原则:

    1. 指代当前实例: self 允许你在类的方法内部引用和操作当前对象实例的属性和方法

    2. 实现封装: 使用 self 可以将对象的状态和行为封装在一起。这意味着你可以在类的内部定义属性和方法,同时可以确保这些属性和方法只对对象实例可见和可操作。

    3. 实现方法调用: 当你调用类的方法时,需要告诉方法是哪个对象在调用它。self 在方法的定义中提供了这个信息,使得方法能够正确地操作调用它的对象。

    self 不是 python 关键字,你可以用其他名称代替,但习惯上都使用 self

    # 这段代码输出结果一样
    class Test:
        def prt(runoob):
            print(runoob)
            print(runoob.__class__)
     
    t = Test()
    t.prt()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    10.3 继承

    10.3.1 构造函数的继承

      构造函数__init__()的主要作用是初始化对象的属性(实例化类时自动调用),确保对象在创建时具有适当的初始状态。

      当子类继承父类时,子类通常希望继承并初始化父类的属性,可以通过在子类的构造函数中调用父类的构造函数来实现,这分两种情况。

    1. 默认继承父类构造函数
      如果子类没有显式定义构造函数(__init__方法),它会默认继承父类的构造函数来初始化对象。这个默认的继承行为确保子类可以继承父类的属性和方法。例如:
    class Parent:
        def __init__(self):
            self.attribute = 42
    
    class Child(Parent):
        pass
    
    # 创建子类对象
    child_obj = Child()
    
    # 子类对象可以访问父类和子类的属性
    print(child_obj.attribute) 				# 输出42
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. 改写子类构造函数
      如果显式地定义子类的构造函数,它将覆盖默认的继承行为。通常建议在覆写构造函数时,同时调用父类的构造函数,以确保父类的初始化逻辑也被执行。调用方式有两种:

      • 使用 super().__init__()
        在Python中,可以使用 super().__init__()来调用父类的构造函数。这种方式会自动识别父类,并且不需要显式指定父类的名称。
      • 显式调用父类的构造函数
        在某些编程语言中,或者如果你希望显式指定父类的名称,你可以使用 父类名.__init__(self, ...) 来调用父类的构造函数。这种方式需要明确指定父类的名称。

      注意,super().__init__()只能调用一个父类的构造函数,如果是多继承的情况,推荐使用第二种方式来调用父类的构造函数。

    下面是一个复杂的示例,演示了如何在子类中继承并初始化父类的属性:

    # 定义一个父类 People
    class People:
        name = '' 											    # 类属性,用于存储人名
        age = 0    												# 类属性,用于存储年龄
        __weight = 0											# 定义私有属性,私有属性在类外部无法直接进行访问
    
        # 构造方法,初始化对象的属性
        def __init__(self, n, a, w):
            self.name = n         								# 实例属性,存储人名
            self.age = a          								# 实例属性,存储年龄
            self.__weight = w     								# 实例属性,存储体重
    
    	    
        def speak(self):										# 定义一个方法,用于输出人的信息
            print("%s 说: 我 %d 岁。" % (self.name, self.age))
    
    # 定义一个子类 Student,继承自 People
    class Student(People):
        grade = ''  											# 类属性,用于存储年级
        
        def __init__(self, n, a, w, g):							# 构造方法,初始化学生对象的属性
            # 调用父类 People 的构造方法进行属性的初始化
            super().__init__(n, a, w)  							# 或者使用 People.__init__(self, n, a, w)
            self.grade = g  									# 实例属性,存储年级
    
        # 覆写(重写)父类的方法
        def speak(self):
            print("%s 说: 我 %d 岁了,我在读 %d 年级" % (self.name, self.age, self.grade))
    
    # 创建一个 Student 类的对象 s,传入姓名、年龄、体重和年级
    s = Student('小马的程序人生', 10, 60, 3)
    s.speak()  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    小马的程序人生 说:10 岁了,我在读 3 年级
    
    • 1

      在这个示例中,子类 student 继承了父类 people的属性,但子类也可以有自己的属性,如 grade

      在子类 student 的构造方法 __init__(self, n, a, w, g) 中,首先通过 super().__init__(n, a, w) 来调用父类 people 的构造方法,传递了相同的参数,以便父类初始化这些属性。这是为了确保子类对象同时拥有父类和子类的属性。

      如果没有调用父类 people 的构造函数,则会发生以下问题:

    1. 父类属性不会被初始化:父类 people 中的属性 nameage 和私有属性 __weight 不会被初始化。这意味着子类 student 的对象将不具有这些属性的初始值
    2. 可能导致错误或不一致的行为:如果子类 student 的方法或其他代码依赖于这些属性的初始值,那么没有初始化这些属性可能导致错误或不一致的行为。例如,在 student 类的 speak 方法中,如果使用了 self.nameself.age,并且这些属性没有被初始化,将导致 NameErrorAttributeError 等错误。

      总之,不调用父类的构造方法会导致父类属性未初始化,可能会破坏类的一致性,因此通常在子类的构造方法中应该调用父类的构造方法,以确保父类和子类的属性得到正确的初始化。

    10.3.2 多继承中方法的解析顺序

      当一个类继承自多个父类时,如果这些父类中有相同名字的方法,而子类又没有指定使用哪个父类的方法时,解析顺序是从左到右,即使用靠前的父类的方法。

    class A:
        def speak(self):
            print("A speaks")
    
    class B:
        def speak(self):
            print("B speaks")
    
    class C(A, B):
        pass
    
    my_c = C()
    my_c.speak()  # 输出: "A speaks",因为 C 继承自 A 和 B,但 A 在继承列表的左边,所以优先使用 A 的方法
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

      在这个示例中,类 C 继承自两个父类 A 和 B,但由于 A 在继承列表的左边,所以 C 中的 speak 方法使用了 A 类的方法。

    10.3.3 多继承的注意事项

      多继承(组合继承)允许子类同时继承多个父类的属性和方法。这种继承方式结合了类继承和对象组合(将其他类的对象作为成员属性)两种方式,以实现更多灵活性和复用性。以下是一些组合继承的注意事项和示例:

    1. 潜在的方法冲突: 如果多个父类中具有相同名称的方法,子类可能会在调用时出现方法冲突。在这种情况下,子类必须明确指定要调用的方法,或者通过重写方法来解决冲突。

    2. 构造函数的调用: 子类通常需要在其构造函数中调用每个父类的构造函数,以确保父类的属性正确初始化。

      super() 默认只调用一个父类的构造函数,如果使用 super().__init__(),它将只调用第一个父类的构造函数

    3. 深度继承链: 当使用多层次的组合继承时,需要小心继承链变得过于复杂,可能会导致不必要的复杂性和性能问题。尽量保持继承链的层次不要过深。

      假设我们有两个父类,AnimalMachine,它们分别代表动物和机器的特征和行为。然后,我们创建一个子类 Robot,它同时继承了这两个父类的属性和方法。

    # 定义 Animal 类
    class Animal:
        def __init__(self, name):
            self.name = name
    
        def speak(self):
            pass
    
    # 定义 Machine 类
    class Machine:
        def __init__(self, model):
            self.model = model
    
        def start(self):
            pass
    
    # 定义 Robot 类,同时继承 Animal 和 Machine
    class Robot(Animal, Machine):
        def __init__(self, name, model):
            # 方式1:显示调用父类的构造函数,初始化属性.
            Animal.__init__(self, name)
            Machine.__init__(self, model)
    
        def speak(self):
            return f"{self.name} says 'Beep boop!'"
    
        def start(self):
            return f"{self.model} starts working."
    
    # 创建 Robot 实例
    my_robot = Robot("Robo", "R2000")
    
    # 使用继承的属性和方法
    print(my_robot.name)   				 				 # 输出:Robo
    print(my_robot.model)   		  					 # 输出:R2000
    print(my_robot.speak()) 							 # 输出:Robo says 'Beep boop!'
    print(my_robot.start())  							 # 输出:R2000 starts working.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

      在上述示例中,Robot 类同时继承了 AnimalMachine 两个父类的属性和方法,然后定义了自己的属性和方法。子类初始化也可以写成:

    class Robot(Animal, Machine):
        def __init__(self, name, model):
            # 方式2:直接初始化属性,不调用父类的初始化函数。推荐第一种方式
            self.name=name
            self.model=model
    
    • 1
    • 2
    • 3
    • 4
    • 5

    10.4 私有和公有

    10.4.1 私有属性

      私有属性是指在类中以双下划线 __ 开头命名的属性,它在类的外部无法直接访问。私有属性具有以下特点:

    1. 增强数据的封装性: 私有属性在类的外部无法直接访问,所以它可以隐藏类内部的实现细节,使外部代码无法直接修改属性的值,提高了对数据的保护。通常,只允许通过类的方法来访问或修改这些属性。

    2. 名称重整: 在Python中,私有属性的名称会被重整,即会在属性名称前加上一个下划线和类名。例如,一个名为 __weight 的私有属性在类 People 中,其实际名称会被重整为 _People__weight,这避免子类意外地覆盖父类的私有属性。

    3. 仍然可以访问: 你可以通过类的方法来访问和修改私有属性,这提供了一定程度的控制,以确保属性的访问和修改是通过类的接口进行的,从而确保数据的完整性和一致性。

      # 一个电子设备类可能具有内部状态属性,但用户不需要知道这些细节
      
      class ElectronicDevice:
          def __init__(self):
              self.__is_on = False  # 私有属性
      
          def turn_on(self):
              # 打开设备方法
              self.__is_on = True
      
          def turn_off(self):
              # 关闭设备方法
              self.__is_on = False
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      你可以通过重整的私有属性名(例如_People__weight)在类之外直接访问私有属性,但一般不建议这么做,因为这违反了封装的原则。建议只通过类的公有方法来访问和修改私有属性,而不是直接在类外部访问它们。这样做有以下好处:

    1. 安全性: 通过公有方法,你可以在内部控制访问私有属性的逻辑,确保数据的合法性和安全性。如果直接在外部访问私有属性,就失去了这种控制能力,可能导致不可预测的错误。
    2. 可维护性: 当你需要修改类的内部实现时,如果使用了公有方法来访问属性,你可以在不影响外部代码的情况下更改内部实现。如果外部代码直接访问了私有属性,那么任何内部实现的更改都可能导致外部代码的破坏。
    3. 文档和接口: 公有方法提供了类的接口,它们可以被文档化,让其他开发人员更容易理解如何正确使用类。直接访问私有属性会使类的接口变得模糊,降低了代码的可读性。
    4. 继承和子类化: 在子类中,你可以覆盖父类的方法,而不必担心破坏父类的内部状态。如果直接访问父类的私有属性,可能会导致不希望的副作用。
    10.4.2 私有方法

      同理私有方法是在类中以双下划线 __ 开头命名的方法,它在类的外部无法被调用,它也具有一些特点:

    • 增强数据的封装性: 私有方法将一些类内部的操作和逻辑隐藏在类的内部,不让外部代码访问,从而提高类的封装性。
    • 提供辅助功能: 私有方法可以用于执行一些辅助功能和操作,使公有方法更专注于核心功能,以提供更清晰和简洁的公有方法接口。
    • 防止不合法调用: 私有方法通常用于执行类的内部操作,不允许外部代码直接调用。

      你可以通过重整的私有方法名(例如_People__method)在类之外直接访问私有方法,但也不建议这么做。

    class BankAccount:
        def __init__(self, initial_balance):
            self.__balance = initial_balance  		# 私有属性,表示账户余额
    	
    	# 存款方法,控制访问私有属性
        def deposit(self, amount):        
            if amount > 0:
                self.__balance += amount
                
    	# 取款方法,控制访问私有属性
        def withdraw(self, amount):        
            if 0 < amount <= self.__balance:
                self.__balance -= amount
    
        def get_balance(self):
            return self.__balance
    	
    	# 私有方法,记录交易日志
        def __log_transaction(self, transaction_type, amount):        
            print(f"{transaction_type}: ${amount}")
    
    # 创建银行账户对象
    account = BankAccount(1000)
    
    # 使用公有方法进行操作
    account.deposit(500)
    account.withdraw(200)
    
    # 无法直接访问私有属性和私有方法
    # print(account.__balance)  					 # 报错:AttributeError: 'BankAccount' object has no attribute '__balance'
    # account.__log_transaction("Deposit", 500) 	 # 报错:AttributeError: 'BankAccount' object has no attribute '__log_transaction'
    
    account.get_balance()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    1300
    
    • 1

      在上面的示例中,银行账户的余额是敏感信息,通过将BankAccount 类的 __balance 设为私有属性,将 __log_transaction()设为私有方法,隐藏了账户余额和交易日志的实现细节,只能通过公有方法来访问和记录交易,这提供了更好的封装和安全性。

    10.4.3 父类的私有属性和私有方法

      在Python中,子类是无法直接继承父类的私有属性和私有方法。私有属性和私有方法是被设计为在类内部使用的,不会被子类继承。这是Python中封装的一部分,目的是防止子类意外地修改或访问父类的私有成员。

    class Parent:
        def __init__(self):
            self.__private_attribute = 42
    
        def __private_method(self):
            print("This is a private method in Parent")
    
    class Child(Parent):
    	# 不显示地定义构造函数,也会继承父类的属性,但不会继承其私有属性
        def access_parent_private(self):
            # 子类无法直接继承父类的私有属性和方法
            # 这里实际上是创建了一个新的子类私有属性和方法
            print(self.__private_attribute)  # 这是一个新的子类私有属性
            self.__private_method()  # 这是一个新的子类私有方法
    
    child = Child()
    child.access_parent_private()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    AttributeError: 'Child' object has no attribute '_Child__private_attribute'
    
    • 1

      上面这段代码中,因为子类并不能继承父类的私有属性好私有方法,所以access_parent_private函数中的调用,会被Python认为是子类Child的私有属性和私有方法,但这些在子类中我们没有定义,所以会报错AttributeError

      不过,私有属性可以通过类的方法来访问,所以子类如果想访问父类的私有属性,可以在父类中加入调用其私有属性的方法,并继承给子类。

    class Parent:
        def __init__(self, value):
            self.__private_attribute = value				# 父类私有属性
    
        def get_private_attribute(self):
            return self.__private_attribute					# 父类方法调用其私有属性
    
    class Child(Parent):
        def __init__(self, value_child, value):
            super().__init__(value)  						# 继承父类构造函数并传入父类的参数
            self.__private_attribute_child = value_child    # 子类私有属性
    
        def get_child_attributes(self):
        	# 返回子类私有属性,以及继承的父类方法来调用父类的私有属性
            return self.__private_attribute_child, self.get_private_attribute()
    
    child_obj = Child(42, 100)
    
    # 访问子类和父类的私有属性
    child_attr, parent_attr = child_obj.get_child_attributes()
    print("子类私有属性:", child_attr)  						
    print("父类私有属性:", parent_attr)  					
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    子类私有属性: 42
    父类私有属性: 100
    
    • 1
    • 2

    10.5 魔法方法(特殊方法)

    10.5.1 魔法方法简介

      魔法方法(Magic Methods),也称为双下划线方法或特殊方法,是Python中的一类特殊方法,它们以双下划线开头和结尾,例如__init____str____eq__等。这些方法有特殊的用途和行为,用于自定义类的行为和操作,会在特定的时候被调用,它们和普通方法的差异,主要在于其调用方式和用途:

    1. 调用方式:普通方法是通过对象实例调用的,而魔法方法通常由Python解释器在特定的情况下自动调用。例如,__init__方法是在创建对象时由Python自动调用的,普通方法则是根据需要在代码中显式调用的。

    2. 用途:魔法方法用于实现对象的特殊行为和操作,例如初始化对象、自定义对象的字符串表示、支持对象的比较、支持算术操作等。它们允许你覆盖默认的操作,以满足你的特定需求。普通方法是类的一般行为,用于实现类的功能。

      总的来说,魔法方法使你的类可以模拟内置数据类型的行为,从而使代码更具表现力和可读性。通过实现适当的魔法方法,你可以让你的自定义对象更自然地与Python语言和标准库进行交互。以下是一些常见的魔法方法

    魔法函数作用示例作用
    __init__(self, ...)初始化对象的属性def __init__(self, ...)obj = MyClass(...)
    __del__()用于在对象销毁前执行清理操作,例如关闭文件,释放资源,通常不需要手动指定def __del__(self, ...)
    字符串魔法方法作用示例作用
    __str__(self)返回对象的字符串表示def __str__(self)str(obj)
    容器操作魔法方法作用示例作用
    __getitem__(self, key)获取对象的元素def __getitem__(self, key)obj[key]
    __setitem__(self, key, value)设置对象的元素def __setitem__(self, key, value)obj[key] = value
    __delitem__(self, key)删除对象的元素def __delitem__(self, key)del obj[key]
    __len__(self)返回对象的长度def __len__(self)len(obj)
    __iter__(self)返回对象的迭代器def __iter__(self)iter(obj)
    __next__(self)返回迭代器的下一个元素def __next__(self)next(iterator)
    __contains__(self, item)检查对象是否包含某个元素def __contains__(self, item)item in obj
    比较&运算魔法方法作用示例作用
    __eq__(self, other)比较两个对象是否相等def __eq__(self, other)obj1 == obj2
    __ne__(self, other)比较两个对象是否不相等def __ne__(self, other)obj1 != obj2
    __lt__(self, other)比较两个对象是否小于def __lt__(self, other)obj1 < obj2
    __le__(self, other)比较两个对象是否小于等于def __le__(self, other)obj1 <= obj2
    __gt__(self, other)比较两个对象是否大于def __gt__(self, other)obj1 > obj2
    __ge__(self, other)比较两个对象是否大于等于def __ge__(self, other)obj1 >= obj2
    算术操作魔法方法作用示例作用
    __add__(self, other)实现对象的加法操作def __add__(self, other)obj1 + obj2
    __sub__(self, other)实现对象的减法操作def __sub__(self, other)obj1 - obj2
    __mul__(self, other)实现对象的乘法操作def __mul__(self, other)obj1 * obj2
    __div__(self, other)实现对象的除法操作def __div__(self, other)obj1 / obj2
    __pos__(self)正号操作符,用于实现正号操作+obj
    __neg__(self)负号操作符,用于实现负号操作-obj
    __abs__(self)绝对值操作,用于实现绝对值操作abs(obj)
    __invert__(self)按位求反操作符,用于实现按位求反~obj

      这些是一些常见的魔法方法,它们可以帮助你自定义类的行为,使其更符合你的需求。你可以根据需要选择性地实现这些方法,以改变类的默认行为。

    10.5.2 容器操作魔法方法

    容器对象分为可变(Mutable)和不可变(Immutable),根据需要来实现的魔法方法:

    1. 不可变容器:如果你希望你的容器对象是不可变的(元组、字符串),也就是说一旦创建就不能修改其内容,那么只需要定义两个魔法方法:

      • __len__():这个方法用于返回容器中元素的数量(长度)。
      • __getitem__(self, key):这个方法用于获取容器中指定位置的元素。

      不可变容器通常用于表示一组数据,这些数据在创建后不能被更改,比如元组(tuple)。

    2. 可变容器:如果你希望你的容器对象是可变的(列表、字典),也就是说你可以修改、添加、删除容器中的元素,那么除了上述两个方法,还需要定义以下两个魔法方法:

      • __setitem__(self, key, value):这个方法用于设置容器中指定位置的元素的值。
      • __delitem__(self, key):这个方法用于删除容器中指定位置的元素。

    示例:编写一个可改变的自定义列表,要求记录列表中每个元素被访问的次数。

    class CountList:
        def __init__(self, *args):
            self.values = [x for x in args]
            # fromkeys(iterable, value) 是字典的一个方法,前者是字典的键,后者是所有键的初始值
            # 创建了一个字典,其中的键是从 0 到 len(self.values) - 1 的整数,所有的值都被初始化为 0
            self.count = {}.fromkeys(range(len(self.values)), 0)
    
        def __len__(self):
            return len(self.values)
    
        def __getitem__(self, item):
            self.count[item] += 1
            return self.values[item]
    
        def __setitem__(self, key, value):
            self.values[key] = value
    
        def __delitem__(self, key):
            del self.values[key]
            for i in range(0, len(self.values)):
                if i >= key:
                    self.count[i] = self.count[i + 1]
            self.count.pop(len(self.values))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    c1 = CountList(1, 3, 5, 7, 9)
    c2 = CountList(2, 4, 6, 8, 10)
    print(c1[1],c2[2])  				# 3,6
    
    c2[2] = 12
    print(c1[1] + c2[2])  				# 15
    print(c1.count)						# {0: 0, 1: 2, 2: 0, 3: 0, 4: 0}
    print(c2.count)						# {0: 0, 1: 0, 2: 2, 3: 0, 4: 0}
    del c1[1]
    print(c1.count)						# {0: 0, 1: 0, 2: 0, 3: 0}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    10.5.3 迭代器
    10.5.3.1 迭代器协议

      在Python中,迭代器(Iterator)是一种用于遍历可迭代对象(Iterable)元素的对象,而可迭代对象是那些可以被迭代(遍历)的对象,如列表、元组、字典、集合等。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

      迭代器是一种特殊的对象,它有两个基本的方法——iter()next(),这是通过实现魔法方法 __iter__()__next__()来实现的。

    1. __iter__() 方法:这是迭代器必须实现的方法,用于返回一个带有 __next__() 方法的迭代器对象

    2. __next__() 方法:用于获取迭代器中的下一个元素。遍历结束后再次调用会 引发 StopIteration 异常,表示迭代结束。

    3. iter(object) :用于生成迭代器,通常由 for 循环隐式调用。当你使用 iter() 函数来获取一个可迭代对象的迭代器时,实际上就是调用了该对象的 __iter__() 方法,从而获取了迭代器。

    4. next(iterator, default) :内置函数,用来显式调用迭代器对象的 __next__() 方法,从而逐一获取容器中的元素。

      • iterator:要获取下一个元素的迭代器对象。
      • default(可选):如果迭代器已遍历完最后一个元素,则返回 default 值(可以是任何合法的Python对象)。如果不提供 default 参数,那么继续会引发 StopIteration 异常。

      next() 函数中的 default 参数可以是任何合法的 Python 数据类型,例如整数、浮点数、字符串、 列表、元组、集合、字典等数据结构,以及自定义对象(只要是合法的)。

      下面是一个简单的示例,演示了如何使用类创建一个迭代器。

    # 定义一个自定义迭代器类 MyIterator
    class MyIterator:
        def __init__(self, start, end):
            self.current = start  							# 初始化当前值为起始值
            self.end = end        							# 存储结束值
    
        # 实现 __iter__() 方法,返回迭代器对象本身
        def __iter__(self):
            return self
    
        # 实现 __next__() 方法,用于获取下一个元素
        def __next__(self):        
            if self.current < self.end:						# 如果当前值小于结束值,生成下一个元素
                result = self.current  						# 保存当前值到 result
                self.current += 1      						# 更新当前值为下一个值
                return result          						# 返回当前值
            else:
                raise StopIteration    						# StopIteration 用于标识迭代的完成,防止出现无限循环的情况
    
    my_iterator = MyIterator(1, 5)
    
    # 使用迭代器遍历元素
    for item in my_iterator:
        print(item)  										# 输出1,2,3,4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

      定义 __iter__() 方法是为了返回一个带有 __next__() 方法的对象。 如果类已定义了 __next__(),而且这个方法返回了下一个迭代值,那么 __iter__() 可以简单地返回 self,因为这个类的实例本身已经充当了迭代器。

    10.5.3.2 for 循环原理

      大多数内置的可迭代对象(如字符串、列表、元组、集合、字典的键、字典的值)都支持迭代器功能,因此您可以使用 iter()next() 函数来显示地进行迭代。

    my_list = [1, 2, 3]
    my_iterator = iter(my_list)  # 获取列表的迭代器
    
    print(next(my_iterator))  # 获取下一个元素,输出:1
    print(next(my_iterator))  # 获取下一个元素,输出:2
    print(next(my_iterator))  # 获取下一个元素,输出:3
    
    print(next(my_iterator))  # 迭代器耗尽,再次调用next会引发StopIteration异常
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    你也可以设置 default 参数,例如:

    print(next(my_iterator,0))						# 输出:0
    print(next(my_iterator,"No more items"))		# 输出:"No more items"
    print(next(my_iterator,[1, 2, 3]))				# 输出:[1, 2, 3]
    
    • 1
    • 2
    • 3

    但通常情况下,使用 for 循环通常更加简洁和直观。在使用for循环时:

    1. 当你使用 for 语句遍历一个容器对象时,例如 for item in container,Python 首先会在容器对象上调用 iter() 函数。

    2. iter() 函数返回一个迭代器对象,这个迭代器对象包含了一个特殊的方法 __next__(),用于逐一访问容器中的元素。

    3. 在每次循环迭代中,for 循环会自动调用迭代器对象的 __next__() 方法,从而实现逐一访问容器中的元素。当迭代到容器末尾时,__next__() 方法会引发 StopIteration 异常,告知循环停止迭代。

      这个过程是 Python 遍历容器对象的基本机制,它使得 for 循环可以用来迭代许多不同类型的数据结构,而无需知道底层实现的细节。

    10.5.3.3 自定义迭代行为

      用户可以轻松地自定义对象的迭代行为,只需实现 __iter__()__next__() 方法即可。下面是一个用于生成斐波那契数列的自定义迭代器:

    # 定义一个名为 Fibs 的自定义迭代器类,用于生成斐波那契数列
    class Fibs:
        def __init__(self, n=10):
            self.a = 0           							 # 初始化第一个斐波那契数为 0
            self.b = 1           							 # 初始化第二个斐波那契数为 1
            self.n = n          		   					 # 存储生成斐波那契数列的上限值 n
    
        def __iter__(self):
            return self
    
        # 实现 __next__() 方法,用于生成下一个斐波那契数
        def __next__(self):
            self.a, self.b = self.b, self.a + self.b  		 # 计算下一个斐波那契数
            if self.a > self.n:                       		 # 如果当前斐波那契数大于上限值 n
                raise StopIteration                   		 # 则抛出 StopIteration 异常,标识迭代结束
            return self.a                             		 # 返回当前斐波那契数
    
    
    fibs = Fibs(100)										 # 创建一个名为 fibs 的斐波那契数列迭代器,上限值为 100
    
    for each in fibs:
        print(each, end=' ')  								 # 打印每个斐波那契数,并以空格分隔
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    1 1 2 3 5 8 13 21 34 55 89 
    
    • 1
    10.5.4 生成器

      生成器(Generator)是一种特殊类型的迭代器,它允许你在迭代过程中逐个生成值,而不是一次性生成并存储所有值,其特点有:

    1. 延迟生成、节省内存:生成器一次生成一个值,而不是一次性生成所有值,即不需要保存所有值在内存中,降低了内存的占用,更适用于处理大型数据集或无限数据流。

    2. 语法简洁,易于实现:在函数中使用 yield 关键字就可以定义生成值的规则,而不需要显式地编写 __iter__()__next__() 方法,实现相对简单。

    3. 简化迭代过程:生成器隐藏了迭代的复杂性,使代码更简洁易读。

    4. 局部变量和执行状态自动保存:在生成器函数中,局部变量和执行状态会在每次生成值时自动保存和恢复。这意味着你可以在生成器函数中使用普通的局部变量,而不需要使用类的实例变量(如 self.index 和 self.data)。这让代码编写更容易理解和维护。

    10.5.4.1 生成器的生成方式

    生成器可以通过两种方式来定义:

    1. 使用生成器表达式:类似于列表推导式,但使用圆括号而不是方括号。

      # 使用生成器表达式创建生成器
      generator_expr = (x * 2 for x in range(5))
      
      # 使用生成器表达式生成值
      for value in generator_expr:
          print(value)  					  # 输出 0, 2, 4, 6, 8
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    2. 使用函数和 yield 关键字:在函数中使用 yield 关键字可以将函数转变为生成器。每次调用生成器的 __next__() 方法时,函数将从上一次 yield 语句的位置恢复执行,然后继续执行直到下一个 yield 语句或函数结束。这样就允许你在迭代过程中逐个生成值,而不会从头开始执行函数。

      def my_generator():
          yield 1
          yield 2
          yield 3
      
      gen = my_generator()
      
      print(next(gen))  					 # 第一次调用 __next__(),执行第一个 yield 语句,生成值 1
      print(next(gen)) 					 # 第二次调用 __next__(),从上一次的位置继续执行,生成值 2
      print(next(gen))  					 # 第三次调用 __next__(),从上一次的位置继续执行,生成值 3
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      # 使用函数和 yield 创建生成器
      def my_generator():
          for x in range(5):
              yield x * 2
      
      # 使用函数和 yield 生成值
      gen = my_generator()
      for value in gen:
          print(value)  					# 0, 2, 4, 6, 8
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    10.5.4.2 特性:延迟生成,节省内存

      下面例子说明了生成器表达式逐个生成值的特点,以及与列表等可迭代对象在处理大数据或无限数据流时的区别。

    1. 生成器:

      • 创建时只是生成器对象:这个生成器对象包含了生成元素的规则和状态信息,不会立即生成或存储所有的元素。
      • 迭代时逐个生成元素:当你迭代生成器时,它会根据定义的规则逐个生成元素,每次生成一个元素,并在需要时(例如for循环中)按需生成下一个元素。
      • 我们使用生成器表达式来创建一个生成器,但是在创建时,它并不会立即计算并存储所有的元素,只会在需要时(比如for循环中),随着一次次迭代来生成一个个的值。即生成器只在需要时生成值,而不会一次性生成并存储所有值
      import sys
      
      # 创建一个生成器,用于生成一组偶数
      generator_expr = (x * 2 for x in range(10**6))
      # 生成器只包含规则和状态信息,不占用大量内存
      gen_size = sys.getsizeof(generator_expr)
      
      print(f"生成器类型:{type(generator_expr)} , 生成器的大小(字节):{gen_size}")
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      生成器类型:<class 'generator'> , 生成器的大小(字节):208
      
      • 1
      # 迭代生成器表达式,逐个生成值
      	for value in generator_expr:
      	    print(value)
      
      • 1
      • 2
      • 3
    2. 使用列表:我们使用列表推导式创建了一个列表,这个列表在一开始创建的时候,就会立即生成并存储所有的元素,这会占用大量内存,特别是当数据集很大时。

      # 列表推导式,用于生成一组偶数
      list_comp = [x * 2 for x in range(10**6)]
      print("列表占用的内存:", big_list.__sizeof__(), "字节")
      
      • 1
      • 2
      • 3
      列表占用的内存: 8448712 字节
      
      • 1

      所以,生成器非常适合处理大型数据集或需要延迟生成值的情况,而列表等序列则适用于小型数据集或需要一次性访问所有值的情况。

    10.5.4.3 特性:简洁优雅

      生成器不仅语法简洁,而且生成器函数允许你在函数内部使用普通的局部变量来管理状态,而不需要像类的实例方法那样使用实例变量。下面通过斐波那契数列的例子来说明。

    def fibonacci_generator():
        a, b = 1, 1  # 使用局部变量 a 和 b 来保存斐波那契数列的前两个元素
        while True:
            yield a  # 生成当前斐波那契数列的值
            a, b = b, a + b  # 更新局部变量 a 和 b,计算下一个斐波那契数列的值
    
    # 创建斐波那契数列生成器
    fib_gen = fibonacci_generator()
    
    # 逐个生成并打印斐波那契数列的值
    for _ in range(11):
        print(next(fib_gen),end=' ')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1 1 2 3 5 8 13 21 34 55 89 
    
    • 1

      在上述示例中,我们使用局部变量 ab 来管理斐波那契数列的前两个元素。生成器函数 fibonacci_generator() 使用 yield 语句生成当前的斐波那契数值,并在每次迭代中更新局部变量 ab 来计算下一个值。这使得我们可以使用普通的局部变量来管理状态,而不需要使用类的实例变量,从而让代码更加清晰和易于理解。

    如果是用类来实现,代码为:

    # 定义一个名为 Fibs 的自定义迭代器类,用于生成斐波那契数列
    class Fibs:
        def __init__(self, n=10):
            self.a = 0           							 # 初始化第一个斐波那契数为 0
            self.b = 1           							 # 初始化第二个斐波那契数为 1
            self.n = n          		   					 # 存储生成斐波那契数列的上限值 n
    
        def __iter__(self):
            return self
    
        # 实现 __next__() 方法,用于生成下一个斐波那契数
        def __next__(self):
            self.a, self.b = self.b, self.a + self.b  		 # 计算下一个斐波那契数
            if self.a > self.n:                       		 # 如果当前斐波那契数大于上限值 n
                raise StopIteration                   		 # 则抛出 StopIteration 异常,标识迭代结束
            return self.a                             		 # 返回当前斐波那契数
    
    
    fibs = Fibs(100)										 # 创建一个名为 fibs 的斐波那契数列迭代器,上限值为 100
    
    for each in fibs:
        print(each, end=' ')  								 # 打印每个斐波那契数,并以空格分隔
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    1 1 2 3 5 8 13 21 34 55 89
    
    • 1

      总之,生成器提供了一种更简洁、更易于编写和理解的方式来实现迭代器。生成器的语法和特性使得处理迭代任务更加方便和优雅。

    10.5.5 类和属性相关操作

      Python中有一些内置函数,用于类继承关系的判断、对象类型的检查,以及对象属性的操作。这些函数可以让开发者更好地管理和操作类和对象的行为,使代码更具表现力和可读性。

    函数描述
    type(obj)获取对象的类型。
    issubclass(class, classinfo)检查一个类是否是另一个类的子类。
    isinstance(obj, classinfo)检查对象是否是指定类型的实例。
    hasattr(obj, name)检查对象是否包含指定名称的属性。
    getattr(obj, name[, default])获取对象的指定属性的值,可选地提供默认值。
    setattr(obj, name, value)设置对象的属性值,如果属性不存在则创建新属性。
    delattr(obj, name)删除对象的指定属性。
    property([fget[, fset[, fdel[, doc]]]])创建属性,允许定义属性的访问、设置和删除操作。

    下面是一个简单的使用示例:

    # 定义一个简单的类
    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def say_hello(self):
            print(f"Hello, my name is {self.name} and I am {self.age} years old.")
    
    # 创建一个对象
    person1 = Person("Alice", 30)
    
    # 使用内置函数 type() 获取对象的类型
    print(type(person1))  										# 输出:
    
    # 使用内置函数 isinstance() 检查对象是否是特定类型的实例
    print(isinstance(person1, Person)) 						    # 输出:True
    
    # 使用内置函数 hasattr() 检查对象是否包含指定属性
    print(hasattr(person1, 'name'))  							# 输出:True
    
    # 使用内置函数 getattr() 获取对象的属性值
    name = getattr(person1, 'name')
    print(name)  												# 输出:Alice
    
    # 使用内置函数 setattr() 设置对象的属性值
    setattr(person1, 'age', 35)
    person1.say_hello()  										# 输出:Hello, my name is Alice and I am 35 years old.
    
    # 使用内置函数 delattr() 删除对象的属性
    delattr(person1, 'age')
    print(hasattr(person1, 'age'))  							# 输出:False
    
    # 使用内置函数 issubclass() 检查类之间的继承关系
    class Student(Person):
        def __init__(self, name, age, student_id):
            super().__init__(name, age)
            self.student_id = student_id
    
    print(issubclass(Student, Person))  						# 输出:True
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
  • 相关阅读:
    uniapp 模糊搜索(小白必看)
    【Javaweb】会话跟踪技术Cookie&Session
    Leetcode 137. 只出现一次的数字 II
    《HelloGitHub》第 90 期
    无人机反制:车载侦测干扰一体设备技术详解
    leetcode406 根据身高重建队列
    有关springboot Unauthorized 问题
    约瑟夫环问题——《算法》1.1.37的解法
    推荐系统笔记(八):推荐系统中的长尾效应
    C# 遍历
  • 原文地址:https://blog.csdn.net/qq_56591814/article/details/132765313