msgspec icon indicating copy to clipboard operation
msgspec copied to clipboard

__post_init__ not called from replace

Open kramar11 opened this issue 2 months ago • 3 comments

Description

When using msgspec.structs.replace to create a new object, the __post_init__ method is not called (unlike with dataclass.replace).

from dataclasses import dataclass, replace
import msgspec
                                                                                                                   
@dataclass
class D:                                                                                                           
    x: int
    def __post_init__(self):
        print("dataclass: post init called")
                                                                                                                   
                                                                                                                   
class M(msgspec.Struct):                                                                                           
    x: int                                                                                                         
    def __post_init__(self):                                                                                       
        print("msgspec: post init called")                                                                         
                                                                                                                   
                                                                                                                   
d = D(1)                                                                                                           
m = M(1)                                                                                                           
                                                                                                                   
d2 = replace(d, x=2)                                                                                               
m2 = msgspec.structs.replace(m, x=2) 

# Output:
# dataclass: post init called
# msgspec: post init called
# dataclass: post init called

Tested with python 3.13.5.

kramar11 avatar Oct 07 '25 08:10 kramar11

Looking at the PR that introduced this, it seems like __post_init__ is intentionally not called when doing copy or replace operations. I am not entirely sure why that is, but it should probably at least be mentioned in the documentation, as this behaviour differs from e.g. dataclasses.

provinzkraut avatar Oct 26 '25 15:10 provinzkraut

Thanks for looking into this. But I think msgspec should really be calling __post_init__ to behave like dataclasses. Also when using copy.replace currently __post_init__ is not called unlike dataclasses does. This is a very unexpected behaviour for devs using msgspec as a dataclass replacement and only mentioning this in the documentation is in my opinion not really the right thing to do.

kramar11 avatar Nov 03 '25 08:11 kramar11

Yeah, I guess it makes sense to keep the behaviour the same. I've created #933 with a fix and some open questions.

provinzkraut avatar Nov 15 '25 15:11 provinzkraut