在Python中创建单例模式

1655
这个问题并不讨论单例设计模式是否可取,是否是反模式或任何宗教战争,而是讨论如何以最符合Python风格的方式实现此模式。在这种情况下,我将“最符合Python风格”定义为遵循“最小惊奇原则”。
我有多个类需要成为单例(我的用例是一个记录器,但这不重要)。我不希望在几个类中添加额外的内容,而是可以简单地继承或装饰。
最佳方法:

方法1:装饰器

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

@singleton
class MyClass(BaseClass):
    pass

优点

  • 装饰器的特性通常比多重继承更加直观。

缺点

  • While objects created using MyClass() would be true singleton objects, MyClass itself is a function, not a class, so you cannot call class methods from it. Also for

    x = MyClass();
    y = MyClass();
    t = type(n)();
    

那么x == y,但是x != t && y != t


方法二:基类
class Singleton(object):
    _instance = None
    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = object.__new__(class_, *args, **kwargs)
        return class_._instance

class MyClass(Singleton, BaseClass):
    pass

优点

  • 它是一个真正的类

缺点

  • 多重继承 - 呃!从第二个基类继承时可能会覆盖__new__?这需要比必要更多的思考。

方法三:元类

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

#Python2
class MyClass(BaseClass):
    __metaclass__ = Singleton

#Python3
class MyClass(BaseClass, metaclass=Singleton):
    pass

优点

  • 它是一个真正的类
  • 自动覆盖继承
  • 使用__metaclass__实现其正确的目的(并让我意识到了这一点)

缺点

  • 有吗?

方法四:返回同名类的装饰器


def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class_, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w,
                                    class_).__new__(class_,
                                                    *args,
                                                    **kwargs)
                class_w._instance._sealed = False
            return class_w._instance
        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = class_.__name__
    return class_w

@singleton
class MyClass(BaseClass):
    pass

优点

  • 它是一个真正的类
  • 自动地覆盖继承

缺点

  • 创建每个新类是否有开销?在这里,我们为每个要使其成为单例的类创建了两个类。虽然在我的情况下这很好,但我担心这可能不能扩展。当然,是否应该轻松扩展这种模式存在争议...
  • _sealed属性的意义是什么
  • 无法使用super()调用基类上同名的方法,因为它们会递归。这意味着您无法自定义__new__并且无法子类化需要您调用__init__的类。

方法五:一个模块

一个名为singleton.py的模块文件。

优点

  • 简单胜于复杂。

缺点

  • 不是延迟实例化的。

20
另外三种技术:使用模块替代(通常情况下,我认为这是Python中更适合的模式,但这也取决于你用它做什么);创建一个单一实例并直接操作它(foo.x 或者如果你坚持可以用 Foo.x 替代 Foo().x);使用类属性和静态/类方法(Foo.x)。 - Chris Morgan
23
如果你只打算使用类方法或静态方法,那么实际上就不需要创建一个类了。 - Cat Plus Plus
5
@猫: 效果类似,但创建全局变量的原因可能是任何事情,包括不知道更好的方法。为什么要创建一个单例?如果你不知道答案,那就不应该在这里。这种明确性不仅更符合Python的风格,而且使维护更加简单。是的,单例是全局变量的语法糖,但类也是一堆难看东西的语法糖,没有人会告诉你总是最好不用它们。 - theheadofabroom
35
反单例情绪是最糟糕的模拟船运动编程。对于那些听过(很少有人真正阅读过)“Goto语句被认为是有害的”并认为无论上下文如何,goto语句都是糟糕代码的标志的人也是如此。 - Hejazzman
5
你好,感谢你详细的帖子。我对模式编程和Python都比较新,而且我很惊讶尽管方法2似乎是最为人熟知的一种(它无处不在),但几乎没有人提到每次使用Singleton()或MyClass()时,尽管仅创建了一个对象,init()也会被调用。我没有尝试过,但据我所知,其他方法也是如此。 当实现单例模式时,这似乎并不理想,难道我漏掉了什么吗?当然,解决方案是设置属性以避免两次执行__init__。只是好奇。 - chrisvp
显示剩余30条评论
42个回答

1109

使用元类

我推荐使用方法#2,但你最好使用一个元类而不是一个基类。以下是一个示例实现:

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]
        
class Logger(object):
    __metaclass__ = Singleton

或者在 Python3 中

class Logger(metaclass=Singleton):
    pass

如果你想要每次调用类时运行__init__,添加

        else:
            cls._instances[cls].__init__(*args, **kwargs)

关于Singleton.__call__语句中的if语句,需要说明一下。

先简单介绍一下元类。元类是一个类的类;也就是说,一个类是其元类的实例。在Python中,可以用type(obj)来查找对象的元类。普通新式类的元类是type。以上代码中的Logger将是class 'your_module.Singleton'类型,而Logger的(唯一)实例将是class 'your_module.Logger'类型。当您使用Logger()调用logger时,Python首先询问Singleton,即Logger的元类,该怎么做,从而允许预先创建实例。这个过程与Python通过调用__getattr__来询问类何时执行的操作相同,当您通过myclass.attribute引用其属性之一时。

元类本质上决定了一个类的定义意味着什么以及如何实现该定义。例如,请参见http://code.activestate.com/recipes/498149/,它使用元类在Python中重新创建C风格的struct。线程What are some (concrete) use-cases for metaclasses?也提供了一些示例,它们通常与声明式编程有关,特别是在ORM中使用。

在这种情况下,如果使用Method #2,并且一个子类定义了__new__方法,则每次调用SubClassOfSingleton()时它将被执行--因为它负责调用返回存储的实例的方法。使用元类则仅在创建唯一实例时调用一次。您希望自定义调用类的方式,这是由其类型决定的。

总的来说,使用元类实现singleton是有意义的。Singleton很特殊,因为只创建一次,而元类是您自定义类创建的方式。使用元类可以在其他方面自定义singleton类定义时更加灵活。

你的单例模式不需要多重继承(因为元类不是基类),但对于使用多重继承的创建类的子类,你需要确保单例类是第一个/最左边的具有重新定义__call__的元类。这很不可能成为问题。实例字典不在实例的命名空间中,因此不会意外地覆盖它。

你还会听到单例模式违反了“单一职责原则”--每个类应该只做一件事情。这样你就不必担心更改另一个代码所做的某些内容,因为它们是分离和封装的。元类的实现通过了这个测试。元类负责强制模式,创建的类和子类不需要知道它们是单例方法#1未通过此测试,如你所指出的:“MyClass本身是一个函数,而不是一个类,因此无法从中调用类方法。”

Python 2和3兼容版本

编写既适用于Python2又适用于Python3的东西需要使用稍微复杂的方案。由于元类通常是type的子类,因此可以使用其中一个在运行时动态创建一个中介基类,并将其作为其元类,然后使用那个作为公共Singleton基类的基类。这比解释要难得多,如下所示:

# works in Python 2 & 3
class _Singleton(type):
    """ A metaclass that creates a Singleton base class when called. """
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Singleton(_Singleton('SingletonMeta', (object,), {})): pass

class Logger(Singleton):
    pass

这种方法的讽刺之处在于它使用子类化来实现元类。其中一个可能的优点是,与纯元类不同,isinstance(inst, Singleton)将返回True

更正

另一个话题是,您可能已经注意到了,在您原始帖子中基类的实现是不正确的。_instances需要在类上引用,您需要使用super()否则会递归调用,而__new__实际上是一个静态方法,您必须将类传递给它,而不是类方法,因为在调用它时实际类还没有被创建。所有这些事情对于元类实现也是成立的。

class Singleton(object):
  _instances = {}
  def __new__(class_, *args, **kwargs):
    if class_ not in class_._instances:
        class_._instances[class_] = super(Singleton, class_).__new__(class_, *args, **kwargs)
    return class_._instances[class_]

class MyClass(Singleton):
  pass

c = MyClass()

返回一个类的装饰器

