-
-
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 ?