The secret of programming skills: the gorgeous encounter between closures, decorators, design patterns and Python!

a closure

1.1 First introduction to closures

  • Closure (Closure) refers to a function defined inside a function. The internal function can access the variables of the external function, and the variables of these external functions can maintain their values ​​when the internal function is called. In other words, a closure is a combination of a function and its associated reference environment.

  • Closures in Python usually consist of a nested function (an inner function) and an outer function that contains the inner function . The inner function can access the local variables and parameters of the outer function. Even if the outer function has completed execution, the inner function can still access the state of the outer function.

  • Simple Python closure example:

    def outer_function(x):
        def inner_function(y):
            return x + y
        return inner_function
    
    closure = outer_function(10)  # 创建闭包,x 被设置为 10
    
    result = closure(5)  # 调用闭包的内部函数,相当于 inner_function(5),结果为 10 + 5 = 15
    print(result)
    
  • inner_functionis a closure that allows access to outer_functionvariables in the outer function xeven after outer_functionit has completed execution. When the closure is created, the inner function captures the context of the outer function, so when the closure is called, it can still use the state of the outer function.


Application scenarios for closures include:

  1. Function Factory : Functions can be dynamically generated through closures, and different functions can be generated based on different parameters.

  2. Preserving state : When some state needs to be maintained between function calls, closures can be used to retain this state.

  3. Callback function : In scenarios such as event processing and asynchronous programming, closures can be used as callback functions.

  4. Encapsulating data : Closures can be used to encapsulate private data so that it is not easily accessible from the outside.

  • Note that closures may cause memory leaks because external variables retained in the closure may persist and cannot be garbage collected. Therefore, when using closures, you need to pay attention to reasonable management of memory and resources.

1.2 nonlocal

  • If you want to modify the value of an external function variable in an internal function, you need to use nonlocalkeywords to modify the external function variable.

  • nonlocalThe keyword is used to declare a variable in a nested function to tell the interpreter that the variable is not a local variable or a global variable, but comes from the scope of the outer nested function . This is useful for handling variable assignments within nested functions, especially within closures.

  • Demo:

    def outer_function():
        x = 10
    
        def inner_function():
            nonlocal x
            x += 1
            print("Inner x:", x)
        
        inner_function()
        print("Outer x:", x)
    
    outer_function()
    
    • outer_functionContains a local variable xand then defines one internally inner_function. inner_functionUsed internally nonlocal x, which means that inner_functionthe used in xcomes from the outer layer outer_function, rather than inner_functionre-creating a new local variable in.
  • output

    Inner x: 11
    Outer x: 11
    
  • Note that using the keyword in a closure nonlocalcan modify the local variables of the outer function, but if you want to modify the global variable, you need to use globalthe keyword.

1.3 Notes on closures

advantage:

  1. State retention: Closures allow a function to maintain a reference to a variable in the context in which it was created, even if the outer function has completed execution. This allows the closure to maintain state and use it in subsequent calls.

  2. Data encapsulation: Closures can be used to encapsulate data and achieve privatization, because the inner function can access the local variables of the outer function, but the scope of the outer function is invisible to external code.

  3. Function factory: Through closures, functions can be generated dynamically, and each closure can have different context and behavior.

  4. Callback functions: Closures are often used as callback functions to perform some action when a specific event occurs.

  5. Code simplicity: In some cases, closures can reduce the use of global variables and make the code clearer and modular.

shortcoming:

  1. Memory usage: Closures retain references to outer scopes, which may lead to some memory leaks, because these references may prevent garbage collection from recycling inner functions and variables in outer scopes.

  2. Performance impact: Since closures involve references to variables in the outer scope, function access may be slightly slower than ordinary functions.

  3. Reduced readability: Overuse of closures can cause code to become difficult to understand because they introduce more context and variables.

  4. Maintenance difficulty: Using closures in complex nested functions can lead to code that is difficult to maintain, especially when dealing with multiple levels of nesting.

  5. Ambiguous local variables: If the variables of the external function are modified in the internal function, but nonlocalthe keyword is not used to indicate it clearly, it may lead to confusion in different scopes.

  • Although closures can bring many advantages, they need to be used with caution, especially when it comes to memory management and code readability.

Two decorators

  • Decorator is a powerful programming tool in Python, used to modify or enhance the behavior of functions or methods.
  • A decorator is actually a closure, essentially a function that accepts a function as input and returns a new function.
  • Decorators are usually used to add new functionality to the target function without destroying the original code and functionality of the target function.
  • The syntax for decorators uses @the notation, which applies the decorator function to the function immediately below.

