Python3.11 Tutorial 5: Classes and Objects

10. Python classes and objects

Reference: Python 3.11 official document "Class" , "datawhale——PythonLanguage" , "Python3 Object-Oriented"

10.1 Object-oriented programming

  Object-Oriented Programming (OOP) is a programming paradigm or method that is widely used in many programming languages, including Python, Java, C++, C#, etc. It takes objects as its core and encapsulates data and methods (properties and methods) for operating data to simulate things and interactions in the real world.

  The main idea of ​​OOP is to break down problems into objects and solve them through interactions between these objects, helping to build clearer, maintainable and scalable code. The following are the key concepts of object-oriented programming:

  • Object : An object is an abstract representation of an entity or thing in the real world, and is an instance of a class. It includes properties (data) and methods to manipulate the data. Objects can be physical entities (e.g., cars, cell phones) or conceptual entities (e.g., users, orders).

  • Class (Class) : A class is a template or structure of an object, which defines the properties and methods of the object, and it specifies what characteristics and behaviors the object should have. Class has three major characteristics - encapsulation, inheritance and polymorphism.

    1. Encapsulation: Encapsulation is the concept of bundling data (properties) and methods (methods) for operating data. It hides the internal details of the object and only provides a limited interface for external access. Encapsulation allows the implementation details of an object to be changed at any time without affecting the code that uses the object, which improves data security and code maintainability.

    2. Inheritance : Inheritance allows a subclass to inherit the properties and methods of another parent class . Subclasses can reuse the code of the parent class and extend or modify it. Inheritance establishes hierarchical relationships between classes.

    3. Polymorphism :

      • The same method can have different implementations in different classes, that is, subclasses can override the methods of the parent class to achieve different functions .
      • Polymorphism implements dynamic binding of methods and improves flexibility. You can handle different objects through a unified interface, which allows you to write common code and reduce code complexity.

  The above three characteristics together form the basis of object-oriented programming. They provide an effective way to organize and manage complex code, making the code easier to understand, maintain and extend. Through encapsulation, inheritance and polymorphism, you can create code with high cohesion and low coupling, improving the quality and reusability of the code.

	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!

  In the above example, we defined a base class Animal, which has a speakmethod, and then created two subclasses Dogand Cat, which inherit Animalthe class and override speakthe method to provide their own implementations. This demonstrates the concepts of inheritance and polymorphism, where subclasses can inherit the properties and methods of the parent class and customize them in different subclasses.

Finally, let’s summarize the advantages of OOP:

  • Modularization: Breaking down problems into objects makes the code easier to understand and maintain.
  • Reusability: Generic classes and methods can be created for reuse in different projects.
  • Extensibility: Functionality can be extended by adding new classes and methods without having to modify existing code.
  • Abstraction: allows abstract classes and objects to be extracted from concepts in the real world, making the modeling of problems closer to the actual situation.

10.2 Classes and Objects

  Classes and objects are the core concepts of object-oriented programming. The three major characteristics of classes are encapsulation, inheritance and polymorphism. In Python, you can use the keyword classto define a class. Then, use the constructor __init__to initialize the properties of the object, for example:

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.")

  This code defines a Dogclass named with nameand ageproperties. We created an my_dogobject called and accessed its properties.

  Note that selfis a special parameter that refers to the object itself. It is commonly seen in methods of a class selfin order to access and manipulate properties of an object.

10.2.1 Basic concepts of classes and objects

  1. Attributes : In the declaration of a class, attributes are represented by variables. These attributes are used to store the data of the object.

  2. Class Attributes or Class Variables : attributes defined inside the class and outside the method

  3. Instance Attributes (Instance Attributes) or instance variables (Instance Variables) : attributes defined in the method of the class

    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
    
  4. Methods : The functions defined in the class are called methods. They define the operations that the object can perform and are used selfas the first parameter to refer to the object instance.

  5. Method overriding : When a subclass inherits from a parent class and a method or attribute with the same name as the parent class is defined in the subclass, the method in the subclass will automatically override (override) the method of the parent class. This means that when the method is called in a subclass, the method implementation in the subclass will be used instead of the method in the parent class.

  6. Special Methods : Methods starting and ending with double underscores are called special methods, such as __init__and __str__, they will be called by Python under specific circumstances.

  7. Constructor : A constructor is a special method, commonly called __init__, that is used to initialize properties of an object.

  8. Class Methods :

    • A class method is a decorator, usually called a @classmethod.
    • They are methods belonging to the class rather than the object instance.
    • Class methods can access and modify class attributes, but not instance attributes.
  9. Static Methods :

    • A static method is a decorator, usually called a @staticmethod.
    • They are methods that do not belong to a class or object instance and have nothing to do with the class.
    • Static methods are often used to implement functionality related to a class, but do not require access to class or object state.
  10. Instantiation : Create an instance of a class, that is, a concrete object of the class.

10.2.2 The difference between class attributes and instance attributes and their access methods

  1. Class Attributes or Class Variables :

    • Class attributes are attributes defined at the class level (not instance level). They are usually defined at the top of the class, that is, attributes defined inside the class and outside methods.
    • Class attributes are similar to global variables, which are shared throughout the class and are the same for all instances of the class.
    • In the class : call the class attribute by the class name or self
    • Outside the class : It is recommended to access through the class name rather than using the class instance. In addition, it is generally not recommended to modify class attributes outside the class because this will affect all object instances.
    • When modifying a class attribute, if you use an instance object to modify it, you actually create an instance attribute with the same name as the class attribute, rather than modifying the class attribute itself. This causes the instance object to have an instance property with the same name as the class property, but does not affect other instance or class properties.
      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) 
      
      类属性: 100
      实例属性: 200
      新对象的类属性: 100
      新对象的类属性: 300
      
  2. Instance Attributes or Instance Variables :

    • Properties defined in class methods are properties of object instances.
    • Each object instance can have different instance property values.
    • Inside the class : Instance attributes can only be called through self, because whoever calls self, its value belongs to the object.
    • Outside the class : Instance properties can only be accessed and modified through the instance object

  In general, class properties should be accessed or modified through the class name, and instance properties can only be accessed or modified through the instance object. Another thing to note is that properties have the same name as methods, properties override methods , so you need to be careful when writing code to use the same name, this can lead to conflicts.

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
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

  In this example, we create a MyClassclass called which contains a property my_attributeand a method my_method. Then, we created an my_objectobject and accessed the properties and methods.

  But then, we my_methodset the property to a string, overriding the original method. So when we access it again my_method, it's actually a string property and no longer a method. This results in the property overriding the method, making the original method no longer available.

10.2.3 Example of self representing a class

  There is only one difference between class methods and ordinary functions - they must have an additional first parameter name, which by convention is self.

class Test:
    def prt(self):
        print(self)
        print(self.__class__)
 
t = Test()
t.prt()
<__main__.Test instance at 0x100771878>		 
__main__.Test

  The result of the first line above is the string representation of object t, and its address in memory is 0x100771878, that is, self represents an instance of the class. The second line shows the name of the class to which object t belongs, namely the Test class.

  As can be seen from this example, selfthe role of a class is to refer to the current object instance and implement the basic principles of encapsulation and object-oriented programming:

  1. Refers to the current instance : selfallows you to reference and operate the properties and methods of the current object instance within the methods of the class

  2. Implement encapsulation : Use selfto encapsulate the state and behavior of an object. This means that you can define properties and methods inside a class while ensuring that these properties and methods are only visible and operable to object instances.

  3. Implement method invocation : When you call a method of a class, you need to tell the method which object is calling it. selfThis information is provided in the method definition so that the method can correctly operate on the object that calls it.

self is not a python keyword, you can use other names instead, but it is customary to use it self.

# 这段代码输出结果一样
class Test:
    def prt(runoob):
        print(runoob)
        print(runoob.__class__)
 
t = Test()
t.prt()

10.3 Inheritance

10.3.1 Inheritance of constructors

  The main function of the constructor __init__()is to initialize the properties of the object (automatically called when the class is instantiated) and ensure that the object has an appropriate initial state when it is created.

  When a subclass inherits a parent class, the subclass usually wants to inherit and initialize the properties of the parent class. This can be achieved by calling the parent class's constructor in the subclass's constructor. There are two situations .

  1. Default inheritance of parent class constructor
    If a subclass does not explicitly define a constructor ( __init__method), it will inherit the parent class's constructor by default to initialize the object. This default inheritance behavior ensures that subclasses can inherit the properties and methods of the parent class. For example:
class Parent:
    def __init__(self):
        self.attribute = 42

class Child(Parent):
    pass

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

# 子类对象可以访问父类和子类的属性
print(child_obj.attribute) 				# 输出42
  1. Overriding Subclass Constructors
    If a subclass constructor is explicitly defined, it overrides the default inheritance behavior. It is generally recommended to call the constructor of the parent class at the same time when overriding the constructor to ensure that the initialization logic of the parent class is also executed. There are two calling methods:

    • Use super().__init__()
      In Python, you can use super().__init__()to call the constructor of the parent class. This method will automatically identify the parent class, and does not need to explicitly specify the name of the parent class.
    • Explicitly calling the parent class's constructor
      In some programming languages, or if you wish to explicitly specify the name of the parent class, you can use 父类名.__init__(self, ...)to call the parent class's constructor. This approach requires explicitly specifying the name of the parent class.

    Note that super().__init__()you can only call the constructor of one parent class. If there is multiple inheritance, it is recommended to use the second method to call the constructor of the parent class.

The following is a complex example that demonstrates how to inherit and initialize the properties of the parent class in the child class:

# 定义一个父类 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()  
小马的程序人生 说:10 岁了,我在读 3 年级

  In this example, the subclass studentinherits peoplethe properties of the parent class, but subclasses can also have their own properties, such as grade.

studentIn the constructor of   the subclass __init__(self, n, a, w, g), first super().__init__(n, a, w)call the constructor of the parent class by peoplepassing the same parameters so that the parent class can initialize these properties. This is to ensure that the subclass object has the properties of both the parent class and the subclass.

  If the constructor of the parent class people is not called, the following problems will occur:

  1. Parent class properties will not be initialized : properties and private properties peoplein the parent class will not be initialized. This means that objects of subclasses will not have initial values ​​for these propertiesnameage__weightstudent
  2. May cause errors or inconsistent behavior : Failure to initialize these properties may lead to errors or inconsistent behavior if a subclass student's methods or other code relies on the initial values ​​of these properties. For example, in studenta class method, if or speakis used , and these properties are not initialized, it will result in errors such as or .self.nameself.ageNameErrorAttributeError

  In short, not calling the constructor of the parent class will cause the properties of the parent class to be uninitialized, which may destroy the consistency of the class. Therefore, usually the constructor of the parent class should be called in the constructor of the subclass to ensure that the parent class and the subclass are consistent. Properties are properly initialized.

10.3.2 Resolution order of methods in multiple inheritance

  When a class inherits from multiple parent classes, if there are methods with the same name in these parent classes, and the subclass does not specify which parent class method to use, the parsing order is from left to right, even if the previous parent class is used. class methods.

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 的方法

  In this example, class C inherits from two parent classes, A and B, but since A is on the left side of the inheritance list, the speak method in C uses the method of class A.

10.3.3 Things to note about multiple inheritance

  Multiple inheritance (combined inheritance) allows subclasses to inherit the properties and methods of multiple parent classes at the same time. This inheritance method combines class inheritance and object composition (using objects of other classes as member properties) to achieve more flexibility and reusability. Here are some considerations and examples of compositional inheritance:

  1. Potential method conflicts: If there are methods with the same name in multiple parent classes, subclasses may have method conflicts when called. In this case, the subclass must explicitly specify the method to be called or resolve the conflict by overriding the method.

  2. Constructor calls: Subclasses usually need to call the constructor of each parent class in its constructor to ensure that the parent class's properties are initialized correctly.

    super()By default, only one parent class's constructor is called. If used super().__init__(), it will only call the first parent class's constructor.

  3. Deep inheritance chains: When using multiple levels of combined inheritance, you need to be careful that the inheritance chain becomes too complex, which may lead to unnecessary complexity and performance issues. Try to keep the inheritance chain from being too deep.

  Suppose we have two parent classes, Animaland Machine, which represent the characteristics and behaviors of animals and machines respectively. Then, we create a subclass Robotthat inherits the properties and methods of both parent classes.

# 定义 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.

  In the above example, Robotthe class inherits the properties and methods Animalof Machineboth parent classes and then defines its own properties and methods. Subclass initialization can also be written as:

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

10.4 Private and public

10.4.1 Private properties

  Private properties are properties named starting with a double underscore in a class __and cannot be directly accessed outside the class. Private properties have the following characteristics:

  1. Enhance data encapsulation: Private attributes cannot be directly accessed from outside the class, so it can hide the implementation details inside the class so that external code cannot directly modify the value of the attribute, improving data protection. Typically, these properties are only allowed to be accessed or modified through methods of the class.

  2. Name mangling: In Python, the name of a private attribute will be mangled, that is, an underscore and the class name will be added before the attribute name. For example, a __weightprivate property named in class Peoplewill have its actual name mangled to _People__weight, which prevents subclasses from accidentally overwriting the parent class's private properties.

  3. Still accessible: You can access and modify private properties through the class's methods, which provides a certain degree of control to ensure that property access and modification is through the class's interface, thus ensuring data integrity and consistency.

    # 一个电子设备类可能具有内部状态属性,但用户不需要知道这些细节
    
    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
    

_People__weightYou can access private properties directly outside the class   via mangled private property names (for example ), but this is generally not recommended because it violates the principle of encapsulation. It is recommended to access and modify private properties only through the public methods of the class, rather than accessing them directly outside the class. This has the following benefits:

  1. Security: Through public methods, you can internally control the logic of accessing private properties to ensure the legality and security of data. If you access private properties directly externally, you lose this control and may cause unpredictable errors.
  2. Maintainability: When you need to modify the internal implementation of a class, if you use public methods to access properties, you can change the internal implementation without affecting external code. If the external code directly accesses the private property, then any internal implementation changes may cause the external code to break.
  3. Documentation and interfaces: Public methods provide the interface of a class, and they can be documented, making it easier for other developers to understand how to use the class correctly. Direct access to private properties will obscure the interface of the class and reduce the readability of the code.
  4. Inheritance and subclassing: In a subclass, you can override the methods of the parent class without worrying about destroying the internal state of the parent class. If you directly access the private properties of the parent class, it may cause undesired side effects.

10.4.2 Private methods

  Similarly, a private method__ is a method named starting with a double underscore in the class . It cannot be called outside the class. It also has some characteristics:

  • Enhance the encapsulation of data: Private methods hide some internal operations and logic inside the class and prevent external code from accessing it, thereby improving the encapsulation of the class.
  • Provide auxiliary functions: Private methods can be used to perform some auxiliary functions and operations, making public methods more focused on core functions to provide a clearer and more concise public method interface.
  • Prevent illegal calls: Private methods are usually used to perform internal operations of the class and are not allowed to be called directly by external code.

_People__methodYou can access private methods directly outside the class   by reshaping the private method name (for example ), but this is not recommended.

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()
1300

  In the above example, the balance of the bank account is sensitive information. By setting BankAccountthe class as __balancea private attribute and setting it __log_transaction()as a private method, the implementation details of the account balance and transaction log are hidden. Transactions can only be accessed and recorded through public methods. , which provides better encapsulation and security.

10.4.3 Private properties and private methods of the parent class

  In Python, subclasses cannot directly inherit the private properties and private methods of the parent class . Private properties and private methods are designed to be used inside the class and will not be inherited by subclasses. This is part of encapsulation in Python to prevent subclasses from accidentally modifying or accessing private members of the parent class.

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()
AttributeError: 'Child' object has no attribute '_Child__private_attribute'

  In the above code, because the subclass cannot inherit the private attributes and private methods of the parent class, the call in the access_parent_private function will be considered by Python as the private attributes and private methods of the subclass Child, but these are not in the subclass. It is not defined, so an error will be reported AttributeError.

  However, private properties can be accessed through class methods, so if a subclass wants to access the private properties of the parent class, it can add a method to call its private properties in the parent class and inherit it to the subclass.

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)  					
子类私有属性: 42
父类私有属性: 100

