Python: создание экземпляра класса без вызова инициализатора
Есть ли способ избежать вызова __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]
на инициализацию нового объекта срезами?
EDIT:
Как вдохновлено ответом, написанным ниже, инициализатор был отредактирован, чтобы быстро проверить отсутствие аргументов.
def __init__(self, string=None):
if string is None:
self.__string = self.__simple = ()
else:
self.__string = tuple(string.split())
self.__simple = tuple(self.__simple())
Ответы
Ответ 1
Использование метакласса обеспечивает хорошее решение в этом примере. Метакласс имеет ограниченное использование, но отлично работает.
>>> 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',))
>>>
Добавление:
По прошествии шести лет мое мнение одобряет ответ Алекса Мартелли больше, чем мой собственный подход. С мета-классами все еще в уме, следующий ответ показывает, как проблема может быть решена как с ними, так и без них:
#! /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()
Ответ 2
Когда это возможно, желательно, чтобы вызов __init__
вызывался (и делал вызов безобидным по подходящим аргументам). Однако, если это требует слишком большого количества искажений, у вас есть альтернатива, если вы избегаете пагубного выбора использования классов старого стиля (существует нет разумная причина использовать классы старого стиля в новом коде и несколько веских причин не):
class String(object):
...
bare_s = String.__new__(String)
Эта идиома обычно используется в classmethod
, которая предназначена для работы как "альтернативные конструкторы", поэтому вы обычно видите, что она используется такими способами, как...:
@classmethod
def makeit(cls):
self = cls.__new__(cls)
# etc etc, then
return self
(таким образом метод класса будет правильно наследоваться и генерировать экземпляры подкласса при вызове в подклассе, а не в базовом классе).
Ответ 3
Трюк, используемый стандартными программами рассола и копирования, заключается в создании пустого класса, создании экземпляра объекта с его помощью, а затем присвоении этого экземпляра __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__
может иметь некоторые неожиданные побочные эффекты, особенно если вы не знакомы с оригинальным классом, поэтому убедитесь, что знаете, что делаете.
Ответ 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
. Если только для удобства чтения, вы должны изменить это.
Ответ 5
Используйте @staticmethod
в верхней части вашей функции.
Например:
class TestClass:
def __init__(self, a):
print(1)
self.arg = a
@staticmethod
def test(name):
print(name)
TestClass.test('God')