ENB: How to mimic Python's overriding of the dunder methods in typescript.

23 views
Skip to first unread message

Félix

unread,
Aug 4, 2023, 9:53:24 PM8/4/23
to leo-editor
Dunder methods being (unexhaustive list) : __contains__, __delitem__, __getitem__, __setitem__, __iter__ and __repr__ in my case.

Using javascript little-known 'Proxy object', and passing itself as the target, it's possible  to implement the equivalent of Leo's SqlitePickleShare class!

simple proof-of-concept below where it just mimics a dummy dict. Customize as needed for other behaviors!

Félix 🚀😊

class MyClass {
  data = {};

  constructor() {
    return new Proxy(this, {
      get(target, prop) {
        prop = prop.toString();
        console.log(`Getting property: ${prop}`);
        if(prop==="toString"){
          return target.toString.bind(target);
        }
        if(prop==='valueOf'){
          return target.toString.bind(target);
        }
        if(prop==="Symbol(Symbol.iterator)"){
          return target[Symbol.iterator].bind(target);
        }
        return target.data[prop];
      },
      set(target, prop, value) {
        console.log(`Setting property: ${prop.toString()} to ${value}`);
        target.data[prop] = value;
        return true;
      },
      deleteProperty(target, prop) {
        console.log(`Deleting property: ${prop.toString()}`);
        delete target.data[prop.toString()];
        return true;
      },
      has(target, prop) {
        console.log(`Checking property presence: ${prop.toString()}`);
        return prop in target.data;
      },
    });
  }

  valueOf() {
    return 'running valueOf: ' + JSON.stringify(this.data);
  }
  toString() {
    return 'running tostring:' + JSON.stringify(this.data);
  }

  *[Symbol.iterator]() {
    for (const key in this.data) {
      console.log('called iterator!');
      yield [key, this.data[key]];
    }
  }

}

const obj = new MyClass();
obj['key'] = 'value'; // Setting property: key to value
console.log("testing setting and getting 'key'");
console.log(obj['key']); // Getting property: key, value
delete obj['key']; // Deleting property: key
console.log("deleted, should be false: ")
console.log('key' in obj); // Checking property presence: key, false

console.log("testing toString ")
obj['key3'] = 'value1';
obj['key4'] = 'value2';
console.log(obj.toString()); // {"key1":"value1","key2":"value2"}

console.log("testing Symbol.iterator");
for (const [key, value] of obj) {
  console.log(`${key}: ${value}`);
}

Félix

unread,
Aug 4, 2023, 9:55:32 PM8/4/23
to leo-editor
Oops: corrected the typo in the example!
class MyClass {
  data = {};

  constructor() {
    return new Proxy(this, {
      get(target, prop) {
        prop = prop.toString();
        console.log(`Getting property: ${prop}`);
        if(prop==="toString"){
          return target.toString.bind(target);
        }
        if(prop==='valueOf'){
          return target.valueOf.bind(target);

Félix

unread,
Aug 4, 2023, 10:35:32 PM8/4/23
to leo-editor
Lastly, an even better optimization: I'll conclude this by showing how to be used even more specifically like the class I'm trying to implement, Here's a version where the object itself is the handler, along with it's target! Therefore returning new Proxy(this, this); as the only line of the constructor! 

class MyClass {
  data = {};

  constructor() {
    return new Proxy(this, this);
  }

  get(target, prop) {
    prop = prop.toString();
    console.log(`Getting property: ${prop}`);
    if(prop==="toString"){
      return this.toString.bind(target);
    }
    if(prop==='valueOf'){
      return this.valueOf.bind(target);
    }
    if(prop==="Symbol(Symbol.iterator)"){
      return this[Symbol.iterator].bind(target);
    }
    return this.data[prop];
  }

  set(target, prop, value) {
    console.log(`Setting property: ${prop.toString()} to ${value}`);
    this.data[prop] = value;
    return true;
  }

  deleteProperty(target, prop) {
    console.log(`Deleting property: ${prop.toString()}`);
    delete this.data[prop.toString()];
    return true;
  }

  has(target, prop) {
    console.log(`Checking property presence: ${prop.toString()}`);
    return prop in this.data;
  }

  valueOf() {
    return 'running valueOf: ' + JSON.stringify(this.data);
  }
  toString() {
    return 'running tostring:' + JSON.stringify(this.data);
  }

  *[Symbol.iterator]() {
    for (const key in this.data) {
      console.log('called iterator!');
      yield [key, this.data[key]];
    }
  }

}

 
Hope sharing this will help someone! :D

Félix


Edward K. Ream

unread,
Aug 5, 2023, 9:06:31 AM8/5/23
to leo-e...@googlegroups.com
On Fri, Aug 4, 2023 at 8:53 PM Félix <felix...@gmail.com> wrote:

Dunder methods being (unexhaustive list) : __contains__, __delitem__, __getitem__, __setitem__, __iter__ and __repr__ in my case.

Thanks for this. Your post shows how much behind-the-scenes work you have done with leoJS.

Edward
Reply all
Reply to author
Forward
0 new messages