我最近遇到了Python的元类概念,我试图对一些现有的类强制执行Singleton
。当类再次实例化时,我想重用先前创建的实例。
应该执行Singleton
的现有类。我有多个这样的类,比如继承了Vehicle
的Bike, Train,所有这些都应该是单例的。
from singleton import Singleton
from vehicle import Vehicle
class Car(Vehicle, object):
__metaclass__ = Singleton
def __init__(self):
print("Constructor: Car is starting")
pass
def print_car(self):
print("Car is running")
Car类继承了Vehicle
from abc import ABC
class Vehicle(ABC):
def __init__(self):
pass
这是我在Singleton
的尝试。我使用了一些参考SO帖子。
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]
然而,当我为Car
创建多个对象时,我没有看到单例被强制执行。
car = Car()
car.print_car()
car2 = Car()
car2.print_car()
print(car is car2) # Returns False
我还在学习Python的元类以及它是如何工作的,想知道我在这里错过了什么?
所有这些类(基类和派生类)都在我的控制之下,我可以对它们进行更改。我如何实现强制单例我的子类,如Car
,Bike
等?
(更新1)使用Python 3版本的元类给了我以下错误
from singleton import Singleton
from vehicle import Vehicle
class Car(Vehicle, object, metaclass=Singleton):
# class Car(Vehicle, object):
# __metaclass__ = Singleton
def __init__(self):
print("Constructor: Car is starting")
def print_car(self):
print("Car is running")
错误:
File "/Users/user/HelloVsCode/hello.py", line 5, in <module>
from car import Car
File "/Users/user/HelloVsCode/car.py", line 7, in <module>
class Car(Vehicle, object, metaclass=Singleton):
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
第一:元类对于单例来说是多余的。
如果你想要一个单例,你可以在类被声明后立即实例化它,并扔掉"class";并且只保留它的实例。
像:
class _Sing1:
...
Sing1 = _Sing1()
# done.
如果你希望单例的行为像一个被调用的类,并且它们将总是产生相同的实例,那么一个注入__call__
方法并立即实例化类的装饰器也足够简单,并且可以跨多个类使用:
def singleton(cls):
cls.__call__ = lambda self: self
return cls()
@singleton
class Sing2:
...
这对你来说应该足够了,除非你的单例实例本身需要是可调用的。
除此之外,你找到的使用元类创建单例的方法是有效的——问题是如何指定一个类应该使用它作为元类。
你在你的帖子中使用的语法,在类体内声明一个__metaclass__
属性,是Python 2确定使用自定义元类的方法。在Python 3中,我们在声明类时只需在metaclas
kwarg中指定元类。
这意味着,这将工作,与上面的元类,使您的"汽车"类作为单例工厂工作:
class Car(Vehicle, object, metaclass=Singleton):
def __init__(self):
print("Constructor: Car is starting")
def print_car(self):
print("Car is running")
如果你想在元类的单例反模式中使用ABCMeta,你的元类必须继承ABCMeta——否则你会有元类冲突:
import abc
class Singleton(abc.ABCMeta):
def __call__(...):
...
感谢@jsbueno的回答。以下是解决我的用例的两个解决方案。
方法1:A decorator
使用这种方法,我可以为每个派生类使用decorator
。
singleton.py
import functools
# https://github.com/siddheshsathe/handy-decorators/blob/master/src/decorators.py
def singleton(cls):
previous_instances = {}
@functools.wraps(cls)
def wrapper(*args, **kwargs):
if cls in previous_instances and previous_instances.get(cls, None).get('args') == (args, kwargs):
return previous_instances[cls].get('instance')
else:
previous_instances[cls] = {
'args': (args, kwargs),
'instance': cls(*args, **kwargs)
}
return previous_instances[cls].get('instance')
return wrapper
car.py
from singleton import singleton
from vehicle import Vehicle
@singleton
class Car(Vehicle):
def __init__(self):
print("Constructor: Car is starting")
def print_car(self):
print("Car is running")
vehicle.py
from abc import ABC
class Vehicle(ABC):
def __init__(self, vehicle_type, can_fly: bool = False, enable_timeout: bool = False):
self._vehicle_type = vehicle_type
self._can_fly = can_fly
self._enable_timeout = enable_timeout
方法2:元类
裁判:https://stackoverflow.com/a/33364149/73137
使用这种方法,我可以使我的base class
为单例。从而在所有派生类上强制执行。
singleton.py
class Singleton(ABCMeta):
_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]
car.py
from vehicle import Vehicle
class Car(Vehicle):
def __init__(self):
print("Constructor: Car is starting")
def print_car(self):
print("Car is running")
vehicle.py
from abc import ABC
from singleton import Singleton
class Vehicle(ABC, metaclass=Singleton):
def __init__(self, vehicle_type, can_fly: bool = False, enable_timeout: bool = False):
self._vehicle_type = vehicle_type
self._can_fly = can_fly
self._enable_timeout = enable_timeout
通用代码:
test.py
from car import Car
car = Car()
car.print_car()
car2 = Car()
car2.print_car()
print(car is car2)
输出:
Constructor: Car is starting
Car is running
Car is running
True
由于我对Python的metaclass
和singleton
还不熟悉,请告诉我这两种方法之间是否有任何具体的优缺点。