brython-dev / brython

Brython (Browser Python) is an implementation of Python 3 running in the browser

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

When extending JS classes, the super class' constructor isn't called + an exception is raised when calling `super()` in the subclass

denis-migdal opened this issue · comments

It seems Brython doesn't call the constructor of the super class when inheriting a JS class.

The JS class:

function BLISS() {
    return class {
        constructor() {
            console.log("called");
        }

        foo() {
            console.log("foo");
        }

        faa = 43;

        set fii(a: number) {
            console.log("fii");
        }

        get fuu() {
            console.log("fuu");
            return 43;
        }
    }
}

The Brython class:

import BLISS

class X( BLISS() ):
    def __init__(self):
        print(self)
        print("?", dir(self) )
        print( self.foo() )
        print( self.faa )
        self.fii(43)
        print( self.fuu )

It prints:

<Javascript object: [object Object]>
? []
foo
<Javascript undefined> # should be 43

Uncaught (in promise) TypeError: $B.frame_obj is null
    set_exception_offsets http://127.0.0.1:5500/brython/www/src/brython.js:4075
    call http://127.0.0.1:5500/brython/www/src/brython.js:1604
    PyClass http://127.0.0.1:5500/index.js:36
    init http://127.0.0.1:5500/LISS/index.js:243
    connectedCallback http://127.0.0.1:5500/LISS/index.js:194
    define http://127.0.0.1:5500/LISS/index.js:332
    test http://127.0.0.1:5500/index.js:40
    res http://127.0.0.1:5500/brython/www/src/brython.js:9555
    call http://127.0.0.1:5500/brython/www/src/brython.js:1603
    anonymous http://127.0.0.1:5500/brython/www/src/brython.js line 1117 > Function:89
    loop http://127.0.0.1:5500/brython/www/src/brython.js:1117
    run_script http://127.0.0.1:5500/brython/www/src/brython.js:963
    loop http://127.0.0.1:5500/brython/www/src/brython.js:1133
    onreadystatechange http://127.0.0.1:5500/brython/www/src/brython.js:1086
    ajax_load_script http://127.0.0.1:5500/brython/www/src/brython.js:1082
    loop http://127.0.0.1:5500/brython/www/src/brython.js:1133
    run_scripts http://127.0.0.1:5500/brython/www/src/brython.js:945
    brython http://127.0.0.1:5500/brython/www/src/brython.js:891
    onload http://127.0.0.1:5500/brython/www/src/brython.js:842
    EventHandlerNonNull* http://127.0.0.1:5500/brython/www/src/brython.js:842
    EventListener.handleEvent* http://127.0.0.1:5500/brython/www/src/brython.js:840
    <anonymous> http://127.0.0.1:5500/brython/www/src/brython.js:964

Hum, it seems calling super() in the subclass raises an error.

I'll have to check more in depth the error as I currently have TypeError: $B.frame_obj is null.

It seems when calling super(), the error is here :

