Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a class instance without calling initializer?

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 insensitive string class in Python used for efficient comparison purposes but am having trouble creating a new instance without calling __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)
>>> 

What should I replace string = String(); string.__string = self.__string[key]; string.__simple = self.__simple[key] with to initialize the new object with the slices?

EDIT:

As inspired by the answer written below, the initializer has been edited to quickly check for no arguments.

def __init__(self, string=None):
    if string is None:
        self.__string = self.__simple = ()
    else:
        self.__string = tuple(string.split())
        self.__simple = tuple(self.__simple())
like image 424
Noctis Skytower Avatar asked Jan 30 '10 18:01

Noctis Skytower


People also ask

Can I create a class without init?

We can create a class without any constructor definition. In this case, the superclass constructor is called to initialize the instance of the class. The object class is the base of all the classes in Python.

Can you create an instance of a class?

Note: The phrase "instantiating a class" means the same thing as "creating an object." When you create an object, you are creating an "instance" of a class, therefore "instantiating" a class. The new operator requires a single, postfix argument: a call to a constructor.

What is __ new __ in Python?

The __new__() is a static method of the object class. When you create a new object by calling the class, Python calls the __new__() method to create the object first and then calls the __init__() method to initialize the object's attributes.

What is the process for creating a class instance in Python?

The first method __init__() is a special method, which is called class constructor or initialization method that Python calls when you create a new instance of this class. You declare other class methods like normal functions with the exception that the first argument to each method is self.


3 Answers

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.

like image 23
Cerin Avatar answered Oct 06 '22 10:10

Cerin


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

like image 156
Alex Martelli Avatar answered Oct 06 '22 09:10

Alex Martelli


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()
like image 23
Noctis Skytower Avatar answered Oct 06 '22 09:10

Noctis Skytower