lark-parser / lark

Lark is a parsing toolkit for Python, built with a focus on ergonomics, performance and modularity.
MIT License
4.88k stars 414 forks source link

partials may not work in Transformers #398

Closed night199uk closed 5 years ago

night199uk commented 5 years ago

The simple test case below generates this for me:

AttributeError: 'functools.partial' object has no attribute '__func__'

From what I can see partial objects have no func in Py2 or Py3? Should it be f.func?

import functools

from lark import Lark
from lark.visitors import Transformer, v_args

def test(x):
    print(x)

@v_args(inline=True)
class T(Transformer):
    start = functools.partial(test)

parser = Lark(r"""
        start: "test"
        """,    parser="earley", transformer=T())

if __name__ == '__main__':
    sample_conf = """test"""
    r = parser.parse(sample_conf)
MegaIng commented 5 years ago

Yes, you are correct, but the fix you propose is not really helping. You can not use partial in this context (At least not with positional arguments)

import functools

from lark import Lark
from lark.visitors import Transformer, v_args

def test(self, x):
    print(self, x)

@v_args(inline=True)
class T(Transformer):
    start = functools.partial(test, "test")

parser = Lark(r"""
        start: "test"
        """, parser="lalr", transformer=T())

if __name__ == '__main__':
    sample_conf = """test"""
    r = parser.parse(sample_conf)

With this code, the following outputs for changes in utils.py occur:

return create_decorator(f.func, True)

TypeError: test() missing 1 required positional argument: 'x'

Makes sense. We now completely discard the partial and only use the underlying function.

return create_decorator(f, True)

test <__main__.T object at 0x00000181841E6940>

The seems promising! But wait... if you remember what we are printing: print(self, x) you can see that x contains the reference to the Transformer object. Also makes sense: partial puts all arguments in front of all arguments passed afterwards. The only thing I found that works coorectly is if partial only uses keyword arguments:

@v_args(inline=True)
class T(Transformer):
    start = functools.partial(test, x="test")

This prints the expected output (with the last change in utils.py):

<__main__.T object at 0x0000020A6C246908> test

I am not sure what the best course of action is. One could:

erezsh commented 5 years ago

My solution was to simply manually removed the self argument, as it doesn't make sense in partials

The following test works now:

        tree = Tree("start", [Tree("a", ["test1"]), Tree("b", ["test2"])])

        def test(prefix, s, postfix):
            return prefix + s.upper() + postfix

        @v_args(inline=True)
        class T(Transformer):
            a = functools.partial(test, "@", postfix="!")
            b = functools.partial(lambda s: s + "!")

        res = T().transform(tree)
        assert res.children == ["@TEST1!", "test2!"]
night199uk commented 5 years ago

lgtm!