PythonRNGs.jl
Random numbers in Julia, generated by Python.
It is not a faster random number generator — sampling calls into Python, so it is slower than native Julia RNGs. Its purpose is to help port existing Python libraries to Julia: because it reproduces Python's draws exactly, a translated routine can be validated against the original Python implementation value-for-value.
PythonRNGs exposes Python's random module and NumPy's two random APIs as Julia AbstractRNG subtypes through PythonCall.jl. It is intended for reproducing draws from Python inside Julia: floats, integers, integer ranges, and discrete ranges return exactly the values the corresponding Python generator would return for the same operation (see Reproducibility for the few values that are derived rather than taken verbatim from Python).
Standard normal draws, permutations, and vector shuffles also delegate to the Python backends, preserving their random-stream consumption and normal caches.
Three backends are provided:
| Backend | Python API |
|---|---|
PythonRandom | random.Random |
NumPyRandomDefaultRNG | numpy.random.default_rng (Generator) |
NumPyRandom | legacy numpy.random.RandomState (np.random.seed / np.random.random) |
Installation
Install the registered package from the General registry:
using Pkg
Pkg.add("PythonRNGs")Requirements:
Julia 1.10 or newer.
A Python interpreter with NumPy, for the
NumPyRandomDefaultRNGandNumPyRandombackends. PythonCall.jl manages one with CondaPkg.jl; install NumPy with:using CondaPkg CondaPkg.add("numpy")PythonRandomneeds only the Python standard library.
Usage
using PythonRNGs, Random
rng = PythonRandom(1234) # wraps Python's random.Random(1234)
rand(rng) # 0.9664535356921388
default = NumPyRandomDefaultRNG(1234) # wraps numpy.random.default_rng(1234)
rand(default) # 0.9766997666981422
legacy = NumPyRandom(999) # wraps numpy.random.RandomState(999)
rand(legacy) # 0.8034280400796879All three are ordinary AbstractRNGs, so the usual Random entry points work:
rand(rng, Float64, 3) # 3 uniform values in [0, 1)
rand(rng, Float32) # uniform Float32 in [0, 1)
rand(rng, 1:6) # uniform integer (== random.choice([1,2,3,4,5,6]))
rand(rng, Bool) # uniform Bool
rand(rng, 10) # 10-element Vector{Float64} in [0, 1)
rand(rng, 1.0:0.5:2.0) # uniform element of a discrete float range
rand(rng, 'a':'z') # uniform character (== random.choice("abc..."))
rand(rng, Float64, 2, 2) # 2×2 matrix
rand!(rng, zeros(3)) # fill an existing array
randn(rng) # native standard normal draw
randn(rng, 2, 3) # normal matrix in C order
randn!(rng, zeros(2, 3)) # fill an existing matrix in C order
randperm(rng, 5) # Python permutation, shifted to 1:5
shuffle(rng, [1, 2, 3, 4]) # shuffled copy using the backend
shuffle!(rng, [1, 2, 3, 4]) # shuffle a vector in placeSeeding
rng = PythonRandom(42)
rand(rng)
Random.seed!(rng, 42) # reseed: reproduces the stream
Random.seed!(rng) # reseed from OS entropyTesting
using Pkg
Pkg.test("PythonRNGs")The test suite verifies, for all backends, that floats, integers, ranges (including step, Char, and discrete float ranges), and array draws reproduce the corresponding Python calls draw-for-draw, that unsupported ranges raise NotSupportedError, and that scalar types, reseeding, copying, and independence behave as expected. NumPy is provided to the test environment through CondaPkg (CondaPkg.toml at the repository root).