How to set the default value for an argument of type PythonObject?

I need to set a default value within a Mojo function call which has an argument of type PythonObject. I managed to cause the compiler to die horribly, and found a different compiler error using an inline if-else. In the repro below, Scenario 2.0 is preferred (most Pythonic), but Scenario 3.0 would work too if the compiler didn’t die. Using Optional[PythonObject] as the argument type won’t work because a value is required to be passed by the caller.
Suggestions/alternatives? TIA!

main.mojo

from python import Python, PythonObject

fn main() raises:
    Foo = Python.import_module("py.foo").Foo

    bar = PyBar(Foo())
    print(String(bar))  # Wizard

    bar = PyBar(Foo("Warlock"))
    print(String(bar))  # Warlock

    # IMPORTANT: Comment next line to hide compiler error
    # bar = PyBar()
    # print(String(bar))  # Witch


@value
struct PyBar:
    alias DEFAULT_NAME = "Witch"

    var foo: PythonObject

    # fn __init__(out self, foo: PythonObject) raises:  # Requires foo argument
    # fn __init__(out self, foo: Optional[PythonObject]) raises:  # Requires foo argument
    fn __init__(out self, foo: PythonObject = None) raises:  # Compiler error
        Foo = Python.import_module("py.foo").Foo

        # Scenario_1.0
        # OK if foo
        #   when `foo: PythonObject = None` in `fn __init__()`
        # KO if foo = None [compiler dies horribly]
        # self.foo = foo  # Uncomment

        # Scenario_2.0 [This is required to create a new Foo if not passed into __init__]
        # KO if foo [does not handle inline if-else]
        #   when `foo: PythonObject = None` in `fn __init__()`
        # error: failed to lower module to LLVM IR for archive compilation, translate module to LLVMIR failed
        self.foo = foo if foo else Foo(self.DEFAULT_NAME)  # Uncomment

        # Scenario_2.1
        # OK
        #   when `foo: Optional[PythonObject]` in `fn __init__()`
        # self.foo = foo.value() if foo else Foo(self.DEFAULT_NAME)  # Uncomment

        # Scenario_3.0 [This is required to create a new Foo if not passed into __init__]
        # OK if foo
        #   when `foo: PythonObject = None` in `fn __init__()`
        # KO if foo = None [compiler dies horribly]
        # if foo:  # Uncomment if-else
        #     self.foo = foo
        # else:
        #     self.foo = Foo(self.DEFAULT_NAME)

    fn __str__(self, out result: String):
        result = "Name = " + String(self.foo)

foo.py

class Foo:
    DEFAULT_NAME = "Wizard"
    name: str

    def __init__(self, name=DEFAULT_NAME) -> None:
        self.name = name

    def __str__(self) -> str:
        return self.name

The optional should work fine, you just need to supply a default value for it, and use a regular if-else:

fn __init__(out self, foo: Optional[PythonObject] = None) raises:
        Foo = Python.import_module("foo").Foo

        if foo:
            self.foo = foo.value()
        else:
            self.foo = Foo(self.DEFAULT_NAME)

The error with the inline if-else is an odd one and I cannot shed any light on it. Could you open an issue for that one? It seems like we should have a better error message, at the very least.

1 Like

Ugh…I was so close.! :grinning_face: Thanks for the suggestion Arthur!
I created a repro project on Github and created two issues as shown below.

  1. [BUG] Mojo `inline if-else` error with PythonObject. · Issue #4147 · modular/max · GitHub
  2. [BUG] Mojo compiler crash if an argument variable of type `PythonObject` defaults to None in a function call. · Issue #4148 · modular/max · GitHub