草庐IT

用Python实现单例模式

小胖虎* 2023-05-16 原文

什么是单例模式

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。

单例模式的应用场景

单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:

1.需要频繁实例化然后销毁的对象。

2.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。

3.有状态的工具类对象。

4.频繁访问数据库或文件的对象。

以下都是单例模式的经典使用场景:

1.资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。

2.控制资源的情况下,方便资源之间的互相通信。如线程池等。

单例模式的优缺点

优点:

1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例

2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。

3.提供了对唯一实例的受控访问。

4.由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。

5.允许可变数目的实例。

6.避免对共享资源的多重占用。

缺点:

1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。

2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。

3.单例类的职责过重,在一定程度上违背了“单一职责原则”。

4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

实现单例模式的几个实例

使用类变量实现单例模式

在 Python 中,可以通过定义一个类变量来实现单例模式,这个类变量可以存储单例实例的引用,如果该变量已经存储了一个实例,就直接返回该实例的引用,否则就创建一个新实例并将其存储在类变量中。

class Singleton:
    __instance = None   # 用于存储单例实例的类变量

    def __new__(cls):
        if cls.__instance is None:
            cls.__instance = super().__new__(cls)
        return cls.__instance

s1 = Singleton()
s2 = Singleton()
print(s1 == s2)   # True,因为 s1 和 s2 引用的是同一个实例

在这个例子中,我们定义了一个名为 `Singleton` 的类,它有一个类变量 `__instance`,用于存储单例实例的引用。在类的构造方法 `__new__` 中,我们首先检查 `__instance` 是否为 None,如果是,则说明还没有创建实例,于是调用父类的 `__new__` 方法创建一个新实例并将其赋值给 `__instance`,然后返回该实例的引用。如果 `__instance` 不为 None,则说明已经存在一个实例,于是直接返回该实例的引用。

最后,我们创建了两个 `Singleton` 类的实例 `s1` 和 `s2`,并将它们分别赋值给变量 `s1` 和 `s2`。由于单例模式的特性,我们期望 `s1` 和 `s2` 引用的是同一个实例,因此打印 `s1 == s2` 的结果应该为 True。除了使用类变量存储单例实例的引用之外,还可以使用装饰器或元类来实现单例模式。

使用装饰器实现单例模式

def singleton(cls):
    instances = {}
    def getinstance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return getinstance

@singleton
class MyClass:
    pass

a = MyClass()
b = MyClass()
print(a == b)   # True,因为 a 和 b 引用的是同一个实例

在这个例子中,我们定义了一个名为 `singleton` 的装饰器,它会接受一个类作为参数,返回一个新的函数 `getinstance`。在 `getinstance` 函数中,我们首先检查 `cls` 是否在 `instances` 字典中,如果不在,则说明还没有创建实例,于是调用 `cls` 的构造方法创建一个新实例并将其存储在 `instances` 中,然后返回该实例的引用。如果 `cls` 已经在 `instances` 中,则直接返回该实例的引用。最后,我们用 `@singleton` 装饰器修饰了一个名为 `MyClass` 的类,使其成为单例类。

使用元类实现单例模式

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]

class MyClass(metaclass=Singleton):
    pass

a = MyClass()
b = MyClass()
print(a == b)   # True,因为 a 和 b 引用的是同一个实例

在这个例子中,我们定义了一个名为 `Singleton` 的元类,它会在每次创建类时被调用。在 `Singleton` 的 `__call__` 方法中,我们首先检查 `cls` 是否在 `_instances` 字典中,如果不在,则说明还没有创建实例,于是调用父类的 `__call__` 方法创建一个新实例并将其存储在 `_instances` 中,然后返回该实例的引用。如果 `cls` 已经在 `_instances` 中,则直接返回该实例的引用。最后,我们用 `metaclass=Singleton` 指定了一个元类,使其成为 `MyClass` 的元类,从而使 `MyClass` 成为单例类。除了上述的几种实现方式外,还有其他的一些实现单例模式的方法。

使用模块实现单例模式

在 Python 中,模块在导入时只会被执行一次,因此可以使用模块来实现单例模式。例如,我们可以创建一个名为 `mysingleton.py` 的模块,并在其中定义一个单例类:

class MySingleton:
    def __init__(self):
        self.name = "Singleton"

my_singleton = MySingleton()

在另一个文件中,我们可以导入 `mysingleton` 模块,并使用其中的单例对象 `my_singleton`:

from mysingleton import my_singleton
print(my_singleton.name)   # "Singleton"

在这个例子中,我们在 `mysingleton.py` 模块中定义了一个名为 `MySingleton` 的单例类,并在该类的构造方法中定义了一个名为 `name` 的实例变量。在该模块中,我们还创建了一个名为 `my_singleton` 的 `MySingleton` 类的实例,并将其赋值给一个全局变量。在另一个文件中,我们通过 `from mysingleton import my_singleton` 导入了 `my_singleton` 对象,并打印了它的 `name` 变量。

使用闭包实现单例模式

在 Python 中,闭包是一种可以捕获其定义域中的变量并将其封装在函数对象中的方法。可以使用闭包来实现单例模式。例如,我们可以创建一个函数 `singleton`,它会返回一个内部函数 `get_instance`,该函数用于创建并返回单例对象:

def singleton(cls):
    instance = None
    def get_instance(*args, **kwargs):
        nonlocal instance
        if instance is None:
            instance = cls(*args, **kwargs)
        return instance
    return get_instance

@singleton
class MyClass:
    pass

a = MyClass()
b = MyClass()
print(a == b)   # True,因为 a 和 b 引用的是同一个实例

在这个例子中,我们定义了一个名为 `singleton` 的函数,它接受一个类作为参数,并返回一个内部函数 `get_instance`。在 `get_instance` 函数中,我们首先声明一个名为 `instance` 的变量,并将其初始化为 None。然后,我们检查 `instance` 是否为 None,如果是,则说明还没有创建实例,于是调用 `cls` 的构造方法创建一个新实例并将其赋值给 `instance`,然后返回该实例的引用。如果 `instance` 不为 None,则说明已经存在一个实例,直接返回该实例的引用。最后,我们用 `@singleton` 装饰器修饰了一个名为 `MyClass` 的类,使其成为单例类。

使用单例模式的注意事项

在使用单例模式时,需要注意以下几点:

  1. 确保单例类的唯一性:单例类应该只有一个实例,并且该实例应该在整个程序中都可用。因此外,还需要注意以下几点:

  1. 线程安全:如果多个线程同时调用单例类的构造方法,可能会创建多个实例。因此,需要采取线程安全的措施,如使用锁或原子操作。

  1. 生命周期管理:由于单例对象在整个程序中都可用,因此需要考虑它的生命周期。单例对象可能需要在程序退出时进行清理操作,例如关闭数据库连接或保存缓存数据。

  1. 依赖管理:如果单例对象依赖于其他对象,则需要确保这些依赖对象也是单例对象。否则,可能会出现多个实例之间的状态不一致的问题。

总之,单例模式是一种非常有用的设计模式,它可以确保一个类只有一个实例,并且该实例在整个程序中都可用。在 Python 中,可以使用多种方式实现单例模式,如使用类装饰器、元类、模块、闭包等。在实现单例模式时,需要考虑线程安全、生命周期管理和依赖管理等问题,以确保单例对象的正确性和可靠性。

有关用Python实现单例模式的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  5. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  6. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  7. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  8. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  9. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  10. python - 如何读取 MIDI 文件、更改其乐器并将其写回? - 2

    我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的

随机推荐