Skip to main content

Effortlessly extend NumPy and pandas with custom operators using Python AST magic — replace verbose code with concise, expressive operations for advanced data workflows.

Project description

opkit

Custom operators for NumPy and Pandas via AST transformation. For example, concat two matrices with +: (vertically) or +.. (horizontally); stack them in depth with /. or vertically with /:

Installation

pip install opkit

# Enable (one-time setup)
python -m opkit install

Restart Python, and custom operators work everywhere!

Operators Reference

Unary Operators

Unary $

This suffix creates typed data structures:

Input Output
[1, 2, 3]$ np.array([1, 2, 3])
(1, 2, 3)$ np.array((1, 2, 3))
{'a': 1, 'b': 2}$ pd.DataFrame({'a': [1], 'b': [2]})

Unary _ and |

Limited use. Convenience ops introduced for vectors; their effect may be obtained with $ and extra [].

These suffixes are reshape operators for converting vectors to matrices:

Operator Name Description Example
_ as_row Reshapes (n,) vector to (1, n) row matrix [1, 2, 3]_[[1, 2, 3]]
| as_column Reshapes (n,) vector to (n, 1) column matrix [1, 2, 3]|[[1], [2], [3]]

Note: For nD arrays, _ inserts an axis at position 0, and | inserts an axis at position 1.

Binary Operators for NumPy

Concatenation Operators (+ prefix)

Concatenation operators combine arrays along existing axes without adding dimensions. They use np.concatenate internally.

Operator Name Description Example
+.. horizontal concat Concatenates along axis 1 (horizontal) [[1], [2]]$ +.. [[3], [4]]$[[1, 3], [2, 4]]
+: vertical concat Concatenates along axis 0 (vertical) [[1, 2]]$ +: [[3, 4]]$[[1, 2], [3, 4]]
+. last-axis concat Concatenates along axis -1 (last) [1, 2]$ +. [3, 4]$[1, 2, 3, 4]

Important: +: and +.. reject two 1D arrays (vectors have no vertical/horizontal axis). For vectors, use +. to concatenate along the last axis, or use stacking operators (/:, /.., /.). One operand can be 1D if the other is 2D+ (the 1D array will be expanded appropriately).

Stacking Operators (/ prefix)

Stacking operators add a new dimension and require operands to have the same shape. They use np.stack internally.

Operator Name Description Example
/: vertical stack Stacks along axis 0 (adds new vertical axis) [1, 2]$ /: [3, 4]$[[1, 2], [3, 4]]
/.. horizontal stack Stacks along axis 1 (adds new horizontal axis) [[1], [2]]$ /.. [[3], [4]]$[[[1], [3]], [[2], [4]]]
/. last-axis stack Stacks along axis -1 (adds new last axis) [1, 2]$ /. [3, 4]$[[1, 3], [2, 4]]

Note: For 1D arrays (vectors), /.. and /. produce the same result since they both add the horizontal axis first.

Conceptual Clarification

  • : and .. in operators refer to the vertical axis (axis 0) and horizontal axis (axis 1) respectively; because of the operator-suggested orientation, an nD array with at least two dimensions is required as operand.
  • . (single dot) refers to the last axis (axis -1).
  • + in operators means concatenation (spreading) and does not increase dimensionality.
  • / in operators means stacking (layering) and adds one new dimension.
  • * in operators means tiling (repeating) along a specified axis.

Tiling Operators (* prefix)

Tiling operators repeat an array n times along a specified axis using np.tile internally.

Operator Name Description Example
*: vertical tile Tiles along axis 0 (vertical) [[1, 2]]$ *: 3[[1, 2], [1, 2], [1, 2]]
*.. horizontal tile Tiles along axis 1 (horizontal) [[1], [2]]$ *.. 3[[1, 1, 1], [2, 2, 2]]
*. last axis tile Tiles along axis -1 (last) [1, 2, 3]$ *. 2[1, 2, 3, 1, 2, 3]
*:. 2D tile Tiles (m, n) times vertically and horizontally [[1, 2]]$ *:. (3, 2) → tiles 3×2

Important: *: and *.. reject 1D arrays (vectors have no preset orientation). Only *. accepts 1D arrays. For vectors, either:

  • Use *. to tile along the last axis
  • Stack the vector first, then tile: [1, 2]$ /: 3 *.. 5 (row tiling) or [1, 2]$ /.. 3 *: 5 (column tiling)
  • Reshape to a matrix first: [1, 2]_ *: 3 or [1, 2]| *.. 7

Binary +: for Pandas

Vertical stack (vstack) for DataFrames with typed operands only:

df = pd.DataFrame({'a': [1, 2], 'b': [3, 4]})

# Append dict (convert to DataFrame with $)
df = df +: {'a': 5, 'b': 6}$

# Append list (convert to numpy array with $, length must match columns)
df = df +: [7, 8]$

# Append DataFrame directly
df = df +: pd.DataFrame({'a': [9], 'b': [10]})

# Append 1-D numpy array (length must match columns)
df = df +: np.array([11, 12])

# Append 2-D numpy array (width must match columns)
df = df +: np.array([[13, 14], [15, 16]])

Note: The +: operator for DataFrames requires typed operands. Use the $ operator to convert dict/list/tuple literals before appending. pd.Series is not directly supported - convert using dict(series)$ or series.values.

Examples

NumPy Operations

import numpy as np

# Create arrays with $
v1 = [1, 2, 3, 4]$        # 1D vector
v2 = [5, 6, 7, 8]$        # 1D vector

# Concatenation along last axis (for vectors)
result = v1 +. v2
print(result)             # [1 2 3 4 5 6 7 8]

# Stacking creates new dimension
result = v1 /: v2         # Stack vertically
print(result)             # [[1 2 3 4]
                          #  [5 6 7 8]]

result = v1 /. v2         # Stack along last axis
print(result)             # [[1 5]
                          #  [2 6]
                          #  [3 7]
                          #  [4 8]]

# For 2D arrays, concatenation works along specified axis
m1 = [[1, 2], [3, 4]]$
m2 = [[5, 6], [7, 8]]$

result = m1 +: m2         # Vertical concatenation
print(result)             # [[1 2]
                          #  [3 4]
                          #  [5 6]
                          #  [7 8]]

result = m1 +.. m2        # Horizontal concatenation
print(result)             # [[1 2 5 6]
                          #  [3 4 7 8]]

# Tiling repeats arrays (whole array, not individual elements)
v = [1, 2, 3]$
result = v *. 2           # Tile vector along last axis
print(result)             # [1 2 3 1 2 3]

m = [[1, 2]]$
result = m *: 3           # Tile matrix vertically 3 times
print(result)             # [[1 2]
                          #  [1 2]
                          #  [1 2]]

result = m *:. (2, 3)     # Tile 2 times vertically, 3 times horizontally
print(result)             # [[1 2 1 2 1 2]
                          #  [1 2 1 2 1 2]]

# Reshape operators for vectors
v = [1, 2, 3]$
row = v_                  # Convert to row matrix (1, 3)
print(row)                # [[1 2 3]]

col = v|                  # Convert to column matrix (3, 1)
print(col)                # [[1]
                          #  [2]
                          #  [3]]

# Combined usage
result = v_ *: 4          # Convert to row, then tile vertically
print(result)             # [[1 2 3]
                          #  [1 2 3]
                          #  [1 2 3]
                          #  [1 2 3]]

Pandas Operations

import pandas as pd

# Start with a DataFrame
df = {'name': 'Alice', 'age': 25}$

# Append rows (use $ to convert literals)
df = df +: {'name': 'Bob', 'age': 30}$
df = df +: ['Charlie', 35]$
df = df +: {'name': 'Diana', 'age': 28}$

print(df)
#       name  age
# 0    Alice   25
# 1      Bob   30
# 2  Charlie   35
# 3    Diana   28

Where It Works

After python -m opkit install:

  • Python scripts: python my_script.py - Custom operators work via automatic syntax error recovery
  • Standard Python REPL: python interactive shell - Launches custom console with opkit syntax support
  • Imported modules: Works automatically via import hook
  • Dynamic code: eval(), exec(), compile() with custom operators
  • Jupyter notebooks (with optional setup - see below)

Note: python -c "..." commands are not supported due to parsing limitations.

