'Mypy Function "lxml.etree.ElementTree" is not valid as a type but why?

I am using Mypy for this function

import funcy as fu
from lxml import etree
from lxml.etree import Element, ElementTree

def find_nodes(tree: ElementTree, paths: Iterable[str]) -> Iterable[Element]:
    """Yield all non-zero nodes for the given paths in the tree."""
    return fu.remove(fu.isnone, map(tree.find, paths))

checking this file with mypy (0.942) and lxml-stubs gives me this strange error:

xxx\layout\treeutils.py:9: error: Function "lxml.etree.ElementTree" is not valid as a type
xxx\layout\treeutils.py:9: note: Perhaps you need "Callable[...]" or a callback protocol?

How can I fix that?



Solution 1:[1]

In lxml-stubs, ElementTree is indeed declared as a function. It returns an object of type _ElementTree, which is declared as a class. Something similar is happening for Element and _Element.

Looking into lxml itself, it turns out that this is right: _ElementTree is defined as a Cython class, and ElementTree is defined as a function. I don't know enough about Cython to say why it's implemented this way.

So this works:

from lxml.etree import Element, ElementTree, _Element, _ElementTree

def find_nodes(tree: _ElementTree, paths: Iterable[str]) -> Iterable[_Element]:

I wouldn't worry too much about the underscore here. A single underscore is merely a convention to indicate protected types, and doesn't have a special meaning to the Python interpreter. If lxml chooses to expose such types as return values in its public API, chances are they're not going anywhere in future versions of lxml. Your choice is to use these types and enjoy some type safety, or resort to Any and have no type safety at all.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Thomas