我原本想写一个评论,但是它太长了,所以我在这里加上。 方法 #4 比另一种装饰器版本更好,但对于单例来说代码过多,而且不太清楚它的作用。

主要问题源于类本身就是其自己的基类。首先,让一个类成为一个几乎相同的同名类的子类,在其 __class__属性中存在,这不是很奇怪吗?这也意味着您不能定义任何调用其基类上同名方法的方法,否则它们将递归。这意味着您的类无法自定义__new__,并且无法派生自需要调用它们的__init__的任何类。

何时使用单例模式

您的用例是想要使用单例的较好示例之一。你在其中一段评论中说:“对我来说,日志记录一直是单例的自然候选者。” 你完全正确

当人们说单例模式是不好的时,最常见的原因是它们是隐式共享状态。虽然全局变量和顶级模块导入是显式共享状态,其他传递的对象通常是实例化的。这是一个很好的观点,但有两个例外

第一个在各个地方都提到的是当单例是常数时。使用全局常量,特别是枚举类型,被广泛接受,并被认为是合理的,因为无论如何,任何用户都不能将它们搞砸给其他用户。对于常数单例来说同样正确。

第二个例外很少提到,正好相反——当单例只是一个数据汇而不是直接或间接的数据源时。这就是为什么记录器感觉像单例的“自然”用途的原因。由于各个用户在没有以其他用户关心的方式更改记录器的情况下使用它们,因此没有真正的共享状态。这抵消了反对单例模式的主要论据,并使它们成为一种因其易于使用而合理的选择。

以下是http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html中的一句话:

现在,有一种类型的单例是可以接受的。那就是所有可达对象都是不可变的单例。如果所有对象都是不可变的,那么单例就没有全局状态,因为一切都是常量。但是很容易将这种单例变成可变的,这是一个非常棘手的问题。因此,我也反对这些单例,不是因为它们不好,而是因为它们很容易变坏。(顺便说一下,Java枚举就是这种类型的单例。只要您不将状态放入枚举中,您就可以使用,所以请不要这样做。)

另一种单例,半可接受的是那些不影响代码执行的单例,它们没有“副作用”。日志记录是一个完美的例子。它充满了单例和全局状态。它是可以接受的(也就是说,它不会伤害你),因为无论给定的记录器是否启用,您的应用程序都不会表现出任何不同。这里的信息从您的应用程序流向记录器。即使记录器是全局状态,因为没有信息从记录器流向您的应用程序,记录器仍然是可以接受的。如果您想要测试断言某些内容被记录,则仍然应该注入记录器,但总的来说,记录器并不会造成伤害,尽管它们充满状态。


5
不,单例模式从来都不是好的选择。虽然记录(Logging)可能是一个适合成为全局对象的候选项(尽管它们很糟糕),但绝对不能使用单例模式。 - Cat Plus Plus
17
请看 http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html。该文章通常反对使用单例模式(有充分的理由),但是它很好地解释了为什么不可变的单例模式和没有副作用的单例模式不会有同样的问题,如果你小心使用的话。我会在我的文章末尾引用一些相关内容。 - agf
5
我对单例的问题在于“只有一个实例”的愚蠢前提以及大量线程安全性问题和依赖隐藏。全局变量不好,而单例只是带着更多问题的全局变量。 - Cat Plus Plus
10
@猫, 单例模式有很好的用途。其中之一就是延迟实例化硬件模块(特别是在单线程应用程序中),但也存在线程安全的单例模式。 - Paul Manta
4
在元类中,__new__ 方法是在定义类时调用的,而不是创建实例时调用的。重载类的定义并不会起作用,你需要重载调用类(MyClass())时的操作。如果你真的想理解 Python 是如何工作的,最好的方法是阅读 http://docs.python.org/reference/datamodel.html。关于元类的良好参考资料可以看 http://eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example/。在单例模式方面,一篇不错的文章是我在回答中链接到的 Google 博客系列文章。 - agf
显示剩余30条评论

146
class Foo(object):
     pass

some_global_variable = Foo()

模块只会被导入一次,其他的都是过度思考。不要使用单例模式,尽量避免使用全局变量。


51
为什么你说“不要使用单例模式”?有任何原因吗? - Alcott
4
如果必须将单例pickle化,那么这种方法行不通。使用您提供的示例:s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False - dividebyzero
18
@dividebyzero:is 运算符用于测试指针相等性。如果 pickle.loads 返回对预先存在的对象的引用而不是对新创建对象的引用,我会非常惊讶——甚至会认为这是一个错误。因此,测试 s is s1 并不能告诉您关于将模块用作单例是否合适的任何信息。 - Jonas Kölker
5
@leo-the-manic提出了一个公正的观点;但是这只是Python将对象TrueFalseNone内部化的副作用,与pickle.loads背后的代码无关。此外,这仅适用于只读对象,如果pickle.loads返回对已存在的可修改对象的引用(如模块),那将是一个错误。因此,我坚持认为dividebyzero的代码示例并没有证明任何事情。 - Jonas Kölker
5
我在几个不同的脚本文件中导入了一个模块(所有这些文件都使用“单例”),使用import ABC as X,每个导入“单例”模块的脚本实际上都导入了一次。因此,我定义的变量每次导入该模块时都会被重新赋值。我还尝试删除别名,并在项目的每个文件中导入它,但结果相同。我使用的是 Python 3.9.6。使用元类的单例模式(来自agf的答案)对我的用例非常有帮助(日志记录,必须在编译时初始化一次的变量)。 - Vinícius Queiroz
显示剩余9条评论

92

使用一个模块。它只会被导入一次。在其中定义一些全局变量 - 它们将成为单例的“属性”。添加一些函数 - 单例的“方法”。


18
因此你最终得到的是......不是一个类。你不能将其用作一个类,也不能基于它构建其他类,当你使用导入语法时,突然间你失去了面向对象编程的所有好处... - theheadofabroom
29
如果你可以基于它创建其他类,那么它可能不是单例模式。你可以创建一个派生类和一个基类的实例,但是由于派生类也是基类的成员,你就有了两个基类的实例,这时应该使用哪一个呢?请注意,不要改变原文的意思。 - SingleNegationElimination
1
@PaulKenjora,你的代码肯定有错误。如果你在一个模块中定义了一个全局变量,在从另一个模块访问它时,它应该具有相应的值。 - warvariuc
1
@theheadofabroom 你可以使用 import * from base_module... 重新思考面向对象编程,我的朋友!哈哈哈 - polvoazul
3
在一个模块中如何使用参数初始化单例对象? - nn0p
显示剩余3条评论

68

你在Python中可能从不需要使用单例模式。只需在模块中定义所有数据和函数,你就拥有了一种事实上的单例:

import datetime
file_name=None

def set_file_name(new_file_name: str):
    global file_name
    file_name=new_file_name

def write(message: str):
    global file_name
    if file_name:
        with open(file_name, 'a+') as f:
            f.write("{} {}\n".format(datetime.datetime.now(), message))
    else:
        print("LOG: {}", message)

使用方法:

import log
log.set_file_name("debug.log")
log.write("System starting")
...

如果您确实绝对必须拥有单例类,那么我会选择:

class MySingleton(object):
    def foo(self):
        pass

my_singleton = MySingleton()

使用方法:

from mysingleton import my_singleton
my_singleton.foo()

mysingleton.py文件中定义了MySingleton,因为Python在第一次导入文件后不会重新执行代码,所以这样可以正常工作。


14
大多数情况下是正确的,但有时这并不足够。例如,我有一个需要在DEBUG级别记录许多类实例化的项目。我需要在这些类被实例化之前解析启动时的命令行选项,以设置用户指定的日志记录级别。模块级别的实例化使这个过程有问题。虽然“单例模式不好”的教条主义很干净,但是我可能需要仔细地组织我的应用程序,以便所有这些类在CLI处理完成之前都不会被导入,但我的应用程序的自然结构比遵循教条主义更重要,因为它们可以被很清楚地完成。 - mikenerone
如果您在修补my_singleton时测试代码,这是否可能?因为my_singleton可能在其他模块中被实例化。 - Naveen
这在某些情况下可能有效,但有时候懒惰初始化是很重要的。在我的使用场景中,初始化需要400毫秒,所以我不想因为导入模块而产生这个开销。它只有在真正需要单例时才会产生。 - joanis
@joanis。同意没有适用于每种可能用例的完美解决方案。也许,您仍然可以使用延迟初始化来处理耗时的代码部分,而不将其放入构造函数中。或者也许您需要本页面上的其他更复杂的建议之一。 - Alan Dyke
这个解决方案是否适合处理数据库连接?我想只有一个地方来处理我的数据库连接。我的应用程序使用100多个线程的多进程,需要在数据库中执行批处理操作,因此我正在寻找更有效地使用资源的方法,并考虑单例模式,但不确定它们是否能在多进程应用程序中按预期工作。 - Welsige
显示剩余3条评论

17
您只需要一个装饰器,根据Python版本的不同选择相应的方式:

Python 3.2+

实现

from functools import lru_cache

@lru_cache(maxsize=None)
class CustomClass(object):

    def __init__(self, arg):
        print(f"CustomClass initialised with {arg}")
        self.arg = arg

使用方法

c1 = CustomClass("foo")
c2 = CustomClass("foo")
c3 = CustomClass("bar")

print(c1 == c2)
print(c1 == c3)

输出

>>> CustomClass initialised with foo
>>> CustomClass initialised with bar
>>> True
>>> False

注意只输出了一次 foo


Python 3.9+

实现:

from functools import cache

@cache
class CustomClass(object):
    ...

1
在这种情况下,对象不会被垃圾回收,如果该类不应该在应用程序的生命周期内存在,则必须清除缓存 *(某个地方/某个时间)*,这显然是对已经为此任务设计的__new__方法的一个缺点。 - Thingamabobs
2
术语“单例”(singleton)描述了它只有一个实例。考虑一个使用该模式的“分支”的较大应用程序,您可能会意外地在内存中保留一个巨大的类。我不是说您的解决方案是“不行”的,但应该在某个地方提到。 - Thingamabobs
这是正确的答案,即使另一个回答有超过一千个赞。 - vy32
@vy32 另一个答案已经很老了,在 Python 3.2 出现之前,它可能是正确的。 - ciurlaro
是的!这就是为什么像这样的评论很有用的原因!这也是 SO 的一个缺陷之一——它对于那些需要更新的旧东西效果不佳。 - vy32
显示剩余2条评论

16

这里有一个一句话的玩笑:

singleton = lambda c: c()

以下是使用方法:

@singleton
class wat(object):
    def __init__(self): self.x = 1
    def get_x(self): return self.x

assert wat.get_x() == 1

你的对象被急切地实例化了。这可能是你想要的,也可能不是。


2
为什么需要知道单例的类?直接使用单例对象即可。 - Tolli
1
这不是单例模式,所以在我看来,这个函数应该被命名为不同的名称。 - GingerPlusPlus
9
维基百科上的定义是“单例模式是一种设计模式,其将类的实例化限制为一个对象。”我认为我的解决方案正是如此。好吧,我猜一个人可以这样做wat2 = type(wat)(),但这是Python,我们都是自愿的成年人之类的。你不能保证只有一个实例,但你可以保证如果有人创建第二个实例,它会看起来很丑,并且——如果他们是体面、正直的人——像警告标志。我错过了什么? - Jonas Kölker
1
如果你真的在寻找一行解决方案,可以尝试使用Python的单例模块,其实这是一个零行解决方案。 - Sławomir Lenart

13

4
虽然这并不是对我的问题的真正回答,但你提供的资源非常有用。我勉强给你一个+1。 - theheadofabroom
这与Python 3相关吗? - Nicolay77

9
  • 如果想要创建同一个类的多个实例,但只有args或kwargs不同时才创建,请使用第三方python包Handy Decorators(packagedecorators)。
  • 示例:
    1. 如果你有一个处理serial通信的类,并且要创建一个实例时需要将串口作为参数发送,那么传统的方法行不通。
    2. 使用上述装饰器,可以在args不同的情况下创建类的多个实例。
    3. 对于相同的args,装饰器会返回已经创建好的相同实例。
