Skip to content

Pollute a namespace (global, window supported) with JS implementation of (most of) Python's built-in functions

License

Notifications You must be signed in to change notification settings

jneuendorf/pybi-js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

91 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

coverage

pybi

An implementation of most of Python's built-in functions in JavaScript.

These functions can be attached to a certain namespace -- including the global namespace (globalThis, global / window).

Install

npm install --save pybi
# or
yarn add pybi

Usage

import {install} from 'pybi'
// or
// const {install} = require('pybi')


// The global namespace is polluted by default:
install()
// Now you can do something like
print(list(zip([1,2,3], [4,5,6], [7,8,9,10])))

// Optionally install into a certain namespace:
install(MyApp)

// or just have all functions in one place:
const py3funcs = install({})
// Now you could also use the functions without pollution:
(function({print, list, zip}) {
    print(list(zip([1,2,3], [4,5,6], [7,8,9,10])))
})(py3funcs)
// or shorter:
(function({print, list, zip}) {
    print(list(zip([1,2,3], [4,5,6], [7,8,9,10])))
})(install({}))

What's included?

Literals

The following literals are (or can) be used:

  • boolean: True, False
  • None
  • string literals: r`\n` , f-strings are currently not explicitly supported because you can just use JavaScript's template strings.
  • binary literals: b`\a` , br`\a` , rb`\a`
  • ❌ __import__()
    • Nope sorry, not messin' with this kinda stuff.
  • abs()
  • all()
  • any()
  • πŸ‘Œ ascii()
    • This lib's implementation should be ok.
  • bin()
  • bool()
  • breakpoint() 🚩
    • This is just a function calling debugger. Thus when used for debugging 1 up-step is necessary.
  • bytearray() 🚩
    • ⚠️ Only works on Node.js. It will not be installed unless there is a global process variable.
    • There is no (third) errors argument.
  • bytes()
  • callable()
  • chr()
  • classmethod()
    • There are 3 different behavoirs depending on the usage and environment. See section Caveats for details.
  • πŸ›‘ compile()
    • I guess I could do that using babylon but not for now. πŸ˜‰
  • ❌ complex()
    • There is no JS built-in equivalent (or something similar) that I am aware of. We don't want to auto inject whole libraries (like math.js) into somewhere. πŸ˜‰
  • delattr()
  • dict()
  • dir() 🚩
    • Calling this function without arguments is not supported (and throws a NotImplementedError).
  • divmod()
  • enumerate() 🚩
    • Returns an Array instead of an enumerate instance.
  • eval() 🚩
    • There already is a global eval function in JS. 🌍 But this lib's version wraps the passed expression string in parentheses so that its value is returned.
  • exec()
  • filter()
  • float()
  • format() 🚩
    • ⚠️ The format specification mini-language is not supported. The implementation supports the basic behavoir and optionally uses sprintf-js under the hood. Thus the format specification of sprintf-js must be used instead of Python's.
  • frozenset()
  • getattr()
  • ❌ globals()
    • Just use globalThis (or window/global respectively) please. πŸ˜‰
  • hasattr()
  • hash()
    • Using hash-sum if the dependant project has it installed.
  • ❌ help()
  • hex()
  • id()
  • input() 🚩
    • ⚠️ Only works on Node.js. It will not be installed unless there is a global process variable.
    • ❗ Asynchronous by default. There is a 2nd argument async (default true). If false, busy waiting is used but system-sleep can be used to relax the busy waiting.
  • int()
  • isinstance()
  • issubclass()
  • iter(object, sentinel=undefined, equality=(x, y) => x === y) 🚩
    • There is an additional argument equality that is used for comparing the sentinel to each iteration's value (i.e. object()) because equality is not defined as well as in Python.
  • len()
  • list()
  • ❌ locals()
    • There seems to be no way in JavaScript to get the local scope and its variables.
  • map()
  • max() 🚩
    • Without the keyword arguments.
  • ❌ memoryview()
    • I am afraid that's not possible.
  • min()
  • next()
  • object() 🚩
    • Assigning properties is not forbidden like in Python.
  • oct()
  • open()
    • Arguments are interpreted in a special way: If the last argument is an object it is interpreted as keyword arguments. Those keyword arguments have precedence over positional arguments (but should not overlap them for clarity). For example, the following 2 calls are equivalent: open('/path', 'rb', {encoding: 'utf8'}) and open('/path', 'rb', undefined, 'utf8')
    • mode allows more than in Python. That way it may be more convenient for people used to the fs.open interface. E.g. 'ax' is not allowed in Python but it is in fs.
  • ord()
  • pow()
  • print()
    • kwargs can be passed by passing an object with the following shape as the last argument, for example: {__kwargs__, end='-------'}. __kwargs__ is a named export of pybi. Note that the end keyword argument is prepended to the default (unavoidable?) line break. This means it behaves differently than in Python.
  • ❌ property()
    • I couldn't find a good way to make it nice enough to be actually useful: Proxy didn't work the way I wanted and making this function an alias for Object.defineProperty is pointless IMHO. The built-in getter and setter are easy to use and save using property as a decorator. The only added value would be reacting to delete (which could indeed be accomplished with a Proxy). This is as far as I got. πŸ˜‰
  • range()
  • πŸ‘Œ repr()
    • No memory address but most classes have a good evalable representation by their .toString method. Otherwise <${object.constructor.name} object> is returned.
  • reversed()
  • round()
  • set()
  • setattr()
  • slice()
    • Returns a custom instance of Slice but is currently not really usable, because it can't be used on any built-in functionality of JavaScript. I guess, there could be an Array Proxy that intercepts the array accessor (see this question) and uses the Slice class.
  • sorted(iterable, key=undefined, reversed=false)
  • staticmethod()
  • str()
  • sum()
  • ❌ super() (keyword)
  • tuple() 🚩
    • It works like in Python but returns an instance of Array which is mutable!
  • πŸ‘Œ type()
    • This lib's implementation should be ok. Passing more than 1 base is not supported due to JavaScript's single inheritance model. But passing a single base without wrapping it in a tuple is supported, because it is a very likely case. Not sure how e.g. the classmethod decorator works when using type in Python. The created class has the __name__, __bases__ and __dict__ attributes like in Python.
  • vars() 🚩
    • This libs implementation only works if an argument is passed and returns this arg's __dict__.
  • zip()

