第61集:静态方法与类方法

学习目标

  • 理解静态方法和类方法的概念和作用
  • 掌握@staticmethod和@classmethod装饰器的使用
  • 学会区分实例方法、静态方法和类方法
  • 了解各种方法的适用场景和最佳实践

1. 方法类型回顾

在Python中,类可以定义三种类型的方法:

  • 实例方法:最常用的方法,第一个参数是self
  • 静态方法:不需要访问实例或类,用@staticmethod装饰
  • 类方法:需要访问类但不依赖实例,用@classmethod装饰

2. 实例方法回顾

class Calculator:
    def add(self, a, b):
        """实例方法 - 需要访问实例属性"""
        return a + b

# 调用时需要创建实例
calc = Calculator()
result = calc.add(5, 3)  # 10

3. 静态方法 (@staticmethod)

3.1 什么是静态方法

静态方法是类中的普通函数,不需要访问实例(self)或类(cls)。它们只是逻辑上属于这个类。

3.2 定义静态方法

使用@staticmethod装饰器定义:

class MathUtils:
    @staticmethod
    def add(a, b):
        """静态方法 - 不依赖实例或类"""
        return a + b
    
    @staticmethod
    def multiply(a, b):
        """静态方法 - 纯数学运算"""
        return a * b
    
    @staticmethod
    def is_even(number):
        """静态方法 - 判断是否为偶数"""
        return number % 2 == 0

3.3 调用静态方法

静态方法可以通过类名直接调用,也可以通过实例调用:

# 通过类名调用(推荐)
result1 = MathUtils.add(10, 5)      # 15
result2 = MathUtils.multiply(4, 6)  # 24
is_even = MathUtils.is_even(8)      # True

# 通过实例调用(不推荐)
utils = MathUtils()
result3 = utils.add(10, 5)          # 15

3.4 静态方法的特点

  • 不需要self或cls参数
  • 不能访问实例属性或类属性
  • 类似于类的工具函数
  • 可以通过类名直接调用

4. 类方法 (@classmethod)

4.1 什么是类方法

类方法可以访问和修改类属性,但不能访问实例属性。第一个参数是cls(表示类本身)。

4.2 定义类方法

使用@classmethod装饰器定义:

class Student:
    # 类属性
    total_students = 0
    school_name = "Python学院"
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        # 每次创建实例时增加总数
        Student.total_students += 1
    
    @classmethod
    def get_total_students(cls):
        """类方法 - 获取学生总数"""
        return cls.total_students
    
    @classmethod
    def get_school_info(cls):
        """类方法 - 获取学校信息"""
        return f"学校:{cls.school_name},学生总数:{cls.total_students}"
    
    @classmethod
    def create_anonymous_student(cls, age):
        """类方法 - 工厂方法,创建匿名学生"""
        return cls("匿名", age)

4.3 调用类方法

类方法可以通过类名直接调用,也可以通过实例调用:

# 通过类名调用(推荐)
total = Student.get_total_students()
info = Student.get_school_info()
anonymous = Student.create_anonymous_student(20)

print(f"学生总数:{total}")           # 输出:学生总数:0(如果还没创建实例)
print(info)                         # 输出:学校:Python学院,学生总数:0
print(anonymous.name)               # 输出:匿名

4.4 类方法的特点

  • 第一个参数是cls(类本身)
  • 可以访问和修改类属性
  • 不能访问实例属性(除非通过参数传入)
  • 可以通过类名直接调用
  • 常用于工厂方法、修改类状态

5. 三种方法的对比

5.1 语法对比

class Example:
    class_attr = "类属性"
    
    def __init__(self, value):
        self.instance_attr = value  # 实例属性
    
    # 实例方法
    def instance_method(self):
        return f"实例方法:{self.instance_attr}, {self.class_attr}"
    
    # 静态方法
    @staticmethod
    def static_method(x, y):
        return f"静态方法:{x} + {y} = {x + y}"
    
    # 类方法
    @classmethod
    def class_method(cls):
        return f"类方法:{cls.class_attr}"

5.2 调用方式对比

obj = Example("实例值")

# 实例方法 - 只能通过实例调用
print(obj.instance_method())
# print(Example.instance_method())  # 错误!缺少self参数

# 静态方法 - 类和实例都可以调用
print(Example.static_method(3, 4))   # 推荐
print(obj.static_method(3, 4))      # 可以但不推荐

# 类方法 - 类和实例都可以调用
print(Example.class_method())       # 推荐
print(obj.class_method())            # 可以但不推荐

5.3 访问权限对比

class TestAccess:
    class_attr = "我可以访问"
    
    def __init__(self, value):
        self.instance_attr = value
    
    def instance_method(self):
        # 可以访问:实例属性、类属性、其他实例方法
        return f"实例:{self.instance_attr},类:{self.class_attr}"
    
    @staticmethod
    def static_method():
        # 只能访问:传入的参数
        # 不能访问:self.instance_attr, cls.class_attr
        return "静态方法:无法访问实例或类属性"
    
    @classmethod
    def class_method(cls):
        # 可以访问:类属性、其他类方法
        # 不能访问:self.instance_attr
        return f"类方法:{cls.class_attr}"

6. 实际应用场景

6.1 静态方法的典型应用

工具函数集合

class StringUtils:
    """字符串工具类"""
    
    @staticmethod
    def is_empty(text):
        """检查字符串是否为空"""
        return text is None or text.strip() == ""
    
    @staticmethod
    def reverse(text):
        """反转字符串"""
        return text[::-1]
    
    @staticmethod
    def capitalize_words(text):
        """每个单词首字母大写"""
        return ' '.join(word.capitalize() for word in text.split())

# 使用
print(StringUtils.is_empty(""))              # True
print(StringUtils.reverse("hello"))         # olleh
print(StringUtils.capitalize_words("hello world"))  # Hello World

数据验证

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_phone_number(phone):
        """手机号验证(简单版)"""
        return len(phone) == 11 and phone.isdigit()

# 使用
print(Validator.is_valid_email("test@example.com"))  # True
print(Validator.is_phone_number("13800138000"))     # True

6.2 类方法的典型应用

工厂方法模式

class Date:
    """日期类"""
    
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day
    
    @classmethod
    def from_string(cls, date_string):
        """从字符串创建日期对象"""
        # 支持格式:"2024-01-15" 或 "2024/1/15"
        parts = date_string.replace('/', '-').split('-')
        year, month, day = map(int, parts)
        return cls(year, month, day)
    
    @classmethod
    def today(cls):
        """创建今天的日期对象"""
        import datetime
        today = datetime.date.today()
        return cls(today.year, today.month, today.day)
    
    def __str__(self):
        return f"{self.year}-{self.month:02d}-{self.day:02d}"

# 使用
date1 = Date.from_string("2024-01-15")
date2 = Date.from_string("2024/1/15")
date3 = Date.today()

print(date1)  # 2024-01-15
print(date2)  # 2024-01-15

单例模式实现

class DatabaseConnection:
    """数据库连接类(单例模式)"""
    
    _instance = None
    _connection_count = 0
    
    def __init__(self):
        if DatabaseConnection._instance is not None:
            raise Exception("这是单例类,请使用get_instance()方法获取实例")
        DatabaseConnection._connection_count += 1
    
    @classmethod
    def get_instance(cls):
        """获取单例实例"""
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance
    
    @classmethod
    def get_connection_count(cls):
        """获取连接次数"""
        return cls._connection_count
    
    def connect(self):
        return "数据库连接已建立"

# 使用
db1 = DatabaseConnection.get_instance()
db2 = DatabaseConnection.get_instance()

print(db1.connect())                    # 数据库连接已建立
print(db1 is db2)                      # True(同一个实例)
print(DatabaseConnection.get_connection_count())  # 1

7. 综合示例:配置管理器

让我们创建一个完整的配置管理器来展示三种方法的配合使用:

class ConfigManager:
    """配置管理器"""
    
    # 类属性 - 默认配置
    _default_config = {
        'debug': False,
        'max_connections': 100,
        'timeout': 30
    }
    
    # 类属性 - 当前配置
    _current_config = _default_config.copy()
    
    def __init__(self, config_dict=None):
        """初始化配置管理器"""
        if config_dict:
            self.update_config(config_dict)
    
    # 实例方法 - 更新配置
    def update_config(self, new_config):
        """更新配置(实例方法)"""
        self._current_config.update(new_config)
        return "配置已更新"
    
    def get_config(self, key=None):
        """获取配置(实例方法)"""
        if key:
            return self._current_config.get(key)
        return self._current_config.copy()
    
    # 静态方法 - 配置验证
    @staticmethod
    def validate_config(config):
        """验证配置是否有效(静态方法)"""
        required_keys = ['debug', 'max_connections', 'timeout']
        
        if not isinstance(config, dict):
            return False, "配置必须是字典类型"
        
        for key in required_keys:
            if key not in config:
                return False, f"缺少必需的配置项:{key}"
        
        if not isinstance(config['max_connections'], int) or config['max_connections'] <= 0:
            return False, "max_connections必须是正整数"
        
        if not isinstance(config['timeout'], (int, float)) or config['timeout'] <= 0:
            return False, "timeout必须是正数"
        
        return True, "配置有效"
    
    # 类方法 - 重置为默认配置
    @classmethod
    def reset_to_default(cls):
        """重置为默认配置(类方法)"""
        cls._current_config = cls._default_config.copy()
        return "已重置为默认配置"
    
    # 类方法 - 从文件加载配置
    @classmethod
    def load_from_file(cls, filename):
        """从文件加载配置(类方法)"""
        try:
            with open(filename, 'r', encoding='utf-8') as f:
                import json
                config = json.load(f)
            
            # 验证配置
            is_valid, message = cls.validate_config(config)
            if is_valid:
                cls._current_config = config
                return f"配置已从{filename}加载"
            else:
                return f"配置无效:{message}"
        except FileNotFoundError:
            return f"文件{filename}不存在"
        except json.JSONDecodeError:
            return "配置文件格式错误"
    
    # 类方法 - 获取配置信息
    @classmethod
    def get_config_info(cls):
        """获取配置信息(类方法)"""
        return {
            'current_config': cls._current_config.copy(),
            'default_config': cls._default_config.copy(),
            'is_default': cls._current_config == cls._default_config
        }

