欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
- 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老
- 导航
- kwan 的解忧杂货铺:全面总结 java 核心技术,jvm,并发编程 redis,kafka,Spring,微服务等
- 常用开发工具系列:常用的开发工具,IDEA,Mac,Alfred,Git,typora 等
- 数据库系列:详细总结了常用数据库 mysql 技术点,以及工作中遇到的 mysql 问题等
- 新空间代码工作室:提供各种软件服务,承接各种毕业设计,毕业论文等
- 懒人运维系列:总结好用的命令,解放双手不香吗?能用一个命令完成绝不用两个操作
- 数据结构与算法系列:总结数据结构和算法,不同类型针对性训练,提升编程思维,剑指大厂
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。 ✨✨ 欢迎订阅本专栏 ✨✨
在 Python 开发中,我们经常会遇到各种各样的参数传递错误。在编写用户注册功能时遇到了一个典型的错误:TypeError: register() got multiple values for argument 'username'
。
错误发生在调用UserService.register(username=args["username"], password=args["password"])
时。表面上看,这是一个普通的函数调用,传递了两个关键字参数。然而,问题出在UserService.register
被定义为类方法(@classmethod
),但参数列表中却缺少了关键的cls
参数。
class UserService:
@classmethod
def register(username: Optional[str] = None, password: Optional[str] = None) -> User:
# 方法实现
这种定义方式导致了参数传递的冲突:Python 解释器会隐式地将类本身作为第一个参数传递,而开发者又显式地传递了username
参数,造成username
参数被重复赋值。
要理解这个错误,我们需要深入理解@classmethod
装饰器的工作原理。@classmethod
是 Python 中用于定义类方法的内置装饰器,它与普通实例方法有本质区别:
cls
当使用@classmethod
装饰器时,Python 会在方法调用时自动将类作为第一个参数传入。这就是为什么我们必须显式地在参数列表中添加cls
参数——它实际上是为这个自动传入的参数提供一个接收位置。
在本案例中,错误的根源在于方法签名与装饰器行为的不匹配。具体来说:
@classmethod
,Python 会隐式传递类对象作为第一个参数username
,而非预期的cls
username
参数username
关键字参数username
参数被赋值两次:一次隐式通过位置,一次显式通过关键字这种参数冲突正是 Python 抛出got multiple values for argument
错误的原因。
解决这个问题的正确方法是在类方法的参数列表中添加cls
参数:
class UserService:
@classmethod
def register(cls, username: Optional[str] = None, password: Optional[str] = None) -> User:
db.session.begin_nested()
try:
user = UserService.create_user(username=username, password=password)
db.session.commit()
except Exception as e:
db.session.rollback()
logging.error(f"Register failed: {e}")
raise AccountRegisterError(f"Registration failed: {e}") from e
return user
这种定义方式明确了:
cls
接收自动传入的类对象username
和password
作为可选参数User
实例基于这个案例,我们可以总结出一些类方法设计的重要原则:
@classmethod
时必须包含cls
参数cls
(区别于实例方法的self
)@staticmethod
)混淆cls
参数添加类型提示可以进一步提高代码可读性很多开发者容易混淆类方法和静态方法。它们的关键区别在于:
特性 | 类方法(@classmethod) | 静态方法(@staticmethod) |
---|---|---|
第一个参数 | 接收类对象(cls) | 无特殊参数 |
访问类属性 | 可以通过 cls 访问 | 不能直接访问 |
使用场景 | 工厂方法、替代构造函数 | 与类相关但不需要类或实例的实用方法 |
继承行为 | 子类调用时传入子类作为 cls | 与普通函数相同 |
在用户注册的例子中,如果register
方法不需要访问任何类属性或方法,理论上也可以使用@staticmethod
。但通常推荐使用@classmethod
,因为它提供了更大的灵活性,特别是在继承场景下。
在实际开发中,何时使用类方法需要仔细考量:
工厂模式:当需要根据不同类型或条件创建对象时
@classmethod
def from_csv(cls, csv_file):
"""从CSV文件创建用户"""
# 实现细节
替代构造函数:当初始化逻辑复杂或有多种初始化方式时
@classmethod
def create_admin(cls, username, password):
"""创建管理员用户"""
# 特殊初始化逻辑
单例模式:实现全局唯一的实例
@classmethod
def get_instance(cls):
"""获取单例实例"""
if not cls._instance:
cls._instance = cls()
return cls._instance
在用户服务的例子中,register
方法作为类方法是合适的,因为它代表了一个与类相关但不依赖于特定实例的操作,且可能需要访问类级别的配置或方法(如create_user
)。
为了增强代码的可读性和可维护性,建议为类方法添加完整的类型提示和文档字符串:
class UserService:
@classmethod
def register(cls, username: Optional[str] = None, password: Optional[str] = None) -> User:
"""注册新用户
参数:
username: 可选用户名,如未提供将生成随机用户名
password: 可选密码,如未提供将生成随机密码
返回:
新创建的User实例
异常:
AccountRegisterError: 当注册失败时抛出
"""
# 方法实现
这种文档方式明确了:
针对类方法的测试应该特别注意:
def test_register_with_defaults():
"""测试使用默认参数的注册"""
user = UserService.register()
assert user.username is not None
assert user.password is not None
def test_register_with_credentials():
"""测试使用指定凭据的注册"""
user = UserService.register(username="test", password="pass")
assert user.username == "test"
assert user.check_password("pass")
觉得有用的话点个赞
呗。
❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!
Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!