Python 单例数据类

问题描述 投票:0回答:2

我有一堂这样的课:

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
python singleton python-dataclasses
2个回答
4
投票

我发现实现单例数据类的最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


0
投票

您真正需要的只是用

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
© www.soinside.com 2019 - 2024. All rights reserved.