0

I am writing an HTML generator. The class Element("elemente name") generates elements. It has a self.html() method that returns the complete element as a string:

import json

def dict2json(dictionary):
    return json.dumps(dictionary, sort_keys=False, indent=4, separators=(',', ': '))

class Element():

    def __init__(self, name, attrs={}):
        self.__name = name
        self.ending_tag = True
        self.__attrs = attrs
        self.required_attrs = []
        self.children = []

    @property
    def name(self): 
        return self.__name

    @property
    def attrs(self): 
       return dict2json(self.__attrs)
      
    def attr(self, a): 
        key, val = a.split("=")
        self.__attrs[key] = val

    def append(self, element):
        "Take another Element object and add it as a child to self.children."
        self.children.append(element)

    def html(self):
        "Returns a string of the HTML element."

        if self.children: # generate the HTML string for the children
            nodes = "".join([e.html() for e in self.children])
        else:
            nodes = ""

        if self.__attrs:
            attributes = " " + " ".join(['{}="{}"'.format(k, v) for k, v in self.__attrs.items()])
        else:
            attributes = ""

        if self.ending_tag:
            return "<{}{}>{}</{}>".format(self.__name, attributes, nodes, self.__name)
        else:
            return "<{}{}>".format(self.__name, attributes)

The problem is, when I modify an element's attributes with self.attr(), all child nodes inherit the same value I set for the parent element:

if __name__ == '__main__':

    div = Element("div")
    div.attr("class=printable") # should affect div only, and not its children
    div.append(Element("p")) # div > p
    div.children[0].append(Element("b")) # div > p > b
    print(div.children[0].children[0].html()) # this returns <b class="printable"></b>
    print(div.html()) # this returns <div class="printable"><p class="printable"><b class="printable"></b></p></div>

When I use print(div.html()), I am actually expecting it to return <div class="printable"><p><b></b></p></div>, and not <div class="printable"><p class="printable"><b class="printable"></b></p></div>. Why is that happening?

Also, I am not sure this code will properly handle deeper levels of element ancestors when generating the HTML string for, say, html > body > div > ul > li... etc. I would appreciate tips on this!

EDIT:

Thanks Asocia for pointing out the other post. So basically, default arguments are part of the function object, and the common solution is to use None as the default and then initialize in the function body, creating a new property for each instance:

class Element():

    def __init__(self, name, attrs=None): # don't initialize a dict here
        self.__name = name
        self.ending_tag = True
        self.__attrs = attrs
        self.required_attrs = []
        self.children = []


    def attr(self, a):
        if not self.__attrs:
            self.__attrs = {} # do it here
        key, val = a.split("=")
        self.__attrs[key] = val
EMartins
  • 477
  • 1
  • 5
  • 13
  • It's because all of your `Element`s are sharing the same dictionary which you define as `attrs={}` in the parameters of `__init__`. – Asocia Jan 27 '21 at 13:32
  • This has nothing to do with inheritance. Define a parentless element, say `Element('html')` and you will see it will have the attribute `class=printable`. Because they're sharing the same dictionary as I said. – Asocia Jan 27 '21 at 13:36

0 Answers0