SpecialistOff.NET / Вопросы / Статьи / Фрагменты кода / Резюме / Метки / Помощь / Файлы
НазадМетки: python
It can be confusing when you see metaclass examples that appear to arbitrarily use __new__ or __init__ – why choose one over the other?
__new__ is called for the creation of a new class, while __init__ is called after the class is created, to perform additional initialization before the class is handed to the caller:
# Metaclasses/NewVSInit.py from pprint import pprint class Tag1: pass class Tag2: pass class Tag3: def tag3_method(self): pass class MetaBase(type): def __new__(mcl, name, bases, nmspc): print('MetaBase.__new__\n') return super(MetaBase, mcl).__new__(mcl, name, bases, nmspc) def __init__(cls, name, bases, nmspc): print('MetaBase.__init__\n') super(MetaBase, cls).__init__(name, bases, nmspc) class MetaNewVSInit(MetaBase): def __new__(mcl, name, bases, nmspc): # First argument is the metaclass ``MetaNewVSInit`` print('MetaNewVSInit.__new__') for x in (mcl, name, bases, nmspc): pprint(x) print('') # These all work because the class hasn't been created yet: if 'foo' in nmspc: nmspc.pop('foo') name += '_x' bases += (Tag1,) nmspc['baz'] = 42 return super(MetaNewVSInit, mcl).__new__(mcl, name, bases, nmspc) def __init__(cls, name, bases, nmspc): # First argument is the class being initialized print('MetaNewVSInit.__init__') for x in (cls, name, bases, nmspc): pprint(x) print('') if 'bar' in nmspc: nmspc.pop('bar') # No effect name += '_y' # No effect bases += (Tag2,) # No effect nmspc['pi'] = 3.14159 # No effect super(MetaNewVSInit, cls).__init__(name, bases, nmspc) # These do work because they operate on the class object: cls.__name__ += '_z' cls.__bases__ += (Tag3,) cls.e = 2.718 class Test(object): __metaclass__ = MetaNewVSInit def __init__(self): print('Test.__init__') def foo(self): print('foo still here') def bar(self): print('bar still here') t = Test() print('class name: ' + Test.__name__) print('base classes: ', [c.__name__ for c in Test.__bases__]) print([m for m in dir(t) if not m.startswith("__")]) t.bar() print(t.e) """ Output: MetaNewVSInit.__new__ <class '__main__.MetaNewVSInit'> 'Test' (<type 'object'>,) {'__init__': <function __init__ at 0x7ecf0>, '__metaclass__': <class '__main__.MetaNewVSInit'>, '__module__': '__main__', 'bar': <function bar at 0x7ed70>, 'foo': <function foo at 0x7ed30>} MetaBase.__new__ MetaNewVSInit.__init__ <class '__main__.Test_x'> 'Test' (<type 'object'>,) {'__init__': <function __init__ at 0x7ecf0>, '__metaclass__': <class '__main__.MetaNewVSInit'>, '__module__': '__main__', 'bar': <function bar at 0x7ed70>, 'baz': 42} MetaBase.__init__ Test.__init__ class name: Test_x_z ('base classes: ', ['object', 'Tag1', 'Tag3']) ['bar', 'baz', 'e', 'tag3_method'] bar still here 2.718 """
The primary difference is that when overriding __new__() you can change things like the ‘name’, ‘bases’ and ‘namespace’ arguments before you call the super constructor and it will have an effect, but doing the same thing in __init__() you won’t get any results from the constructor call.
One special case in __new__() is that you can manipulate things like __slots__, but in __init__() you can’t.
Note that, since the base-class version of __init__() doesn’t make any modifications, it makes sense to call it first, then perform any additional operations. In C++ and Java, the base-class constructor must be called as the first operation in a derived-class constructor, which makes sense because derived-class constructions can then build upon base-class foundations.
In many cases, the choice of __new__() vs __init__() is a style issue and doesn’t matter, but because __new__() can do everything and __init__() is slightly more limited, some people just start using __new__() and stick with it. This use can be confusing – I tend to hunt for the reason that __init__() has been chosen, and if I can’t find it wonder whether the author knew what they were doing. I prefer to only use __new__() when it has meaning – when you must in order to change things that only __new__() can change.