Skip to content

Give types with an implicit numeric conversion Python's number protocol - #151

Open
jhonabreul wants to merge 4 commits into
QuantConnect:masterfrom
jhonabreul:feature-implicit-numeric-operators
Open

jhonabreul wants to merge 4 commits into
QuantConnect:masterfrom
jhonabreul:feature-implicit-numeric-operators

Conversation

@jhonabreul

Copy link
Copy Markdown
Collaborator

What does this implement/fix? Explain your changes.

Stacked on #150; the last two commits are this change.

A C# type with an implicit conversion to a number is usable as a number in C#, but not in Python:

unsupported operand type(s) for +: 'OrderFee' and 'OrderFee'
float() argument must be a string or a real number, not 'OrderFee'

Cause: Python evaluates a + b through type(a).__add__, pythonnet wires those slots from C# operator methods only, and an op_Implicit conversion is never consulted by an operator expression.

The change:

  • ImplicitNumericOps<T> is an [Ops] class like EnumOps<T>: arithmetic, comparison, unary negation, __float__ and __int__ methods delegating to the type's implicit numeric conversion.
  • ClassManager.GetClassInfo grafts those methods onto any type whose public static op_Implicit returns a numeric type, skipping operator names the type declares itself.
  • OperatorMethod.GetOperatorCandidates includes them so FixupSlots wires the number slots, and a new __float__ slot definition maps to nb_float.
  • Reverse comparisons are not synthesized; Python reflects 1 < x onto x > 1 by itself.

Does this close any currently open issues?

No.

Any other comments?

Tests, in TestImplicitNumericOperators:

  • Arithmetic between two convertible instances and against Python numbers on both sides, plus unary negation.
  • sum, max and sorted over convertible instances.
  • Forward and reflected comparisons against instances and numbers.
  • float() on a decimal-backed type, float() and int() on a double-backed type.
  • A type declaring its own + keeps it, while the undeclared - is synthesized.
  • A type without a conversion gets no __add__ or __float__.
  • Full embedding suite: 1009 passed, 0 failed.

… the instance

C# extension methods were only reachable from Python through their static
class, so instance-style calls such as order_status.is_open() failed with
AttributeError. Extension methods declared in non-framework assemblies are
now indexed during the assembly scan and bound as members of the type they
extend, including interface targets on implementing classes. The binder
passes the instance as the first argument when an extension method is called
through it. Real members with the same name win, and open generic targets are
skipped since the binder cannot infer them from the instance.
Python operators never consult C# implicit conversions, so a type convertible
to decimal or double still raised TypeError on addition, comparison and
float(). Such types now get arithmetic, comparison, __float__ and __int__
methods delegating to the conversion, grafted at class binding the same way
EnumOps is for enums. Operators the type declares itself are kept.

This branch has not been deployed

No deployments
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.

1 participant