10.5 Magical Methods (Special Methods)

10.5.1 Introduction to magic methods

  Magic Methods, also known as double-underscore methods or special methods, are a type of special methods in Python that begin and end with double underscores, such as __init__, __str__, __eq__etc. These methods have special purposes and behaviors. They are used to customize the behavior and operations of the class and will be called at specific times. The difference between them and ordinary methods mainly lies in their calling method and purpose:

  1. Calling method : Ordinary methods are called through object instances, while magic methods are usually called automatically by the Python interpreter under certain circumstances. For example, __init__methods are called automatically by Python when an object is created, and normal methods are called explicitly in code as needed.

  2. Purpose : Magic methods are used to implement special behaviors and operations of objects, such as initializing objects, customizing string representations of objects, supporting comparisons of objects, supporting arithmetic operations, etc. They allow you to override the default operation to suit your specific needs. Ordinary methods are the general behavior of a class and are used to implement the functionality of the class.

  Collectively, magic methods allow your classes to mimic the behavior of built-in data types, making your code more expressive and readable. By implementing the appropriate magic methods, you can make your custom objects interact more naturally with the Python language and standard library. Here are some common magic methods

magic function effect Example effect
__init__(self, ...) Initialize object properties def __init__(self, ...) obj = MyClass(...)
__del__() Used to perform cleanup operations before object destruction, such as closing files and releasing resources. Usually there is no need to manually specify def __del__(self, ...)
String magic methods effect Example effect
__str__(self) Returns the string representation of the object def __str__(self) str(obj)
Container operation magic methods effect Example effect
__getitem__(self, key) Get the elements of an object def __getitem__(self, key) obj[key]
__setitem__(self, key, value) Set the elements of the object def __setitem__(self, key, value) obj[key] = value
__delitem__(self, key) Delete an element of an object def __delitem__(self, key) del obj[key]
__len__(self) Returns the length of the object def __len__(self) len(obj)
__iter__(self) Returns an iterator of objects def __iter__(self) iter(obj)
__next__(self) Returns the next element of the iterator def __next__(self) next(iterator)
__contains__(self, item) Check if an object contains an element def __contains__(self, item) item in obj
Comparison & Operation Magic Methods effect Example effect
__eq__(self, other) Compares two objects for equality def __eq__(self, other) obj1 == obj2
__ne__(self, other) Compares two objects for inequality def __ne__(self, other) obj1 != obj2
__lt__(self, other) Compares two objects to see if they are less than def __lt__(self, other) obj1 < obj2
__le__(self, other) Compare two objects to see if they are less than or equal to def __le__(self, other) obj1 <= obj2
__gt__(self, other) Compares two objects to see if they are greater than def __gt__(self, other) obj1 > obj2
__ge__(self, other) Compare two objects to see if they are greater than or equal to def __ge__(self, other) obj1 >= obj2
Arithmetic operations magic methods effect Example effect
__add__(self, other) Implement the addition operation of objects def __add__(self, other) obj1 + obj2
__sub__(self, other) Implement object subtraction operation def __sub__(self, other) obj1 - obj2
__mul__(self, other) Implement object multiplication operations def __mul__(self, other) obj1 * obj2
__div__(self, other) Implement object division operation def __div__(self, other) obj1 / obj2
__pos__(self) Positive sign operator, used to implement positive sign operation +obj
__neg__(self) Negative sign operator, used to implement negative sign operation -obj
__abs__(self) Absolute value operation, used to implement absolute value operation abs(obj)
__invert__(self) Bitwise negation operator, used to implement bitwise negation ~obj

  These are some common magic methods that can help you customize the behavior of a class to better suit your needs. You can optionally implement these methods as needed to change the default behavior of the class.

10.5.2 Container operation magic methods

Container objects are divided into mutable and immutable, magic methods implemented according to needs:

  1. Immutable container : If you want your container object to be immutable (tuple, string), that is, its content cannot be modified once created, then you only need to define two magic methods:

    • __len__(): This method is used to return the number (length) of elements in the container.
    • __getitem__(self, key): This method is used to get the element at the specified position in the container.

    Immutable containers are typically used to represent a set of data that cannot be changed after creation, such as tuples.

  2. Mutable container : If you want your container object to be mutable (list, dictionary), that is to say, you can modify, add, and delete elements in the container, then in addition to the above two methods, you also need to define the following two magics method:

    • __setitem__(self, key, value): This method is used to set the value of the element at the specified position in the container.
    • __delitem__(self, key): This method is used to delete the element at the specified position in the container.

