我已经创建了一个elem
类来表示html元素(div
,html
,span
,body
等。)< / p>
我能够像这样派生这个类来为每个元素创建子类:
class elem:
def __init__(self, content="", tag="div", attr={}, tag_type="double"):
"""Builds the element."""
self.tag = tag
self.attr = attr
self.content = content
self.tag_type = tag_type
class head(elem):
"""A head html element."""
def __init__(self, content=None, **kwargs):
super().__init__(tag="head", content=content, **kwargs)
它运作良好。
但我必须为每个子类声明编写这个,如果我想要做每个HTML标记类型,那就非常重复和冗余。
所以我试图通过将相应的标记名称作为字符串参数来创建一个make_elem()
函数来创建我的类。
因此,我只想得到类似的内容,而不是之前的类定义:
head = make_elem_class("head")
此函数应创建一个类。此类中的__init__()
方法应该从它继承的类中调用__init__()
方法。
我尝试制作此make_elem_class()
函数,它看起来像这样:
def make_elem_class(name):
"""Dynamically creates the class with a type() call."""
def init(self, content=None, **kwargs):
super().__init__(tag=name, content=None, **kwargs)
return type(name, (elem,), {"__init__" : init})
但是在运行html = make_elem_class('html')
时,html("html element")
我收到以下错误:
Traceback (most recent call last):
File "elements.py", line 118, in <module>
html("html element")
File "elements.py", line 20, in init
super().__init__(tag=name, content=None, **kwargs)
TypeError: object.__init__() takes no parameters
我猜这与空super()
电话有关,所以我尝试使用super(elem, self)
。但它显然没有更好的效果。
我怎么能实现这个目标?
注意:如果我从"__init__":init
来电中的词典中删除了type()
,它可以正常工作但标签未在我的元素中正确设置。我还尝试直接将{"tag":name}
传递给type()
,但它也没有效果。
答案 0 :(得分:4)
你不能在这里使用super()
的无参数形式,因为这里没有class
语句来提供该函数通常需要的上下文。
或者说,除非你自己提供上下文,否则你不能;您需要在此处将名称__class__
设置为闭包:
def make_elem_class(name):
"""Dynamically creates the class with a type() call."""
def init(self, content=None, **kwargs):
super().__init__(tag=name, content=content, **kwargs)
__class__ = type(name, (elem,), {"__init__" : init})
return __class__
super()
会自动从闭包中获取__class__
值。请注意,我将content
的值,而不是None
传递给elem.__init__
方法;你不会想失去这个价值。
如果您是too magical,请在调用self
时明确命名该课程并super()
;再次,该课程将从闭幕式中取出:
def make_elem_class(name):
"""Dynamically creates the class with a type() call."""
def init(self, content=None, **kwargs):
super(elemcls, self).__init__(tag=name, content=content, **kwargs)
elemcls = type(name, (elem,), {"__init__" : init})
return elemcls
答案 1 :(得分:1)
关于更直接的解决方案,例如推断班级__name__
的标记,该怎么办?
class elem:
def __init__(self, content="", tag=None, attr={}, tag_type="double"):
"""Builds the element."""
self.tag = tag or self.__class__.__name__
...
然后:
class div(elem): pass
class head(elem): "Optional docstring for <head>"
...
有点不那么神奇(有争议),而且更明确一点。 : - )
答案 2 :(得分:0)
我认为这有点像XY问题。因为您已经询问如何在动态创建的类中使用super
,但您真正想要的是为子类设置各种类变量和默认值的简单方法。
由于您不希望同一标记类的所有实例共享相同的标记名称,因此您也可以将其设置为类变量而不是实例变量。例如
from abc import ABC, abstractmethod
class Elem(ABC):
tag_type = "double" # the default tag type
def __init__(self, content="", attr=None, tag_type=None):
"""Builds the element."""
self.attr = attr if attr is not None else {}
self.content = content
if tag_type is not None:
self.tag_type = tag_type
@property
@abstractmethod
def tag(self):
"""All base classes should identify the tag they represent"""
raise TypeError("undefined tag for {}".format(type(self)))
class Head(Elem):
tag = "head"
tag_type = "text"
class Div(Elem):
tag = "div"
h = Head()
d = Div()
h1 = Head(tag_type="int")
assert h.tag == "head"
assert d.tag == "div"
assert h1.tag == "head"
assert h.tag_type == "text"
assert d.tag_type == "double"
assert h1.tag_type == "int"
您现在可以编写非常短的子类,并且仍然显式声明了您的类。您会注意到我将一些默认值更改为None
。对于attr
,这是因为具有可变的默认参数不会按预期工作 - 它的行为更像是一个共享的类变量。相反,默认为None
,如果尚未指定attr
,则为每个实例创建一个新的attr
。第二个(tag_type
)是这样的,如果指定了tag_type
,那么实例将设置它tag_type
,但所有其他实例将依赖该类作为默认值