使用super().__ init __()混合type()和自定义__init __()

时间:2016-04-27 12:47:33

标签: python python-3.x super

到目前为止我成功完成的任务:

我已经创建了一个elem类来表示html元素(divhtmlspanbody等。)< / 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(),但它也没有效果。

3 个答案:

答案 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,但所有其他实例将依赖该类作为默认值