使用元类的 __call__ 方法而不是 __new__?

发布于 2024-11-28 15:02:11 字数 511 浏览 2 评论 0原文

在讨论元类时,文档指出:

您当然也可以重写其他类方法(或添加新的 方法);例如,在 元类允许在调用类时自定义行为,例如不 始终创建一个新实例。

[编者注:这已从 3.3 的文档中删除。在 3.2 中:自定义类创建]

我的问题是:假设我想在调用类时具有自定义行为,例如缓存而不是创建新对象。我可以通过重写类的 __new__ 方法来做到这一点。我什么时候想用 __call__ 定义元类?这种方法提供了哪些 __new__ 无法实现的功能?

When discussing metaclasses, the docs state:

You can of course also override other class methods (or add new
methods); for example defining a custom __call__() method in the
metaclass allows custom behavior when the class is called, e.g. not
always creating a new instance.

[Editor's note: This was removed from the docs in 3.3. It's here in 3.2: Customizing class creation]

My questions is: suppose I want to have custom behavior when the class is called, for example caching instead of creating fresh objects. I can do this by overriding the __new__ method of the class. When would I want to define a metaclass with __call__ instead? What does this approach give that isn't achievable with __new__?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

dawn曙光 2024-12-05 15:02:11

当您仔细观察这些方法的执行顺序时,细微的差异会变得更加明显。

class Meta_1(type):
    def __call__(cls, *a, **kw):
        print "entering Meta_1.__call__()"
        rv = super(Meta_1, cls).__call__(*a, **kw)
        print "exiting Meta_1.__call__()"
        return rv

class Class_1(object):
    __metaclass__ = Meta_1
    def __new__(cls, *a, **kw):
        print "entering Class_1.__new__()"
        rv = super(Class_1, cls).__new__(cls, *a, **kw)
        print "exiting Class_1.__new__()"
        return rv

    def __init__(self, *a, **kw):
        print "executing Class_1.__init__()"
        super(Class_1,self).__init__(*a, **kw)

请注意,上面的代码除了记录我们正在做的事情之外,实际上并没有做任何事情。每个方法都遵循其父实现,即其默认实现。因此,除了记录之外,它实际上就像您简单地声明了以下内容:

class Meta_1(type): pass
class Class_1(object):
    __metaclass__ = Meta_1

现在让我们创建一个 Class_1 的实例

c = Class_1()
# entering Meta_1.__call__()
# entering Class_1.__new__()
# exiting Class_1.__new__()
# executing Class_1.__init__()
# exiting Meta_1.__call__()

因此,如果 typeMeta_1 的父级code> 我们可以想象一下 type.__call__() 的伪实现:

class type:
    def __call__(cls, *args, **kwarg):

        # ... a few things could possibly be done to cls here... maybe... or maybe not...

        # then we call cls.__new__() to get a new object
        obj = cls.__new__(cls, *args, **kwargs)

        # ... a few things done to obj here... maybe... or not...

        # then we call obj.__init__()
        obj.__init__(*args, **kwargs)

        # ... maybe a few more things done to obj here

        # then we return obj
        return obj

从上面的调用顺序注意到 Meta_1.__call__() (或者在本例中type.__call__()) 有机会影响是否调用 Class_1.__new__()Class_1.__init__()最终做成了。在其执行过程中,Meta_1.__call__() 可能会返回一个甚至都没有被触及的对象。以单例模式的这种方法为例:

class Meta_2(type):
    __Class_2_singleton__ = None
    def __call__(cls, *a, **kw):
        # if the singleton isn't present, create and register it
        if not Meta_2.__Class_2_singleton__:
            print "entering Meta_2.__call__()"
            Meta_2.__Class_2_singleton__ = super(Meta_2, cls).__call__(*a, **kw)
            print "exiting Meta_2.__call__()"
        else:
            print ("Class_2 singleton returning from Meta_2.__call__(), "
                    "super(Meta_2, cls).__call__() skipped")
        # return singleton instance
        return Meta_2.__Class_2_singleton__

class Class_2(object):
    __metaclass__ = Meta_2
    def __new__(cls, *a, **kw):
        print "entering Class_2.__new__()"
        rv = super(Class_2, cls).__new__(cls, *a, **kw)
        print "exiting Class_2.__new__()"
        return rv

    def __init__(self, *a, **kw):
        print "executing Class_2.__init__()"
        super(Class_2, self).__init__(*a, **kw)

