Python Interpreter
Run real Python in the middle of your ComfyUI graph
- image1
- image2
- mask1
- mask2
- list1
- dict1
- any1
- any2
- any3
- any4
- image1
- image2
- mask1
- mask2
- number1
- number2
- text1
- text2
- list1
- dict1
- any1
- any2
- any3
- any4
You will eventually hit the wall. You need to loop over a batch of images, pull the newest file from a folder, flip a mask, or blend tensors in a way no existing node covers. The options are grim: hunt for yet another custom node, write your own, or give up. Python Interpreter is the third door - a code editor sitting right in the graph that runs when the workflow queues. It's the escape hatch, and for one-off math nobody ships a node for, it's the one I reach for.
ComfyUI's node class is Exec Python Code Script, shown in the menu as Python Interpreter. You get one big code box, a set of typed inputs, and the same names come back out the other side. Your print()s and tracebacks appear right in the node, so it doubles as a debugging bench for tensor ops. The closest relative is WASasquatch's ASTERR; what sells this one is fourteen outputs instead of one. Return an image and a mask and a text string from one code block - this node just has the sockets.
How it works
Your code runs via exec() inside ComfyUI's own Python environment - same torch, same numpy, same installed packages as everything else. Nothing is sandboxed, no API, no key.
The clever (and confusing) part: every input isn't handed to your code as a raw value. Each one is wrapped in an object that pretends to be that value - it overloads the operators and forwards everything else to the underlying data. That's why number1 * 2 and image1.shape and text1.upper() all just work. It's also why plain reassignment doesn't:
# number1 = float(number1) # does NOTHING to the output
number1.to(float(number1)) # this is what actually writes back
Because your names live in a dict of wrappers, Python's = rebinding points the name elsewhere without touching the wrapper - so the output never changes. That's what .to() is for: it replaces the data inside the wrapper. Two rules of thumb: reassign with .to(), and when you hand a variable to an external function, pass .data:
from torchvision.transforms import ToPILImage
pil_img = ToPILImage()(image1.data) # not image1
image2.to(pil_img) # write the result back
In-place operators are the exception - number1 += 1, text1 += " ok" mutate the wrapper normally. One trap in the README itself: it shows number1++, which is not valid Python - there's no ++; use number1 += 1.
The inputs and outputs that matter
You really only touch a few:
raw_code- the code box. Required, and the entire point.image1/image2(IMAGE),mask1/mask2(MASK) - images arrive asB,H,W,Cfloat tensors scaled 0–1.number1(FLOAT),number2(INT),text1/text2(STRING) - the easy stuff. Wire a Primitive into them and use them in code.verbose- whether to print full tracebacks in the output. Keep it on until things work.list1,dict1,any1–any4- wildcard slots for whatever doesn't fit the types above.
Outputs are the same fourteen names, all typed *, carrying whatever you left in them. image2 into a Preview Image, text1 into a text consumer, number1 into a seed - the usual. Because the node is an output node, it always runs even when nothing downstream changed, which is what you want.
Installing it
ComfyUI Manager is the easy route, with one gotcha: the pack only shows up on the dev channel, so open Manager → Settings and switch channel to dev first, then search Python Interpreter. Or clone it manually:
cd ComfyUI/custom_nodes
git clone https://github.com/christian-byrne/python-interpreter-node
Restart ComfyUI. Requirements are light - Python 3.10+ and Pillow (its only requirements.txt entry), no model downloads. The README's demo workflows import sklearn and webcolors for color extraction, but those aren't in the requirements - copy one and hit ImportError, pip install sklearn webcolors into ComfyUI's Python env.
Where people get burned
- Reassignment that silently does nothing. You
image1 = 0.5 * image1and wonder why the output is unchanged. Use.to(). - Type-switching mid-code. Rebinding an input to a different type can cost you wrapper methods. If you need a novel type out, work in your own variables and
.to()at the very end. - Missing libraries. The node runs in ComfyUI's environment, not whatever Python you have in a terminal - install into the right one.
- The security one, which is real. This executes arbitrary code with your user's full permissions. The author's README says it plainly: do not put this on a server reachable by the public. Same reason installing any custom node is a trust decision - code runs on import, no sandbox.
Keep it as a local tool and it's the most useful "I just need this one weird transform" node in the drawer.
Inputs (16)
| Name | Type | Default | Description |
|---|---|---|---|
| raw_code | STRING | — | |
| image1opt | IMAGE | — | |
| image2opt | IMAGE | — | |
| mask1opt | MASK | — | |
| mask2opt | MASK | — | |
| number1opt | FLOAT | 0.00 | — |
| number2opt | INT | 0 | — |
| text1opt | STRING | hello | — |
| text2opt | STRING | world | — |
| list1opt | * | — | |
| dict1opt | * | — | |
| any1opt | * | — | |
| any2opt | * | — | |
| any3opt | * | — | |
| any4opt | * | — | |
| verboseopt | BOOLEAN | true | Whether to print full tracebacks in the output |
Outputs (14)
| Name | Type | Description |
|---|---|---|
| image1 | * | — |
| image2 | * | — |
| mask1 | * | — |
| mask2 | * | — |
| number1 | * | — |
| number2 | * | — |
| text1 | * | — |
| text2 | * | — |
| list1 | * | — |
| dict1 | * | — |
| any1 | * | — |
| any2 | * | — |
| any3 | * | — |
| any4 | * | — |