避免每次都调用__new__

时间:2020-01-23 15:01:29

标签: python metaclass

我知道类是元类的实例,并且__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()
  1. 很好,因为URL可以像我一样很好地传播

    Foo : https://stackoverflow.com/
    Bar : https://stackoverflow.com/
    Baz : https://stackoverflow.com/
    
  2. 我确实有一些我不太了解的东西:

    Class Foo在2秒后被写入

    Class Bar再写2秒钟

    Class Baz经过2秒钟终于写完

因此,元类被执行了3次。 这必须说明,由于__new__负责创建类,因此必须每次运行3次。

我说得对吗?

如何避免它并使它仅运行一次?

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__方法中-甚至根本不需要元类。

相关问题