让我们观察重复尝试创建 Class_2 类型的对象时会发生什么

a = Class_2()
# entering Meta_2.__call__()
# entering Class_2.__new__()
# exiting Class_2.__new__()
# executing Class_2.__init__()
# exiting Meta_2.__call__()

b = Class_2()
# Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped

c = Class_2()
# Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped

print a is b is c
True

现在使用类的 __new__() 方法观察此实现尝试完成同样的事情。

import random
class Class_3(object):

    __Class_3_singleton__ = None

    def __new__(cls, *a, **kw):
        # if singleton not present create and save it
        if not Class_3.__Class_3_singleton__:
            print "entering Class_3.__new__()"
            Class_3.__Class_3_singleton__ = rv = super(Class_3, cls).__new__(cls, *a, **kw)
            rv.random1 = random.random()
            rv.random2 = random.random()
            print "exiting Class_3.__new__()"
        else:
            print ("Class_3 singleton returning from Class_3.__new__(), "
                   "super(Class_3, cls).__new__() skipped")

        return Class_3.__Class_3_singleton__ 

    def __init__(self, *a, **kw):
        print "executing Class_3.__init__()"
        print "random1 is still {random1}".format(random1=self.random1)
        # unfortunately if self.__init__() has some property altering actions
        # they will affect our singleton each time we try to create an instance 
        self.random2 = random.random()
        print "random2 is now {random2}".format(random2=self.random2)
        super(Class_3, self).__init__(*a, **kw)

请注意,上述实现即使在类上成功注册了单例,也不会阻止调用 __init__(),这种情况在 type.__call__() 中隐式发生 (<如果未指定,则 code>type 为默认元类)。这可能会导致一些不良影响:

a = Class_3()
# entering Class_3.__new__()
# exiting Class_3.__new__()
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.739298365475

b = Class_3()
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.247361634396

c = Class_3()
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.436144427555

d = Class_3()
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.167298405242

print a is b is c is d
# True

The subtle differences become a bit more visible when you carefully observe the execution order of these methods.

class Meta_1(type):
    def __call__(cls, *a, **kw):
        print "entering Meta_1.__call__()"
        rv = super(Meta_1, cls).__call__(*a, **kw)
        print "exiting Meta_1.__call__()"
        return rv

class Class_1(object):
    __metaclass__ = Meta_1
    def __new__(cls, *a, **kw):
        print "entering Class_1.__new__()"
        rv = super(Class_1, cls).__new__(cls, *a, **kw)
        print "exiting Class_1.__new__()"
        return rv

    def __init__(self, *a, **kw):
        print "executing Class_1.__init__()"
        super(Class_1,self).__init__(*a, **kw)

Note that the code above doesn't actually do anything other than log what we're doing. Each method defers to its parent implementation i.e. its default. So beside logging it's effectively as if you had simply declared things as follows:

class Meta_1(type): pass
class Class_1(object):
    __metaclass__ = Meta_1

And now let's create an instance of Class_1

c = Class_1()
# entering Meta_1.__call__()
# entering Class_1.__new__()
# exiting Class_1.__new__()
# executing Class_1.__init__()
# exiting Meta_1.__call__()

Therefore if type is the parent of Meta_1 we can imagine a pseudo implementation of type.__call__() as such:

class type:
    def __call__(cls, *args, **kwarg):

        # ... a few things could possibly be done to cls here... maybe... or maybe not...

        # then we call cls.__new__() to get a new object
        obj = cls.__new__(cls, *args, **kwargs)

        # ... a few things done to obj here... maybe... or not...

        # then we call obj.__init__()
        obj.__init__(*args, **kwargs)

        # ... maybe a few more things done to obj here

        # then we return obj
        return obj

Notice from the call order above that Meta_1.__call__() (or in this case type.__call__()) is given the opportunity to influence whether or not calls to Class_1.__new__() and Class_1.__init__() are eventually made. Over the course of its execution Meta_1.__call__() could return an object that hasn't even been touched by either. Take for example this approach to the singleton pattern:

class Meta_2(type):
    __Class_2_singleton__ = None
    def __call__(cls, *a, **kw):
        # if the singleton isn't present, create and register it
        if not Meta_2.__Class_2_singleton__:
            print "entering Meta_2.__call__()"
            Meta_2.__Class_2_singleton__ = super(Meta_2, cls).__call__(*a, **kw)
            print "exiting Meta_2.__call__()"
        else:
            print ("Class_2 singleton returning from Meta_2.__call__(), "
                    "super(Meta_2, cls).__call__() skipped")
        # return singleton instance
        return Meta_2.__Class_2_singleton__

class Class_2(object):
    __metaclass__ = Meta_2
    def __new__(cls, *a, **kw):
        print "entering Class_2.__new__()"
        rv = super(Class_2, cls).__new__(cls, *a, **kw)
        print "exiting Class_2.__new__()"
        return rv

    def __init__(self, *a, **kw):
        print "executing Class_2.__init__()"
        super(Class_2, self).__init__(*a, **kw)

Let's observe what happens when repeatedly trying to create an object of type Class_2

a = Class_2()
# entering Meta_2.__call__()
# entering Class_2.__new__()
# exiting Class_2.__new__()
# executing Class_2.__init__()
# exiting Meta_2.__call__()

b = Class_2()
# Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped

c = Class_2()
# Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped

print a is b is c
True

Now observe this implementation using a class' __new__() method to try to accomplish the same thing.

import random
class Class_3(object):

    __Class_3_singleton__ = None

    def __new__(cls, *a, **kw):
        # if singleton not present create and save it
        if not Class_3.__Class_3_singleton__:
            print "entering Class_3.__new__()"
            Class_3.__Class_3_singleton__ = rv = super(Class_3, cls).__new__(cls, *a, **kw)
            rv.random1 = random.random()
            rv.random2 = random.random()
            print "exiting Class_3.__new__()"
        else:
            print ("Class_3 singleton returning from Class_3.__new__(), "
                   "super(Class_3, cls).__new__() skipped")

        return Class_3.__Class_3_singleton__ 

    def __init__(self, *a, **kw):
        print "executing Class_3.__init__()"
        print "random1 is still {random1}".format(random1=self.random1)
        # unfortunately if self.__init__() has some property altering actions
        # they will affect our singleton each time we try to create an instance 
        self.random2 = random.random()
        print "random2 is now {random2}".format(random2=self.random2)
        super(Class_3, self).__init__(*a, **kw)

Notice that the above implementation even though successfully registering a singleton on the class, does not prevent __init__() from being called, this happens implicitly in type.__call__() (type being the default metaclass if none is specified). This could lead to some undesired effects:

a = Class_3()
# entering Class_3.__new__()
# exiting Class_3.__new__()
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.739298365475

b = Class_3()
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.247361634396

c = Class_3()
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.436144427555

d = Class_3()
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.167298405242

print a is b is c is d
# True
◇流星雨 2024-12-05 15:02:11

您的问题的直接答案是:当您想要做更多而不仅仅是自定义实例创建时,或者当您想要将类做什么与其创建方式分开时。

请参阅我对 Creating a singleton in Python 的回答以及相关讨论。

有几个优点。

  1. 它允许您将类的功能与其创建的详细信息分开。元类和类各自负责一件事。

  2. 您可以在元类中编写一次代码,并使用它来自定义多个类的调用行为,而不必担心多重继承。

  3. 子类可以重写其 __new__ 方法中的行为,但元类上的 __call__ 甚至根本不必调用 __new__。< /p>

  4. 如果有设置工作,您可以在元类的 __new__ 方法中完成,并且只发生一次,而不是每次调用该类时都会发生。

当然,在很多情况下,如果您不担心单一职责原则,那么自定义 __new__ 也同样有效。

但还有其他用例必须在创建类时(而不是创建实例时)更早发生。当这些发挥作用时,元类就变得必要了。请参阅 元类的(具体)用例是什么在 Python 中? 有很多很棒的例子。

The direct answer to your question is: when you want to do more than just customize instance creation, or when you want to separate what the class does from how it's created.

See my answer to Creating a singleton in Python and the associated discussion.

There are several advantages.

  1. It allows you to separate what the class does from the details of how it's created. The metaclass and class are each responsible for one thing.

  2. You can write the code once in a metaclass, and use it for customizing several classes' call behavior without worrying about multiple inheritance.

  3. Subclasses can override behavior in their __new__ method, but __call__ on a metaclass doesn't have to even call __new__ at all.

  4. If there is setup work, you can do it in the __new__ method of the metaclass, and it only happens once, instead of every time the class is called.

