【问题标题】:How to create a class instance without calling initializer?如何在不调用初始化程序的情况下创建类实例?
【发布时间】:2010-01-30 18:41:37
【问题描述】:

有什么方法可以避免在初始化类时调用__init__,例如从类方法中调用它?

我正在尝试在 Python 中创建一个不区分大小写和标点符号的字符串类,用于进行有效的比较,但在不调用 __init__ 的情况下创建新实例时遇到了麻烦。

>>> class String:

    def __init__(self, string):
        self.__string = tuple(string.split())
        self.__simple = tuple(self.__simple())

    def __simple(self):
        letter = lambda s: ''.join(filter(lambda s: 'a' <= s <= 'z', s))
        return filter(bool, map(letter, map(str.lower, self.__string)))

    def __eq__(self, other):
        assert isinstance(other, String)
        return self.__simple == other.__simple

    def __getitem__(self, key):
        assert isinstance(key, slice)
        string = String()
        string.__string = self.__string[key]
        string.__simple = self.__simple[key]
        return string

    def __iter__(self):
        return iter(self.__string)

>>> String('Hello, world!')[1:]
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    String('Hello, world!')[1:]
  File "<pyshell#1>", line 17, in __getitem__
    string = String()
TypeError: __init__() takes exactly 2 positional arguments (1 given)
>>> 

我应该用什么替换 string = String(); string.__string = self.__string[key]; string.__simple = self.__simple[key] 来用切片初始化新对象?

编辑:

受下面答案的启发,初始化器已经过编辑,可以快速检查是否没有参数。

def __init__(self, string=None):
    if string is None:
        self.__string = self.__simple = ()
    else:
        self.__string = tuple(string.split())
        self.__simple = tuple(self.__simple())

【问题讨论】:

标签: python class constructor initialization


【解决方案1】:

在可行的情况下,让__init__ 被调用(并通过适当的参数使调用无害)是更可取的。然而,如果这需要太多的扭曲,你确实有一个替代方案,只要你避免使用旧式类的灾难性选择(没有充分的理由使用旧式类在新代码中,以及几个充分的理由)...:

   class String(object):
      ...

   bare_s = String.__new__(String)

这个成语通常用在classmethods 中,它的目的是作为“替代构造函数”,所以你通常会看到它以如下方式使用:

@classmethod 
def makeit(cls):
    self = cls.__new__(cls)
    # etc etc, then
    return self

(这样,当在子类而不是基类上调用时,类方法将被正确继承并生成子类实例)。

【讨论】:

  • 你会如何为 Python 3.1 重写这个?
  • 如果String是一个子类,你会如何调用超类的构造函数?
  • 哦,我找到了,你会在创建self 之后执行SuperClass.__init__(self)
  • 哇,我已经使用 python 多年/几十年了,只是发现了这个。一直在寻找这个“一段时间”:)
【解决方案2】:

标准pickle 和复制模块使用的一个技巧是创建一个空类,使用它实例化对象,然后将该实例的__class__ 分配给“真实”类。例如

>>> class MyClass(object):
...     init = False
...     def __init__(self):
...         print 'init called!'
...         self.init = True
...     def hello(self):
...         print 'hello world!'
... 
>>> class Empty(object):
...     pass
... 
>>> a = MyClass()
init called!
>>> a.hello()
hello world!
>>> print a.init
True
>>> b = Empty()
>>> b.__class__ = MyClass
>>> b.hello()
hello world!
>>> print b.init
False

但请注意,这种方法很少需要。绕过__init__ 可能会产生一些意想不到的副作用,尤其是如果您不熟悉原始类,因此请确保您知道自己在做什么。

【讨论】:

    【解决方案3】:

    在这个例子中使用元类提供了一个很好的解决方案。元类的用途有限,但工作正常。

    >>> class MetaInit(type):
    
        def __call__(cls, *args, **kwargs):
            if args or kwargs:
                return super().__call__(*args, **kwargs)
            return cls.__new__(cls)
    
    >>> class String(metaclass=MetaInit):
    
        def __init__(self, string):
            self.__string = tuple(string.split())
            self.__simple = tuple(self.__simple())
    
        def __simple(self):
            letter = lambda s: ''.join(filter(lambda s: 'a' <= s <= 'z', s))
            return filter(bool, map(letter, map(str.lower, self.__string)))
    
        def __eq__(self, other):
            assert isinstance(other, String)
            return self.__simple == other.__simple
    
        def __getitem__(self, key):
            assert isinstance(key, slice)
            string = String()
            string.__string = self.__string[key]
            string.__simple = self.__simple[key]
            return string
    
        def __iter__(self):
            return iter(self.__string)
    
    >>> String('Hello, world!')[1:]
    <__main__.String object at 0x02E78830>
    >>> _._String__string, _._String__simple
    (('world!',), ('world',))
    >>> 
    

    附录:

    六年后,我认为Alex Martelli's answer 胜过我自己的方法。在考虑元类的情况下,以下答案显示了如何在有和没有元类的情况下解决问题:

    #! /usr/bin/env python3
    METHOD = 'metaclass'
    
    
    class NoInitMeta(type):
        def new(cls):
            return cls.__new__(cls)
    
    
    class String(metaclass=NoInitMeta if METHOD == 'metaclass' else type):
        def __init__(self, value):
            self.__value = tuple(value.split())
            self.__alpha = tuple(filter(None, (
                ''.join(c for c in word.casefold() if 'a' <= c <= 'z') for word in
                self.__value)))
    
        def __str__(self):
            return ' '.join(self.__value)
    
        def __eq__(self, other):
            if not isinstance(other, type(self)):
                return NotImplemented
            return self.__alpha == other.__alpha
    
        if METHOD == 'metaclass':
            def __getitem__(self, key):
                if not isinstance(key, slice):
                    raise NotImplementedError
                instance = type(self).new()
                instance.__value = self.__value[key]
                instance.__alpha = self.__alpha[key]
                return instance
        elif METHOD == 'classmethod':
            def __getitem__(self, key):
                if not isinstance(key, slice):
                    raise NotImplementedError
                instance = self.new()
                instance.__value = self.__value[key]
                instance.__alpha = self.__alpha[key]
                return instance
    
            @classmethod
            def new(cls):
                return cls.__new__(cls)
        elif METHOD == 'inline':
            def __getitem__(self, key):
                if not isinstance(key, slice):
                    raise NotImplementedError
                cls = type(self)
                instance = cls.__new__(cls)
                instance.__value = self.__value[key]
                instance.__alpha = self.__alpha[key]
                return instance
        else:
            raise ValueError('METHOD did not have an appropriate value')
    
        def __iter__(self):
            return iter(self.__value)
    
    
    def main():
        x = String('Hello, world!')
        y = x[1:]
        print(y)
    
    
    if __name__ == '__main__':
        main()
    

    【讨论】:

      【解决方案4】:

      将另一个参数传递给构造函数,如下所示:

      def __init__(self, string, simple = None):
          if simple is None:
              self.__string = tuple(string.split())
              self.__simple = tuple(self.__simple())
          else:
              self.__string = string
              self.__simple = simple
      

      你可以这样称呼它:

      def __getitem__(self, key):
          assert isinstance(key, slice)
          return String(self.__string[key], self.__simple[key])
      

      另外,我不确定是否允许同时命名字段和方法__simple。如果只是为了可读性,你应该改变它。

      【讨论】:

      • 我更喜欢你的第一个答案。考虑到str() == '',我想我会把代码改成def __init__(self, string=''):。感谢您的帮助和想法!我希望完全避免__init__,但是使用提到的更改调用String() 根本不应该是一个非常昂贵的操作。这似乎是目前最好的解决方案。
      • 对不起,我认为我的第一个答案并没有真正解决问题:) 我似乎无法找回它......?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-12-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多