Question

My project involves the translation of Python 2.7 code to Dart code. In order to fully emulate all the features of Python data types I have created wrapper classes in Dart which extend the functionality of the primitive Dart data types to match the corresponding Python type. There are wrappers for all types such as $PyNum for numbers, $PyString for strings and so on. All is fine and the translated code works fine. For code is like:

def fib(n):
    if n <= 2:
        return 1
    else:
        return fib (n - 1) + fib (n - 2)

print (fib(36))

The corresponding generated Dart code is:

import 'lib/inbuilts.dart';
import 'dart:io';

fib(n) {
    if (n <= new $PyNum(2)) {
        return new $PyNum(1);
    } else {
        return (fib((n - new $PyNum(1))) + fib((n - new $PyNum(2))));
    }
}

main() {
    stdout.writeln(fib(new $PyNum(36)));
}

The code works fine but in codes like this where there is extreme recursions, the excessive number of wrapper objects created at each function instance is taking a severe toll on the running time of the code. For example the unwrapped Dart code:

import'dart:io';

fib(n) {
    if (n <= 2) {
        return 1;
    } else {
        return (fib((n - 1)) + fib((n - 2)));
    }
}

main() {
    stdout.writeln(fib(36));
}

This runs almost 15x faster than the wrapped code for obvious reasons. All calculations involving wrapped data types return a new instance of that Class. Its absolutely critical for me to emulate all features that Python provides in its data types via Dart and wrapping is the only thing that comes to my mind at the moment. I tried using singleton classes to make a common object for calculations but fails in recursive and threaded situations.

My $PyNum wrapper class is like this:

class $PyNum {
    num _value;

    $PyNum(value) {
        switch ($getType(value)) {
            case 6:
                _value = value;
                break;
            case 7:
                try {
                    _value = num.parse(value);
                } catch (ex) {
                    print("Invalid string literal for num parsing");
                    exit(1);
                }
                break;
            case 5:
                _value = value.value();
                break;
            default:
                throw "Invalid input for num conversion";
        }
    }

    value() => _value;
    toString() => _value.toString();

    operator +(other) => new $PyNum(_value + other.value());
    operator -(other) => new $PyNum(_value - other.value());
    operator *(other) => new $PyNum(_value * other.value());
    operator ~/(other) => new $PyNum(_value ~/ other.value());
    operator |(other) => new $PyNum(_value | other.value());
    operator &(other) => new $PyNum(_value & other.value());
    operator ^(other) => new $PyNum(_value ^ other.value());
    operator %(other) => new $PyNum(_value % other.value());
    operator <<(other) => new $PyNum(_value << other.value());
    operator >>(other) => new $PyNum(_value >> other.value());
    operator ==(other) {
        switch ($getType(other)) {
            case 6:
                return _value == other;
            case 5:
                return _value == other.value();
            default:
                return false;
        }
    }
    operator <(other) {
        switch ($getType(other)) {
            case 6:
                return _value < other;
            case 5:
                return _value < other.value();
            default:
                return true;
        }
    }
    operator >(other) => !(this < other) && (this != other);
    operator <=(other) => (this < other) || (this == other);
    operator >=(other) => (this > other) || (this == other);
}

$getType(variable) {
    if (variable is bool)
        return 0;
    else if (variable is $PyBool)
        return 1;
    else if (variable is $PyDict)
        return 2;
    else if (variable is $PyList)
        return 3;
    else if (variable is List)
        return 4;
    else if (variable is $PyNum)
        return 5;
    else if (variable is num)
        return 6;
    else if (variable is $PyString)
        return 7;
    else if (variable is $PyTuple)
        return 8;
    else
        return -1;
}

can const objects be made out off this class? I am not very sure how to do it exactly.

Is there any other way to efficiently do this and still be able to emulate all of Python's features? Any help is much appreciated!

Was it helpful?

Solution

I have a similar situation where I need to connect additional information with basic data types like String, int, double, ...
I didn't find a solution other than wrapping them.