2.1 General writing method of decorator (closure writing method)

# 装饰器的一般写法(闭包)
def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper


def say_hello():
    print("Hello!")

fn=my_decorator(say_hello)
fn()

2.2 Syntax sugar for decorators

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

  • my_decoratorIs a decorator function that accepts a function funcas a parameter and returns an inner function wrapper. The function adds additional output before and after wrappercalling .func
  • When say_helloadding in front of a function @my_decorator, it is actually equivalent to execution say_hello = my_decorator(say_hello), that is, say_hellopassing the function to my_decoratorand wrapperreplacing the original say_hellofunction with the function.

2.3 Application scenarios of decorators

  1. Logging: You can use decorators to record function call times, parameters, and return values ​​for debugging and analysis.

  2. Permission verification: Decorators can be used to check whether a user has permission to access a specific feature.

  3. Performance analysis: You can use decorators to measure the execution time of a function in order to optimize performance.

  4. Caching: Decorators can be used to cache the calculation results of functions to avoid repeated calculations.

  5. Exception handling: Decorators can be used to handle exceptions in functions to provide more friendly error messages.

  6. Modify function behavior: Decorators can modify the behavior of functions, such as adding parameter checking, retry logic, etc.

There are also some built-in decorators in the Python standard library, such as @staticmethodand @classmethod, for defining static methods and class methods.

2.4 Python built-in decorators

2.4.1 Overview of common built-in decorators

  • Some common built-in decorators and their functions:
Decorator effect Example
@staticmethod Define static methods that do not require access to instance state and are called through the class name. @staticmethod def my_static_method():
print("Static method")
@classmethod Define class methods, automatically passing the class itself as the first parameter, and giving access to class properties and methods. @classmethod def my_class_method(cls):
print(f"Class method. Class variable: {cls.class_variable}")
@property Convert the method to a read-only property and use it like a property. @property
def get_value(self):
return self._value
@setter Method for setting a property, @propertyused in conjunction with , allows setting a value via a property. @value.setter
def set_value(self, value):
if value >= 0:
self._value = value
@deleter The delete attribute method, @propertyused in conjunction with , allows deleting a value via an attribute. @value.deleter
def delete_value(self):
del self._value
@abstractmethod Define abstract methods, which must be implemented in subclasses. @abstractmethod
def my_abstract_method(self):
pass
@classmethod Define class methods, automatically passing the class itself as the first parameter, and giving access to class properties and methods. @classmethod
def my_class_method(cls):
print(f"Class method. Class variable: {cls.class_variable}")

2.4.2 Detailed introduction of common decorators

1. @staticmethodDecorator:

  • @staticmethodUsed to define static methods that are independent of class instances and therefore do not pass class instances or the class itself as parameters. They can be called directly by class name without instantiation.

    class MathUtility:
        @staticmethod
        def add(x, y):
            return x + y
    
    result = MathUtility.add(5, 3)  # 调用静态方法
    print(result)  # 输出: 8
    

2. @classmethodDecorator:

  • @classmethodUsed to define class methods, which accept an additional parameter clsrepresenting the class itself. They can access class properties and call other class methods.

    class MathUtility:
        class_variable = 10
        
        @classmethod
        def multiply(cls, x, y):
            return x * y * cls.class_variable
    
    result = MathUtility.multiply(3, 4)  # 调用类方法
    print(result)  # 输出: 120
    

3. @propertyDecorator:

  • @propertyUsed to convert a method into a read-only property so that it can be accessed like a property without using calling brackets.
    class Circle:
        def __init__(self, radius):
            self._radius = radius
        
        @property
        def radius(self):
            return self._radius
    
    circle = Circle(5)
    print(circle.radius)  # 访问属性,输出: 5
    

4. @setterDecorator:

  • @setterUsed to define the setting method of a property, @propertyused in conjunction with allows the value to be set via the property.
    class Circle:
        def __init__(self, radius):
            self._radius = radius
        
        @property
        def radius(self):
            return self._radius
        
        @radius.setter
        def radius(self, value):
            if value >= 0:
                self._radius = value
    
    circle = Circle(5)
    circle.radius = 8
    print(circle.radius)  # 输出: 8
    

5. @deleterDecorator:

  • @deleterUsed to define the delete method of a property, @propertyused in conjunction with allows deletion of values ​​via the property.
    class Circle:
        def __init__(self, radius):
            self._radius = radius
        
        @property
        def radius(self):
            return self._radius
        
        @radius.deleter
        def radius(self):
            print("Deleting radius...")
            del self._radius
    
    circle = Circle(5)
    del circle.radius  # 删除属性
    

6. @abstractmethodDecorator:

  • @abstractmethodUsed to define abstract methods that must be implemented in subclasses. Abstract methods only have method signatures and no actual implementation.

    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
    
    circle = Circle(5)
    print(circle.area())  # 输出: 78.5
    

Three basic design patterns

3.1 Singleton pattern

  • Singleton pattern is a design pattern that aims to ensure that a class has only one instance and provides a global access point to obtain that instance .
  • In Python, there are many ways to implement the singleton pattern, two common ways: using decorators and using classes and singleton encapsulation methods .

3.1.1 Use decorators to implement singleton mode

  • Using decorators is a simple and elegant way to implement the singleton pattern.
  • Sample code:
def singleton(cls):
    instances = {
    
    }

    def get_instance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]

    return get_instance

@singleton
class SingletonClass:
    def __init__(self):
        print("SingletonClass instance created")

# 测试
instance1 = SingletonClass()
instance2 = SingletonClass()

print(instance1 is instance2)  # 输出: True,两个实例是同一个实例

  • In this example, a singletondecorator is defined that maintains a dictionary instancesto hold singleton instances of different classes. The decorator wraps the constructor of the original class, ensuring that only one instance is created each time the constructor is called.

3.1.2 Use classes to implement singleton pattern

  • Another way to implement the singleton pattern is through the class itself. This approach uses class variables to save the instance and checks in the constructor whether the instance already exists.
  • Sample code:
class SingletonClass:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(SingletonClass, cls).__new__(cls)
            print("SingletonClass instance created")
        return cls._instance

# 测试
instance1 = SingletonClass()
instance2 = SingletonClass()

print(instance1 is instance2)  # 输出: True,两个实例是同一个实例

  • __new__Control instance creation by overriding methods. When an instance is created for the first time, __new__the method of the parent class is called to create the instance and is saved in the class variable _instance. Subsequent instance creation operations will directly return the existing instance.

  • It should be noted that in a multi-threaded environment, in order to ensure the correctness of the singleton mode, thread safety needs to be considered.

3.1.3 Singleton encapsulation

  • Implementation method: Define the singleton class in a separate file, and then reference the class in other files, which can effectively encapsulate the singleton logic and reuse it in different modules . The advantage is that it can reduce the coupling of the code and make the implementation of the singleton clearer and more modular.

  • Sample demonstration

    • Assume a singleton.pyfile named in which a singleton class is defined SingletonClass:
    # singleton.py
    class SingletonClass:
        _instance = None
    
        def __new__(cls):
            if cls._instance is None:
                cls._instance = super().__new__(cls)
                cls._instance.value = 0
            return cls._instance
    
    • Then reference this class in another file and use it
    # main.py
    from singleton import SingletonClass
    
    instance1 = SingletonClass()
    instance2 = SingletonClass()
    
    instance1.value = 42
    
    print(instance1.value)  # 输出: 42
    print(instance2.value)  # 输出: 42,因为实例是同一个
    print(instance1 is instance2)  # 输出: True,两个实例是同一个实例
    
  • This approach separates singleton logic and business logic, making the code easier to maintain and extend. At the same time, it also follows the modular design principle.

3.2 Factory mode

  • Factory pattern is a creational design pattern that aims to create objects by providing a common interface without exposing the object's creation logic. This can help reduce coupling in your code and support code maintainability and scalability .
  • There are many ways to implement the factory pattern in Python, the most common of which include the simple factory pattern, factory method pattern and abstract factory pattern .

3.2.1 Simple Factory Pattern

Client
SimpleFactory
ConcreteProductA
ConcreteProductB
  • The simple factory pattern is the simplest form of the factory pattern. It creates objects through a factory class without requiring client code to instantiate objects directly.
  • Here is an example of a simple factory pattern:
    class Product:
        def operation(self):
            pass
    
    class ConcreteProductA(Product):
        def operation(self):
            return "ConcreteProductA operation"
    
    class ConcreteProductB(Product):
        def operation(self):
            return "ConcreteProductB operation"
    
    class SimpleFactory:
        def create_product(self, product_type):
            if product_type == "A":
                return ConcreteProductA()
            elif product_type == "B":
                return ConcreteProductB()
            else:
                raise ValueError("Invalid product type")
    
    # 使用简单工厂创建对象
    factory = SimpleFactory()
    product_a = factory.create_product("A")
    product_b = factory.create_product("B")
    
    print(product_a.operation())  # 输出: ConcreteProductA operation
    print(product_b.operation())  # 输出: ConcreteProductB operation
    

3.2.2 Factory method pattern

Client
Factory
ConcreteFactoryA
ConcreteFactoryB
ConcreteProductA
ConcreteProductB
  • The factory method pattern further abstracts the factory class so that each specific product has a corresponding factory subclass. Each factory subclass is responsible for creating a specific type of product.
  • Here is an example of the factory method pattern:
    class Product:
        def operation(self):
            pass
    
    class ConcreteProductA(Product):
        def operation(self):
            return "ConcreteProductA operation"
    
    class ConcreteProductB(Product):
        def operation(self):
            return "ConcreteProductB operation"
    
    class Factory:
        def create_product(self):
            pass
    
    class ConcreteFactoryA(Factory):
        def create_product(self):
            return ConcreteProductA()
    
    class ConcreteFactoryB(Factory):
        def create_product(self):
            return ConcreteProductB()
    
    # 使用工厂方法创建对象
    factory_a = ConcreteFactoryA()
    product_a = factory_a.create_product()
    
    factory_b = ConcreteFactoryB()
    product_b = factory_b.create_product()
    
    print(product_a.operation())  # 输出: ConcreteProductA operation
    print(product_b.operation())  # 输出: ConcreteProductB operation
    

3.2.3 Abstract Factory Pattern

Client
AbstractFactory
ConcreteFactory1
ConcreteFactory2
ConcreteProductA1
ConcreteProductB1
ConcreteProductA2
ConcreteProductB2
  • The Abstract Factory pattern further extends the Factory Method pattern to allow the creation of a set of related objects rather than just one product.
  • The following is an example of the Abstract Factory pattern:
class AbstractProductA:
    def operation(self):
        pass

class ConcreteProductA1(AbstractProductA):
    def operation(self):
        return "ConcreteProductA1 operation"

class ConcreteProductA2(AbstractProductA):
    def operation(self):
        return "ConcreteProductA2 operation"

class AbstractProductB:
    def operation(self):
        pass

class ConcreteProductB1(AbstractProductB):
    def operation(self):
        return "ConcreteProductB1 operation"

class ConcreteProductB2(AbstractProductB):
    def operation(self):
        return "ConcreteProductB2 operation"

class AbstractFactory:
    def create_product_a(self):
        pass

    def create_product_b(self):
        pass

class ConcreteFactory1(AbstractFactory):
    def create_product_a(self):
        return ConcreteProductA1()

    def create_product_b(self):
        return ConcreteProductB1()

class ConcreteFactory2(AbstractFactory):
    def create_product_a(self):
        return ConcreteProductA2()

    def create_product_b(self):
        return ConcreteProductB2()

# 使用抽象工厂创建对象
factory1 = ConcreteFactory1()
product_a1 = factory1.create_product_a()
product_b1 = factory1.create_product_b()

factory2 = ConcreteFactory2()
product_a2 = factory2.create_product_a()
product_b2 = factory2.create_product_b()

print(product_a1.operation())  # 输出: ConcreteProductA1 operation
print(product_b1.operation())  # 输出: ConcreteProductB1 operation
print(product_a2.operation())  # 输出: ConcreteProductA2 operation
print(product_b2.operation())  # 输出: ConcreteProductB2 operation

3.2.4 Summary of engineering mode

  1. Simple factory pattern
    • The simple factory pattern consists of a factory class responsible for creating different types of products.
Client
SimpleFactory
ConcreteProductA
ConcreteProductB
  1. Factory method pattern
  • The factory method pattern moves specific product creation logic to the corresponding factory subclass.
Client
Factory
ConcreteFactoryA
ConcreteFactoryB
ConcreteProductA
ConcreteProductB
  1. abstract factory pattern
    • The abstract factory pattern allows the creation of a set of related products, with each product family having a corresponding factory subclass.
Client
AbstractFactory
ConcreteFactory1
ConcreteFactory2
ConcreteProductA1
ConcreteProductB1
ConcreteProductA2
ConcreteProductB2

Guess you like

Origin blog.csdn.net/yang2330648064/article/details/132292876