There are certainly lots of cases where customizing __new__ works just as well if you're not worried about the single responsibility principle.

But there are other use cases that have to happen earlier, when the class is created, rather than when the instance is created. It's when these come in to play that a metaclass is necessary. See What are your (concrete) use-cases for metaclasses in Python? for lots of great examples.

り繁华旳梦境 2024-12-05 15:02:11

一个区别是,通过定义元类 __call__ 方法,您要求在任何类或子类的 __new__ 方法有机会被调用之前调用它。

class MetaFoo(type):
    def __call__(cls,*args,**kwargs):
        print('MetaFoo: {c},{a},{k}'.format(c=cls,a=args,k=kwargs))

class Foo(object):
    __metaclass__=MetaFoo

class SubFoo(Foo):
    def __new__(self,*args,**kwargs):
        # This never gets called
        print('Foo.__new__: {a},{k}'.format(a=args,k=kwargs))

 sub=SubFoo()
 foo=Foo()

 # MetaFoo: <class '__main__.SubFoo'>, (),{}
 # MetaFoo: <class '__main__.Foo'>, (),{}

请注意,SubFoo.__new__ 永远不会被调用。相反,如果您在没有元类的情况下定义 Foo.__new__,则允许子类覆盖 Foo.__new__

当然,您可以定义 MetaFoo.__call__ 来调用 cls.__new__,但这取决于您。通过拒绝这样做,您可以阻止子类调用其 __new__ 方法。

我认为在这里使用元类没有明显的优势。由于“简单胜于复杂”,我建议使用 __new__ 。

One difference is that by defining a metaclass __call__ method you are demanding that it gets called before any of the class's or subclasses's __new__ methods get an opportunity to be called.

class MetaFoo(type):
    def __call__(cls,*args,**kwargs):
        print('MetaFoo: {c},{a},{k}'.format(c=cls,a=args,k=kwargs))

class Foo(object):
    __metaclass__=MetaFoo

class SubFoo(Foo):
    def __new__(self,*args,**kwargs):
        # This never gets called
        print('Foo.__new__: {a},{k}'.format(a=args,k=kwargs))

 sub=SubFoo()
 foo=Foo()

 # MetaFoo: <class '__main__.SubFoo'>, (),{}
 # MetaFoo: <class '__main__.Foo'>, (),{}

Notice that SubFoo.__new__ never gets called. In contrast, if you define Foo.__new__ without a metaclass, you allow subclasses to override Foo.__new__.

Of course, you could define MetaFoo.__call__ to call cls.__new__, but that's up to you. By refusing to do so, you can prevent subclasses from having their __new__ method called.

I don't see a compelling advantage to using a metaclass here. And since "Simple is better than complex", I'd recommend using __new__.

捂风挽笑 2024-12-05 15:02:11

我认为 Pyrscope 的答案的充实的 Python 3 版本对于某人复制、粘贴和破解可能会很方便(可能是我,当我发现自己在 6 个月内再次回到这个页面查找它时)。它取自这篇文章

class Meta(type):

     @classmethod
     def __prepare__(mcs, name, bases, **kwargs):
         print('  Meta.__prepare__(mcs=%s, name=%r, bases=%s, **%s)' % (
             mcs, name, bases, kwargs
         ))
         return {}

     def __new__(mcs, name, bases, attrs, **kwargs):
         print('  Meta.__new__(mcs=%s, name=%r, bases=%s, attrs=[%s], **%s)' % (
             mcs, name, bases, ', '.join(attrs), kwargs
         ))
         return super().__new__(mcs, name, bases, attrs)

     def __init__(cls, name, bases, attrs, **kwargs):
         print('  Meta.__init__(cls=%s, name=%r, bases=%s, attrs=[%s], **%s)' % (
             cls, name, bases, ', '.join(attrs), kwargs
         ))
         super().__init__(name, bases, attrs)

     def __call__(cls, *args, **kwargs):
         print('  Meta.__call__(cls=%s, args=%s, kwargs=%s)' % (
             cls, args, kwargs
         ))
         return super().__call__(*args, **kwargs)

print('** Meta class declared')

