我知道类是元类的实例,并且__new__
在__init__
之前运行,因为必须在初始化实例之前创建实例。
现在想象一下:
import time
class ConfigurationsMeta(type):
def __new__(cls, name, bases, attr):
# Potentially a long task here (eg: Getting value from a web service)
time.sleep(2)
# Which class inherit from me (debug)
print(f'Class {name}')
config = super().__new__(cls, name, bases, attr)
#Set a variable to be propagated (Variable coming from web service)
setattr(config, "URL", "https://stackoverflow.com/")
return config
class Foo(metaclass=ConfigurationsMeta):
def __init__(self):
print(f'{__class__.__name__} : {self.URL}')
class Bar(Foo):
def __init__(self):
print(f'{__class__.__name__} : {self.URL}')
class Baz(Bar):
def __init__(self):
print(f'{__class__.__name__} : {self.URL}')
e = Foo()
s = Bar()
c = Baz()
很好,因为URL可以像我一样很好地传播
Foo : https://stackoverflow.com/
Bar : https://stackoverflow.com/
Baz : https://stackoverflow.com/
我确实有一些我不太了解的东西:
Class Foo
在2秒后被写入
Class Bar
再写2秒钟
Class Baz
经过2秒钟终于写完
因此,元类被执行了3次。
这必须说明,由于__new__
负责创建类,因此必须每次运行3次。
我说得对吗?
如何避免它并使它仅运行一次?
答案 0 :(得分:1)
您实际上不需要这里的元类。假设您希望URL
是一个 class 属性,而不是实例属性,则只需定义一个具有合适的__init_subclass__
定义的基类。应该首先检索该URL,并将其作为参数传递给__init_subclass__
(通过class
语句中的关键字参数)。
class Base:
def __init_subclass__(cls, /, url=None):
super().__init_subclass__(cls)
if url is not None:
cls.URL = url
some_url = call_to_webservice()
class Foo(Base, url=some_url):
pass
class Bar(Foo):
pass
class Baz(Bar):
pass
如果URL
应该是实例属性,则将__init_subclass__
替换为__init__
:
some_url = call_to_webservice()
class Base:
def __init__(self, /, url):
self.url = url
class Foo(Base):
pass
f = Foo(some_url)
答案 1 :(得分:0)
如前所述,您不需要元类。
您已经有了一个出色而灵活的答案,该答案使用__init_subclass__
。
一种简单的简单方法是在超类中设置共享属性,然后让子类的实例找到它(就像它们通常沿着实例,类,超类的链条一样)
class Configurations:
URL = 'https://stackoverflow.com/'
class Foo(Configurations):
def __init__(self):
print(f'{__class__.__name__} : {self.URL}')
class Bar(Foo):
def __init__(self):
print(f'{__class__.__name__} : {self.URL}')
class Baz(Bar):
def __init__(self):
print(f'{__class__.__name__} : {self.URL}')
e = Foo()
s = Bar()
c = Baz()
或者,如果配置更加复杂,请使用类方法来保持代码整洁
class Configurations:
@classmethod
def create_cfg(cls):
cls.URL = 'https://stackoverflow.com/'
...
Configurations.create_cfg()
e = Foo()
s = Bar()
c = Baz()
两种方法仅初始化一次配置即可产生
Foo : https://stackoverflow.com/
Bar : https://stackoverflow.com/
Baz : https://stackoverflow.com/
答案 2 :(得分:0)
其他答案涵盖了为什么您在这里不需要元类。
该答案是为了简要说明元类的__new__
的作用:
它确实会构建您的 class -它在处理class <name>(bases, ...): <body>
语句时由Python运行时本身调用。不,你不能不打电话就上课
元类__new__
方法-或至少是type
的{{1}}方法的“所有元类的根”。
这就是说,如果由于某种原因您需要拥有一个长任务,而该长任务只能针对您创建的所有类运行一次,那么您要做的就是缓存长任务的结果,并使用后续调用中的缓存值。
如果由于某种原因无法缓存该值,并且必须在元类中执行该值,则必须安排您的类主体本身以不同的方式执行,或者使用asyncio循环执行。更为优雅的形式可能包括:首次调用元类__new__
内(并使其作为元类属性保持活动状态),以及为每个类调用__new__
后,实例化并发一个元类type.__new__
内部的parallel.futures.ThreadPoolExecutor提交需要较长时间的部分。
如您所见,由于所需的只是设置一个类属性,因此应避免将此操作作为元类。
仍然,设计可能像这样:
from concurrent.futures import ThreadPoolExecutor
TIMEOUT = 5
class PostInitParallelMeta(type):
executor = None
def __init__(cls, name, bases, ns, **kw):
super().__init__(name, bases, ns, **kw)
mcls = cls.__class__
if not mcls.executor:
mcls.executor = ThreadPoolExecutor(20)
mcls.executor.submit(cls._post_init)
class Base(metaclass=PostInitParallelMeta):
_initalized = False
def _post_init(cls, url):
# do long calculation and server access here
result = ...
cls.url = result
cls._initialized = True
def __init__(self, *args, **kw):
super.__init__(*args, **kw)
counter = 0
while not self.__class__._initialized:
time.sleep(0.2)
counter += 0.2
if counter > TIMEOUT:
raise RunTimeError(f"failed to initialize {self.__class__}")
class Foo(Base):
# set any parameters needed to the initilization task
# as class attributes
...
PS-我刚刚写了这本书,并且意识到可以将元类中的代码安全地放入Base的__init_subclass__
方法中-甚至根本不需要元类。