Example: Write a mutable custom list that requires recording the number of times each element in the list is accessed.

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))
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}

10.5.3 Iterators

10.5.3.1 Iterator protocol

  In Python, an iterator (Iterator) is an object used to traverse the elements of an iterable object (Iterable), and iterable objects are those objects that can be iterated (traversed), such as lists, tuples, dictionaries, sets, etc. . The iterator object starts accessing from the first element of the collection until all elements have been accessed. Iterators can only go forward and not backward.

  An iterator is a special kind of object that has two basic methods - iter()and , which is implemented next()by implementing the magic method __iter__()and .__next__()

  1. __iter__()Method: This is the method that the iterator must implement to return an __next__()iterator object with the method

  2. __next__()Method: used to get the next element in the iterator. Calling again after the traversal ends will raise StopIterationan exception, indicating the end of the iteration.

  3. iter(object): Used to generate iterators, usually called implicitly by for loops. When you use iter()a function to obtain the iterator of an iterable object, you are actually calling the object's __iter__()method, thereby obtaining the iterator.

  4. next(iterator, default): Built-in function, used to explicitly call __next__()the method of the iterator object to get the elements in the container one by one.

    • iterator: Iterator object to get the next element.
    • default(Optional): If the iterator has reached the last element, return defaultthe value (can be any legal Python object). If no defaultparameters are provided, continuing will throw StopIterationan exception.

  next()Parameters in the function defaultcan be any legal Python data type, such as integers, floating point numbers, strings, lists, tuples, sets, dictionaries and other data structures, as well as custom objects (as long as they are legal).

  Below is a simple example that demonstrates how to create an iterator using a class.

# 定义一个自定义迭代器类 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

  A method is defined __iter__()to return an __next__()object with a method. If the class is defined __next__()and this method returns the next iteration value, then __iter__()it can simply return selfbecause the instance of the class itself already acts as the iterator.

10.5.3.2 Principle of for loop

  Most built-in iterable objects (such as strings, lists, tuples, sets, dictionary keys, dictionary values) support iterator functionality, so you can use the and functions iter()to next()iterate explicitly.

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异常

You can also set defaultparameters, for example:

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]

But usually, using fora loop is usually more concise and intuitive. When using a for loop:

  1. When you use forthe statement to iterate over a container object, for example for item in container, Python first calls iter()the function on the container object.

  2. iter()The function returns an iterator object, which contains a special method __next__()for accessing the elements in the container one by one.

  3. In each loop iteration, forthe loop will automatically call the method of the iterator object __next__()to access the elements in the container one by one. When iterating to the end of the container, __next__()the method raises StopIterationan exception, telling the loop to stop iterating.

  This process is the basic mechanism for iterating container objects in Python, and it allows for loops to be used to iterate over many different types of data structures without knowing the details of the underlying implementation.

10.5.3.3 Custom iteration behavior

  Users can easily customize the object's iteration behavior by implementing __iter__()the and __next__()methods. Here is a custom iterator for generating the Fibonacci sequence:

# 定义一个名为 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 1 2 3 5 8 13 21 34 55 89 

10.5.4 Generators

  Generator is a special type of iterator that allows you to generate values ​​one by one during the iteration process, instead of generating and storing all values ​​at once. Its characteristics are:

  1. Delayed generation and memory saving : The generator generates one value at a time instead of all values ​​at once, that is, there is no need to save all values ​​in memory, which reduces memory usage and is more suitable for processing large data sets or infinite data streams.

  2. The syntax is concise and easy to implement : you can use the keyword in the function yieldto define the rules for generating values ​​without explicitly writing the __iter__()and __next__()methods. The implementation is relatively simple.

  3. Simplify the iteration process : Generators hide the complexity of iteration, making the code more concise and readable.

  4. Local variables and execution state are automatically saved : In generator functions, local variables and execution state are automatically saved and restored each time a value is generated. This means that you can use normal local variables in the generator function without using the class's instance variables (such as self.index and self.data). This makes writing code easier to understand and maintain.

