Skip to content

Commit 0bd8c47

Browse files
patstewtonyroberts
authored andcommitted
Use the c# dynamic functionality for python objects.
This means that python functions can be called directly, members can be accessed as normal (a.b) and mathematical operations work, and run in python (a = b*c).
1 parent e6eac73 commit 0bd8c47

File tree

1 file changed

+220
-4
lines changed

1 file changed

+220
-4
lines changed

pythonnet/src/runtime/pyobject.cs

Lines changed: 220 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// ==========================================================================
99

1010
using System;
11+
using System.Dynamic;
12+
using System.Linq.Expressions;
1113

1214
namespace Python.Runtime {
1315

@@ -17,7 +19,7 @@ namespace Python.Runtime {
1719
/// http://www.python.org/doc/current/api/object.html for details.
1820
/// </summary>
1921

20-
public class PyObject : IDisposable {
22+
public class PyObject : DynamicObject, IDisposable {
2123

2224
protected internal IntPtr obj = IntPtr.Zero;
2325
private bool disposed = false;
@@ -95,7 +97,7 @@ public static PyObject FromManagedObject(object ob) {
9597

9698
public object AsManagedObject(Type t) {
9799
Object result;
98-
if (!Converter.ToManaged(this.Handle, t, out result, false)) {
100+
if (!Converter.ToManaged(this.obj, t, out result, false)) {
99101
throw new InvalidCastException("cannot convert object to target type");
100102
}
101103
return result;
@@ -609,7 +611,7 @@ public PyObject Invoke(PyTuple args) {
609611

610612
public PyObject Invoke(PyObject[] args, PyDict kw) {
611613
PyTuple t = new PyTuple(args);
612-
IntPtr r = Runtime.PyObject_Call(obj, t.obj, kw.obj);
614+
IntPtr r = Runtime.PyObject_Call(obj, t.obj, kw != null ? kw.obj : IntPtr.Zero);
613615
t.Dispose();
614616
if (r == IntPtr.Zero) {
615617
throw new PythonException();
@@ -628,7 +630,7 @@ public PyObject Invoke(PyObject[] args, PyDict kw) {
628630
/// </remarks>
629631

630632
public PyObject Invoke(PyTuple args, PyDict kw) {
631-
IntPtr r = Runtime.PyObject_Call(obj, args.obj, kw.obj);
633+
IntPtr r = Runtime.PyObject_Call(obj, args.obj, kw != null ? kw.obj : IntPtr.Zero);
632634
if (r == IntPtr.Zero) {
633635
throw new PythonException();
634636
}
@@ -862,8 +864,222 @@ public override int GetHashCode() {
862864
return Runtime.PyObject_Hash(obj).ToInt32();
863865
}
864866

867+
public override bool TryGetMember(GetMemberBinder binder, out object result)
868+
{
869+
if (this.HasAttr(binder.Name))
870+
{
871+
result = this.GetAttr(binder.Name);
872+
return true;
873+
}
874+
else
875+
return base.TryGetMember(binder, out result);
876+
}
877+
878+
public override bool TrySetMember(SetMemberBinder binder, object value)
879+
{
880+
if (this.HasAttr(binder.Name))
881+
{
882+
this.SetAttr(binder.Name, (PyObject)value);
883+
return true;
884+
}
885+
else
886+
return base.TrySetMember(binder, value);
887+
}
888+
889+
private void GetArgs(object[] inargs, out PyTuple args, out PyDict kwargs)
890+
{
891+
int arg_count;
892+
for (arg_count = 0; arg_count < inargs.Length && !(inargs[arg_count] is Py.KeywordArguments); ++arg_count);
893+
IntPtr argtuple = Runtime.PyTuple_New(arg_count);
894+
for (int i = 0; i < arg_count; i++)
895+
{
896+
IntPtr ptr;
897+
if (inargs[i] is PyObject)
898+
{
899+
ptr = ((PyObject)inargs[i]).Handle;
900+
Runtime.Incref(ptr);
901+
}
902+
else
903+
{
904+
ptr = Converter.ToPython(inargs[i], inargs[i].GetType());
905+
}
906+
if (Runtime.PyTuple_SetItem(argtuple, i, ptr) < 0)
907+
throw new PythonException();
908+
}
909+
args = new PyTuple(argtuple);
910+
kwargs = null;
911+
for (int i = arg_count; i < inargs.Length; i++)
912+
{
913+
if (!(inargs[i] is Py.KeywordArguments))
914+
throw new ArgumentException("Keyword arguments must come after normal arguments.");
915+
if (kwargs == null)
916+
kwargs = (Py.KeywordArguments)inargs[i];
917+
else
918+
kwargs.Update((Py.KeywordArguments)inargs[i]);
919+
}
920+
}
865921

922+
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
923+
{
924+
if (this.HasAttr(binder.Name) && this.GetAttr(binder.Name).IsCallable())
925+
{
926+
PyTuple pyargs;
927+
PyDict kwargs;
928+
GetArgs(args, out pyargs, out kwargs);
929+
result = InvokeMethod(binder.Name, pyargs, kwargs);
930+
return true;
931+
}
932+
else
933+
return base.TryInvokeMember(binder, args, out result);
866934
}
867935

936+
public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
937+
{
938+
if (this.IsCallable())
939+
{
940+
PyTuple pyargs;
941+
PyDict kwargs;
942+
GetArgs(args, out pyargs, out kwargs);
943+
result = Invoke(pyargs, kwargs);
944+
return true;
945+
}
946+
else
947+
return base.TryInvoke(binder, args, out result);
948+
}
949+
950+
public override bool TryConvert(ConvertBinder binder, out object result)
951+
{
952+
return Converter.ToManaged(this.obj, binder.Type, out result, false);
953+
}
868954

955+
public override bool TryBinaryOperation(BinaryOperationBinder binder, Object arg, out Object result) {
956+
IntPtr res;
957+
if (!(arg is PyObject))
958+
arg = arg.ToPython();
959+
960+
switch (binder.Operation)
961+
{
962+
case ExpressionType.Add:
963+
res = Runtime.PyNumber_Add(this.obj, ((PyObject)arg).obj);
964+
break;
965+
case ExpressionType.AddAssign:
966+
res = Runtime.PyNumber_InPlaceAdd(this.obj, ((PyObject)arg).obj);
967+
break;
968+
case ExpressionType.Subtract:
969+
res = Runtime.PyNumber_Subtract(this.obj, ((PyObject)arg).obj);
970+
break;
971+
case ExpressionType.SubtractAssign:
972+
res = Runtime.PyNumber_InPlaceSubtract(this.obj, ((PyObject)arg).obj);
973+
break;
974+
case ExpressionType.Multiply:
975+
res = Runtime.PyNumber_Multiply(this.obj, ((PyObject)arg).obj);
976+
break;
977+
case ExpressionType.MultiplyAssign:
978+
res = Runtime.PyNumber_InPlaceMultiply(this.obj, ((PyObject)arg).obj);
979+
break;
980+
case ExpressionType.Divide:
981+
res = Runtime.PyNumber_Divide(this.obj, ((PyObject)arg).obj);
982+
break;
983+
case ExpressionType.DivideAssign:
984+
res = Runtime.PyNumber_InPlaceDivide(this.obj, ((PyObject)arg).obj);
985+
break;
986+
case ExpressionType.And:
987+
res = Runtime.PyNumber_And(this.obj, ((PyObject)arg).obj);
988+
break;
989+
case ExpressionType.AndAssign:
990+
res = Runtime.PyNumber_InPlaceAnd(this.obj, ((PyObject)arg).obj);
991+
break;
992+
case ExpressionType.ExclusiveOr:
993+
res = Runtime.PyNumber_Xor(this.obj, ((PyObject)arg).obj);
994+
break;
995+
case ExpressionType.ExclusiveOrAssign:
996+
res = Runtime.PyNumber_InPlaceXor(this.obj, ((PyObject)arg).obj);
997+
break;
998+
case ExpressionType.GreaterThan:
999+
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) > 0;
1000+
return true;
1001+
case ExpressionType.GreaterThanOrEqual:
1002+
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) >= 0;
1003+
return true;
1004+
case ExpressionType.LeftShift:
1005+
res = Runtime.PyNumber_Lshift(this.obj, ((PyObject)arg).obj);
1006+
break;
1007+
case ExpressionType.LeftShiftAssign:
1008+
res = Runtime.PyNumber_InPlaceLshift(this.obj, ((PyObject)arg).obj);
1009+
break;
1010+
case ExpressionType.LessThan:
1011+
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) < 0;
1012+
return true;
1013+
case ExpressionType.LessThanOrEqual:
1014+
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) <= 0;
1015+
return true;
1016+
case ExpressionType.Modulo:
1017+
res = Runtime.PyNumber_Remainder(this.obj, ((PyObject)arg).obj);
1018+
break;
1019+
case ExpressionType.ModuloAssign:
1020+
res = Runtime.PyNumber_InPlaceRemainder(this.obj, ((PyObject)arg).obj);
1021+
break;
1022+
case ExpressionType.NotEqual:
1023+
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) != 0;
1024+
return true;
1025+
case ExpressionType.Or:
1026+
res = Runtime.PyNumber_Or(this.obj, ((PyObject)arg).obj);
1027+
break;
1028+
case ExpressionType.OrAssign:
1029+
res = Runtime.PyNumber_InPlaceOr(this.obj, ((PyObject)arg).obj);
1030+
break;
1031+
case ExpressionType.Power:
1032+
res = Runtime.PyNumber_Power(this.obj, ((PyObject)arg).obj);
1033+
break;
1034+
case ExpressionType.RightShift:
1035+
res = Runtime.PyNumber_Rshift(this.obj, ((PyObject)arg).obj);
1036+
break;
1037+
case ExpressionType.RightShiftAssign:
1038+
res = Runtime.PyNumber_InPlaceRshift(this.obj, ((PyObject)arg).obj);
1039+
break;
1040+
default:
1041+
result = null;
1042+
return false;
1043+
}
1044+
result = new PyObject(res);
1045+
return true;
1046+
}
1047+
1048+
public override bool TryUnaryOperation(UnaryOperationBinder binder, out Object result)
1049+
{
1050+
int r;
1051+
IntPtr res;
1052+
switch (binder.Operation)
1053+
{
1054+
case ExpressionType.Negate:
1055+
res = Runtime.PyNumber_Negative(this.obj);
1056+
break;
1057+
case ExpressionType.UnaryPlus:
1058+
res = Runtime.PyNumber_Positive(this.obj);
1059+
break;
1060+
case ExpressionType.OnesComplement:
1061+
res = Runtime.PyNumber_Invert(this.obj);
1062+
break;
1063+
case ExpressionType.Not:
1064+
r = Runtime.PyObject_Not(this.obj);
1065+
result = r == 1;
1066+
return r != -1;
1067+
case ExpressionType.IsFalse:
1068+
r = Runtime.PyObject_IsTrue(this.obj);
1069+
result = r == 0;
1070+
return r != -1;
1071+
case ExpressionType.IsTrue:
1072+
r = Runtime.PyObject_IsTrue(this.obj);
1073+
result = r == 1;
1074+
return r != -1;
1075+
case ExpressionType.Decrement:
1076+
case ExpressionType.Increment:
1077+
default:
1078+
result = null;
1079+
return false;
1080+
}
1081+
result = new PyObject(res);
1082+
return true;
1083+
}
1084+
}
8691085
}

0 commit comments

Comments
 (0)