Config

There are some configuration flags for some of the functions. config is just an object and can be reset using the reset function:

const {config, reset} = require('pybi')
// or
import {config, reset} from 'pybi'

console.log(config) // {
//     classmethod_firstArgClass: true,
//     hash_useHashSum: true,
//     hash_warnNoHashSum: true,
//     type_warnArrow: true,
// }
*/

config.classmethod_firstArgClass = false
reset('classmethod_firstArgClass')
console.log(config.classmethod_firstArgClass) // true

// or: reset everything
reset()

Caveats

classmethod

There are 3 different ways to use this function and 3 according behaviors which can be slightly different.

TL;DR

Since you're probably using babel (for either babel-plugin-proposal-class-properties or babel-plugin-proposal-decorators) the most powerful and most pythonic way is using actual decorators in legacy mode, because

  • it's pythonic to write decorators using the @ syntax,
  • the class is bound to the method immediately so it can be called without dot notation,
  • it allows calls on different classes, and
  • it allows calls on the prototype.

as decorator according to the legacy proposal

This is the most robust and flexible implementation: It behaves like in Python (I think πŸ˜‰).

as decorator according to the current proposal

Internally, the same function as for as wrapper function (below) is used, thus see that section.

as wrapper function

When using classmethod beware that the returned functions cannot be used with multiple/different classes like you could do in Python. This is due to the fact that in JavaScript we cannot determine the class that contains the according method definition (without additional effort like additional class decorators). Thus, the 1st class, the method is called on, is cached (in order to support "standalone-calls" (see below)). In particular, this means that the following is invalid:

const f = classmethod(cls => cls)

class A {
    static m1 = f
}
class B {
    static m2 = f
}
// still no errors thrown

A.m1()
// all good
const m1 = A.m1
m1()
// still all good
B.m2()
// THROWING UP!

Additionally, decorators in JavaScript can only result in a single descriptor which means, that the classmethod can only be defined either on the class or in the prototype with a single call/assignment (unlike in Python where classmethods can also be accessed called from instances).

There is another slight difference to Python: classmethod returns functions, so f can be called but in Python classmethod objects are not callable.

About

Pollute a namespace (global, window supported) with JS implementation of (most of) Python's built-in functions

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages