import os try: from email.mime.multipart import MIMEMultipart except ImportError: from email.MIMEMultipart import MIMEMultipart m = MIMEMultipart('form-data') print m.items() m.as_string() print m.items() print out: [('Content-Type', 'multipart/form-data'), ('MIME-Version', '1.0')] [('Content-Type', 'multipart/form-data; boundary="===============0836597002796039051=="'), ('MIME-Version', '1.0')] The latter is correct, the former is not - it is missing the boundary! items() should behave the same regardless of whether or not as_string() has been called. Confirmed in 2.4 and 2.6
The problem here is that when you are constructing a message the boundary is calculated "as needed", and returning the list of items is not one of the times when the boundary is needed. I agree that it is not good design that as_string can mutate the object it is called on; however the alternative (throwing away the calculated boundary after message Generation) means that stringifying the same message twice would produce two strings that are not equal...which also seems like bad design. But perhaps that would be less bad design. The underlying problem here is that the things being returned by items are strings, when they should be Header objects. That problem isn't going to be resolved until the email package rewrite happens.
Thinking about this further, it seems to me that it is in fact more important that turning a message into a string produces the same result no matter how many times it is called on the same (otherwise unmutated) object than that it avoid mutating the object tree itself. (This is called idempotence and it is an important interface concept in certain circumstances, and I think this is one of them). The documentation should make the expectations clear, however.