POOP β Python Object Oriented Programming. A Python 3.14 interpreter that enforces Smalltalk-style message passing by rejecting if/for/print/isinstance and rewriting Python literals (1, "hi", True, [β¦], {β¦}) into POOP types where every operation is a message to a receiver.
POOP is for educational exploration of message-passing semantics inside the Python ecosystem, not for production. POOP is not distributed via PyPI by design, and there are no tagged releases; clone and run it locally.
git clone https://github.com/cassiobotaro/poop.git
cd poop
uv sync
uv run poop examples/basics/hello_world.pyContributor / development setup lives in CONTRIBUTING.md.
"Hello, World!".print()Run it:
poop hello.py # run a file
poop # interactive REPL (Ctrl+D to exit)Inside the REPL, colon-prefixed meta-commands help you explore, Smalltalk-browser style:
>>> :methods "abc" # the messages an object understands
>>> :explain if # why a construct is forbidden + the substitute
>>> :help # list the meta-commands
| Python | POOP |
|---|---|
print(x) |
x.print() |
if cond: β¦ / else: |
cond.if_true(lambda: β¦) / cond.if_true_if_false(lambda: β¦, lambda: β¦) |
for x in col: β¦ |
col.do(lambda x: β¦) |
while cond: β¦ |
(lambda: cond).while_true(lambda: β¦) |
len(x) |
x.len() |
x[i] |
x.at(i) |
x[a:b] |
x.slice(a, b) |
not x |
x.not_() |
x and y / x or y |
x.and_(lambda: y) / x.or_(lambda: y) |
-x |
x.negated() |
Full Python β POOP recipe book: MIGRATION.md. Design rationale: INFECTIONS.md.
POOP runs 71 validators on every program. Grouped by theme:
Control flow β messages on a Boolean, not statements.
if/else/ ternary βcond.if_true(lambda: β¦)/cond.if_true_if_false(lambda: β¦, lambda: β¦)for/while/matchβcol.do(β¦)/(lambda: cond).while_true(β¦)/ polymorphismwith/try/raise/assertβWith(lambda: cm()).do(β¦)/Try(lambda: β¦).except_(ExcType, lambda e: β¦).run()/ValueError.raise_("msg")/obj.assert_("msg")
Free functions β call as a method on the receiver.
print/len/abs/hash/round/pow/divmod/min/max/sum/sorted/reversedβx.print(),x.len(),x.abs(),x.hash(), β¦map/filterβcol.map(lambda x: β¦)/col.filter(lambda x: β¦)ascii/bin/chr/repr/formatβ corresponding methods onInt/Strinputβ"prompt".input()(openis banned outright β POOP has no file I/O)iter(col)βcol.iter()returns an iterator;it.next()advances it andit.has_next()asks whether it can, so(lambda: it.has_next()).while_true(β¦)walks it without running off the end
Introspection β call the method on the receiver, or use polymorphism.
isinstance(x, T)/issubclass(C, P)βx.is_instance(T)/C.is_subclass(P)callable(x)/dir(x)βx.callable()/x.dir();id(x)βa.is_identical(b)(POOP answers the identity question, never an address)hasattr(x, n)/getattr(x, n)/setattr(x, n, v)βx.has_attr(n)/x.get_attr(n)/x.set_attr(n, v)type(x)/x.__class__/xs.__len__()βx.class_()/x.class_name()/xs.len()β dunder attribute access is banned outright
Operator sugar β methods on the receiver.
x[i]/x[a:b]βx.at(i)/x.slice(a, b)not x/x and y/x or yβx.not_()/x.and_(lambda: y)/x.or_(lambda: y)-x/+x/~xβx.negated()/ drop the+/x.bit_invert()x is None/x in yβx.is_none()/y.includes(x)(identity viax.is_identical(y))
Syntax shortcuts that hide behaviour.
- comprehensions (
[x for x in β¦],{β¦}, generator exprs) β explicit.map/.filter/.do - top-level
def(free functions) β define as a method inside a class yield/ walrus:=/del/globalβ out of scope@decoratorβ send the message; only@staticmethod/@classmethod/@propertyare allowed, being class-definition machinery rather than runtime operations
Side-channels.
exec/breakpoint/exitβ interpreter escape hatches forbidden__builtins__/__loader__/__spec__/__name__β dunder globals are raw Python natives (and__builtins__is mutable), so bare dunder names are banned alongside dunder attributesobj._value/obj._itemsβ a leading underscore is another object's own state βobj.get_attr(name), which refuses it by saying so.self,clsand the enclosing class by name stay legal: reaching your own state is not reaching into anything
No library. POOP is the language, not the library β it mirrors no stdlib module.
importβ if Python needs an import to reach something, POOP does not offer it. There is nomath, nojson, noos.- The only names injected are
TryandWithβ the two constructs replacingtry/exceptandwith, which are Python keywords rather than modules. - Python's builtins are not there either: user code runs against an allow-list (
super,classmethod,staticmethod,property, plus what theclassstatement needs), soOSError,copyrightand the rest answerNameErrorrather than handing back a live Python object. async def/awaitfollow from this:asynciowas the only way to drive a coroutine, so async has no substitute and is banned outright. So isopenβ there is no file I/O; a program talks through"prompt".input()andobj.print().
The full catalog with one row per validator and the substitute recipe lives in INFECTIONS.md.
examples/ ships 44 programs across three subfolders, grouped by what they teach.
Language basics (examples/basics/)
hello_world.pyβ the smallest POOP programgreet.pyβ string input + concatenationfizzbuzz.pyβ control flow viaif_true_if_falseleap_year.pyβand_/or_/not_collatz.pyβ while-style recursiongrades.pyβ collection processingstring_iteration.pyβ a string is a collection tooslicing.pyβSliceas a reusable value objectbank_account.pyβ encapsulation
Idiomatic POOP (examples/idiomatic/)
pipeline.pyβfilter/filter_false/map/dochainsafe_config.pyβif_none/if_not_nonecascadecommon_interests.pyβ set operationsstatistics.pyβ number aggregationrpn_calculator.pyβ stack as a POOPListroman_numerals.pyβ string mappingmanaged_resource.pyβWithand the__exit__protocol
Classic OO patterns (Sandi Metz / GoF) (examples/patterns/)
Creational
abstract_factory.pyβ Abstract Factorybuilder.pyβ Builderfactory_method.pyβ Factory Methodprototype.pyβ Prototype (cloning a configured example)singleton.pyβ Singleton (class-side cached instance)
Structural
adapter.pyβ Adapterbridge.pyβ Bridgetree.pyβ Composite (replacingisinstance)decorators.pyβ Decorator (composition by delegation)facade.pyβ Facadeflyweight.pyβ Flyweight (shared intrinsic state)proxy.pyβ Proxy (lazy-loading virtual proxy)
Behavioral
chain_of_responsibility.pyβ Chain of Responsibilitycommand.pyβ Command (with undo)interpreter.pyβ Interpreteriterator.pyβ Iteratormediator.pyβ Mediatormemento.pyβ Mementoobserver.pyβ Observerdoor.pyβ Statediscounts.pyβ Strategytemplate_method.pyβ Template Methodvisitor.pyβ Visitor
Other OO patterns (Fowler / Beck / Evans / Metz)
null_customer.pyβ Null Objectpayroll.pyβ polymorphism replacingif employee.type == ...specification.pyβ Specification (composable rules replacingand/or/not)money.pyβ Money value objectexecute_around.pyβ Execute Around Method
Annotations (x: int, def f(x: int) -> str:) are not evaluated at runtime in Python and do not cause errors in POOP programs, but they are misleading: POOP transforms every literal to its own types (Int, Str, β¦), so a variable annotated as int holds an Int at runtime. Avoid annotations in POOP code. type X = int is explicitly banned by the validator pipeline.
MIGRATION.mdβ full Python β POOP recipe bookINFECTIONS.mdβ every validator / transformer / type, with rationaleproposals.mdβ open design backlogCONTRIBUTING.mdβ dev setup, atomic-commit conventions, versioning- Issues β bugs and feature requests
