Browse Source

Update python.js

Lutz Roeder 3 years ago
parent
commit
5c3749ad84
4 changed files with 176 additions and 161 deletions
  1. 112 95
      source/python.js
  2. 54 62
      source/pytorch.js
  3. 6 0
      source/view-sidebar.js
  4. 4 4
      test/models.json

+ 112 - 95
source/python.js

@@ -1612,14 +1612,14 @@ python.Execution = class {
         const dict = class extends Map {};
         this._modules = new dict();
         this._registry = new Map();
-        this._builtins = this.register('builtins');
+        const builtins = this.register('builtins');
+        this._builtins = builtins;
         this._builtins.type = { __module__: 'builtins', __name__: 'type' };
         this._builtins.type.__class__ = this._builtins.type;
         this._builtins.module = { __module__: 'builtins', __name__: 'module', __class__: this._builtins.type };
         this._builtins.module.__type__ = this._builtins.module;
         this._registry.set('__builtin__', this._builtins);
-        this._context = new python.Execution.Context();
-        this._context.set('__builtins__', this.import('builtins'));
+        this.import('builtins');
         const typing = this.register('typing');
         this._typing = typing;
         this.register('_codecs');
@@ -1699,6 +1699,8 @@ python.Execution = class {
                 }
             }
         });
+        this.registerType('builtins.Warning', class {});
+        this.registerType('builtins.FutureWarning', class extends builtins.Warning {});
         this.registerType('typing._Final', class {});
         this.registerType('typing._SpecialForm', class extends typing._Final {});
         this.registerType('typing._BaseGenericAlias', class extends this._typing._Final {});
@@ -1919,6 +1921,14 @@ python.Execution = class {
                 }
             }
         });
+        this.registerType('numpy.generic', class {});
+        this.registerType('numpy.inexact', class {});
+        this.registerType('numpy.number', class extends numpy.generic {});
+        this.registerType('numpy.integer', class extends numpy.number {});
+        this.registerType('numpy.signedinteger', class extends numpy.integer {});
+        this.registerType('numpy.floating', class extends numpy.inexact {});
+        this.registerType('numpy.float64', class extends numpy.floating {});
+        this.registerType('numpy.int64', class extends numpy.signedinteger {});
         this.registerType('gensim.models.doc2vec.Doctag', class {});
         this.registerType('gensim.models.doc2vec.Doc2Vec', class {});
         this.registerType('gensim.models.doc2vec.Doc2VecTrainables', class {});