Uncaught (in promise) Error: 
    exc_class http://127.0.0.1:5500/brython/www/src/brython.js:4220
    super http://127.0.0.1:5500/brython/www/src/brython.js:3448
    call http://127.0.0.1:5500/brython/www/src/brython.js:1603
    __init__4 http://127.0.0.1:5500/brython/www/src/brython.js line 1117 > Function:54
    anonymous http://127.0.0.1:5500/brython/www/src/brython.js line 9866 > Function:11
    instance_creator http://127.0.0.1:5500/brython/www/src/brython.js:2445
    call http://127.0.0.1:5500/brython/www/src/brython.js:1603
    PyClass http://127.0.0.1:5500/index.js:48
    init http://127.0.0.1:5500/LISS/index.js:243
    connectedCallback http://127.0.0.1:5500/LISS/index.js:194
    define http://127.0.0.1:5500/LISS/index.js:332
    test http://127.0.0.1:5500/index.js:52
    res http://127.0.0.1:5500/brython/www/src/brython.js:9555
    call http://127.0.0.1:5500/brython/www/src/brython.js:1603
    anonymous http://127.0.0.1:5500/brython/www/src/brython.js line 1117 > Function:83
    loop http://127.0.0.1:5500/brython/www/src/brython.js:1117
    run_script http://127.0.0.1:5500/brython/www/src/brython.js:963
    loop http://127.0.0.1:5500/brython/www/src/brython.js:1133
    onreadystatechange http://127.0.0.1:5500/brython/www/src/brython.js:1086
    ajax_load_script http://127.0.0.1:5500/brython/www/src/brython.js:1082
    loop http://127.0.0.1:5500/brython/www/src/brython.js:1133
    run_scripts http://127.0.0.1:5500/brython/www/src/brython.js:945
    brython http://127.0.0.1:5500/brython/www/src/brython.js:891
    onload http://127.0.0.1:5500/brython/www/src/brython.js:842
    EventHandlerNonNull* http://127.0.0.1:5500/brython/www/src/brython.js:842
    EventListener.handleEvent* http://127.0.0.1:5500/brython/www/src/brython.js:840
    <anonymous> http://127.0.0.1:5500/brython/www/src/brython.js:964
brython.js:4220:57
    init http://127.0.0.1:5500/LISS/index.js:253
    connectedCallback http://127.0.0.1:5500/LISS/index.js:194
    define http://127.0.0.1:5500/LISS/index.js:332
    InterpretGeneratorResume self-hosted:1465
    AsyncFunctionNext self-hosted:852
    (Asynchrone : async)
    test http://127.0.0.1:5500/index.js:52
    res http://127.0.0.1:5500/brython/www/src/brython.js:9555
    call http://127.0.0.1:5500/brython/www/src/brython.js:1603
    anonymous http://127.0.0.1:5500/brython/www/src/brython.js line 1117 > Function:83
    loop http://127.0.0.1:5500/brython/www/src/brython.js:1117
    run_script http://127.0.0.1:5500/brython/www/src/brython.js:963
    loop http://127.0.0.1:5500/brython/www/src/brython.js:1133
    onreadystatechange http://127.0.0.1:5500/brython/www/src/brython.js:1086
    (Asynchrone : EventHandlerNonNull)
    ajax_load_script http://127.0.0.1:5500/brython/www/src/brython.js:1082
    loop http://127.0.0.1:5500/brython/www/src/brython.js:1133
    run_scripts http://127.0.0.1:5500/brython/www/src/brython.js:945
    brython http://127.0.0.1:5500/brython/www/src/brython.js:891
    onload http://127.0.0.1:5500/brython/www/src/brython.js:842
    (Asynchrone : EventHandlerNonNull)
    <anonyme> http://127.0.0.1:5500/brython/www/src/brython.js:842
    (Asynchrone : EventListener.handleEvent)
    <anonyme> http://127.0.0.1:5500/brython/www/src/brython.js:840
    <anonyme> http://127.0.0.1:5500/brython/www/src/brython.js:964

Proposal:

When extending a JS class:

  1. Make super() returning {__init__: function(){ ... }}.
  2. The subclass must call super().__init__(...).
  3. super().__init__(*args) will construct a new instance of the inherited JS class : new SuperClass(...args).
  4. Then, consecutive calls to super() will return the built instance.
  5. When accessing self.foo, if not in the subclass, search it in the built instance.

Alternative Proposal:

When doing : class PyClass(JSClass), do not directly inherit JSClass, but instead inherit an ad hoc python class wrapping the JS class :

class Wrapper:
    def __init__(self, *args):
        self.__js = JSClass.new(*args)

    def foo(self):
        self.__js.foo()

class PyClass(Wrapper):
    pass

This could be implemented in two ways :

  1. internally, i.e. when Brython detects we inherit a JS class, automatically build the wrapper (more secure).

  2. explicitly with an helper : PyClass(BuildWrapper(JSClass)) (can be implemented directly in Brython) + raise an exception when trying to directly inherit a JS class.