Вот версия моего ответа для декоратора, которая работает с любым классом.Декоратор возвращает фабричную функцию, которая возвращает подкласс исходного класса с требуемыми атрибутами.Приятной особенностью этого подхода является то, что он не требует метакласса, поэтому вы можете использовать метакласс (например, ABCMeta
), если это необходимо, без конфликтов.
Также обратите внимание, что если базовый класс использует метакласс, этот метакласс будет использоваться для создания экземпляра сгенерированного подкласса.Вы можете, если хотите, жестко закодировать нужный метакласс или, вы знаете, написать декоратор, который превращает метакласс в декоратор для шаблонных классов ... это декораторы до конца!
Если этосуществует, метод класса __classinit__()
передает аргументы, передаваемые фабрике, поэтому сам класс может иметь код для проверки аргументов и установки своих атрибутов.(Это будет вызвано после метакласса __init__()
.) Если __classinit__()
возвращает класс, этот класс возвращается фабрикой вместо сгенерированного, так что вы даже можете расширить процедуру генерации таким способом (например, для типапроверенный класс списка, вы можете вернуть один из двух внутренних классов в зависимости от того, следует ли привести элементы к типу элемента или нет).
Если __classinit__()
не существует, аргументы, передаваемые фабрике,просто установите в качестве атрибутов класса для нового класса.
Для удобства создания контейнерных классов с ограниченным типом я обрабатывал тип элемента отдельно от dict функции.Если оно не будет передано, оно будет проигнорировано.
Как и прежде, классы, сгенерированные фабрикой, кэшируются, так что каждый раз, когда вы вызываете класс с одинаковыми функциями, вы получаете один и тот же экземпляр объекта класса.
def template_class(cls, classcache={}):
def factory(element_type=None, **features):
key = (cls, element_type) + tuple(features.items())
if key in classcache:
return classcache[key]
newname = cls.__name__
if element_type or features:
newname += "("
if element_type:
newname += element_type.__name__
if features:
newname += ", "
newname += ", ".join(key + "=" + repr(value)
for key, value in features.items())
newname += ")"
newclass = type(cls)(newname, (cls,), {})
if hasattr(newclass, "__classinit__"):
classinit = getattr(cls.__classinit__, "im_func", cls.__classinit__)
newclass = classinit(newclass, element_type, features) or newclass
else:
if element_type:
newclass.element_type = element_type
for key, value in features.items():
setattr(newclass, key, value)
classcache[key] = newclass
return newclass
factory.__name__ = cls.__name__
return factory
Пример списка классов с ограничением типов (на самом деле, с преобразованием типов):
@template_class
class ListOf(list):
def __classinit__(cls, element_type, features):
if isinstance(element_type, type):
cls.element_type = element_type
else:
raise TypeError("need element type")
def __init__(self, iterable):
for item in iterable:
try:
self.append(self.element_type(item))
except ValueError:
raise TypeError("value '%s' not convertible to %s"
% (item, self.element_type.__name__))
# etc., to provide type conversion for items added to list
Создание новых классов:
Floatlist = ListOf(float)
Intlist = ListOf(int)
Затем создать экземпляр:
print FloatList((1, 2, 3)) # 1.0, 2.0, 3.0
print IntList((1.0, 2.5, 3.14)) # 1, 2, 3
Или просто создайте класс и создайте его экземпляр за один шаг:
print ListOf(float)((1, 2, 3))
print ListOf(int)((1.0, 2.5, 3.14))