pybi v0.0.2
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 pybiUsage
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`
Python3 built-in functions
- :x:
__import__()- Nope sorry, not messin' with this kinda stuff.
abs()all()any()- :ok_hand:
ascii()- This lib's implementation should be ok.
bin()bool()breakpoint():triangular_flag_on_post:- This is just a function calling
debugger. Thus when used for debugging 1 up-step is necessary.
- This is just a function calling
bytearray():triangular_flag_on_post:- :warning: Only works on Node.js.
It will not be installed unless there is a global
processvariable. - There is no (third)
errorsargument.
- :warning: Only works on Node.js.
It will not be installed unless there is a global
bytes()callable()chr()classmethod()- There are 3 different behavoirs depending on the usage and environment.
See section
Caveatsfor details.
- There are 3 different behavoirs depending on the usage and environment.
See section
- :stop_sign:
compile()- I guess I could do that using babylon but not for now. :wink:
- :x:
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. :wink:
delattr()dict()dir():triangular_flag_on_post:- Calling this function without arguments is not supported (and throws a
NotImplementedError).
- Calling this function without arguments is not supported (and throws a
divmod()enumerate():triangular_flag_on_post:- Returns an
Arrayinstead of anenumerateinstance.
- Returns an
eval():triangular_flag_on_post:- There already is a global
evalfunction in JS. :earth_africa: But this lib's version wraps the passed expression string in parentheses so that its value is returned.
- There already is a global
exec()filter()float()format():triangular_flag_on_post:- :warning: 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()- :x:
globals()- Just use
globalThis(orwindow/globalrespectively) please. :wink:
- Just use
hasattr()hash()- Using
hash-sumif the dependant project has it installed.
- Using
- :x:
help() hex()id()input():triangular_flag_on_post:- :warning: Only works on Node.js.
It will not be installed unless there is a global
processvariable. - :exclamation: Asynchronous by default.
There is a 2nd argument
async(defaulttrue). Iffalse, busy waiting is used butsystem-sleepcan be used to relax the busy waiting.
- :warning: Only works on Node.js.
It will not be installed unless there is a global
int()isinstance()issubclass()iter(object, sentinel=undefined, equality=(x, y) => x === y):triangular_flag_on_post:- There is an additional argument
equalitythat is used for comparing thesentinelto each iteration's value (i.e.object()) because equality is not defined as well as in Python.
- There is an additional argument
len()list()- :x:
locals()- There seems to be no way in JavaScript to get the local scope and its variables.
map()max():triangular_flag_on_post:- Without the keyword arguments.
- :x:
memoryview()- I am afraid that's not possible.
min()next()object():triangular_flag_on_post:- 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'})andopen('/path', 'rb', undefined, 'utf8') modeallows more than in Python. That way it may be more convenient for people used to thefs.openinterface. E.g.'ax'is not allowed in Python but it is infs.
- 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:
ord()pow()print()kwargscan be passed by passing an object with the following shape as the last argument, for example:{__kwargs__, end='-------'}.__kwargs__is a named export ofpybi. Note that theendkeyword argument is prepended to the default (unavoidable?) line break. This means it behaves differently than in Python.
- :x:
property()- I couldn't find a good way to make it nice enough to be actually useful:
Proxydidn't work the way I wanted and making this function an alias forObject.definePropertyis pointless IMHO. The built-in getter and setter are easy to use and save usingpropertyas a decorator. The only added value would be reacting todelete(which could indeed be accomplished with aProxy). This is as far as I got. :wink:
- I couldn't find a good way to make it nice enough to be actually useful:
range()- :ok_hand:
repr()- No memory address but most classes have a good
evalable representation by their.toStringmethod. Otherwise<${object.constructor.name} object>is returned.
- No memory address but most classes have a good
reversed()round()set()setattr()slice()- Returns a custom instance of
Slicebut is currently not really usable, because it can't be used on any built-in functionality of JavaScript. I guess, there could be anArrayProxythat intercepts the array accessor (see this question) and uses theSliceclass.
- Returns a custom instance of
sorted(iterable, key=undefined, reversed=false)staticmethod()str()sum()- :x:
super()(keyword) tuple():triangular_flag_on_post:- It works like in Python but returns an instance of
Arraywhich is mutable!
- It works like in Python but returns an instance of
- :ok_hand:
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
classmethoddecorator works when usingtypein Python. The created class has the__name__,__bases__and__dict__attributes like in Python.
- 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
vars():triangular_flag_on_post:- This libs implementation only works if an argument is passed
and returns this arg's
__dict__.
- This libs implementation only works if an argument is passed
and returns this arg's
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 :wink:).
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.