Python 的 __new__() 方法与实践

本文主要转自:http://www.cnblogs.com/ifantastic/p/3175735.html

__new__() 是在新式类中新出现的方法,它作用在构造方法建造实例之前,可以这么理解,在 Python 中存在于类里面的构造方法 __init__() 负责将类的实例化,而在 __init__() 启动之前,__new__() 决定是否要使用该 __init__() 方法,因为__new__() 可以调用其他类的构造方法或者直接返回别的对象来作为本类的实例

如果将类比喻为工厂,那么__init__()方法则是该工厂的生产工人,__init__()方法接受的初始化参数则是生产所需原料,__init__()方法会按照方法中的语句负责将原料加工成实例以供工厂出货。而__new__()则是生产部经理,__new__()方法可以决定是否将原料提供给该生产部工人,同时它还决定着出货产品是否为该生产部的产品,因为这名经理可以借该工厂的名义向客户出售完全不是该工厂的产品。

__new__() 方法的特性:

  • __new__() 方法是在类准备将自身实例化时调用
  • __new__() 方法始终都是类的静态方法,即使没有被加上静态方法装饰器

类的实例化和它的构造方法通常都是这个样子:

class MyClass(object):
    def __init__(self, *args, **kwargs):
        pass

myclass = MyClass(*args, **kwargs)  # 实例化

正如以上所示,一个类可以有多个位置参数和多个命名参数,而在实例化开始之后,在调用 __init__() 方法之前,Python 首先调用 __new__() 方法:

def __new__(cls, *args, **kwargs):
    pass

第一个参数cls是当前正在实例化的类。

如果要得到当前类的实例,应当在当前类中的 __new__() 方法语句中调用当前类的父类的 __new__() 方法。例如,如果当前类是直接继承自 object,那当前类的 __new__() 方法返回的对象应该为:

def __new__(cls, *args, **kwargs):
    ...
    return object.__new__(cls, *args, **kwargs)  # Python 2 是这样的,但这不适用于Python 3,会报错,后文不重复提示这点

注意:
      事实上如果(新式)类中没有重写__new__()方法,即在定义新式类时没有重新定义__new__()时,Python默认调用该类的直接父类的__new__()方法来构造该类的实例,如果该类的父类也没有重写__new__(),那么将一直按此规矩追溯至object的__new__()方法,因为object是所有新式类的基类。
      而如果新式类中重写了__new__()方法,那么你可以自由选择任意一个的其他的新式类(必定要是新式类,只有新式类必定都有__new__(),因为所有新式类都是object的后代,而经典类则没有__new__()方法)的__new__()方法来制造实例,包括这个新式类的所有前代类和后代类,只要它们不会造成递归死循环。具体看以下代码解释:

class Foo(object):
    def __init__(self, *args, **kwargs):
        ...
    def __new__(cls, *args, **kwargs):
        return object.__new__(cls, *args, **kwargs)    

# 以上return等同于 
# return object.__new__(Foo, *args, **kwargs)
# return Stranger.__new__(cls, *args, **kwargs)
# return Child.__new__(cls, *args, **kwargs)

class Child(Foo):
    def __new__(cls, *args, **kwargs):
        return object.__new__(cls, *args, **kwargs)

class Stranger(object):  # 在制造Stranger实例时,会自动调用 object.__new__(cls)
    ...

# 如果Child中没有定义__new__()方法,那么会自动调用其父类的__new__()方法来制造实例,即 Foo.__new__(cls, *args, **kwargs)。
# 在任何新式类的__new__()方法,不能调用自身的__new__()来制造实例,因为这会造成死循环。因此必须避免类似以下的写法:
# 在Foo中避免:return Foo.__new__(cls, *args, **kwargs) 或 return cls.__new__(cls, *args, **kwargs)。Child同理。
# 使用object或者没有血缘关系的新式类的__new__()是安全的,但是如果是在有继承关系的两个类之间,应避免互调造成死循环,例如:(Foo)return Child.__new__(cls), (Child)return Foo.__new__(cls)。

通常来说,新式类开始实例化时,__new__()方法会返回cls(cls指代当前类)的实例,然后该类的__init__()方法作为构造方法会接收这个实例(即self)作为自己的第一个参数,然后依次传入__new__()方法中接收的位置参数和命名参数。

注意:如果__new__()没有返回cls(即当前类)的实例,那么当前类的__init__()方法是不会被调用的。如果__new__()返回其他类(新式类或经典类均可)的实例,那么只会调用被返回的那个类的构造方法【不包括__init__()方法】

class Foo(object):
    def __init__(self, *args, **kwargs):
        ...
    def __new__(cls, *args, **kwargs):
        return object.__new__(Stranger, *args, **kwargs)  

class Stranger(object):
    ...

foo = Foo()
print type(foo)    

# 打印的结果显示foo其实是Stranger类的实例

# 因此可以这么描述__new__()和__init__()的区别,在新式类中__new__()才是真正的实例化方法,为类提供外壳制造出实例框架,然后调用该框架内的构造方法__init__()使其丰满。
# 如果以建房子做比喻,__new__()方法负责开发地皮,打下地基,并将原料存放在工地。而__init__()方法负责从工地取材料建造出地皮开发招标书中规定的大楼,__init__()负责大楼的细节设计,建造,装修使其可交付给客户。

================ 以上是转载的原文 =========================

下面是我的一点使用经验。此前我也没具体使用过__new__()方法,只是大概听过有这么一个事。

最近项目新增了一种网络协议,而API不变,希望同时适配不同的网络协议(数据包中有给出版本号可以判断)。然后就想到了在实例化的__new__()方法里面返回一个其他类的实例,进而拜读了原文……

 

粗略阅读原文后,写出来了类似这样的代码:

condition = None

class A(object):
    def __new__(cls, *args, **kwargs):
        print('A new')
        if condition == 'A':
            return object.__new__(cls)  # cls == A
        elif condition == 'B':
            return object.__new__(B)

    def __init__(self, value):
        self.value = value


class B(object):
    def __new__(cls, *args, **kwargs):
        print('B new')
        return object.__new__(B)

    def __init__(self, value):
        self.value = value

condition = 'A'
a = A(123)
print(type(a))
print(a.value)

condition = 'B'
b = A(456)
print(type(b))
print(b.value)

运行结果为:

A new
<class '__main__.A'>
123

A new
<class '__main__.B'>
Traceback (most recent call last):
  File "/xxxxx/main.py", line 31, in <module>
    print(b.value)
AttributeError: 'B' object has no attribute 'value'

A这块是正常的,而B这块就很不正常了:没有value这个属性不说,print(‘B new’) 都没有执行!
——这是 return object.__new__(B) 导致的,因为object的__new__() 方法没有 print(‘B new’) 这句。

我们把 return object.__new__(B) 改成 return B.__new__(cls) ,像这样:

class A(object):
    def __new__(cls, *args, **kwargs):
        print('A new')
        if condition == 'A':
            return object.__new__(cls)  # cls == A
        elif condition == 'B':
            return B.__new__(cls)

这下执行结果有显示 B new 了。然而,’B’ object has no attribute ‘value’,还是没有。
把 return B.__new__(cls) 改为 return B.__new__(cls, *args, **kwargs) ,把参数传进去,结果还是一样。

难道是B类的问题?把它改成:

class B(object):
    def __new__(cls, *args, **kwargs):
        print('B new')
        return object.__new__(B, *args, **kwargs)

依旧报错(在Python 3下还会报 TypeError: object() takes no parameters)!有点抓狂了,再去精读一下,发现了上面被我标红的一句“如果__new__()返回其他类(新式类或经典类均可)的实例,那么只会调用被返回的那个类的构造方法”。就是说,__init__()方法没有被调用啊!我来手动调用一下:

condition = None

class A(object):
    def __new__(cls, *args, **kwargs):
        print('A new')
        if condition == 'A':
            return object.__new__(cls)  # cls == A
        elif condition == 'B':
            return B.__new__(cls, *args, **kwargs)

    def __init__(self, value):
        self.value = value


class B(object):
    def __new__(cls, *args, **kwargs):
        print('B new')
        self = object.__new__(B)  # 创建对象
        self.__init__(*args, **kwargs)  # 初始化对象
        return self  # 初始化之后再返回结果

    def __init__(self, value):
        self.value = value

condition = 'A'
a = A(123)
print(type(a))
print(a.value)

condition = 'B'
b = A(456)
print(type(b))
print(b.value)

这次终于和期望一致了……

 

总结:

  1. 如果__new__()返回其他类的实例,那么只会调用被返回的那个类的构造方法【不包括__init__()方法】;
  2. 如果__new__()返回当前类的实例,可以直接 return object.__new__(cls) ,不需要手动传参数;若返回其他类的参数,需 return ClassName.__new__(cls, *args, **kwargs) 手动传参数,万万不能 return object.__new__(ClassName, *args, **kwargs) 。

发表评论