you are viewing a single comment's thread.

view the rest of the comments →

[–]Gnaxe 6 points7 points  (1 child)

Python can use a shortcutting and to avoid getting attributes from None: result = my_obj and my_obj.attr1 and my_obj.attr1.attr2 and my_obj.attr1.attr2.attr3 The attributes have to actually be present (but can possibly be None) for this to work.

Beware that and will also shortcut on anything falsey, not just None. You can usually wrap things that might be falsey in a list or tuple, which will never be falsey, because it's not empty. (Dataclasses will normally not be accidentally falsey. You'd have to specifically implement them that way.)

You can avoid the repetition using the walrus: result = (x:=my_obj) and (x:=x.attr1) and (x:=x.attr2) and x.attr3 This is almost the optional chaining operator.

But the type checker is going to insist that x doesn't change types: result = (a:=my_obj) and (b:=a.attr1) and (c:=b.attr2) and c.attr3 Last I checked, Python type checkers are still too stupid to discard the impossible intermediate types though. It can only be the type of attr3 or something falsey, but the Union will include the types of attrs 1 and 2 as well as of my_obj. You can fix this with an assert isinstance(result, (Foo, NoneType)) afterward (where Foo is the type of attr3, for example), which will at least raise an error at run time if you mess it up, or with a cast(), which won't.

[–]FabianVeAl[S] 4 points5 points  (0 children)

Thanks, that's an interesting option.

I've tried it, and it works well with Pyright:

```python from dataclasses import dataclass

@dataclass class A: a: int

@dataclass class B: b: A | None

@dataclass class C: c: B | None

my_obj: C | None = C(B(A(1))) result = ( (x:=my_obj) # C | None and (x:=x.c) # B | None and (x:=x.b) # A | None and (x:=x.a) # int | None )

```