attrs icon indicating copy to clipboard operation
attrs copied to clipboard

asdict and astuple raise exceptions on instances with uninitialized attributes

Open tmewett opened this issue 3 years ago • 2 comments

attrs 20.1.0, Python 3.8.3.

The asdict and astuple functions raise exceptions on instances with unset init=False attributes. I ran into this while looking into Tinche/cattrs#67, and was surprised to find attrs' serialization fails too. Consider the following code:

import attr

@attr.s
class TestClass:
    a = attr.ib()
    b = attr.ib(init=False)

x = TestClass(1)
# x.b = 2
print(x)
print(attr.asdict(x))

attr.astuple(x) raises an AttributeError as x.b is unset. However, it is reported fine by print(x) as TestClass(a=1, b=NOTHING). Uncommenting the line x.b = 2 fixes the problem.

Two alternatives I would suggest for dealing with this:

  1. Have the functions return attr.NOTHING for uninitialized attributes, and document this; or
  2. Document that these functions are invalid on such instances

I think (1) would be preferable, and I'm happy to submit a PR if that's wanted?

tmewett avatar Aug 24 '20 14:08 tmewett

How exactly would those functions determine, that the attribute is uninitialized? Just blanket catching AttributeErrors is likely to mask bugs.

If you declare an attribute and tell attrs to not initialize it, you're saying that you are taking responsibility for said attributes. So what are the use-cases here that you'd enable? If you want to initialize it later in the lifetime of an object, give it some default value and overwrite it then.

hynek avatar Aug 25 '20 05:08 hynek

How exactly would those functions determine that the attribute is uninitialized?

They can just check if an attr.ib registered on the class is actually set, using hasattr. Just a check before the getattr at the top of the attribute loop.

I don't have an identified use-case, it's just something that I expected to work. It's a question of correctness; should these functions work on any instance of an attrs class? Or should they only work for instances which fulfill extra criteria? I don't have a preference, but if the latter is chosen I would prefer a hint in the docs about that--maybe I could add a note to the init docs like this?

init (bool) – Include this attribute in the generated __init__ method. If you set this to False, you should ensure the attribute is initialized properly by a post-init method.

tmewett avatar Aug 25 '20 09:08 tmewett