lebrice / SimpleParsing

Simple, Elegant, Typed Argument Parsing with argparse
MIT License
384 stars 46 forks source link

Postprocessing wraps defaults during postprocessing #311

Open mezuzza opened 2 months ago

mezuzza commented 2 months ago

Describe the bug The raw type sometimes gets passed into it's own constructor. This is fine for many classes which automatically create a new instance with the same values, but for classes which can be nested, this is problematic behavior.

To Reproduce

from dataclasses import dataclass, field

import simple_parsing

class Foo:
    def __init__(self, value: str) -> None:
        self.value = value

    def __str__(self) -> str:
        return f"Foo({str(self.value)})"

@dataclass
class Config:
    foo: Foo = field(default_factory=lambda: Foo("foo"))
    bar: Foo | None = field(default_factory=lambda: Foo("bar"))
    baz: Foo = field(default=Foo("baz"))
    baaz: Foo = Foo("baaz")

c = simple_parsing.parse(Config)

print(c.foo) # Foo(Foo("foo"))
print(c.bar) # Foo("bar")
print(c.baz) # Foo(Foo("baz"))
print(c.baaz) # Foo(Foo("baaz"))

Expected behavior None of the calls above should return a wrapped value.

print(c.foo) # Foo("foo")
print(c.bar) # Foo("bar")
print(c.baz) # Foo("baz")
print(c.baaz) # Foo("baaz")

Actual behavior A clear and concise description of what is happening.

print(c.foo) # Foo(Foo("foo"))
print(c.bar) # Foo("bar")
print(c.baz) # Foo(Foo("baz"))
print(c.baaz) # Foo(Foo("baaz"))

Desktop (please complete the following information):

Additional context Add any other context about the problem here.

lebrice commented 2 months ago

Hi @mezuzza, thanks for posting!

Interesting bug you found there! I'll take a look at whats going on.

mezuzza commented 2 months ago

A quick thing to help: I noticed that the actual extra wrapping happens in postprocessing. I understand the code path, but don't have enough context to figure out why it's taking the code path.