Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit recursion in reprs to avoid stack overflow for circular references #396

Closed
MarcSkovMadsen opened this issue Apr 15, 2020 · 16 comments
Closed
Labels
component: "serialization" pickle, json, script_repr, etc etc - help me name this :) type-bug Bug report
Milestone

Comments

@MarcSkovMadsen
Copy link
Collaborator

System Info

  • param 1.9.3
  • pytest 5.3.1
  • Windows 8.1

My Pain

I'm developing a component based on Param and Panel and would like to test it with pytest. During development my tests stopped working/ running but without errors. After some debugging I discovered it was due to a stackoverflow that pytest did not report. It just stopped.

I've developed a smaller code example below. I've also included the stack trace.

Stack Trace

$ python 'test_issue_multple_param_action.py'
Fatal Python error: Cannot recover from stack overflow.

Current thread 0x00001be8 (most recent call first):
  File "C:\Users\masma\source\repos\orsteddcsmarketsanalytics\api\.venv\lib\site-packages\param\parameterized.py", line 1535 in get_param_values
  File "C:\Users\masma\source\repos\orsteddcsmarketsanalytics\api\.venv\lib\site-packages\param\parameterized.py", line 2408 in __repr__
  File "C:\Users\masma\source\repos\orsteddcsmarketsanalytics\api\.venv\lib\site-packages\param\parameterized.py", line 2408 in <listcomp>

..........

  File "C:\Users\masma\source\repos\orsteddcsmarketsanalytics\api\.venv\lib\site-packages\param\parameterized.py", line 2408 in <listcomp>
  File "C:\Users\masma\source\repos\orsteddcsmarketsanalytics\api\.venv\lib\site-packages\param\parameterized.py", line 2408 in __repr__
  ...
(.venv)

Code

import param

class InputDataComponent(param.Parameterized):
    update_data = param.Action()
    download_data = param.Action()

    def __init__(self, **params):
        super().__init__(**params)

        self.update_data = self._update_data
        self.download_data = self._download_data

    def _update_data(self, event):
        raise NotImplementedError()

    def _download_data(self, event):
        raise NotImplementedError()

repr(InputDataComponent())
@MarcSkovMadsen
Copy link
Collaborator Author

Additional Context

It's no problem with only one param.Action

$ python 'test_issue_multple_param_action.py'
(.venv)

Code

import param

class InputDataComponent(param.Parameterized):
    update_data = param.Action()
    # download_data = param.Action()

    def __init__(self, **params):
        super().__init__(**params)

        self.update_data = self._update_data
        # self.download_data = self._download_data

    def _update_data(self, event):
        raise NotImplementedError()

    def _download_data(self, event):
        raise NotImplementedError()

repr(InputDataComponent())

@MarcSkovMadsen
Copy link
Collaborator Author

Workaround

Setting the __repr__ to something specific works around the problem.

Thanks to @jbednar

$ python 'test_issue_multple_param_action.py'
(.venv)
import param

class InputDataComponent(param.Parameterized):
    update_data = param.Action()
    download_data = param.Action()

    def __init__(self, **params):
        super().__init__(**params)

        self.update_data = self._update_data
        # self.download_data = self._download_data

    def _update_data(self, event):
        raise NotImplementedError()

    def _download_data(self, event):
        raise NotImplementedError()

    def __repr__(self):
        return f"InputDataComponent(name='{self.name}')"

repr(InputDataComponent())

@ceball ceball added the component: "serialization" pickle, json, script_repr, etc etc - help me name this :) label Apr 15, 2020
@ceball
Copy link
Contributor

ceball commented Apr 15, 2020

xref #209

@ceball
Copy link
Contributor

ceball commented Apr 15, 2020

@MarcSkovMadsen sorry, unrelated question! When I click to open a new issue on a holoviz repo (including param), I get the view below, and then the issue is created with labels like "triage" and "bug" (depending which option I pick):

iss

Meanwhile, your issue is somehow without that stuff.

We've been trying to improve all this stuff across holoviz, having org-wide templates and so forth - but seems like we haven't done something right somewhere...

@ceball ceball added the type-bug Bug report label Apr 15, 2020
@jbednar jbednar changed the title Stackoverflow when using repr on Parameterized class with two param.Action parameters Limit recursion in reprs to avoid stack overflow for circular references Apr 23, 2020
@jbednar
Copy link
Member

jbednar commented Apr 23, 2020

Ok, this issue should now be about a request to add proper limits on the levels of recursion to which we expand Parameterized objects in reprs.

I vote for two levels, so that printing a Parameterized prints its parameter values (which may themselves be Parameters), but does not print the parameter values of the Parameterized subobjects: Component1(a=Component2(...), b=Component2(...)).

Three levels is a bit more informative but to me seems likely to be more overwhelming than useful (though it's not a strong preference for 2): Component1(a=Component2(x=Component3(...)), b=Component2(x=Component3(...))).

Beyond that seems like overkill to me...

@philippjfr
Copy link
Member

The real problem seems to be infinite recursion, i.e. two Parameterizeds that are nested and reference each other. Ideally we should make sure that a Parameterized should not appear more then once in the repr and indicate the recursion through some abbreviation. Not saying we shouldn't also limit the depth of the repr but it doesn't seem like the real root of the issue.

@jbednar
Copy link
Member

jbednar commented Apr 23, 2020

Sure, the serious problem is infinite recursion, but it seems like we can easily avoid it rather than solving it. Solving it would seem to require keeping a list of id values that have been presented so far and ensuring that we never print it the second time. That seems like a lot more work than simply always limiting how deep we display, which shouldn't require any tracking of ids or other special mechanisms. E.g. for two-level reprs we'd simply iterate through our parameters and call repr with a value saying not to show sub-parameters, which I think would be trivial to implement and has the advantage of simplifying our reprs in general (unless people find that representation impoverished).

@philippjfr
Copy link
Member

philippjfr commented Apr 23, 2020

. That seems like a lot more work than simply always limiting how deep we display, which shouldn't require any tracking of ids or other special mechanisms. E.g. for two-level reprs we'd simply iterate through our parameters and call repr with a value saying not to show sub-parameters

Doesn't seem at all straightforward to me, the simple case of a Parameter that holds a Parameterized is of course, but even in this example that wasn't the issue. Here the param.Action points to a method on the class, so now we have to inspect methods as well to be able to pass the depth through. However then we still need to handle parameterizeds that are nested inside some container, i.e. list or dictionary. I think only a global variable which stores the current repr depth will work, which also isn't the nicest.

@jbednar
Copy link
Member

jbednar commented Apr 24, 2020

Good point. Yes, that's annoying.

@jbednar
Copy link
Member

jbednar commented Apr 24, 2020

It looks like the repr in this case should be:

InputDataComponent(download_data=<bound method 
    InputDataComponent._download_data of ...>, name='InputDataComponent00001', 
    update_data=<bound method InputDataComponent._update_data of ...>)"

(where ... refers to itself). I don't suppose there's any way to control the repr of a bound method. I guess Parameterized's repr could check for a value that's a bound method of self, but that wouldn't catch a reference to something that points back to this object.

@MarcSkovMadsen
Copy link
Collaborator Author

Just for info. I just experienced this again in another use case. I can live with it now that I know the cause and a workaround. But I think it could be annoying for a lot of other users.

@MarcSkovMadsen
Copy link
Collaborator Author

Justed wasted 30 minutes on debugging strange stack overflow when testing with Pytest before I released it was due to this again.

I get no indication that this is the error. But happens often when I have two or more param.Action on my param.Parameterized class.

@philippjfr
Copy link
Member

We should try this https://twitter.com/raymondh/status/1276571193710047232?s=19

@MarcSkovMadsen
Copy link
Collaborator Author

MarcSkovMadsen commented Sep 1, 2020

@jlstevens
Copy link
Contributor

This is another reason to drop Python 2 soon (and use that decorator, assuming it works for our purposes).

@jbednar
Copy link
Member

jbednar commented Jul 2, 2021

Fixed in #499.

@jbednar jbednar closed this as completed Jul 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: "serialization" pickle, json, script_repr, etc etc - help me name this :) type-bug Bug report
Projects
None yet
Development

No branches or pull requests

6 participants