【问题标题】:why defined '__new__' and '__init__' all in a class为什么在一个类中定义了“__new__”和“__init__”
【发布时间】:2011-01-02 08:06:15
【问题描述】:

我认为你可以在一个类中定义“__init__”或“__new__”,但为什么都在 django.utils.datastructures.py 中定义。

我的代码:

class a(object):
    def __init__(self):
        print  'aaa'
    def __new__(self):
        print 'sss'

a()#print 'sss'

class b:
    def __init__(self):
        print  'aaa'
    def __new__(self):
        print 'sss'
b()#print 'aaa'

数据结构.py:

class SortedDict(dict):
    """
    A dictionary that keeps its keys in the order in which they're inserted.
    """
    def __new__(cls, *args, **kwargs):
        instance = super(SortedDict, cls).__new__(cls, *args, **kwargs)
        instance.keyOrder = []
        return instance

    def __init__(self, data=None):
        if data is None:
            data = {}
        super(SortedDict, self).__init__(data)
        if isinstance(data, dict):
            self.keyOrder = data.keys()
        else:
            self.keyOrder = []
            for key, value in data:
                if key not in self.keyOrder:
                    self.keyOrder.append(key)

在什么情况下会调用SortedDict.__init__

谢谢

【问题讨论】:

  • 阅读文档,它们很详尽!

标签: python class


【解决方案1】:

您可以定义__new____init__ 中的一个或两个。

__new__ 必须返回一个对象——它可以是一个新对象(通常该任务被委派给type.__new__),一个现有对象(用于实现单例,从池中“回收”实例等等) ,甚至是一个不是类的实例。如果__new__ 返回类的一个实例(新的或现有的),则__init__ 会被调用;如果__new__ 返回的对象不是 类的实例,则__init__ 被调用。

__init__ 传递了一个类实例作为它的第一项(在相同的状态 __new__ 返回它,即通常为“空”)并且必须根据需要对其进行更改以使其准备好使用(通常通过添加属性)。

一般来说,最好使用__init__ 来完成它所能做的一切——而__new__,如果留下了__init__ 不能做的事情,那么“额外的东西”。

因此,如果您可以在 __init__ 中做一些有用的事情,您通常会定义两者,但不是在类实例化时您希望发生的所有事情。

例如,考虑一个类,它是int 的子类,但也有一个foo 槽——并且您希望使用int 的初始化程序和.foo 的初始化程序对其进行实例化。由于int 是不可变的,这部分必须发生在__new__ 中,所以可以学究式地编写代码:

>>> class x(int):
...   def __new__(cls, i, foo):
...     self = int.__new__(cls, i)
...     return self
...   def __init__(self, i, foo):
...     self.foo = foo
...   __slots__ = 'foo',
... 
>>> a = x(23, 'bah')
>>> print a
23
>>> print a.foo
bah
>>> 

实际上,对于这种简单的情况,如果您丢失了 __init__ 并且只是将 self.foo = foo 移动到 __new__,没有人会介意。但如果初始化足够丰富和复杂,最好放在__init__ 中,这个想法就值得牢记。

【讨论】:

  • 昨天问了一个关于动态继承多个类的问题。根据您当前的示例,我这样做了:gist.github.com/271098 Whats wrong?
  • Alex,您能否解释一下为什么在 SortedDict 实现的特定情况下会覆盖 __new__?在__init__ 中初始化keyOrder 属性有什么不好?我能想到的唯一真正原因是,这样做是为了确保即使 SortedDict 是子类并且子类不调用基类'__init__,也会创建属性。但是话又说回来,你可以对__new__ 做同样的事情,对吧?
  • @becomingGuru,该代码中有许多错误,但第一个是您使用的是旧式类:不要——总是使用新式类(继承自 object) .在这种情况下,这将揭示第二个错误,一旦修复,您将有第三个错误,等等。为什么不打开一个 SO 问题并要求调试您的代码,您的代码中有太多的错误来解释它们评论!
  • @shylent,绕过超类的__new__ 需要预先考虑恶意(因为你总是委派给一些祖先__new__,跳过超类必须是明确的),而绕过__init__ 只需要一个疏忽之罪。但是,是的,只有在某些情况下不调用__init__(或者某些子类在__new__ 和对__init__ 的调用之间做一些事情)时,该代码才有用——编码后的类本身不需要__new__.
  • 亚历克斯;这是:stackoverflow.com/questions/2026572/…
【解决方案2】:

__new____init__ 做完全不同的事情。方法__init__ 初始化一个类的新实例——它是一个构造函数。 __new__ 是一个更微妙的东西 --- 它可以改变参数,事实上,启动对象的类。例如以下代码:

class Meters(object):
    def __new__(cls, value):
        return int(value / 3.28083)

如果您调用Meters(6),您实际上不会创建Meters 的实例,而是创建int 的实例。您可能想知道为什么这很有用;它实际上对元类至关重要,这是一个公认的晦涩(但功能强大)的特性。

您会注意到,在 Python 2.x 中,只有从 object 继承的类才能利用 __new__,如您上面的代码所示。

您在 django 中展示的 __new__ 的使用似乎是试图在 SortedDict 对象上保持理智的方法解析顺序。不过,我承认,通常很难说出为什么需要__new__。标准 Python 风格建议除非必要,否则不要使用它(一如既往,更好的类设计是您首先求助的工具)。

【讨论】:

  • __new__ 是构造函数,__init__ 是......只是初始化
  • @Anurag:学究式地说,你是对的。但是,如果您来自 Python 以外的其他语言,最好告知 __init__ 是构造函数 --- 人们在没有我帮助的情况下滥用功能已经足够好了。因此,为了可能的其他人的利益,我将__init__ 作为构造函数保留在我的回复中。
【解决方案3】:

我唯一的猜测是,在这种情况下,他们(这个类的作者)甚至在调用 SortedDict.__init__ 之前就希望 keyOrder 列表存在于类中。

请注意,SortedDict 在其__init__ 中调用super(),这通常会转到dict.__init__,它可能会调用__setitem__ 等来开始添加项目。 SortedDict.__setitem__ 期望 .keyOrder 属性存在,这就是问题所在(因为在调用 super() 之前通常不会创建 .keyOrder。)这可能只是子类化 dict 的问题,因为我正常的直觉是在调用super()之前初始化.keyOrder

__new__ 中的代码也可用于允许在菱形继承结构中对 SortedDict 进行子类化,其中可能在调用第一个 __setitem__ 之前不调用 SortedDict.__init__ 等。 Django 必须应对各种问题,以支持从 2.3 开始的各种 python 版本;可能此代码在某些版本中完全不需要,而在其他版本中则需要。


定义__new____init__ 有一个共同的用途:访问类属性,这些属性可能会被它们的实例版本所掩盖,而不必执行type(self)self.__class__(在存在元类的情况下,甚至可能不是正确的事情)。

例如:

class MyClass(object):
    creation_counter = 0

    def __new__(cls, *args, **kwargs):
        cls.creation_counter += 1
        return super(MyClass, cls).__new__(cls)

    def __init__(self):
         print "I am the %dth myclass to be created!" % self.creation_counter

最后,__new__ 实际上可以返回包装器的实例或与您认为实例化的完全不同的类。这用于提供类似于元类的功能,而实际上不需要元类。

【讨论】:

    【解决方案4】:

    在我看来,在您描述的示例中,没有必要覆盖 __new__。 实例的创建和实际的内存分配发生在__new__ 中,__init____new__ 之后调用,用于初始化实例,以经典 OOP 术语为构造函数提供服务。所以,如果你想做的只是初始化变量,那么你应该去覆盖__init__。 当您使用元类时,__new__ 的真正作用就出现了。如果您想在创建类之前执行更改属性或添加属性等操作,您应该重写__new__

    考虑一个完全假设的情况,您希望将类的某些属性设为私有,即使它们没有这样定义(我并不是说应该这样做)。

    class PrivateMetaClass(type):
          def __new__(metaclass, classname, bases, attrs):
              private_attributes = ['name', 'age']
    
              for private_attribute in private_attributes:
                  if attrs.get(private_attribute):
                     attrs['_' + private_attribute] = attrs[private_attribute]
                     attrs.pop(private_attribute)
    
              return super(PrivateMetaClass, metaclass).__new__(metaclass, classname, bases, attrs)
    
    
    class Person(object):
    
          __metaclass__ = PrivateMetaClass
    
          name = 'Someone'
          age = 19
    
    person = Person()
    >>> hasattr(person, 'name')
    False
    >>> person._name
    'Someone'
    

    再次重申,这只是出于教学目的,我并不建议人们应该做这样的事情。

    【讨论】:

      猜你喜欢
      • 2014-03-01
      • 1970-01-01
      • 2016-11-06
      • 2018-11-11
      • 1970-01-01
      • 2010-10-15
      相关资源
      最近更新 更多