我有一堂这样的课:
from __future__ import annotations
import os
from dataclasses import dataclass
@dataclass(frozen=True)
class Config:
name: str
age: str
@staticmethod
def init() -> Config:
return Config(
name=...
age=...
)
我想确保 init 方法始终返回相同的
Config
实例。
我可以通过这样做来实现这一目标:
@dataclass(frozen=True)
class Config:
name: str
age: str
@staticmethod
def init() -> Config:
if not _private_instance:
global _private_instance = Config(
name=...
age=...
)
return _private_instance
_private_instance: Optional[Config] = None
But I am wondering if there is a more Pythonic way of doing this. Thanks
我发现实现单例数据类的最Pythonic方法是使用单例元类,如@quamrana评论的answer中所建议,并添加默认值,以防止类型检查器在调用数据类时发出警告稍后不再争论。
from dataclasses import dataclass
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
@dataclass(frozen=True)
class Config(metaclass=Singleton):
name: str = ''
age: str = ''
p1 = Config('John', '25')
print(p1) # Config(name='John', age='25')
p2 = Config()
print(p2) # Config(name='John', age='25')
print(p1 == p2) # True
print(p1 is p2) # True
此代码的唯一问题是您不必填写数据类的所有参数。如果您想使用类型检查器强制执行此操作,请删除默认值,但请注意,如果您稍后不提供默认值,它将引发警告。或者,您可以检查 dunder post_init 方法中是否设置了正确的值:
@dataclass(frozen=True)
class Config(metaclass=Singleton):
name: str = ''
age: str = ''
def __post_init__(self):
if self.name == '' or self.age == '':
raise ValueError('name or age is empty')
但这只会在运行时引发。
我在你的问题中看到全局变量。您应该避免使用它。 示例(这不是执行单例数据类的好方法,仅修复您的示例而不使用全局):
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Optional
@dataclass(frozen=True)
class Config:
name: str
age: str
_private_instance: Optional[Config] = field(init=False, repr=False)
@classmethod
def init(cls) -> Config:
if not cls._private_instance:
cls._private_instance = Config(
name=...,
age=...,
)
return cls._private_instance
另外:您的数据类用于配置,也许您应该考虑该库OmegaConf。
您真正需要的只是用
init()
装饰 functools.lru_cache(maxsize=1)
,您将得到一个开箱即用的单例:
from __future__ import annotations
from dataclasses import dataclass
from functools import lru_cache
@dataclass(frozen=True)
class Config:
name: str
age: str
@staticmethod
@lru_cache(maxsize=1)
def init() -> Config:
return Config(
name="test",
age="test",
)
c1 = Config.init()
c2 = Config.init()
assert c1 is c2