>>> from decorators import singleton
>>>
>>> @singleton
... class A:
...     def __init__(self, *args, **kwargs):
...         pass
...
>>>
>>> a = A(name='Siddhesh')
>>> b = A(name='Siddhesh', lname='Sathe')
>>> c = A(name='Siddhesh', lname='Sathe')
>>> a is b  # has to be different
False
>>> b is c  # has to be same
True
>>>

制作这种类型的单例模式是我提出这个问题的原因。非常感谢!我尝试了pip install handy-decorators,但是出现了ERROR: Could not find a version that satisfies the requirement handy-decorators的错误。有什么建议吗? - Ben Kovitz
我已经从这里复制了源代码并装饰了一个数据类。第一次就成功了。令人高兴的是,它不依赖于任何其他代码!那个模块中的所有内容都非常简单和直接,真正符合Python风格。如果你不是在教Python,那么你应该去教。 - Ben Kovitz
1
注意:@singleton 实现中的 previous_instances 字典看起来不是线程安全的。如果一个线程在构造对象时,另一个线程检查字典,那么就存在竞争条件... - Jonathan Komar

8

使用函数属性也非常简单。

def f():
    if not hasattr(f, 'value'):
        setattr(f, 'value', singletonvalue)
    return f.value

7

我更喜欢这个解决方案,我认为它非常清晰和简单。 例如,它使用了双重检查,以防其他线程已经创建了它。 另一个需要考虑的问题是确保反序列化不会创建任何其他实例。

https://gist.github.com/werediver/4396488
import threading


# Based on tornado.ioloop.IOLoop.instance() approach.
# See https://github.com/facebook/tornado
class SingletonMixin(object):
    __singleton_lock = threading.Lock()
    __singleton_instance = None

    @classmethod
    def instance(cls):
        if not cls.__singleton_instance:
            with cls.__singleton_lock:
                if not cls.__singleton_instance:
                    cls.__singleton_instance = cls()
        return cls.__singleton_instance


if __name__ == '__main__':
    class A(SingletonMixin):
        pass

    class B(SingletonMixin):
        pass

    a, a2 = A.instance(), A.instance()
    b, b2 = B.instance(), B.instance()

    assert a is a2
    assert b is b2
    assert a is not b

    print('a:  %s\na2: %s' % (a, a2))
    print('b:  %s\nb2: %s' % (b, b2))

请原谅我的无知,但为什么您需要两次检查 __singleton_instance?难道您不能始终使用 __singleton_lock,然后只检查一次吗? - theheadofabroom
2
正如我之前提到的,我们需要确保在执行“if”并使用锁时,其他线程尚未创建此实例。这是一个相当流行的概念,在面试中经常被问到 :) https://en.wikipedia.org/wiki/Double-checked_locking - Andrei R.
但是毫无疑问,获取未受争议的锁的成本足够低,如果它很显著,你最好用C实现它吧?如果我没记错的话,获取锁的成本约为函数调用的一半,因此在这里更好的优化可能是避免使用上下文管理器并手动获取锁。如果这是不必要的优化,那么我认为二次检查更是如此。 - theheadofabroom
双重检查不是一种优化,而是为了确保我们不会创建两个单例实例。同时,需要指出的是,这些检查只会在第一次初始化时执行一次。之后就只是返回该实例。因此,任何优化都是无意义的。 - Andrei R.
这是我似乎无法理解的地方。只要在持有锁的同时进行检查,那么你只需要检查一次,对吧?这就是锁的作用,用于同步访问。 - theheadofabroom
Python是否需要进行双重检查?该模式不涉及成本或优化,而是与环境的内存模型有关。 问题在于,在对象构造时,GIL不会确保正确的锁定吗? 有些语言中使用双重锁定模式是错误的,因为存在重新排序,并且你并不总是需要它。 - Luiz Felipe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接