How to create a class instance without calling initializer?

后端 未结 4 938
-上瘾入骨i
-上瘾入骨i 2020-12-08 19:31

Is there any way to avoid calling __init__ on a class while initializing it, such as from a class method?

I am trying to create a case and punctuation i

相关标签:
4条回答
  • 2020-12-08 20:10

    A trick the standard pickle and copy modules use is to create an empty class, instantiate the object using that, and then assign that instance's __class__ to the "real" class. e.g.

    >>> 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
    

    But note, this approach is very rarely necessary. Bypassing the __init__ can have some unexpected side effects, especially if you're not familiar with the original class, so make sure you know what you're doing.

    0 讨论(0)
  • 2020-12-08 20:13

    Using a metaclass provides a nice solution in this example. The metaclass has limited use but works fine.

    >>> 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',))
    >>> 
    

    Addendum:

    After six years, my opinion favors Alex Martelli's answer more than my own approach. With meta-classes still on the mind, the following answer shows how the problem can be solved both with and without them:

    #! /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()
    
    0 讨论(0)
  • 2020-12-08 20:20

    When feasible, letting __init__ get called (and make the call innocuous by suitable arguments) is preferable. However, should that require too much of a contortion, you do have an alternative, as long as you avoid the disastrous choice of using old-style classes (there is no good reason to use old-style classes in new code, and several good reasons not to)...:

       class String(object):
          ...
    
       bare_s = String.__new__(String)
    

    This idiom is generally used in classmethods which are meant to work as "alternative constructors", so you'll usually see it used in ways such as...:

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

    (this way the classmethod will properly be inherited and generate subclass instances when called on a subclass rather than on the base class).

    0 讨论(0)
  • 2020-12-08 20:23

    Pass another argument to the constructor, like so:

    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
    

    You can then call it like this:

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

    Also, I'm not sure it's allowed to name both the field and the method __simple. If only for readability, you should change that.

    0 讨论(0)
提交回复
热议问题