class Class(metaclass=Meta, extra=1):

     def __new__(cls, myarg):
         print('  Class.__new__(cls=%s, myarg=%s)' % (
             cls, myarg
         ))
         return super().__new__(cls)

     def __init__(self, myarg):
         print('  Class.__init__(self=%s, myarg=%s)' % (
             self, myarg
         ))
         self.myarg = myarg
         super().__init__()

     def __str__(self):
         return "<instance of Class; myargs=%s>" % (
             getattr(self, 'myarg', 'MISSING'),
         )

print('** Class declared')

Class(1)
print('** Class instantiated')

输出:

** Meta class declared
  Meta.__prepare__(mcs=<class '__main__.Meta'>, name='Class', bases=(), **{'extra': 1})
  Meta.__new__(mcs=<class '__main__.Meta'>, name='Class', bases=(), attrs=[__module__, __qualname__, __new__, __init__, __str__, __classcell__], **{'extra': 1})
  Meta.__init__(cls=<class '__main__.Class'>, name='Class', bases=(), attrs=[__module__, __qualname__, __new__, __init__, __str__, __classcell__], **{'extra': 1})
** Class declared
  Meta.__call__(cls=<class '__main__.Class'>, args=(1,), kwargs={})
  Class.__new__(cls=<class '__main__.Class'>, myarg=1)
  Class.__init__(self=<instance of Class; myargs=MISSING>, myarg=1)
** Class instantiated

另一个很棒的资源同一篇文章重点介绍的是 David Beazley 的 PyCon 2013 Python 3 元编程教程

I thought a fleshed out Python 3 version of pyroscope's answer might be handy for someone to copy, paste and hack about with (probably me, when I find myself back at this page looking it up again in 6 months). It is taken from this article:

class Meta(type):

     @classmethod
     def __prepare__(mcs, name, bases, **kwargs):
         print('  Meta.__prepare__(mcs=%s, name=%r, bases=%s, **%s)' % (
             mcs, name, bases, kwargs
         ))
         return {}

     def __new__(mcs, name, bases, attrs, **kwargs):
         print('  Meta.__new__(mcs=%s, name=%r, bases=%s, attrs=[%s], **%s)' % (
             mcs, name, bases, ', '.join(attrs), kwargs
         ))
         return super().__new__(mcs, name, bases, attrs)

     def __init__(cls, name, bases, attrs, **kwargs):
         print('  Meta.__init__(cls=%s, name=%r, bases=%s, attrs=[%s], **%s)' % (
             cls, name, bases, ', '.join(attrs), kwargs
         ))
         super().__init__(name, bases, attrs)

     def __call__(cls, *args, **kwargs):
         print('  Meta.__call__(cls=%s, args=%s, kwargs=%s)' % (
             cls, args, kwargs
         ))
         return super().__call__(*args, **kwargs)

print('** Meta class declared')

class Class(metaclass=Meta, extra=1):

     def __new__(cls, myarg):
         print('  Class.__new__(cls=%s, myarg=%s)' % (
             cls, myarg
         ))
         return super().__new__(cls)

     def __init__(self, myarg):
         print('  Class.__init__(self=%s, myarg=%s)' % (
             self, myarg
         ))
         self.myarg = myarg
         super().__init__()

     def __str__(self):
         return "<instance of Class; myargs=%s>" % (
             getattr(self, 'myarg', 'MISSING'),
         )

print('** Class declared')

Class(1)
print('** Class instantiated')

Outputs:

** Meta class declared
  Meta.__prepare__(mcs=<class '__main__.Meta'>, name='Class', bases=(), **{'extra': 1})
  Meta.__new__(mcs=<class '__main__.Meta'>, name='Class', bases=(), attrs=[__module__, __qualname__, __new__, __init__, __str__, __classcell__], **{'extra': 1})
  Meta.__init__(cls=<class '__main__.Class'>, name='Class', bases=(), attrs=[__module__, __qualname__, __new__, __init__, __str__, __classcell__], **{'extra': 1})
** Class declared
  Meta.__call__(cls=<class '__main__.Class'>, args=(1,), kwargs={})
  Class.__new__(cls=<class '__main__.Class'>, myarg=1)
  Class.__init__(self=<instance of Class; myargs=MISSING>, myarg=1)
** Class instantiated

Another great resource highlighted by the same article is David Beazley's PyCon 2013 Python 3 Metaprogramming tutorial.

感情废物 2024-12-05 15:02:11

