Day 29 - @staticmethod 装饰器与单例模式

什么是静态方法?

静态方法(Static Method)是使用 @staticmethod 装饰器装饰的方法。它不需要 selfcls 参数,可以像普通函数一样定义,但在类内部定义,属于类的命名空间。

静态方法的主要用途:

  1. 将一些与类相关的功能封装在类内部,使代码更组织化
  2. 不需要访问类属性或实例属性的功能
  3. 可以被子类继承(但不能通过 super() 调用父类的静态方法)
class MathUtils:
    """数学工具类"""
    
    @staticmethod
    def add(a, b):
        """加法"""
        return a + b
    
    @staticmethod
    def multiply(a, b):
        """乘法"""
        return a * b
    
    @staticmethod
    def is_prime(n):
        """判断素数"""
        if n < 2:
            return False
        for i in range(2, int(n**0.5) + 1):
            if n % i == 0:
                return False
        return True

# 调用静态方法
print(MathUtils.add(3, 5))      # 8
print(MathUtils.multiply(4, 7)) # 28
print(MathUtils.is_prime(17))   # True
print(MathUtils.is_prime(15))   # False

# 也可以通过实例调用
utils = MathUtils()
print(utils.add(2, 3))  # 5

三种方法的对比

class Example:
    class_attr = "类属性"
    
    def __init__(self, value):
        self.value = value
    
    def instance_method(self):
        """实例方法:需要 self"""
        return f"实例方法,value={self.value}"
    
    @classmethod
    def class_method(cls):
        """类方法:需要 cls"""
        return f"类方法,class_attr={cls.class_attr}"
    
    @staticmethod
    def static_method():
        """静态方法:不需要 self 或 cls"""
        return "静态方法,不依赖类或实例"

# 对比
obj = Example("test")

# 实例方法:需要实例调用
print(obj.instance_method())
# Example.instance_method(obj)  # 需要显式传递实例

# 类方法:类或实例都可以调用
print(Example.class_method())
print(obj.class_method())

# 静态方法:类或实例都可以调用
print(Example.static_method())
print(obj.static_method())

静态方法与命名空间

静态方法的一个重要作用是将相关的函数组织在类的命名空间中,避免污染全局命名空间。

import math

# 不推荐:所有函数都在全局命名空间
def calculate_circle_area(r):
    return math.pi * r ** 2

def calculate_circle_circumference(r):
    return 2 * math.pi * r

# 推荐:将相关函数组织在类中
class Circle:
    def __init__(self, radius):
        self.radius = radius
    
    @staticmethod
    def area(radius):
        """计算圆面积"""
        return math.pi * radius ** 2
    
    @staticmethod
    def circumference(radius):
        """计算圆周长"""
        return 2 * math.pi * radius
    
    def total_area(self, other):
        """计算两个圆的总面积"""
        return Circle.area(self.radius) + Circle.area(other.radius)

c1 = Circle(5)
c2 = Circle(3)

# 静态方法可以直接通过类调用
print(Circle.area(5))        # 78.54
print(Circle.circumference(5))  # 31.42

# 也可以通过实例调用
print(c1.area(5))            # 78.54

# 在实例方法中调用静态方法
print(c1.total_area(c2))     # 98.96

静态方法与继承

静态方法可以被继承,但调用方式与类方法不同。

class Base:
    @staticmethod
    def static_method():
        print("Base.static_method")

class Derived(Base):
    @staticmethod
    def static_method():
        print("Derived.static_method")

# 测试
Base.static_method()        # Base.static_method
Derived.static_method()     # Derived.static_method

base = Base()
base.static_method()        # Base.static_method

derived = Derived()
derived.static_method()     # Derived.static_method

# 通过子类调用父类的静态方法(不推荐)
base.static_method()  # 仍然是 Base 的版本

单例模式详解

单例模式(Singleton Pattern)是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。

方法1:使用 __new__

class Singleton:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance
    
    def __init__(self):
        if not self._initialized:
            self.data = None
            Singleton._instance._initialized = True
    
    def set_data(self, data):
        self.data = data
    
    def get_data(self):
        return self.data

# 测试
s1 = Singleton()
s2 = Singleton()

print(s1 is s2)  # True

s1.set_data(42)
print(s2.get_data())  # 42

方法2:使用装饰器

def singleton(cls):
    """单例装饰器"""
    instances = {}
    
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    
    return get_instance

@singleton
class Database:
    def __init__(self, host, port):
        self.host = host
        self.port = port
        print(f"连接数据库:{host}:{port}")

# 测试
db1 = Database("localhost", 3306)
db2 = Database("remote", 5432)

print(db1 is db2)  # True
print(db1.host)   # localhost
print(db2.host)   # localhost(来自第一次创建)

方法3:使用元类(Metaclass)