10.5.4.1 How generators are generated

Generators can be defined in two ways:

  1. Use generator expressions: Similar to list comprehensions, but using parentheses instead of square brackets.

    # 使用生成器表达式创建生成器
    generator_expr = (x * 2 for x in range(5))
    
    # 使用生成器表达式生成值
    for value in generator_expr:
        print(value)  					  # 输出 0, 2, 4, 6, 8
    
  2. Using functions and yieldthe keyword: Using the keyword inside a function yieldturns the function into a generator. Each time a generator's __next__()method is called, the function resumes execution from the point of the previous yieldstatement and continues execution until the next yieldstatement or the end of the function. This allows you to generate values ​​one by one during iteration without executing the function from scratch.

    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
    
    # 使用函数和 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
    
10.5.4.2 Features: Delayed generation, saving memory

  The following example illustrates the characteristics of generator expressions that generate values ​​one by one, and the difference from iterable objects such as lists when processing large data or infinite data streams.

  1. Builder:

    • When created, it is just a generator object : this generator object contains the rules and status information for generating elements, and does not generate or store all elements immediately.
    • Generate elements one by one while iterating : When you iterate over a generator, it generates elements one by one according to defined rules, one element at a time, and the next element on demand when needed (for example, in a for loop).
    • We use a generator expression to create a generator, but when it is created, it does not calculate and store all the elements immediately. It will only generate each element when needed (such as in a for loop) with each iteration. value. That is, the generator only generates values ​​when needed, rather than generating and storing all values ​​at once
    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}")
    
    生成器类型:<class 'generator'> , 生成器的大小(字节):208
    
    # 迭代生成器表达式,逐个生成值
    	for value in generator_expr:
    	    print(value)
    
  2. Using a list: We use list comprehension to create a list. When this list is initially created, all elements will be generated and stored immediately, which will occupy a lot of memory, especially when the data set is large.

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

  So, generators are great for working with large data sets or when you need to generate values ​​lazily, while sequences like lists are good for small data sets or when you need to access all values ​​at once.

10.5.4.3 Features: Simple and elegant

  Generators not only have a concise syntax, but generator functions allow you to use ordinary local variables to manage state inside the function, instead of using instance variables like instance methods of a class. The following is an example of the Fibonacci sequence.

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 1 2 3 5 8 13 21 34 55 89 

  In the above example, we use local variables aand bto manage the first two elements of the Fibonacci sequence. The generator function generates the current Fibonacci value fibonacci_generator()using the statement and updates the local variables and on each iteration to calculate the next value. This allows us to use ordinary local variables to manage state instead of using instance variables of the class, making the code clearer and easier to understand.yieldab

If it is implemented using a class, the code is:

# 定义一个名为 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 1 2 3 5 8 13 21 34 55 89

  In summary, generators provide a cleaner, easier to write and understand way to implement iterators. The syntax and features of generators make handling iterative tasks more convenient and elegant.

10.5.5 Class and attribute related operations

  There are some built-in functions in Python for judging class inheritance relationships, checking object types, and operating object attributes. These functions allow developers to better manage and manipulate the behavior of classes and objects, making code more expressive and readable.

function describe
type(obj) Get the type of object.
issubclass(class, classinfo) Check if a class is a subclass of another class.
isinstance(obj, classinfo) Checks whether the object is an instance of the specified type.
hasattr(obj, name) Checks whether the object contains a property with the specified name.
getattr(obj, name[, default]) Gets the value of the specified property of the object, optionally providing a default value.
setattr(obj, name, value) Sets the property value of the object, creating a new property if the property does not exist.
delattr(obj, name) Delete the specified attribute of the object.
property([fget[, fset[, fdel[, doc]]]]) Create properties, allowing access, set and delete operations to be defined for properties.

Here's a simple usage example:

# 定义一个简单的类
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))  										# 输出:<class '__main__.Person'>

# 使用内置函数 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

Guess you like

Origin blog.csdn.net/qq_56591814/article/details/132765313