Using the __call__ method of a metaclass instead of __new__?

前端 未结 5 1336
情歌与酒
情歌与酒 2020-11-30 21:40

When discussing metaclasses, the docs state:

You can of course also override other class methods (or add new methods); for example defining a custom

5条回答
  •  离开以前
    2020-11-30 22:46

    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
    

提交回复
热议问题