Text Generation
Transformers
Safetensors
GGUF
English
qwen2
decompilation
reverse-engineering
python
bytecode
code
verified-generation
conversational
text-generation-inference
Instructions to use BlazingCustoms/pybytecode-v3-1.5b with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use BlazingCustoms/pybytecode-v3-1.5b with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="BlazingCustoms/pybytecode-v3-1.5b") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("BlazingCustoms/pybytecode-v3-1.5b") model = AutoModelForCausalLM.from_pretrained("BlazingCustoms/pybytecode-v3-1.5b", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use BlazingCustoms/pybytecode-v3-1.5b with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf BlazingCustoms/pybytecode-v3-1.5b:F16 # Run inference directly in the terminal: llama cli -hf BlazingCustoms/pybytecode-v3-1.5b:F16
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf BlazingCustoms/pybytecode-v3-1.5b:F16 # Run inference directly in the terminal: llama cli -hf BlazingCustoms/pybytecode-v3-1.5b:F16
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf BlazingCustoms/pybytecode-v3-1.5b:F16 # Run inference directly in the terminal: ./llama-cli -hf BlazingCustoms/pybytecode-v3-1.5b:F16
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf BlazingCustoms/pybytecode-v3-1.5b:F16 # Run inference directly in the terminal: ./build/bin/llama-cli -hf BlazingCustoms/pybytecode-v3-1.5b:F16
Use Docker
docker model run hf.co/BlazingCustoms/pybytecode-v3-1.5b:F16
- LM Studio
- Jan
- vLLM
How to use BlazingCustoms/pybytecode-v3-1.5b with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "BlazingCustoms/pybytecode-v3-1.5b" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "BlazingCustoms/pybytecode-v3-1.5b", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/BlazingCustoms/pybytecode-v3-1.5b:F16
- SGLang
How to use BlazingCustoms/pybytecode-v3-1.5b with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "BlazingCustoms/pybytecode-v3-1.5b" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "BlazingCustoms/pybytecode-v3-1.5b", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "BlazingCustoms/pybytecode-v3-1.5b" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "BlazingCustoms/pybytecode-v3-1.5b", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Ollama
How to use BlazingCustoms/pybytecode-v3-1.5b with Ollama:
ollama run hf.co/BlazingCustoms/pybytecode-v3-1.5b:F16
- Unsloth Studio
How to use BlazingCustoms/pybytecode-v3-1.5b with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for BlazingCustoms/pybytecode-v3-1.5b to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for BlazingCustoms/pybytecode-v3-1.5b to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for BlazingCustoms/pybytecode-v3-1.5b to start chatting
- Pi
How to use BlazingCustoms/pybytecode-v3-1.5b with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf BlazingCustoms/pybytecode-v3-1.5b:F16
Configure the model in Pi
# Install Pi: npm install -g @mariozechner/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "BlazingCustoms/pybytecode-v3-1.5b:F16" } ] } } }Run Pi
# Start Pi in your project directory: pi
- OpenClaw new
How to use BlazingCustoms/pybytecode-v3-1.5b with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf BlazingCustoms/pybytecode-v3-1.5b:F16
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "BlazingCustoms/pybytecode-v3-1.5b:F16" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
- Docker Model Runner
How to use BlazingCustoms/pybytecode-v3-1.5b with Docker Model Runner:
docker model run hf.co/BlazingCustoms/pybytecode-v3-1.5b:F16
- Lemonade
How to use BlazingCustoms/pybytecode-v3-1.5b with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull BlazingCustoms/pybytecode-v3-1.5b:F16
Run and chat with the model
lemonade run user.pybytecode-v3-1.5b-F16
List all available models
lemonade list
- Hermes Agent
How to use BlazingCustoms/pybytecode-v3-1.5b with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf BlazingCustoms/pybytecode-v3-1.5b:F16
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default BlazingCustoms/pybytecode-v3-1.5b:F16
Run Hermes
hermes
- Atomic Chat
fix(harness): correct docstring_of — a slot-0 string is a docstring only in function-like scopes; genexps do not reserve slot 0, modules/class bodies store __doc__ explicitly. 0 spurious / 0 missed over 2,158 code objects vs ast.get_docstring, 13 tests. disassemble_v2 defaults to doc_rule=published so published inputs stay reproducible.
Browse files- harness/pybytecode_core/rep.py +73 -4
- harness/test_rep_docstring.py +203 -0
harness/pybytecode_core/rep.py
CHANGED
|
@@ -96,18 +96,87 @@ def signature(co: types.CodeType) -> str:
|
|
| 96 |
return ", ".join(parts)
|
| 97 |
|
| 98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
def docstring_of(co: types.CodeType) -> str | None:
|
| 100 |
-
"""co_consts[0] iff it is the
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
if co.co_consts and isinstance(co.co_consts[0], str):
|
| 102 |
return co.co_consts[0]
|
| 103 |
return None
|
| 104 |
|
| 105 |
|
| 106 |
-
def disassemble_v2(co: types.CodeType, out: list[str] | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
out = [] if out is None else out
|
| 108 |
labels, kids = _labels_for(co), _child_names(co)
|
| 109 |
out.append(f"CODE {co.co_qualname}({signature(co)})")
|
| 110 |
-
doc = docstring_of(co)
|
| 111 |
if doc is not None:
|
| 112 |
out.append(f" DOC {doc!r}")
|
| 113 |
for i in dis.get_instructions(co):
|
|
@@ -132,5 +201,5 @@ def disassemble_v2(co: types.CodeType, out: list[str] | None = None) -> str:
|
|
| 132 |
out.append("END")
|
| 133 |
for c in co.co_consts:
|
| 134 |
if isinstance(c, types.CodeType):
|
| 135 |
-
disassemble_v2(c, out)
|
| 136 |
return "\n".join(out)
|
|
|
|
| 96 |
return ", ".join(parts)
|
| 97 |
|
| 98 |
|
| 99 |
+
CO_OPTIMIZED = 0x01
|
| 100 |
+
_CONST_OPS = ("LOAD_CONST", "RETURN_CONST", "KW_NAMES")
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def docstring_of_published(co: types.CodeType) -> str | None:
|
| 104 |
+
"""The rule used to build the PUBLISHED benchmarks. Kept verbatim; do not 'fix' in place.
|
| 105 |
+
|
| 106 |
+
It is wrong -- see docstring_of() -- but the published `bench.jsonl` inputs, the v3 training
|
| 107 |
+
corpus and every scored generation were produced with it. Reproducing a published number
|
| 108 |
+
requires reproducing the input that produced it, so this stays.
|
| 109 |
+
"""
|
| 110 |
+
if co.co_consts and isinstance(co.co_consts[0], str):
|
| 111 |
+
return co.co_consts[0]
|
| 112 |
+
return None
|
| 113 |
+
|
| 114 |
+
|
| 115 |
def docstring_of(co: types.CodeType) -> str | None:
|
| 116 |
+
"""co_consts[0] iff it is the IMPLICIT docstring slot -- the information v1 destroyed.
|
| 117 |
+
|
| 118 |
+
`docstring_of_published` asked only "is co_consts[0] a string", which is not the question:
|
| 119 |
+
whether a slot-0 string is a docstring depends on WHAT KIND OF SCOPE the code object is.
|
| 120 |
+
Measured over the two published benchmarks, the published rule emitted a DOC line for 98 code
|
| 121 |
+
objects that have no implicit docstring: 94 on csn-600 (13.4% of its DOC lines) and all 4 on
|
| 122 |
+
mbpp-383. A DOC line is presented to the model as recovered ground truth, so a wrong one is
|
| 123 |
+
not noise -- it is an instruction to reproduce a docstring that was never in the source.
|
| 124 |
+
|
| 125 |
+
What CPython 3.12 actually does, measured rather than assumed:
|
| 126 |
+
|
| 127 |
+
* A FUNCTION / lambda / async function with no docstring RESERVES slot 0 and fills it with
|
| 128 |
+
`None`; with a docstring, slot 0 is the docstring. So for these scopes "slot 0 is a string"
|
| 129 |
+
is already exactly right, and the published rule was never wrong here. (This is why the
|
| 130 |
+
defect is invisible at function level: 2 of 605 function-scope emissions.)
|
| 131 |
+
|
| 132 |
+
* A GENERATOR EXPRESSION is CO_OPTIMIZED like a function but does NOT reserve slot 0 -- its
|
| 133 |
+
first ordinary literal lands there. `sum(x for x in xs if x != '=')` yields
|
| 134 |
+
`co_consts[0] == '='`, and the published rule reports `'='` as a docstring. A genexp has no
|
| 135 |
+
`__doc__` at all. 8 such emissions on csn-600, 3 on mbpp-383.
|
| 136 |
+
|
| 137 |
+
* A MODULE or CLASS BODY stores `__doc__` EXPLICITLY (`LOAD_CONST <doc>; STORE_NAME __doc__`),
|
| 138 |
+
so its docstring is already visible in the instruction stream and nothing was destroyed --
|
| 139 |
+
emitting DOC duplicates it. And when there is no docstring, slot 0 is simply the first
|
| 140 |
+
const, which may be a DEFAULT ARGUMENT value reached only inside the defaults tuple
|
| 141 |
+
(`co_consts = ('WMAP5', <code>, None, ('WMAP5',))`) -- so it is not even referenced
|
| 142 |
+
directly, and an "is slot 0 loaded" test would still call it a docstring. 86 emissions on
|
| 143 |
+
csn-600, 1 on mbpp-383.
|
| 144 |
+
|
| 145 |
+
Hence: function-like scope, and not one of the angle-bracketed compiler-generated scopes.
|
| 146 |
+
`<genexpr>`, `<listcomp>`, `<setcomp>`, `<dictcomp>` and `<lambda>` can never collide with a
|
| 147 |
+
user identifier, so co_name is a sound discriminator. A lambda cannot carry a docstring
|
| 148 |
+
either (its body is a single expression), so excluding it costs nothing.
|
| 149 |
+
|
| 150 |
+
Exact on both published benchmarks against `ast.get_docstring` ground truth: 0 spurious,
|
| 151 |
+
0 missed, over 2,158 code objects. See test_rep_docstring.py.
|
| 152 |
+
"""
|
| 153 |
+
if not (co.co_flags & CO_OPTIMIZED):
|
| 154 |
+
return None # module / class body: explicit, nothing was lost
|
| 155 |
+
if co.co_name.startswith("<"):
|
| 156 |
+
return None # <genexpr>/<listcomp>/<lambda>: no __doc__ exists
|
| 157 |
if co.co_consts and isinstance(co.co_consts[0], str):
|
| 158 |
return co.co_consts[0]
|
| 159 |
return None
|
| 160 |
|
| 161 |
|
| 162 |
+
def disassemble_v2(co: types.CodeType, out: list[str] | None = None, *,
|
| 163 |
+
doc_rule: str = "published") -> str:
|
| 164 |
+
"""Render the code object.
|
| 165 |
+
|
| 166 |
+
`doc_rule` selects which DOC rule to apply, and DEFAULTS TO THE PUBLISHED ONE ON PURPOSE.
|
| 167 |
+
The DOC line is part of the model's INPUT: the shipped v3 weights were trained on inputs
|
| 168 |
+
built with `docstring_of_published`, and every published score was measured against those
|
| 169 |
+
inputs. Silently switching the default would change what the public benchmark asks of the
|
| 170 |
+
model and would invalidate the numbers on its own card. Pass doc_rule="fixed" to use the
|
| 171 |
+
corrected rule -- see docstring_of() -- which is the right default only from a retrain
|
| 172 |
+
onward.
|
| 173 |
+
"""
|
| 174 |
+
if doc_rule not in ("published", "fixed"):
|
| 175 |
+
raise ValueError(f"doc_rule must be 'published' or 'fixed', got {doc_rule!r}")
|
| 176 |
out = [] if out is None else out
|
| 177 |
labels, kids = _labels_for(co), _child_names(co)
|
| 178 |
out.append(f"CODE {co.co_qualname}({signature(co)})")
|
| 179 |
+
doc = (docstring_of if doc_rule == "fixed" else docstring_of_published)(co)
|
| 180 |
if doc is not None:
|
| 181 |
out.append(f" DOC {doc!r}")
|
| 182 |
for i in dis.get_instructions(co):
|
|
|
|
| 201 |
out.append("END")
|
| 202 |
for c in co.co_consts:
|
| 203 |
if isinstance(c, types.CodeType):
|
| 204 |
+
disassemble_v2(c, out, doc_rule=doc_rule)
|
| 205 |
return "\n".join(out)
|
harness/test_rep_docstring.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Regression tests for rep.docstring_of -- the fabricated-DOC defect.
|
| 3 |
+
|
| 4 |
+
The DOC line is part of the model's INPUT and is presented as recovered ground truth, so a wrong
|
| 5 |
+
one instructs the model to invent a docstring that was never in the source. The published rule
|
| 6 |
+
(`docstring_of_published`) asked only "is co_consts[0] a string", which emitted a DOC line for 98
|
| 7 |
+
code objects across the two published benchmarks that have no implicit docstring at all.
|
| 8 |
+
|
| 9 |
+
Ground truth here is `ast.get_docstring` on the source, not a hand-written expectation, so these
|
| 10 |
+
tests measure the rule against Python's own definition of a docstring.
|
| 11 |
+
|
| 12 |
+
python3 -m unittest test_rep_docstring -v # unit cases only
|
| 13 |
+
python3 test_rep_docstring.py --benchmarks # + exhaustive sweep of both benchmarks
|
| 14 |
+
"""
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import ast
|
| 18 |
+
import json
|
| 19 |
+
import marshal
|
| 20 |
+
import sys
|
| 21 |
+
import types
|
| 22 |
+
import unittest
|
| 23 |
+
from pathlib import Path
|
| 24 |
+
|
| 25 |
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
| 26 |
+
from pybytecode_core.rep import (CO_OPTIMIZED, disassemble_v2, docstring_of, # noqa: E402
|
| 27 |
+
docstring_of_published)
|
| 28 |
+
|
| 29 |
+
ROOT = Path(__file__).resolve().parent.parent
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def compile_src(src: str) -> types.CodeType:
|
| 33 |
+
return compile(src, "<t>", "exec", dont_inherit=True, optimize=0)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def walk(co: types.CodeType):
|
| 37 |
+
yield co
|
| 38 |
+
for c in co.co_consts:
|
| 39 |
+
if isinstance(c, types.CodeType):
|
| 40 |
+
yield from walk(c)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def by_qualname(src: str) -> dict[str, types.CodeType]:
|
| 44 |
+
return {c.co_qualname: c for c in walk(compile_src(src))}
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class FabricatedDoc(unittest.TestCase):
|
| 48 |
+
"""Each case is a construct where the published rule emits a DOC line and must not."""
|
| 49 |
+
|
| 50 |
+
def test_function_with_docstring_is_still_emitted(self):
|
| 51 |
+
co = by_qualname("def f():\n 'the doc'\n return 1\n")["f"]
|
| 52 |
+
self.assertEqual(docstring_of(co), "the doc")
|
| 53 |
+
|
| 54 |
+
def test_function_without_docstring_reserves_slot_zero(self):
|
| 55 |
+
"""CPython fills slot 0 with None for a docstring-less function, so BOTH rules agree.
|
| 56 |
+
|
| 57 |
+
This is why the defect is invisible at function level and only shows up in genexps,
|
| 58 |
+
modules and class bodies. Asserted so the claim is checked, not assumed.
|
| 59 |
+
"""
|
| 60 |
+
co = by_qualname("def f():\n return 'hello'\n")["f"]
|
| 61 |
+
self.assertIsNone(co.co_consts[0])
|
| 62 |
+
self.assertIsNone(docstring_of_published(co))
|
| 63 |
+
self.assertIsNone(docstring_of(co))
|
| 64 |
+
|
| 65 |
+
def test_generator_expression_slot_zero_is_a_literal(self):
|
| 66 |
+
"""A genexp is CO_OPTIMIZED but does NOT reserve slot 0 -- the real function-level defect."""
|
| 67 |
+
src = "def f(xs):\n return sum(1 for x in xs if x != '=')\n"
|
| 68 |
+
gen = [c for c in by_qualname(src).values() if c.co_name == "<genexpr>"]
|
| 69 |
+
self.assertTrue(gen, "no genexpr code object on this interpreter")
|
| 70 |
+
co = gen[0]
|
| 71 |
+
self.assertEqual(co.co_consts[0], "=")
|
| 72 |
+
self.assertEqual(docstring_of_published(co), "=") # the defect
|
| 73 |
+
self.assertIsNone(docstring_of(co)) # fixed
|
| 74 |
+
|
| 75 |
+
def test_class_body_docstring_is_explicit_not_lost(self):
|
| 76 |
+
# a class stores __doc__ explicitly, so DOC would duplicate the instruction stream
|
| 77 |
+
co = by_qualname("class C:\n 'cdoc'\n x = 1\n")["C"]
|
| 78 |
+
self.assertIsNotNone(docstring_of_published(co))
|
| 79 |
+
self.assertIsNone(docstring_of(co))
|
| 80 |
+
|
| 81 |
+
def test_class_body_without_docstring_emits_nothing(self):
|
| 82 |
+
co = by_qualname("class C:\n x = 1\n")["C"]
|
| 83 |
+
self.assertIsNone(docstring_of(co))
|
| 84 |
+
|
| 85 |
+
def test_module_docstring_is_explicit_not_lost(self):
|
| 86 |
+
co = compile_src("'mdoc'\nx = 1\n")
|
| 87 |
+
self.assertIsNotNone(docstring_of_published(co))
|
| 88 |
+
self.assertIsNone(docstring_of(co))
|
| 89 |
+
|
| 90 |
+
def test_module_first_const_is_a_default_argument(self):
|
| 91 |
+
# the case that makes an "is slot 0 referenced" test insufficient: slot 0 is never loaded
|
| 92 |
+
# directly, only inside the defaults tuple
|
| 93 |
+
co = compile_src("def f(c='WMAP5'):\n return c\n")
|
| 94 |
+
self.assertEqual(co.co_consts[0], "WMAP5")
|
| 95 |
+
self.assertIsNotNone(docstring_of_published(co))
|
| 96 |
+
self.assertIsNone(docstring_of(co))
|
| 97 |
+
|
| 98 |
+
def test_generator_expression_has_no_docstring(self):
|
| 99 |
+
src = "def f(xs):\n return tuple('a' for _ in xs)\n"
|
| 100 |
+
for q, co in by_qualname(src).items():
|
| 101 |
+
if co.co_name == "<genexpr>":
|
| 102 |
+
self.assertIsNone(docstring_of(co), q)
|
| 103 |
+
break
|
| 104 |
+
else:
|
| 105 |
+
self.skipTest("no genexpr code object on this interpreter")
|
| 106 |
+
|
| 107 |
+
def test_docstring_equal_to_a_body_literal_is_not_lost(self):
|
| 108 |
+
"""CPython de-duplicates consts, so one slot serves both the docstring and the literal.
|
| 109 |
+
|
| 110 |
+
A rule that reasoned from "is slot 0 loaded" would drop this docstring -- reintroducing the
|
| 111 |
+
v1 information loss this module exists to undo. Scope kind gets it right.
|
| 112 |
+
"""
|
| 113 |
+
src = "def f():\n 'pass'\n x = 'pass'\n return x\n"
|
| 114 |
+
co = by_qualname(src)["f"]
|
| 115 |
+
self.assertEqual(ast.get_docstring(ast.parse(src).body[0]), "pass")
|
| 116 |
+
self.assertEqual(co.co_consts, ("pass",))
|
| 117 |
+
self.assertEqual(docstring_of(co), "pass")
|
| 118 |
+
|
| 119 |
+
def test_lambda_has_no_docstring(self):
|
| 120 |
+
co = by_qualname("f = lambda: 'a'\n")["<lambda>"]
|
| 121 |
+
self.assertIsNone(docstring_of(co))
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
class RepWiring(unittest.TestCase):
|
| 125 |
+
def test_default_rule_is_the_published_one(self):
|
| 126 |
+
"""Changing this default changes the public benchmark's input. It must be deliberate."""
|
| 127 |
+
co = compile_src("def f(xs):\n return sum(1 for x in xs if x != '=')\n")
|
| 128 |
+
self.assertIn("DOC '='", disassemble_v2(co))
|
| 129 |
+
self.assertIn("DOC '='", disassemble_v2(co, doc_rule="published"))
|
| 130 |
+
self.assertNotIn("DOC '='", disassemble_v2(co, doc_rule="fixed"))
|
| 131 |
+
|
| 132 |
+
def test_bad_rule_rejected(self):
|
| 133 |
+
with self.assertRaises(ValueError):
|
| 134 |
+
disassemble_v2(compile_src("x = 1\n"), doc_rule="v2")
|
| 135 |
+
|
| 136 |
+
def test_fixed_rule_propagates_into_nested_code_objects(self):
|
| 137 |
+
src = "class C:\n 'cdoc'\n def m(self):\n return 'lit'\n"
|
| 138 |
+
co = compile_src(src)
|
| 139 |
+
self.assertNotIn("DOC", disassemble_v2(co, doc_rule="fixed"))
|
| 140 |
+
self.assertIn("DOC", disassemble_v2(co, doc_rule="published"))
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def ast_truth(src: str) -> dict[str, bool]:
|
| 144 |
+
"""qualname -> has an implicit docstring, mirroring co_qualname. Ground truth."""
|
| 145 |
+
out: dict[str, bool] = {}
|
| 146 |
+
|
| 147 |
+
def rec(node, prefix):
|
| 148 |
+
for ch in ast.iter_child_nodes(node):
|
| 149 |
+
if isinstance(ch, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
| 150 |
+
q = f"{prefix}{ch.name}"
|
| 151 |
+
out[q] = ast.get_docstring(ch) is not None
|
| 152 |
+
rec(ch, f"{q}.<locals>.")
|
| 153 |
+
elif isinstance(ch, ast.ClassDef):
|
| 154 |
+
q = f"{prefix}{ch.name}"
|
| 155 |
+
out[q] = ast.get_docstring(ch) is not None
|
| 156 |
+
rec(ch, f"{q}.")
|
| 157 |
+
else:
|
| 158 |
+
rec(ch, prefix)
|
| 159 |
+
|
| 160 |
+
tree = ast.parse(src)
|
| 161 |
+
out["<module>"] = ast.get_docstring(tree) is not None
|
| 162 |
+
rec(tree, "")
|
| 163 |
+
return out
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def sweep_benchmarks() -> int:
|
| 167 |
+
"""Exhaustive: every code object of both published benchmarks against ast.get_docstring."""
|
| 168 |
+
fails = 0
|
| 169 |
+
for name, d in (("csn-3.12-licensed", ROOT / "benchmarks" / "csn-3.12-licensed"),
|
| 170 |
+
("mbpp-ood", ROOT / "benchmarks" / "mbpp-ood")):
|
| 171 |
+
if not (d / "bench.jsonl").exists():
|
| 172 |
+
print(f" SKIP {name} (not present)")
|
| 173 |
+
continue
|
| 174 |
+
rows = [json.loads(l) for l in (d / "bench.jsonl").read_text().splitlines() if l.strip()]
|
| 175 |
+
n = spurious_old = spurious_new = missed_new = 0
|
| 176 |
+
for r in rows:
|
| 177 |
+
src = (d / r["src_path"]).read_text()
|
| 178 |
+
truth = ast_truth(src)
|
| 179 |
+
co = marshal.loads((d / r["pyc_path"]).read_bytes()[16:])
|
| 180 |
+
for c in walk(co):
|
| 181 |
+
n += 1
|
| 182 |
+
real = truth.get(c.co_qualname) is True and bool(c.co_flags & CO_OPTIMIZED)
|
| 183 |
+
spurious_old += (docstring_of_published(c) is not None) and not real
|
| 184 |
+
got = docstring_of(c) is not None
|
| 185 |
+
spurious_new += got and not real
|
| 186 |
+
missed_new += real and not got
|
| 187 |
+
ok = spurious_new == 0 and missed_new == 0
|
| 188 |
+
print(f" {'OK ' if ok else 'FAIL'} {name}: {n} code objects | spurious DOC "
|
| 189 |
+
f"published={spurious_old} fixed={spurious_new} | real docstrings missed="
|
| 190 |
+
f"{missed_new}")
|
| 191 |
+
fails += 0 if ok else 1
|
| 192 |
+
return fails
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
if __name__ == "__main__":
|
| 196 |
+
if "--benchmarks" in sys.argv:
|
| 197 |
+
sys.argv.remove("--benchmarks")
|
| 198 |
+
print("=== exhaustive sweep of both published benchmarks ===")
|
| 199 |
+
rc = sweep_benchmarks()
|
| 200 |
+
print()
|
| 201 |
+
r = unittest.main(exit=False, verbosity=2).result
|
| 202 |
+
raise SystemExit(1 if rc or not r.wasSuccessful() else 0)
|
| 203 |
+
unittest.main()
|