- 
      
 - 
        
Save freakboy3742/d98c513e92a8badcb632d38581ae54b1 to your computer and use it in GitHub Desktop.  
| class PyType { | |
| constructor(name, bases, dict) { | |
| this.name = name | |
| this.bases = bases | |
| this.dict = dict | |
| } | |
| __str__() { | |
| return this.name | |
| } | |
| __call__(...args) { | |
| console.log(`Create new <${this.name}> with ${args.length} args...`) | |
| return `New <${this.name}> object created with ${args.length} args` | |
| } | |
| __getattribute__(attr) { | |
| return this[attr] | |
| } | |
| __setattr__(attr, value) { | |
| this[attr] = value | |
| } | |
| } | |
| let descriptor = { | |
| get: function(obj, attr) { | |
| if (attr === Symbol.toPrimitive) { | |
| console.log('get as str', obj) | |
| console.log('__str__', obj.__str__) | |
| return obj.__str__.bind(obj) | |
| } else { | |
| console.log(`Getting ${attr}`) | |
| return obj.__getattribute__(attr); | |
| } | |
| }, | |
| set: function(obj, attr, value) { | |
| console.log(`Setting ${attr} to ${value}`) | |
| obj.__setattr__(attr, value); | |
| return true; | |
| }, | |
| apply: function(obj, that, args) { | |
| console.log(`Calling object with ${args.length} arguments`) | |
| return obj.__call__.apply(that, args) | |
| }, | |
| construct: function(obj, args) { | |
| console.log(`Constructing object with ${args.length} arguments`) | |
| throw "Python objects are constructed by invocation" | |
| } | |
| } | |
| export function tester() { | |
| let mt = new PyType('MyType', ['base1', 'base2'], {'attr1': 1, 'attr2': 2}) | |
| let MyType = new Proxy(mt, descriptor) | |
| console.log('mt = ', MyType) | |
| console.log('mt.name = ', MyType.name) | |
| console.log('mt.bases = ', MyType.bases) | |
| console.log('mt.dict = ', MyType.dict) | |
| console.log('mt.__str__() = ', MyType.__str__()) | |
| console.log('mt.__call__() = ', MyType.__call__()) | |
| // This line fails: | |
| // Uncaught TypeError: MyType is not a function | |
| // at Object.tester (test.js:69) | |
| let obj = MyType(1, 2, 3, 4) | |
| } | 
toString is a good idea, but it only gets called when the object is actually coerced into a string, not when logged directly. (E.g., the difference between console.log('mt = ', mt) and console.log('mt = ' + mt))... Still, might indeed be useful as a partial solution.
I made some progress by extending Function twice-removed:
class Callable extends Function {
  constructor() {
    super('...args', 'return this.__call__(...args)');
    return this.bind(this)
  }
}
class PyType extends Callable {
    constructor() {
      super()
    }
    __str__() {
        return this.nname
    }
    __call__(...args) {
        print(this)
        this.nname = args[0]
        this.bases = args[1]
        this.dict = args[2]
        print(`Create new <${this.nname}> with ${args.length} args...`)
        return `New <${this.nname}> object created with ${args.length} args`
    }
    __getattribute__(attr) {
        return this[attr]
    }
    __setattr__(attr, value) {
        this[attr] = value
    }
}
Now it calls the __call__, but this is undefined within __call__ because Function is weird:
TypeError: Cannot set property 'nname' of undefined
    at __call__ (test.js:46:20)
    at Object.apply (test.js:19:29)
    at tester (test.js:78:15)
    (I got some of this info from https://stackoverflow.com/a/40878674 )
This is the simplest solution with the least amount of functionality, but it's guaranteed to work almost anywhere. Create a new function and clone an object's properties onto it.
class PyType {
    constructor(name, bases, dict) {
        this.name = name
        this.bases = bases
        this.dict = dict
    }
    __str__() {
        return this.name
    }
    __call__(...args) {
        console.log(`Create new <${this.name}> with ${args.length} args...`)
        return `New <${this.name}> object created with ${args.length} args`
    }
    __getattribute__(attr) {
        return this[attr]
    }
    __setattr__(attr, value) {
        this[attr] = value
    }
}
let descriptor = {
    get: function(obj, attr) {
        if (attr === Symbol.toPrimitive) {
            console.log('get as str', obj)
            console.log('__str__', obj.__str__)
            return obj.__str__.bind(obj)
        } else {
            console.log(`Getting ${attr}`)
            return obj.__getattribute__(attr);
        }
    },
    set: function(obj, attr, value) {
        console.log(`Setting ${attr} to ${value}`)
        obj.__setattr__(attr, value);
        return true;
    },
    apply: function(obj, that, args) {
        console.log(`Calling object with ${args.length} arguments`)
        return obj.__call__.apply(that, args)
    },
    construct: function(obj, args) {
        console.log(`Constructing object with ${args.length} arguments`)
        throw "Python objects are constructed by invocation"
    }
}
const ObjectCallable_handler = {
    get: function get(self, key) {
        if (self.hasOwnProperty(key)) {
            return self[key];
        } else { return self.__inherit__[key]; }
    },
    apply: function apply(self, thisValue, args) {
        return (self.__call__ || self.__inherit__.__call__).apply(self, args);
    }
};
function ObjectCallable(cls) {
    var p = new Proxy(function() { }, ObjectCallable_handler);
    p.__inherit__ = cls;
    return p;
}
export function tester() {
    let mt = new PyType('MyType', ['base1', 'base2'], {'attr1': 1, 'attr2': 2})
    let MyType = new Proxy(ObjectCallable(mt), descriptor)
    console.log('mt = ', MyType)
    console.log('mt.name = ', MyType.name)
    console.log('mt.bases = ', MyType.bases)
    console.log('mt.dict = ', MyType.dict)
    console.log('mt.__str__() = ', MyType.__str__())
    console.log('mt.__call__() = ', MyType.__call__())
    // This line fails:
    // Uncaught TypeError: MyType is not a function
    //   at Object.tester (test.js:69)
    let obj = MyType(1, 2, 3, 4)
}However, this approach will only answer your first question about callable classes. Probably you will get another error, but now with name reference. So here's a example about valid approach for deal with it:
var proxyAccount = new Proxy (ObjectCallable(new PyType('MyType', ['base1', 'base2'], {'attr1': 1, 'attr2': 2})) , {
    get: function (target, name, receiver) {
        console.log('get called for field: ', name);
        if(!target.hasOwnProperty(name))
        {
           // do I have to store `name` into global variable, to reference it in `apply trap?
           // methodName = name
            return new Proxy(target[name],this);
        }
        return Reflect.get(target, name, receiver);
    },
    apply: (target,receiver,args) => {
        console.log('methodName: ',  'I need methodName here. how do I get  -withdraw- here?');
        return Reflect.apply(target, receiver, args);
    }
});
proxyAccount(); 
Have you looked at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/apply ?
As to the bonus point, couldn't you intercept toString ?