Limitations

  • Custom binary operators split across lines using a trailing \ do not work in piped/automated REPL input (the REPL processes each piped line separately). Use single-line expressions or parentheses for multi-line REPL input. This does not affect scripts or modules, which are fully transformed before execution.

Jupyter Support

# Install with Jupyter support
pip install opkit[jupyter]

Then in your notebook first cell:

%load_ext opkit

All subsequent cells will support custom operators!

How It Works

opkit uses AST (Abstract Syntax Tree) transformation to intercept and transform your custom operator syntax before Python compiles it. The transformation happens automatically via a sitecustomize.py hook that's installed in your Python environment.

No magic comments or special imports needed - just write code with custom operators!

Advanced: Custom sitecustomize.py

The python -m opkit install command writes a simple stub to your site-packages sitecustomize.py:

# opkit auto-activation
try:
    import opkit
except ImportError:
    pass

When Python starts, it imports opkit, which calls opkit.activate(). This function installs all necessary hooks:

  • Import hook: Transforms modules you import that use custom operators
  • compile() hook: Transforms code in REPL and dynamic execution
  • eval() hook: Enables eval() to work with custom operators
  • excepthook: Catches script syntax errors and retransforms them
  • Runtime operators: Registers __opkit_dollar__, etc. in builtins

Installation lifecycle:

# Install - writes stub to sitecustomize.py
python -m opkit install

# Python startup → sitecustomize runs → imports opkit → activate() runs → hooks installed
python my_script.py  # ✓ Works
python               # ✓ REPL works

# Uninstall - removes opkit stub from sitecustomize.py
python -m opkit uninstall

# Temporary opt-out for one session
OPKIT_DISABLE_SITECUSTOMIZE=1 python my_script.py

Advanced customization:

If you need full control, you can manually copy src/sitecustomize.py to your site-packages directory. This is a reference implementation showing exactly how opkit works. Most users don't need this.

Uninstall

To remove opkit's automatic activation:

# Remove opkit hook from sitecustomize.py
python -m opkit uninstall

# If you manually installed the example sitecustomize.py, remove it:
# python -c "import site, os; print(os.path.join(site.getsitepackages()[0], 'sitecustomize.py'))"
# Then manually delete or edit that file to remove opkit configuration

Restart Python for changes to take effect.

Limitations

  • Custom operators only work after python -m opkit install
  • Requires Python 3.8+
  • NumPy 1.20+ and Pandas 1.3+
  • Code portability: Other developers need opkit installed
  • Not suitable for published libraries (use for applications/scripts)
  • python -c "..." commands are not supported due to parsing limitations

Testing

opkit includes comprehensive test coverage:

# Run all tests
python -m pytest tests/ -v

# Run only REPL tests
python -m pytest tests/test_repl.py -v

Test Summary:

  • 100 total tests across all modules
  • 98 passing - Core functionality, operators, transforms
  • 2 skipped - REPL multi-line input edge cases
  • 11 REPL-specific tests - Custom operators in interactive console

The REPL tests verify that custom operators work correctly when using python interactively, including:

  • Unary $ operator
  • Binary operators (+.., +:, +.)
  • Nested expressions
  • Module imports
  • Error handling

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

opkit-0.1.0.tar.gz (25.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

opkit-0.1.0-py3-none-any.whl (18.7 kB view details)

Uploaded Python 3

File details

Details for the file opkit-0.1.0.tar.gz.

File metadata

  • Download URL: opkit-0.1.0.tar.gz
  • Upload date:
  • Size: 25.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for opkit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ca0516df26b5d2ea8e12a630645a1bf90a758adf929a31b8427c368107eab272
MD5 0d1267a581df1bee1adf6b8eda1015f0
BLAKE2b-256 0600eaf8b13ae4e616c8ca6a4f82e0f86a2e17ae58933405014ec5fbe9972417

See more details on using hashes here.

File details

Details for the file opkit-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: opkit-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for opkit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 718532c529510a80435387184ad09943f5dd959dcae65e6566169f12462671a5
MD5 9a3234187ab26fc1a063cf87f7dc832c
BLAKE2b-256 97f501d1c0688a0326902d9ddb890b4042a115ad8a417276d9f3b4071cbdfd7f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page