@@ -2471,18 +2481,13 @@ python.Execution = class {
                         case OpCode.GLOBAL: {
                             const module = reader.line();
                             const name = reader.line();
-                            // const klass = this.find_class(module, name);
-                            // TODO const klass = this.find_class(module, name);
-                            // TODO stack.push(klass);
-                            stack.push([ module, name ].join('.'));
+                            stack.push(this.find_class(module, name));
                             break;
                         }
                         case OpCode.STACK_GLOBAL: {
                             const name = stack.pop();
                             const module = stack.pop();
-                            // TODO const klass = this.find_class(module, name);
-                            // TODO stack.push(klass);
-                            stack.push([ module, name ].join('.'));
+                            stack.push(this.find_class(module, name));
                             break;
                         }
                         case OpCode.PUT: {
@@ -2799,8 +2804,7 @@ python.Execution = class {
             }
             find_class(module, name) {
                 execution.__import__(module);
-                module = execution.module(module);
-                return module[name];
+                return execution.resolve(module + '.' + name);
             }
             _instantiate(cls, args) {
                 return execution.invoke(cls, args);
@@ -2830,6 +2834,7 @@ python.Execution = class {
         });
         this.registerType('theano.compile.function_module._constructor_Function', class {});
         this.registerType('theano.compile.function_module._constructor_FunctionMaker', class {});
+        this.registerType('theano.compile.function_module.Function', class {});
         this.registerType('theano.compile.function_module.Supervisor', class {});
         this.registerType('theano.compile.io.In', class {});
         this.registerType('theano.compile.io.SymbolicOutput', class {});
@@ -3110,7 +3115,7 @@ python.Execution = class {
         });
         this.registerFunction('dill._dill._import_module', function(import_name, safe) {
             try {
-                return self.context.getx(import_name);
+                return self.__import__(import_name);
             }
             catch (err) {
                 if (safe) {
@@ -3119,11 +3124,23 @@ python.Execution = class {
                 throw err;
             }
         });
-        this.registerFunction('dill.dill._load_type', function(name) {
-            return self.context.getx('types.' + name);
-        });
         this.registerFunction('dill._dill._load_type', function(name) {
-            return self.context.getx('types.' + name);
+            return self.resolve('types.' + name);
+        });
+        this.registerFunction('lasagne.nonlinearities.rectify', function() {
+            throw new python.Error('Function not implemented.');
+        });
+        this.registerFunction('lasagne.nonlinearities.softmax', function() {
+            throw new python.Error('Function not implemented.');
+        });
+        this.registerFunction('lasagne.objectives.categorical_crossentropy', function() {
+            throw new python.Error('Function not implemented.');
+        });
+        this.registerFunction('lasagne.updates.nesterov_momentum', function() {
+            throw new python.Error('Function not implemented.');
+        });
+        this.registerFunction('nolearn.lasagne.base.objective', function() {
+            throw new python.Error('Function not implemented.');
         });
         this.registerFunction('numpy.core._multiarray_umath._reconstruct', function(subtype, shape, dtype) {
             return self.invoke(subtype, [ shape, dtype ]);
@@ -3397,6 +3414,12 @@ python.Execution = class {
         this.registerFunction('numpy.core.numeric._frombuffer', function(/* buf, dtype, shape, order */) {
             return {};
         });
+        this.registerFunction('sklearn.metrics.scorer._passthrough_scorer', function() {
+            throw new python.Error("Function not implemented.");
+        });
+        this.registerFunction('sklearn.feature_selection._univariate_selection.f_classif', function() {
+            throw new python.Error("Function not implemented.");
+        });
         this.registerFunction('re._compile', function(pattern, flags) {
             return self.invoke('re.Pattern', [ pattern, flags ]);
         });
@@ -3405,10 +3428,36 @@ python.Execution = class {
                 return self.invoke('types.' + name, arguments);
             };
         });
+        this.registerFunction('theano.scalar.basic.same_out', function() {
+            throw new python.Error('Function not implemented.');
+        });
+
+        this.registerFunction('theano.scalar.basic.same_out_nocomplex', function() {
+            throw new python.Error('Function not implemented.');
+        });
+        this.registerFunction('theano.scalar.basic.upcast_out', function() {
+            throw new python.Error('Function not implemented.');
+        });
+        this.registerFunction('theano.scalar.basic.upgrade_to_float', function() {
+            throw new python.Error('Function not implemented.');
+        });
+        this.registerFunction('theano.tensor.nnet.conv2d', function() {
+            throw new python.Error('Function not implemented.');
+        });
+        this.registerFunction('theano.tensor.type.values_eq_approx_remove_inf_nan', function() {
+            throw new python.Error('Function not implemented.');
+        });
+        this.registerFunction('theano.tensor.type.values_eq_approx_remove_nan', function() {
+            throw new python.Error('Function not implemented.');
+        });
+    }
+
+    get builtins() {
+        return this._builtins;
     }
 
     get context() {
-        return this._context;
+        throw new Error();
     }
 
     source(file) {
@@ -3463,7 +3512,24 @@ python.Execution = class {
             const program = this.parse(file);
             if (program) {
                 module.__file__ = file;
-                const context = this._context.push(module);
+                const context = new python.Execution.Context(null, module);
+                for (const entry of Object.entries(this.builtins)) {
+                    switch (entry[0]) {
+                        case '__class__':
+                        case '__package__':
+                        case '__module__':
+                        case '__name__':
+                        case '__path__':
+                        case '__file__':
+                            break;
+                        default:
+                            module[entry[0]] = entry[1];
+                            break;
+                    }
+                }
+                if (name !== 'builtins') {
+                    context.set('__builtins__', this._modules.get('builtins'));
+                }
                 this.block(program.body, context);
             }
             if (parent) {
@@ -3530,40 +3596,29 @@ python.Execution = class {
         return this._modules.get(name);
     }
 
-    type(name) {
-        const type = this._context.getx(name);
-        if (type !== undefined) {
-            return type;
-        }
+    resolve(name) {
         const parts = name.split('.');
         const className = parts.pop();
         const moduleName = parts.join('.');
         const module = this.import(moduleName);
-        if (module) {
-            const type = module[className];
-            if (type) {
-                return type;
+        let type = module ? module[className] : null;
+        if (!type) {
+            if (!this._unresolved.has(name)) {
+                const moduleName = name.split('.').shift();
+                if (this._registry.has(moduleName)) {
+                    this._exceptionCallback(new python.Error("Unsupported function '" + name + "'."), false);
+                }
+                const type = this._createType(name, class {});
+                this._unresolved.set(name, type);
             }
+            type = this._unresolved.get(name);
         }
-        return null;
+        return type;
     }
 
     invoke(target, args) {
         if (typeof target === 'string') {
-            const name = target;
-            target = this.type(name);
-            if (!target) {
-                if (!this._unresolved.has(name)) {
-                    const moduleName = name.split('.').shift();
-                    if (this._registry.has(moduleName)) {
-                        this._exceptionCallback(new python.Error("Unsupported function '" + name + "'."), false);
-                    }
-                    const type = this._createType(name, class {});
-                    this._unresolved.set(name, type);
-                    this._context.setx(name, type);
-                }
-                target = this._unresolved.get(name);
-            }
+            target = this.resolve(target);
         }
         if (target) {
             if (target.__class__ === this._builtins.type) {
@@ -3587,7 +3642,7 @@ python.Execution = class {
     }
 
     call(target, name, args, context) {
-        const callTarget = this._target(target, context);
+        const callTarget = this._target(target, name, context);
         const callArguments = args.map((argument) => this.expression(argument, context));
         if (!callTarget || (name !== null && !callTarget[name])) {
             if (name === '__new__' && callArguments.length === 1 && callArguments[0] == callTarget) {
@@ -3596,7 +3651,9 @@ python.Execution = class {
             }
             else {
                 const targetName = python.Utility.target(target) + '.' + name;
-                if (this.type(targetName)) {
+                // console.log('  ' + targetName);
+                // console.log('      -> ' + (callTarget ? callTarget.__package__ : '?'));
+                if (this.resolve(targetName)) {
                     return this.invoke(targetName, callArguments);
                 }
                 throw new python.Error("Unsupported function '" +  targetName + "'.");
@@ -3684,9 +3741,7 @@ python.Execution = class {
             case 'class': {
                 const value = this._createType(context.get('__name__') + '.' + statement.name, class {});
                 context.set(statement.name, value);
-                context = context.push(value.prototype);
-                this.block(statement.body.statements, context);
-                context = context.pop();
+                this.block(statement.body.statements, context.push(value.prototype));
                 break;
             }
             case 'var': {
@@ -3778,7 +3833,7 @@ python.Execution = class {
                         context.set(alias.asname, module);
                     }
                     else {
-                        context.set(alias.name, module);
+                        context.set(alias.name.split('.')[0], module);
                     }
                 }
                 break;
@@ -3912,7 +3967,7 @@ python.Execution = class {
             }
             case '.': {
                 if (expression.member.type == 'id') {
-                    const target = this._target(expression.target, context);
+                    const target = this._target(expression.target, expression.member.value, context);
                     return target[expression.member.value];
                 }
                 throw new python.Error("Unsupported field expression.");
@@ -3984,7 +4039,7 @@ python.Execution = class {
         return undefined;
     }
 
-    _target(expression, context) {
+    _target(expression, name, context) {
         let current = expression;
         let packageName = '';
         for (;;) {
@@ -4004,12 +4059,12 @@ python.Execution = class {
         if (packageName) {
             let target = context.getx(packageName);
             if (!target) {
-                target = this.import(packageName);
-                if (!target) {
-                    target = context.getx(packageName);
-                    if (!target) {
-                        throw new python.Error("Failed to resolve module '" + packageName + "'.");
-                    }
+                const file = packageName.split('.').join('/') + '.py';
+                if (this._sources.has(file)) {
+                    target = this.import(packageName);
+                }
+                else {
+                    target = this.resolve(packageName);
                 }
             }
             return target;
@@ -4050,9 +4105,6 @@ python.Execution = class {
             throw new python.Error("Function '" + name + "' is already registered.");
         }
         module[value.__name__] = value;
-        if (value.__module__ === 'builtins') {
-            this._context.set(value.__name__, value);
-        }
         return value;
     }
 
@@ -4072,9 +4124,6 @@ python.Execution = class {
             throw new python.Error("Class '" + name + "' is already registered.");
         }
         module[value.__name__] = value;
-        if (value.__module__ === 'builtins') {
-            this._context.set(value.__name__, value);
-        }
         return value;
     }
 };
@@ -4090,14 +4139,6 @@ python.Execution.Context = class {
         return new python.Execution.Context(this, scope);
     }
 
-    pop() {
-        return this._parent;
-    }
-
-    get scope() {
-        return this._scope;
-    }
-
     set(name, value) {
         this._scope[name] = value;
     }
@@ -4112,30 +4153,6 @@ python.Execution.Context = class {
         return undefined;
     }
 
-    setx(name, value) {
-        if (typeof name !== 'string' || !name.split) {
-            throw new python.Error("Invalid name '" + JSON.stringify(name) + "'.");
-        }
-        const parts = name.split('.');
-        if (parts.length == 1) {
-            this.set(parts[0], value);
-        }
-        else {
-            let parent = this.get(parts[0]);
-            if (!parent) {
-                parent = {};
-                this.set(parts[0], parent);
-            }
-            parts.shift();
-            while (parts.length > 1) {
-                const part = parts.shift();
-                parent[part] = parent[part] || {};
-                parent = parent[part];
-            }
-            parent[parts[0]] = value;
-        }
-    }
-
     getx(name) {
         const parts = name.split('.');
         let value = this.get(parts[0]);

+ 54 - 62
source/pytorch.js

@@ -1648,8 +1648,7 @@ pytorch.Execution = class extends python.Execution {
             return data;
         });
         this.registerFunction('torch.jit._pickle.build_tensor_from_id', function(data) {
-            const constants = self.context.get('CONSTANTS');
-            return constants['c' + data.toString()];
+            return self.builtins.CONSTANTS['c' + data.toString()];
         });
         this.registerFunction('torch.jit._pickle.restore_type_tag', function(value /*, type_str */) {
             return value;
@@ -1803,6 +1802,9 @@ pytorch.Execution = class extends python.Execution {
             }
             throw new pytorch.Error("Unsupported 'torch.sub' expression type.");
         });
+        this.registerFunction('torch.nn.functional.tanh', function(/* input */) {
+            throw new pytorch.Error("Function not implemented.");
+        });
         this.registerFunction('torch.values', function(dict) {
             return Object.keys(dict).map((key) => dict[key]);
         });
@@ -1841,6 +1843,7 @@ pytorch.Execution = class extends python.Execution {
                 return 'torch.' + this._data.name;
             }
         });
+        this.registerType('torch.qscheme', class {});
         this.registerType('torch.utils.hooks.RemovableHandle', class {
             __setstate__(state) {
                 this.hooks_dict_ref = state[0] || new Map();
@@ -1914,16 +1917,6 @@ pytorch.Execution = class extends python.Execution {
                 throw new python.Error('_LegacyStorage not implemented.');
             }
         });
-        this.registerType('torch.ComplexFloatStorage', class extends torch_storage._StorageBase {
-            constructor(size) {
-                super(size, torch.complex64);
-            }
-        });
-        this.registerType('torch.ComplexDoubleStorage', class extends torch_storage._StorageBase {
-            constructor(size) {
-                super(size, torch.complex128);
-            }
-        });
         this.registerType('torch.BoolStorage', class extends torch_storage._StorageBase {
             constructor(size) {
                 super(size, torch.bool);
@@ -1969,6 +1962,21 @@ pytorch.Execution = class extends python.Execution {
                 super(size, torch.float64);
             }
         });
+        this.registerType('torch.ComplexHalfStorage', class extends torch_storage._StorageBase {
+            constructor(size) {
+                super(size, torch.complex32);
+            }
+        });
+        this.registerType('torch.ComplexFloatStorage', class extends torch_storage._StorageBase {
+            constructor(size) {
+                super(size, torch.complex64);
+            }
+        });
+        this.registerType('torch.ComplexDoubleStorage', class extends torch_storage._StorageBase {
+            constructor(size) {
+                super(size, torch.complex128);
+            }
+        });
         this.registerType('torch.QInt8Storage', class extends torch_storage._StorageBase {
             constructor(size) {
                 super(size, torch.qint8);
@@ -2114,22 +2122,27 @@ pytorch.Execution = class extends python.Execution {
         this.registerType('torch.BFloat16Tensor', class extends torch.Tensor {});
         this.registerType('torch.cuda.FloatTensor', class extends torch.Tensor {});
         this.registerType('torch.cuda.DoubleTensor', class extends torch.Tensor {});
-        torch.uint8 = new torch.dtype(pytorch.ScalarType.uint8);
-        torch.int8 = new torch.dtype(pytorch.ScalarType.int8);
-        torch.int16 = new torch.dtype(pytorch.ScalarType.int16);
-        torch.int32 = new torch.dtype(pytorch.ScalarType.int32);
-        torch.int64 = new torch.dtype(pytorch.ScalarType.int64);
-        torch.float16 = new torch.dtype(pytorch.ScalarType.float16);
-        torch.float32 = new torch.dtype(pytorch.ScalarType.float32);
-        torch.float64 = new torch.dtype(pytorch.ScalarType.float64);
-        torch.complex32 = new torch.dtype(pytorch.ScalarType.complex32);
-        torch.complex64 = new torch.dtype(pytorch.ScalarType.complex64);
-        torch.complex128 = new torch.dtype(pytorch.ScalarType.complex128);
-        torch.bool = new torch.dtype(pytorch.ScalarType.boolean);
-        torch.qint8 = new torch.dtype(pytorch.ScalarType.qint8);
-        torch.quint8 = new torch.dtype(pytorch.ScalarType.quint8);
-        torch.qint32 = new torch.dtype(pytorch.ScalarType.qint32);
-        torch.bfloat16 = new torch.dtype(pytorch.ScalarType.bfloat16);
+        torch.uint8 = torch.ByteStorage.dtype = new torch.dtype(pytorch.ScalarType.uint8);
+        torch.int8 = torch.CharStorage.dtype = new torch.dtype(pytorch.ScalarType.int8);
+        torch.int16 = torch.ShortStorage.dtype = new torch.dtype(pytorch.ScalarType.int16);
+        torch.int32 = torch.IntStorage.dtype = new torch.dtype(pytorch.ScalarType.int32);
+        torch.int64 = torch.LongStorage.dtype = new torch.dtype(pytorch.ScalarType.int64);
+        torch.float16 = torch.HalfStorage.dtype = new torch.dtype(pytorch.ScalarType.float16);
+        torch.float32 = torch.FloatStorage.dtype = new torch.dtype(pytorch.ScalarType.float32);
+        torch.float64 = torch.DoubleStorage.dtype = new torch.dtype(pytorch.ScalarType.float64);
+        torch.complex32 = torch.ComplexHalfStorage.dtype = new torch.dtype(pytorch.ScalarType.complex32);
+        torch.complex64 = torch.ComplexFloatStorage.dtype = new torch.dtype(pytorch.ScalarType.complex64);
+        torch.complex128 = torch.ComplexDoubleStorage.dtype = new torch.dtype(pytorch.ScalarType.complex128);
+        torch.bool = torch.BoolStorage.dtype = new torch.dtype(pytorch.ScalarType.boolean);
+        torch.qint8 = torch.QInt8Storage.dtype = new torch.dtype(pytorch.ScalarType.qint8);
+        torch.quint8 = torch.QUInt8Storage.dtype = new torch.dtype(pytorch.ScalarType.quint8);
+        torch.qint32 = torch.QInt32Storage.dtype = new torch.dtype(pytorch.ScalarType.qint32);
+        torch.bfloat16 = torch.BFloat16Storage.dtype = new torch.dtype(pytorch.ScalarType.bfloat16);
+        torch.per_tensor_affine = new torch.qscheme();
+        torch.per_channel_affine = new torch.qscheme();
+        torch.per_tensor_symmetric = new torch.qscheme();
+        torch.per_channel_symmetric = new torch.qscheme();
+        torch.per_channel_affine_float_qparams = new torch.qscheme();
     }
 
     debug(file) {
@@ -2238,7 +2251,7 @@ pytorch.Container.Tar = class {
             for (let i = 0; i < num_storages; i++) {
                 const args = unpickler.load();
                 const key = args[0];
-                const storage_type = execution.type(args[2]);
+                const storage_type = args[2];
                 const obj = storage_type._new_with_file(unpickler);
                 deserialized_objects[key] = obj;
             }
@@ -2376,8 +2389,7 @@ pytorch.Container.Pickle = class {
                     return data[0];
                 }
                 case 'storage': {
-                    const name = data.shift();
-                    const storage_type = execution.type(name);
+                    const storage_type = data.shift();
                     const root_key = data.shift();
                     data.shift(); // location
                     const size = data.shift();
@@ -2646,20 +2658,15 @@ pytorch.Container.Zip.Script = class {
                 sources.set(file, buffer);
             }
         }
-        for (const entry of sources) {
-            const name = entry[0].replace(/\.py$/, '').split('/').join('.');
-            const module = this._execution.import(name);
-            this._execution.context.setx(name, module);
-        }
         const torch = this._execution.import('torch');
-        this._execution.context.set('torch', torch);
-        this._execution.context.set('Tensor', torch.Tensor);
-        this._execution.context.set('ops', torch.ops);
+        this._execution.builtins.torch = torch;
+        this._execution.builtins.Tensor = torch.Tensor;
+        this._execution.builtins.ops = torch.ops;
         const constants = {};
         for (let i = 0; i < this.constants.length; i++) {
             constants['c' + i.toString()] = this.constants[i];
         }
-        this._execution.context.set('CONSTANTS', constants);
+        this._execution.builtins.CONSTANTS = constants;
         return this._execution;
     }
 
@@ -2671,11 +2678,7 @@ pytorch.Container.Zip.Script = class {
             const typename = saved_id.shift();
             switch (typename) {
                 case 'storage': {
-                    const name = saved_id.shift();
-                    const storage_type = execution.type(name);
-                    if (!storage_type) {
-                        throw new pytorch.Error("Unsupported persistent load data type '" + name + "'.");
-                    }
+                    const storage_type = saved_id.shift();
                     const root_key = saved_id.shift();
                     /* const location = */ saved_id.shift();
                     const size = saved_id.shift();
@@ -2838,7 +2841,7 @@ pytorch.Container.Zip.Json.Script = class extends pytorch.Container.Zip.Script {
                 }
                 const type = tensorTypeMap.get(constant.dataType);
                 const shape = constant.dims ? constant.dims.map((dim) => parseInt(dim, 10)) : null;
-                const storage_type = this.execution.type('torch.' + type + 'Storage');
+                const storage_type = this.execution.resolve('torch.' + type + 'Storage');
                 const size = (shape || []).reduce((a, b) => a * b, 1);
                 const offset = parseInt(constant.offset, 10) || 0;
                 const storage = new storage_type([ size ]);
@@ -2908,21 +2911,10 @@ pytorch.Container.Zip.Json.Script = class extends pytorch.Container.Zip.Script {
             const code = this._data.torchscriptArena;
             if (code && code.key && code.key.startsWith('code/')) {
                 const file = code.key.substring('code/'.length);
-                const program = this.execution.parse(file);
-                for (const statement of program.body) {
-                    if (statement.type == 'def') {
-                        const self = this;
-                        const globals = this.execution.context;
-                        const func = {
-                            __class__: this.execution._builtins.function,
-                            __name__: statement.name,
-                            __code__: statement,
-                            __call__: function(args) {
-                                return self.execution.apply(this.__code__, args, globals);
-                            }
-                        };
-                        this._data[statement.name] = func;
-                    }
+                const name = file.replace(/\.py$/, '').split('/').join('.');
+                const module = this.execution.import(name);
+                if (module.forward.__class__ === this.execution.builtins.function) {
+                    this._data.forward = module.forward;
                 }
             }
             delete this._model;
@@ -3003,7 +2995,7 @@ pytorch.Container.Zip.Package = class extends pytorch.Container.Zip {
                     const typename = saved_id.shift();
                     switch (typename) {
                         case 'storage': {
-                            const storage_type = execution.type(saved_id[0]);
+                            const storage_type = saved_id[0];
                             const key = saved_id[1];
                             /* const location = saved_id[2]; */
                             const size = saved_id[3];

+ 6 - 0
source/view-sidebar.js

@@ -1373,6 +1373,12 @@ sidebar.Formatter = class {
 
     _format(value, type, quote) {
 
+        if (value && value.__class__ && value.__class__.__module__ === 'builtins' && value.__class__.__name__ === 'type') {
+            return value.__module__ + '.' + value.__name__;
+        }
+        if (value && value.__class__ && value.__class__.__module__ === 'builtins' && value.__class__.__name__ === 'function') {
+            return value.__module__ + '.' + value.__name__;
+        }
         if (typeof value === 'function') {
             return value();
         }

+ 4 - 4
test/models.json

@@ -4054,8 +4054,8 @@
   },
   {
     "type":     "pytorch",
-    "target":   "blitz_neural_networks_tutorial_traced.pt",
-    "source":   "https://github.com/lutzroeder/netron/files/3748989/blitz_neural_networks_tutorial.zip[blitz_neural_networks_tutorial_traced.pt]",
+    "target":   "blitz_cifar10_tutorial.pt",
+    "source":   "https://github.com/lutzroeder/netron/files/3748500/blitz_cifar10_tutorial.zip[blitz_cifar10_tutorial.pt]",
     "format":   "TorchScript v1.3",
     "link":     "https://github.com/lutzroeder/netron/issues/281"
   },
@@ -4068,8 +4068,8 @@
   },
   {
     "type":     "pytorch",
-    "target":   "blitz_cifar10_tutorial.pt",
-    "source":   "https://github.com/lutzroeder/netron/files/3748500/blitz_cifar10_tutorial.zip[blitz_cifar10_tutorial.pt]",
+    "target":   "blitz_neural_networks_tutorial_traced.pt",
+    "source":   "https://github.com/lutzroeder/netron/files/3748989/blitz_neural_networks_tutorial.zip[blitz_neural_networks_tutorial_traced.pt]",
     "format":   "TorchScript v1.3",
     "link":     "https://github.com/lutzroeder/netron/issues/281"
   },