What you can try is to optimize these wrapper classes by

  • making them const (with a const constructor
  • making the fields final where possible
  • others might be possible

EDIT
- You definitely want to get rid of all those switch statements.

  1. 0.134285 sec: without wrappers
  2. 0.645971 sec: with simplified constructor, operator <= (see below)
    using const constructor didn't make a notable difference (propably more important when converted to JS)
  3. 1.449707 sec: with simplified constructor
  4. 3.792590 sec: your code
class $PyNum2 {
  final num _value;

  const $PyNum2(this._value);

  factory $PyNum2.from(value) {
    switch ($getType2(value)) {
      case 6:
        return new $PyNum2(value);
        break;
      case 7:
        try {
          return new $PyNum2(num.parse(value));
        } catch (ex) {
          print("Invalid string literal for num parsing");
          exit(1);
        }
        break;
      case 5:
        return new $PyNum2(value.value());
        break;
      default:
        throw "Invalid input for num conversion";
    }
  }

  value() => _value;
  toString() => _value.toString();

  operator +(other) => new $PyNum2(_value + other.value());
  operator -(other) => new $PyNum2(_value - other.value());
  operator *(other) => new $PyNum2(_value * other.value());
  operator ~/(other) => new $PyNum2(_value ~/ other.value());
  operator %(other) => new $PyNum2(_value % other.value());
  operator ==(other) {
    switch ($getType2(other)) {
      case 6:
        return _value == other;
      case 5:
        return _value == other.value();
      default:
        return false;
    }
  }
  operator <(other) {
    switch ($getType2(other)) {
      case 6:
        return _value < other;
      case 5:
        return _value < other.value();
      default:
        return true;
    }
  }
  operator >(other) => !(this < other) && (this != other);
  operator <=(other) => this.value() <= other.value(); //(this < other) || (this == other);
  operator >=(other) => (this > other) || (this == other);
}

see also:

OTHER TIPS

I haven't tried this, but how about never wrapping the object and for any method you need in Python implement it as a standalone function with a type-case instead. So anything that is common between the two would run at full speed. Methods only implemented in one python class would be pretty quick, and you'd only be doing a lot of type tests for things that were megamorphic in Python.

Or just write a Python interpreter in Dart.

Even in the examples you give you'd probably do a lot better if you used const objects instead of allocating a new PyNum every time.

Sample code where the wrappers 6.3 times slower:

  1. Constructor only wrapps value. If you need consversion use anothers approach, eg. other additional constructors.
  2. Comparison operators splitted for reducing unnecessary types checks.
  3. Arithmetic operators improved, added type checks.
  4. Python types combined into group $PyType. This reduces types checks.

Unnecessary code removed from this sample.

import 'dart:io';

fib(n) {
  if (n <= 2) {
    return 1;
  } else {
    return (fib((n - 1)) + fib((n - 2)));
  }
}

fib2(n) {
  if (n <= new $PyNum(2)) {
    return new $PyNum(1);
  } else {
    return (fib2((n - new $PyNum(1))) + fib2((n - new $PyNum(2))));
  }
}

main() {
  measure("fib", () => stdout.writeln(fib(42)));
  measure("fib2", () => stdout.writeln(fib2(new $PyNum(42))));
}

void measure(String msg, f()) {
  var sw = new Stopwatch();
  sw.start();
  f();
  sw.stop();
  print("$msg: ${sw.elapsedMilliseconds}");
}

class $PyTypes {
  static const $PyTypes NUM = const $PyTypes("NUM");

  final String name;

  const $PyTypes(this.name);
}

abstract class $PyType {
  $PyTypes get pyType;
}

class $PyNum extends $PyType {
  final int value;

  $PyTypes get pyType => $PyTypes.NUM;

  $PyNum(this.value);

  operator +(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return new $PyNum(value + pyNum.value);
      }
    } else if (other is int) {
      return new $PyNum(value + other);
    }

    throw new ArgumentError("other: $other");
  }

  operator -(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return new $PyNum(value - pyNum.value);
      }
    } else if (other is int) {
      return new $PyNum(value - other);
    }

    throw new ArgumentError("other: $other");
  }

  operator ==(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return value == pyNum.value;
      }
    } else if (other is int) {
      return value == other;
    }

    throw new ArgumentError("other: $other");
  }

  operator <(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return value < pyNum.value;
      }
    } else if (other is int) {
      return value < other;
    }

    throw new ArgumentError("other: $other");
  }

  operator <=(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return value <= pyNum.value;
      }
    } else if (other is int) {
      return value <= other;
    }

    throw new ArgumentError("other: $other");
  }

  String toString() => value.toString();
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top