使用示例:

# 创建配置管理器实例
config_mgr = ConfigManager()

# 使用实例方法
print(config_mgr.get_config('debug'))  # False
config_mgr.update_config({'debug': True})
print(config_mgr.get_config('debug'))  # True

# 使用静态方法验证配置
custom_config = {'debug': True, 'max_connections': 200, 'timeout': 60}
is_valid, msg = ConfigManager.validate_config(custom_config)
print(msg)  # 配置有效

# 使用类方法
ConfigManager.reset_to_default()
info = ConfigManager.get_config_info()
print(info['is_default'])  # True

8. 常见错误与注意事项

8.1 参数错误

class ErrorExample:
    @staticmethod
    def wrong_static_method(self):  # ❌ 静态方法不应该有self参数
        pass
    
    @classmethod
    def wrong_class_method(self):  # ❌ 应该是cls而不是self
        pass
    
    @classmethod
    def correct_class_method(cls):  # ✅ 正确的写法
        pass

8.2 错误的调用方式

class CallExample:
    @staticmethod
    def static_method():
        return "静态方法"
    
    @classmethod
    def class_method(cls):
        return "类方法"

# ❌ 错误的调用方式
example = CallExample()
# CallExample.static_method(example)  # 静态方法不需要参数
# example.class_method(CallExample)   # 类方法自动传入cls

# ✅ 正确的调用方式
CallExample.static_method()    # 推荐
CallExample.class_method()     # 推荐
example.static_method()         # 可以但不推荐
example.class_method()          # 可以但不推荐

8.3 访问权限混淆

class AccessError:
    class_attr = "类属性"
    
    def __init__(self):
        self.instance_attr = "实例属性"
    
    @staticmethod
    def wrong_static():
        # ❌ 静态方法不能直接访问实例或类属性
        # return self.instance_attr  # NameError: name 'self' is not defined
        # return AccessError.class_attr  # 虽然可以,但不推荐
        return "静态方法应该只使用自己的参数"
    
    @classmethod
    def correct_class(cls):
        # ✅ 类方法可以访问类属性
        return cls.class_attr

9. 方法选择指南

9.1 何时使用实例方法

  • 需要访问或修改实例属性时
  • 方法逻辑依赖于特定实例状态时
  • 大多数情况下都应该使用实例方法

9.2 何时使用静态方法

  • 方法逻辑与类相关,但不需要访问实例或类属性
  • 纯粹的工具函数
  • 可以在任何地方独立存在的函数

9.3 何时使用类方法

  • 需要访问或修改类属性时
  • 实现工厂方法模式时
  • 需要子类继承并修改行为时
  • 方法逻辑依赖于类而不依赖于实例时

10. 课后练习

练习1:数学工具类

创建一个MathTools类,包含:

  • 静态方法:计算圆的面积、计算阶乘、判断素数
  • 类方法:设置圆周率精度、获取当前精度、重置默认精度

练习2:用户管理器

创建一个UserManager类,包含:

  • 实例方法:添加用户、删除用户、查找用户
  • 静态方法:验证用户名格式、验证密码强度
  • 类方法:获取用户总数、获取活跃用户数、批量创建用户

练习3:日志级别管理器

创建一个LogLevelManager类:

  • 类属性:定义不同级别的日志常量
  • 实例方法:设置当前日志级别、记录日志
  • 静态方法:格式化日志消息、解析日志级别字符串
  • 类方法:获取所有支持的级别、创建预设配置

总结

今天我们学习了:

  • 静态方法:用@staticmethod装饰,不需要self或cls参数,适用于工具函数
  • 类方法:用@classmethod装饰,第一个参数是cls,适用于工厂方法和修改类状态
  • 实例方法:需要self参数,可以访问实例和类属性,是最常用的方法
  • 三种方法各有适用场景,要根据具体需求选择合适的方法类型

记住:优先使用实例方法,只有在不需要访问实例状态时才考虑静态方法或类方法

下一集我们将学习属性装饰器,让我们可以像访问属性一样访问方法!

« 上一篇 类属性与实例属性 下一篇 » 属性装饰器