class SingletonMeta(type):
    """单例元类"""
    _instances = {}
    
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=SingletonMeta):
    def __init__(self, host, port):
        self.host = host
        self.port = port
    
    def query(self, sql):
        print(f"执行查询:{sql}")

# 测试
db1 = Database("localhost", 3306)
db2 = Database("remote", 5432)

print(db1 is db2)  # True
db1.query("SELECT * FROM users")

方法4:使用模块

Python 的模块本身就是单例,因为模块只会被导入一次。

# my_singleton.py
class Singleton:
    def __init__(self):
        self.data = None

_instance = Singleton()

def get_instance():
    return _instance

# 在其他文件中使用
# from my_singleton import get_instance
# db = get_instance()

单例模式的线程安全

基础的单例实现在多线程环境下可能有问题。使用线程锁可以解决这个问题。

import threading

class ThreadSafeSingleton:
    _instance = None
    _lock = threading.Lock()
    
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            with cls._lock:
                # 双重检查锁定
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
                    cls._instance._initialized = False
        return cls._instance
    
    def __init__(self, value=None):
        if not self._initialized:
            self.value = value
            ThreadSafeSingleton._instance._initialized = True

# 测试多线程创建
def create_singleton():
    s = ThreadSafeSingleton("test")
    print(f"线程创建的实例:{id(s)}, value={s.value}")

threads = [threading.Thread(target=create_singleton) for _ in range(5)]
for t in threads:
    t.start()
for t in threads:
    t.join()

Borg 模式(共享状态单例)

Borg 模式不是让所有实例都是同一个对象,而是让所有实例共享同一个状态。

class Borg:
    """Borg 模式:所有实例共享同一个状态"""
    _shared_state = {}
    
    def __init__(self):
        self.__dict__ = self._shared_state
        if not hasattr(self, 'initialized'):
            self.initialized = True
            self.data = None
    
    def set_data(self, data):
        self.data = data
    
    def get_data(self):
        return self.data

# 测试
b1 = Borg()
b2 = Borg()

print(b1 is b2)  # False(不同的对象)

b1.set_data(42)
print(b2.get_data())  # 42(共享状态)

b2.data = "shared"
print(b1.data)  # shared

静态方法在实际项目中的应用

示例1:验证器

class Validator:
    """数据验证器"""
    
    @staticmethod
    def is_valid_email(email):
        import re
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        return re.match(pattern, email) is not None
    
    @staticmethod
    def is_valid_phone(phone):
        import re
        pattern = r'^1[3-9]\d{9}$'
        return re.match(pattern, phone) is not None
    
    @staticmethod
    def is_valid_id_card(id_card):
        import re
        pattern = r'^\d{17}[\dXx]$'
        return re.match(pattern, id_card) is not None
    
    @staticmethod
    def validate_range(value, min_val, max_val):
        """验证值是否在指定范围内"""
        return min_val <= value <= max_val

# 使用
print(Validator.is_valid_email("user@example.com"))  # True
print(Validator.is_valid_email("invalid-email"))     # False
print(Validator.is_valid_phone("13800138000"))      # True
print(Validator.is_valid_id_card("11010119900101123X"))  # True
print(Validator.validate_range(5, 1, 10))           # True

示例2:转换工具

class Converter:
    """数据转换工具"""
    
    @staticmethod
    def celsius_to_fahrenheit(c):
        return c * 9/5 + 32
    
    @staticmethod
    def fahrenheit_to_celsius(f):
        return (f - 32) * 5/9
    
    @staticmethod
    def meters_to_feet(m):
        return m * 3.28084
    
    @staticmethod
    def feet_to_meters(ft):
        return ft / 3.28084
    
    @staticmethod
    def kg_to_pounds(kg):
        return kg * 2.20462
    
    @staticmethod
    def pounds_to_kg(lb):
        return lb / 2.20462

# 使用
print(f"25°C = {Converter.celsius_to_fahrenheit(25):.2f}°F")
print(f"77°F = {Converter.fahrenheit_to_celsius(77):.2f}°C")
print(f"100m = {Converter.meters_to_feet(100):.2f}ft")
print(f"328ft = {Converter.feet_to_meters(328):.2f}m")

示例3:配置常量

class AppConfig:
    """应用配置常量"""
    
    DEBUG = False
    TESTING = False
    PRODUCTION = True
    
    # 数据库配置
    DB_HOST = "localhost"
    DB_PORT = 3306
    DB_NAME = "myapp"
    
    # API 配置
    API_VERSION = "v1"
    API_TIMEOUT = 30
    
    @staticmethod
    def is_production():
        return AppConfig.PRODUCTION
    
    @staticmethod
    def is_debug():
        return AppConfig.DEBUG
    
    @classmethod
    def get_db_url(cls):
        """获取数据库 URL"""
        return f"mysql://{cls.DB_HOST}:{cls.DB_PORT}/{cls.DB_NAME}"

# 使用
print(f"运行环境:{'生产' if AppConfig.is_production() else '开发'}")
print(f"数据库URL:{AppConfig.get_db_url()}")

练习题

练习 1:实现字符串工具类

class StringUtils:
    """字符串工具类"""
    
    @staticmethod
    def reverse(s):
        """反转字符串"""
        return s[::-1]
    
    @staticmethod
    def is_palindrome(s):
        """判断回文数"""
        s = ''.join(c.lower() for c in s if c.isalnum())
        return s == s[::-1]
    
    @staticmethod
    def count_vowels(s):
        """统计元音字母数量"""
        vowels = set('aeiouAEIOU')
        return sum(1 for c in s if c in vowels)
    
    @staticmethod
    def remove_whitespace(s):
        """移除所有空白字符"""
        return ''.join(c for c in s if not c.isspace())
    
    @staticmethod
    def truncate(s, length, suffix="..."):
        """截断字符串"""
        if len(s) <= length:
            return s
        return s[:length - len(suffix)] + suffix

# 测试
print(StringUtils.reverse("hello"))          # olleh
print(StringUtils.is_palindrome("A man a plan a canal Panama"))  # True
print(StringUtils.is_palindrome("hello"))    # False
print(StringUtils.count_vowels("Hello World"))  # 3
print(StringUtils.remove_whitespace("  a  b  c  "))  # abc
print(StringUtils.truncate("Hello, World!", 8))  # Hello...

练习 2:实现简单的缓存(单例)

class Cache:
    """简单的内存缓存(单例)"""
    _instance = None
    _lock = threading.Lock()
    
    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self):
        if not hasattr(self, '_data'):
            self._data = {}
    
    @staticmethod
    def get(key, default=None):
        """获取缓存值"""
        instance = Cache()
        return instance._data.get(key, default)
    
    @staticmethod
    def set(key, value):
        """设置缓存值"""
        instance = Cache()
        instance._data[key] = value
    
    @staticmethod
    def delete(key):
        """删除缓存值"""
        instance = Cache()
        if key in instance._data:
            del instance._data[key]
    
    @staticmethod
    def clear():
        """清空缓存"""
        instance = Cache()
        instance._data.clear()
    
    @staticmethod
    def keys():
        """获取所有缓存键"""
        instance = Cache()
        return list(instance._data.keys())

import threading

# 测试
Cache.set("name", "张三")
Cache.set("age", 25)

print(Cache.get("name"))   # 张三
print(Cache.get("age"))    # 25
print(Cache.keys())        # ['name', 'age']

Cache.delete("age")
print(Cache.keys())        # ['name']

# 多线程测试
def worker():
    for i in range(10):
        Cache.set(f"key_{threading.current_thread().name}_{i}", i*i)

threads = [threading.Thread(target=worker, name=f"T{i}") for i in range(3)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(f"缓存条目数:{len(Cache.keys())}")  # 应该是 30

练习 3:实现类工厂

class Animal:
    """动物基类"""
    pass

class AnimalFactory:
    """动物工厂"""
    
    _registry = {}
    
    @classmethod
    def register(cls, animal_type, animal_class):
        """注册动物类"""
        cls._registry[animal_type] = animal_class
    
    @classmethod
    def create(cls, animal_type, *args, **kwargs):
        """创建动物实例"""
        if animal_type not in cls._registry:
            raise ValueError(f"未知的动物类型:{animal_type}")
        return cls._registry[animal_type](*args, **kwargs)
    
    @staticmethod
    def list_types():
        """列出所有已注册的动物类型"""
        return list(AnimalFactory._registry.keys())

class Dog(Animal):
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return "汪汪汪"

class Cat(Animal):
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return "喵喵喵"

# 注册动物类型
AnimalFactory.register("dog", Dog)
AnimalFactory.register("cat", Cat)

# 创建动物实例
dog = AnimalFactory.create("dog", "旺财")
cat = AnimalFactory.create("cat", "小白")

print(f"{dog.name}{dog.speak()}")  # 旺财:汪汪汪
print(f"{cat.name}{cat.speak()}")  # 小白:喵喵喵
print(f"已注册的动物类型:{AnimalFactory.list_types()}")  # ['dog', 'cat']

总结

@staticmethod 和单例模式是 Python 面向对象编程的重要内容:

  1. 静态方法:不需要 selfcls 的方法,用于组织相关功能
  2. 与类方法的对比:静态方法不能访问类属性或实例属性
  3. 单例模式:确保类只有一个实例
  4. 实现方式__new__、装饰器、元类、模块
  5. 线程安全:多线程环境下需要注意线程安全
  6. Borg 模式:共享状态的单例变体

在下一节中,我们将学习异常处理:try-except-else-finally。