Counting density of Python code
The docstrings are vague. Documentation needs to be precise and complete. It should answer questions like "what arguments should I pass?" and "what does it return?" For example, "go through the AST and count lines" should become something like, "Return the number of nodes in the AST rooted at node, and update the set line_nos with the line numbers of these nodes, plus the line number 0 if any node has no line number."
It's not clear that the "plus the line number 0 if any node has no line number" behaviour is needed. Instead of doing this and then having to subtract one later to compensate, why not avoid adding the line number 0 in the first place?
When traversing an AST, it's often convenient to use the ast.NodeVisitor class, like this:

class DensityVisitor(ast.NodeVisitor):
    """AST visitor that accumulates the count of nodes and the set of line
    numbers in the AST.
    """
    def __init__(self):
        self.node_count = 0
        self.line_numbers = set()

    def visit(self, node):
        self.node_count += 1
        try:
            self.line_numbers.add(node.lineno)
        except AttributeError:
            pass
        self.generic_visit(node)

    @property
    def density(self):
        """The density of code (nodes per line) in the visited AST."""
        return self.node_count / len(self.line_numbers)


and then in main you'd write:

visitor = DensityVisitor()
visitor.visit(ast.parse(source))
print('density (nodes/line) =', round(visitor.density, 4)))


Update: Why would you prefer ast.NodeVisitor over explicitly walking the tree using ast.iter_child_nodes plus recursion? Well, because of separation of concerns — the logic for walking the tree is delegated to the ast.NodeVisitor base class, and you only have to specify what happens at each node. (Another possibility would be to use ast.walk.)