这是生命周期阶段以及您可以访问的内容的问题。 __call____new__ 之后被调用,并在初始化参数之前被传递给 __init__< /code>,这样你就可以操纵它们。尝试这段代码并研究它的输出:

class Meta(type):
    def __new__(cls, name, bases, newattrs):
        print "new: %r %r %r %r" % (cls, name, bases, newattrs,)
        return super(Meta, cls).__new__(cls, name, bases, newattrs)

    def __call__(self, *args, **kw):
        print "call: %r %r %r" % (self, args, kw)
        return super(Meta, self).__call__(*args, **kw)

class Foo:
    __metaclass__ = Meta

    def __init__(self, *args, **kw):
        print "init: %r %r %r" % (self, args, kw)

f = Foo('bar')
print "main: %r" % f

It's a matter of lifecycle phases and what you have access to. __call__ gets called after __new__ and is passed the initialization parameters before they get passed on to __init__, so you can manipulate them. Try this code and study its output:

class Meta(type):
    def __new__(cls, name, bases, newattrs):
        print "new: %r %r %r %r" % (cls, name, bases, newattrs,)
        return super(Meta, cls).__new__(cls, name, bases, newattrs)

    def __call__(self, *args, **kw):
        print "call: %r %r %r" % (self, args, kw)
        return super(Meta, self).__call__(*args, **kw)

class Foo:
    __metaclass__ = Meta

    def __init__(self, *args, **kw):
        print "init: %r %r %r" % (self, args, kw)

f = Foo('bar')
print "main: %r" % f
深海少女心 2024-12-05 15:02:11

在问题中给出的特定示例中,覆盖元类中的__call__优于覆盖类中的__new__

假设我想在调用类时具有自定义行为,例如缓存而不是创建新对象

  1. 如果缓存的目的是提高效率,则缓存__new__'结果不是最佳,因为 __init__ 无论如何都会执行(数据模型:基本自定义)。例如:

    from functools import lru_cache
    
    我的班级:
    
        @lru_cache(最大大小=无)
        def __new__(cls, *args, **kwargs):
            返回 super().__new__(cls)
    
        def __init__(自我, ...)
            “始终执行。即使在缓存命中时也是如此。”
    
  2. 缓存__new__的结果是只有当__init__对已经初始化的实例没有明显影响时才有效。 否则在缓存对象上执行__init__可能会导致对其其他引用产生令人讨厌的副作用

  3. 元类级别的缓存既避免了1.的性能问题,也避免了2.的正确性问题。例如:

    from functools import lru_cache
    
    类 CachedInstances(类型):
    
        @lru_cache(最大大小=无)
        def __call__(cls, *args, **kwargs):
            返回 super().__call__(*args, **kwargs)
    
    类 MyClass(元类=CachedInstances):
    
        def __init__(自我, ...)
            “仅在缓存未命中时执行。”
    

请注意,Michael Ekoka 的回答已经提到了由于重复 __init__ 执行而可能出现的不良影响在覆盖 __new__ 方法中(如我的第 2 项)。

In the particular example given in the question, overriding __call__ in the metaclass is just superior to overriding __new__ in the class.

suppose I want to have custom behavior when the class is called, for example caching instead of creating fresh objects

  1. If the purpose of caching is efficiency, then caching __new__'s result is not optimal because __init__ is executed anyhow (Data Model: Basic Customization). For example:

    from functools import lru_cache
    
    class MyClass:
    
        @lru_cache(maxsize=None)
        def __new__(cls, *args, **kwargs):
            return super().__new__(cls)
    
        def __init__(self, ...)
            "Always executed. Even on cache hit."
    
  2. Caching __new__'s result is sound only if __init__ has no noticeable effect on an already initialized instance. Otherwise the execution of __init__ on a cached object could lead to annoying side-effects on its other references.

  3. Caching at the metaclass level avoids both the performance issue of 1. and the correctness issue of 2. For example:

    from functools import lru_cache
    
    class CachedInstances(type):
    
        @lru_cache(maxsize=None)
        def __call__(cls, *args, **kwargs):
            return super().__call__(*args, **kwargs)
    
    class MyClass(metaclass=CachedInstances):
    
        def __init__(self, ...)
            "Only executed on cache miss."
    

Note that Michael Ekoka's answer already mention undesired effects that may arise due to repeated __init__ execution within the override __new__ approach (as in my item 2).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文