Skip to content

Provide hook to implement __repr__ #808

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

Merged
merged 17 commits into from
Oct 18, 2019
Merged

Provide hook to implement __repr__ #808

merged 17 commits into from
Oct 18, 2019

Conversation

koubaa
Copy link
Contributor

@koubaa koubaa commented Jan 20, 2019

Issue: 680

What does this implement/fix? Explain your changes.

This allows C# types to provide __repr__ implementations

Does this close any currently open issues?

680

Any other comments?

Please confirm my expections in the test I added. Inheritance hierarchies in particular were tricky to get right.

Checklist

Check all those that are applicable and complete.

  • Make sure to include one or more tests for your change
  • If an enhancement PR, please create docs and at best an example
  • Add yourself to AUTHORS
  • Updated the CHANGELOG

@codecov
Copy link

codecov bot commented Jan 20, 2019

Codecov Report

Merging #808 into master will decrease coverage by 0.28%.
The diff coverage is 55.26%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #808      +/-   ##
==========================================
- Coverage   76.91%   76.62%   -0.29%     
==========================================
  Files          64       64              
  Lines        5935     5973      +38     
  Branches      976      986      +10     
==========================================
+ Hits         4565     4577      +12     
- Misses       1040     1059      +19     
- Partials      330      337       +7
Flag Coverage Δ
#setup_linux 65.3% <ø> (ø) ⬆️
#setup_windows 75.85% <55.26%> (-0.29%) ⬇️
Impacted Files Coverage Δ
src/runtime/classbase.cs 74.8% <55.17%> (-6.57%) ⬇️
src/runtime/exceptions.cs 79.03% <55.55%> (-1.84%) ⬇️
src/runtime/delegatemanager.cs 81.96% <0%> (-4.92%) ⬇️
src/runtime/pythonexception.cs 76.66% <0%> (-3.34%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 868cd52...82aea1f. Read the comment docs.

@den-run-ai
Copy link
Contributor

@koubaa one error here:

=================================== ERRORS ====================================
___________________ ERROR collecting src/tests/test_repr.py ___________________
ImportError while importing test module 'C:\projects\pythonnet\src\tests\test_repr.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
src\tests\test_repr.py:7: in <module>
    from Python.Test import (ReprTest)
E   ImportError: cannot import name 'ReprTest' from 'Python.Test' (unknown location)
!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!

@koubaa
Copy link
Contributor Author

koubaa commented Jan 21, 2019

@denfromufa How do I run the tests locally? I want to make sure they work before pushing. (I just tested manually in the console and tried to follow the pattern)

@koubaa
Copy link
Contributor Author

koubaa commented Jan 22, 2019

I figured it out (took longer than I'll admit!)

@koubaa
Copy link
Contributor Author

koubaa commented Jan 22, 2019

One test fails because the repr of System.OverflowException went from
OverflowException('Simple message..."
to
<System.OverflowException object at 0xADDRESS>

I'll need to figure out how the old behavior was working to see why its broken now.

@den-run-ai
Copy link
Contributor

@koubaa were you able to run the tests locally? If not, then I can search if we have instructions somewhere.

@koubaa
Copy link
Contributor Author

koubaa commented Jan 23, 2019

@denfromufa yes I can run locally now. I have vs2017 installed and ran this to build:

python setup.py install --xplat

then this to test:

python -m pytest

Should this be in the README?

@den-run-ai
Copy link
Contributor

den-run-ai commented Jan 23, 2019 via email

@koubaa
Copy link
Contributor Author

koubaa commented Jan 23, 2019

@denfromufa sounds good. I didn't see any embedded test fails in previous CI runs so I'm OK running these partial tests for now.

I tried to find where OverflowException repr is coming from (on master) and actually no breakpoints in tp_repr defined were hit. So I really don't know (1) how else to look for it and (2) how I could test that its an exception in classbase tp_repr and fallback to python defaults.

@koubaa
Copy link
Contributor Author

koubaa commented Feb 4, 2019

@denfromufa I am back from a vacation so I looked at this again. I found this change which removed tp_repr from ExceptionClassObject. After some trial and error I discovered that __repr__() works on master because of what's done in SetArgsAndCause and the fact that a __repr__() was not defined. Now that ClassBase implements tp_repr, the CPython runtime will not fall back to the args/cause which were set.

My plan is to re-implement repr in ExceptionClassObject as was done before that change. Please let me know if you have any concerns with this approach.

@koubaa koubaa force-pushed the repr branch 2 times, most recently from 859f80b to 8f6bdce Compare February 5, 2019 01:30
@koubaa
Copy link
Contributor Author

koubaa commented Feb 5, 2019

This is only partial tests, there are also embedding tests run through nunit. Documentation improvements are welcome, probably this one should go into wiki.

I just edited the wiki with what I know about so far.

@koubaa
Copy link
Contributor Author

koubaa commented Feb 5, 2019

python tests now pass in CI. Embedded tests pass locally (on windows) but not in CI, so I'm adding some logging statements to see where.

@filmor filmor added this to the 2.4.0 milestone Feb 5, 2019
Copy link
Member

@filmor filmor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks already for your contribution. I have to think a bit about whether the "new" __str__ behaviour is how I would expect things to work.

@koubaa
Copy link
Contributor Author

koubaa commented Feb 11, 2019

I'm having a lot of trouble with these CI tests.

  • The latest Travis job is not using my latest changes. Appveyor at least reports the correct SHA1 but have the below errors.
  • Appveyor 2.7 x64 xplat hangs for one hour when about to run embedded tests.
  • Appveyor 3.4 x86 xplat fails in pip install
  • Appveyor 3.4 x64 xplat also fails in pip install
  • Appveyor 2.7 x86 seems to have blown up because of the print statements I added. Is there a reason why Console.WriteLine is crashing (i.e. is stdout unavailable?) and if so what's the preferred way to add this sort of debugging logging statements?
  • Appveyor 2.7 x64 complains of a test failure but I cannot find much more details.
  • Appveyor 3.4 x86 also fails in setuptools
  • Appveyor 3.4 x64 also fails in setuptools.

Since all of the appveyor py3.4 are failing in setuptools it seems to me that its fallen out of maintenance. The 1 hour hangs concern me most since everything is queued and it delays other tests.

@den-run-ai
Copy link
Contributor

@filmor we can probably make python 3.4 optional in CI tests as a temporary solution until we drop it?

@filmor
Copy link
Member

filmor commented Feb 25, 2019

I'll have a look into this this weekend. Could you please remove the debug output?

@koubaa
Copy link
Contributor Author

koubaa commented Feb 27, 2019

done

@koubaa
Copy link
Contributor Author

koubaa commented Mar 7, 2019

Seems better but its hard to determine what's wrong with x64 2.7. It says python tests failed but no indication which ones.

@filmor
Copy link
Member

filmor commented Mar 8, 2019

From the logs I think that the issue is aimply the casing of the ReprTests.cs. In the repository it's written in camel case, while in the csproj file ot's reprtests.cs. Correct the csproj and it should go through.

@koubaa
Copy link
Contributor Author

koubaa commented Mar 8, 2019

@filmor done. Hopefully this works. Do you agree with the new str behavior()?

@filmor
Copy link
Member

filmor commented Apr 8, 2019

Each dot indicates a successful test, so you can just count ;)

I'll have a look into this as soon as I have finally finished the 2.4.0 release.

@filmor filmor modified the milestones: 2.4.0, 2.4.1 Apr 12, 2019
@filmor filmor added the next label Sep 12, 2019
@codecov-io
Copy link

codecov-io commented Sep 12, 2019

Codecov Report

Merging #808 into master will decrease coverage by 2.65%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #808      +/-   ##
==========================================
- Coverage   86.71%   84.05%   -2.66%     
==========================================
  Files           1        1              
  Lines         301      301              
==========================================
- Hits          261      253       -8     
- Misses         40       48       +8
Flag Coverage Δ
#setup_linux 65.44% <ø> (ø) ⬆️
#setup_windows 65.78% <ø> (-5.65%) ⬇️
Impacted Files Coverage Δ
setup.py 84.05% <0%> (-2.66%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 60e6045...264c78b. Read the comment docs.

@filmor
Copy link
Member

filmor commented Sep 12, 2019

Looks like the problems were magically fixed :)

}

//If the object defines __repr__, call it.
System.Reflection.MethodInfo reprMethodInfo = instType.GetMethod("__repr__");
Copy link
Member

@lostmsu lostmsu Sep 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since all .NET types have ToString, why does it even try __repr__? If somebody really needs it, they can do override string ToString() => __repr__()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not the same, though. ToString is more akin to __str__, there is no real equivalent to __repr__ in .NET.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is the tp_str implementation. tp_repr is below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lostmsu See the comment:
//The return value must be a string object. If a class defines repr() but not str(),
//then repr() is also used when an “informal” string representation of instances of that
//class is required.

This sentence comes straight from the python manual, I didn't make it up

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@koubaa the default inheritance hierarchy for C# classes from Python's point of view is Python's object <- System.Object <- CustomCSharpClass.

Since for all .NET classes overriding ToString is how you implement __str__, I don't see why it should be different for System.Object. Hence I think .NET classes should just always call ToString for tp_str.

Copy link
Contributor Author

@koubaa koubaa Sep 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lostmsu I took a look. I agree some kind of consensus between interface vs reflection for dunder methods should be reached and applied throughout the code base. One argument for reflection is that you can use extension methods to add these. If I take an existing C# codebase and write some extension methods in a separate assembly, I can provide __repr__ and __getattr__ to it. What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@koubaa, extension methods would be hard to find. You'd have to enumerate all types in all assemblies. GetMethods won't return them.

In addition to that, what would be the semantics of multiple assemblies defining an extension method with the same signature?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lostmsu ah, didn't know that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lostmsu I am still not convinced about the interface approach. It would require a hard build dependency on C# libraries onto PythonNet. It seems like a tough sell for some of the mature C# libraries out there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lostmsu I changed this as you requested. I don't think I can capture the repr function in a Func object since this is a static method.

}

//If the object defines __repr__, call it.
System.Reflection.MethodInfo reprMethodInfo = instType.GetMethod("__repr__");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is the tp_str implementation. tp_repr is below

Copy link
Contributor Author

@koubaa koubaa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the problems were magically fixed :)

This is equal parts annoying and relieving

}

//If the object defines __repr__, call it.
System.Reflection.MethodInfo reprMethodInfo = instType.GetMethod("__repr__");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lostmsu See the comment:
//The return value must be a string object. If a class defines repr() but not str(),
//then repr() is also used when an “informal” string representation of instances of that
//class is required.

This sentence comes straight from the python manual, I didn't make it up

@filmor
Copy link
Member

filmor commented Sep 27, 2019

To help you in disagreeing, I propose the another style: We add a new generic abstract class (or interface with default methods when we convert to C#8) PythonWrapper and allow it to be registered for automatic conversion:

using SomeLib;

[Python.Conversion]
public class SomeClassWrapper : PythonWrapper<SomeClass> {
    public string Repr() => $"<SomeClass {this.wrapped.Name}>";
}

Now this means that we would need to link Python.Runtime.dll (or a much smaller DLL that is only used for these markers and the interface) into the wrapper DLL, but this is not an issue in embedding situations and everywhere else we could use the mentioned "smaller", platform-independent DLL.

@lostmsu
Copy link
Member

lostmsu commented Sep 27, 2019

@filmor not sure I understood your proposal.
Can you give a more complete example? What should PythonWrapper<T> do?

@koubaa
Copy link
Contributor Author

koubaa commented Oct 17, 2019

Hey, can this go in yet?

@filmor filmor merged commit 4a9457f into pythonnet:master Oct 18, 2019
@filmor
Copy link
Member

filmor commented Oct 18, 2019

It can, thanks a lot for your contribution :)

@lostmsu lostmsu modified the milestones: 2.4.1, 2.5.0 Apr 23, 2020
Runtime.PyTuple_SetItem(args, 0, ob);
IntPtr reprFunc = Runtime.PyObject_GetAttrString(Runtime.PyBaseObjectType, "__repr__");
var output = Runtime.PyObject_Call(reprFunc, args, IntPtr.Zero);
Runtime.XDecref(args);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decreasing args reference count actually decreases ob reference count, which marks it as garbage for the GC!
After a while GC frees the object which causes the program to crash if the object is used again.
One fix for this can be adding the line:
Runtime.XIncref(ob);
at line 269 (before setting ob to the tuple's first argument).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for catching this. Would you prepare a small PR?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done
1160

AlexCatarino pushed a commit to QuantConnect/pythonnet that referenced this pull request Jun 27, 2020
Provide hook to implement __repr__
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants