设计模式 - Python元类中的__call__和__new__的区别?
大家讲道理
大家讲道理 2017-04-18 10:04:49
0
2
612

参考文档

creating-a-singleton-in-python和what-is-a-metaclass-in-python

问题描述

下面这两段代码的执行结果反映了一个问题:很明显元类的存在会影响__call__和__new__的优先级,请问大神能否分析一下两者执行结果不同的原因?

实例代码

1.不含元类的单例模式

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        print('__new__')
        return cls._instance

    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
        print('__call__')
        return cls._instance

class Foo(Singleton):
    pass

print('1')
foo1 = Foo()
print('2')
foo2 = Foo()

print(foo1 is foo2)  # True

上面这段代码的执行结果

$ python non_metaclass.py
1
__new__
2
__new__
True

2.含有元类的单例模式

class Singleton(type):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        print('__new__')
        return cls._instance

    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
        print('__call__')
        return cls._instance


class Foo(metaclass=Singleton):
    # 不兼容Python2
    pass

print('1')
foo1 = Foo()
print('2')
foo2 = Foo()

print (foo1 is foo2)  # True

上面这段代码的执行结果

$ python metaclass.py
__new__
1
__call__
2
__call__
True

如果描述不够详细,请在评论区留一下言,我再改进。

大家讲道理
大家讲道理

光阴似箭催人老,日月如移越少年。

全部回覆(2)
黄舟

我在這裡再修改仔細說明下吧

元類別是定義類別結構屬性的, 而類別裡的 "__new__", "__init__" 方法, 是處理類別實例用的

我們所定義的每一個類別, 都可以理解為是 type 的一個實例

class Foo(object):
    version = "1.0.1"
    pass

print(Foo.version) # 1.0.1
print(Foo.__name__) # Foo

# ------------
# 使用type创建类是一样的
Foo = type("Foo", (object,), dict(version="1.0.1"))
print(Foo.version)      # 1.0.1
print(Foo.__name__)     # Foo

好, 說回到 "__new__" 和 "__call__"

元類別中, "__new__" 會在你定義類別的時候執行, 只執行一次, 如果有兩個類別使用了這個元類別, OK, 會執行兩次

class FooMeta(type):
    def __new__(meta, name, bases, dct):
        print("metaclass __new__")
        return type(name, bases, dct)

class Foo():
    __metaclass__ = FooMeta
    
# 没有任何代码了, 你会看到 print了metaclass __new__
# 如果你细心, 并联系上面说的, 理解为
# Foo = type("Foo", tuple(), dict())
# 就明白 FooMeta.__new__原来就是 type.__new__
# Foo 是type的一个实例
# 这是为什么定义元类都要base type的原因: class FooMeta(type)
# 如果你定义 __metaclass__ = type 并没什么错, 因为本来默认就是这样

而__call__會在你每次實例化的時候調用, 其實和Foo.__new__是一樣的, 意思是, 如果你的Foo定義了__new__, 元類中的__call__便不會執行

class FooMeta(type):
    def __new__(meta, name, bases, dct):
        print("metaclass __new__")
        return type(name, bases, dct)

    def __call__(self, *args, **kwargs):
        print("meta __call__")
        return "hello"


class Foo():
    __metaclass__ = FooMeta

    def __new__(cls, *args, **kwargs):
        print("Foo __new__")
        return "hello"

f = Foo()
print(f)
# 输出结果
# metaclass __new__
# Foo __new__
# hello

元類別的 "__new__" 可以變更你所定義的型別, 我在這裡定義Foo成了一個list

# -*- coding: utf-8 -*-
class FooMeta(type):
    def __new__(cls, name, bases, dct):
        return [1, 2, 3, 4]

# 这里相当于执行 Foo = [1, 2, 3, 4]
class Foo():
    __metaclass__ = FooMeta

print(Foo)          # [1, 2, 3, 4]
print(Foo[0:2])     # [1, 2]
print(Foo.pop())    # 4

寫這麼多很辛苦的, 能正解理解metaclass的人非常的少, 不知題主要怎麼感謝我

刘奇

這不是元類別影響new和call優先權的問題,而是元類別在創建類別的時候__new__只會被呼叫一次。而這個__new__就是用來創建出我們的類,如你問題中的。什麼時候被創建呢?當解釋器解釋到Foo類別的時候,會在類別的定義中尋找__metaclass__屬性,如果找到了就用它來建立類別。如果沒有找到,就會用內建的type來建立這個類別。而後為什麼不再呼叫呢,因為元類別已經把我們的類別創建好了,總不能每次Foo()的時候重新去創建一次類別吧。從你的輸出也可以看出foo2 = Foo()並沒有輸出__new__。因為這時候類別已經被元類別即Singleton創建好了,直接開始用就行了。和我們平常用的類別沒什麼差別了,所以每次創建的都是一個新的物件。而為什麼要用__call__呢
因為Foo是元類別Singleton建立出來的類別,可以認為Foo是Singleton的實例物件。所以每次Foo()的時候都會去呼叫__call__。而在__call__中我們拿到已經建立好的實例物件。不就是單例嗎。 。

再透過程式碼詳細說明一下

class Singleton(type):
    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            print(cls)
            cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
            print(cls._instance)
        print('__call__')
        return cls._instance


class Foo():
    __metaclass__ = Singleton
    pass

print('1')
foo1 = Foo()
print('2')
foo2 = Foo()

print (foo1 is foo2)  # True

運行之後你會發現,__call__函數中cls是元類別第一次也是唯一一次呼叫__new__創建出來的類別即Foo, 而cls._instance則是我們的Foo的實例物件。每次Foo()的時候都是取的相同實例物件。 元類別畢竟是創建類別的類別。一旦創建好了,類別就和平時定義的類別沒有什麼兩樣。

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板