Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature request] Instatiate JS classes with (), and depreciate .new() #2513

Open
denis-migdal opened this issue Nov 18, 2024 · 6 comments
Open

Comments

@denis-migdal
Copy link
Contributor

denis-migdal commented Nov 18, 2024

Hi,

Currently Brython has difficulties to distinguish functions and classes, requiring us to write Klass.new() to instantiate it.

However, it seems there is a way to detect whether a variable is a function or a class :

function isClass(obj) {
    // from https://stackoverflow.com/questions/526559/testing-if-something-is-a-class-in-javascript
    return Object.getOwnPropertyDescriptors(obj).prototype.writable === false;
}

With this, it should be possible, to add a __call__() on JS classes enabling to instantiate them, i.e. being able to write Klass().
isClass() may return false on some native classes. However, native classes can be instantiated without using new Klass().

Implemented classes with functions is nowadays not that frequent. We could deprecate .new(), but keep it when an explicit usage of new is required ?

EDIT: I though I already talked about it here, but I couldn't find it anymore. I may have talked about it only on the google group.

@PierreQuentel
Copy link
Contributor

Hi Denis,

This is already how it works; if you use a JS "class" (defined with the JS keyword class) in Brython, using method new is optional

<script type="text/python" debug="2">
from browser import window

a = window.A(5)
print(a.x) # 5

b = window.A.new(6)
print(b.x) # 6

</script>

<script>
class A{
  constructor(x){
    this.x = x
  }
}

window.A = A
</script>

To detect a JS "class", the test is not the one you report, but another one in the same stackoverflow thread, testing if the result of obj.toString() starts with "class ". Not very elegant, but it's the best noted answer.

if(js_attr !== null &&
)

@denis-migdal
Copy link
Contributor Author

Hum, something is strange.

Last month, somebody tried to instantiate a JS Blob using .new() :
#2503

I just tested in the editor :

from browser import window

Blobby = window.Blob(['test'], {'type':'text/plain'} )

Gives :

Traceback (most recent call last):
  File <string>, line 3, in <module>
JavascriptError: TypeError: Blob constructor: 'new' is required

It seems there is a bug.

Blob.toString() gives :

"function Blob() {
    [native code]
}"

With my method, it gives : true.

Can you try out my method to see if everything work with it ?

@PierreQuentel
Copy link
Contributor

PierreQuentel commented Nov 22, 2024

This function isClass returns true for a built-in object like Blob, so we could translate window.Blob() to new Blob().

But if we do so, we would also translate window.Number(x) to new Number(x) and it's not the same result as Number(x) (former is an instance of Number, second is a number primitive), and the same for String.

Javascript's Date (isClass(Date) is true) also gives different results if it is called with or without new, and this behaviour is respected in Brython:

from browser import window

print(window.Date()) # string
print(window.Date.new()) # Javascript Date object

My conclusion is that it's better to keep it simple : in Brython, use method jsobj.new() to get the result of Javascript new jsobj() and jsobj() for Javascript jsobj()

@denis-migdal
Copy link
Contributor Author

I am always reminded how JS is a dirty language...

Here 2 suggestions below :

  1. If isClass() is false, but Object.getOwnPropertyDescriptors(obj).prototype.writable === false; is true, try calling it as a function. If a JavascriptError: TypeError: Blob constructor: 'new' is required is raised, try calling it with new.

  2. providing a javascript.new() function :

from browser.javascript import new

new(klass, args) # would do klass.__new__(klass, args) + klass.__init__(klass, args) ?

The advantage is this would work on any classes (JS or Python), which could be useful in some code where we only know the class we will build at runtime.

@PierreQuentel
Copy link
Contributor

Another example of Javascript inconsistencies : isClass(BigInt) is true, but BigInt.new(5) raises TypeError: BigInt is not a constructor...

Regarding your suggestions:

  1. using the Javascript error message is not reliable: it is not the same across browsers, and it might change without notice
  2. I don't see a case when you don't know whether an object is a Python class or a Javascript "class" (whatever that means: a function or an object created with the JS keyword "class"). And using a function from the javascript module to create an instance of a Python class sounds heretic to me

@denis-migdal
Copy link
Contributor Author

Another example of Javascript inconsistencies : isClass(BigInt) is true, but BigInt.new(5) raises TypeError: BigInt is not a constructor...

I hate JS so much... xD

1. using the Javascript error message is not reliable: it is not the same across browsers, and it might change without notice

Then how about an hint if an exception is raised like :
"To instantiate some JS classes, you might need to use .new()"

2. I don't see a case when you don't know whether an object is a Python class or a Javascript "class" (whatever that means: a function or an object created with the JS keyword "class").

It might be the case if you dev some kind of generic factory for some reasons.

And using a function from the javascript module to create an instance of a Python class sounds heretic to me

True.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants