背景
假如,我们需要写一个创建 User 的方法,很自然地,我们写了一个 UserService 类,把需要实现的方法放进去。就像这样:
class UserService:
@classmethod
def create(cls, username: str, email: str):
pass
接下来实现创建逻辑就好啦。
可是烦人的产品同学又加了一个要求:用户名长度不能超过 20 个字符。
好吧,我们需要加上一个判断了。
if len(username) > 20:
raise Exception(....)
我们对代码颜值有严格的要求,绝对不允许把 20 这样随便地放在代码中。我们会声明一个“常量”,并且在需要它的地方使用它。
USERNAME_MAX_LENGTH = 20
if len(username) > USERNAME_MAX_LENGTH:
raise Exception(....)
最终代码写成了这样
class UserService:
USERNAME_MAX_LENGTH: int = 20
@classmethod
def create(cls, username: str, email: str):
if len(username) > cls.USERNAME_MAX_LENGTH:
raise Exception(....)
pass
啰嗦了那么久,现在才说到了正题:Python 并不像其他语言有 “const” 关键字,这里的 USERNAME_MAX_LENGTH
并不是真的常量呀。怎么样从代码实现的层面彻底避免其他地方不小心修改它呢?(比如手误写了一个 if USERNAME_MAX_LENGTH = 10:
,少写了一个等号)
实现
众所周知,在 Python 中一切皆对象。 UserService
表面上是个浓眉大眼的类,实质上也只个被元类 type
实例化的对象罢了。
作为一个对象,有人要改它的属性 USERNAME_MAX_LENGTH
,就得通过它的 __setattr__
方法来改。直接从元类入手,禁掉 __setattr__
,我们的变量就会安全了。
很容易我们就完成需要的元类了:
class Const(type):
def __setattr__(cls, key, value):
raise Exception(f"variable '{key}' declared const here")
接下来,让我们自定义的元类来生成 UserService
:
class UserService(metaclass=Const):
USERNAME_MAX_LENGTH: = 20
@classmethod
def create(cls, username: str, email: str):
if len(username) > cls.USERNAME_MAX_LENGTH:
raise Exception(....)
pass
大功告成!
其他
在 Python 3.8 版本中,增加了 Final
类型标示,如果有写常量的需求,记得加上它。
相关资料:
[1] What are metaclasses in Python? - stackoverflow.com
[2] typing.Final - docs.python.org
可以的
是不是有误?
谢谢,已纠正~
Final 这个妙啊,真是妙