Mailing List Archive

Type annotation pitfall
Hi all,

this just caused me several hours of my life until I could whittle it down to
this minimal example. Simple question: Why is the x member of object "foo"
modified by initializing "bar"?

Obviously, initializing foo with None doesn't set foo.x at all. So I guess x
stays a class property, not an instance property. And instantiating bar just
added an item to the class property but didn't instantiate a new set. So
basically, neither foo nor bar ever had their "own" x, right?

Oooohh, dangerous! Never use mutable types in type hint, unless it's in
explicit dataclasses (which automatically add an appropriate __init__()?)

Now I must fine-comb all my code for more of these.

class Foo():
x : set = set()

def __init__(self, s):
if s:
self.x.add(s)

foo = Foo(None)
print(foo.x) # prints 'set()'
bar = Foo('abc')
print(foo.x) # prints '{'abc'}

--
https://mail.python.org/mailman/listinfo/python-list
Re: Type annotation pitfall [ In reply to ]
On 24/09/21 5:48 pm, Robert Latest wrote:
> Never use mutable types in type hint,

No, the lesson is: Don't mutate a shared object if you don't want
the changes to be shared.

If you want each instance to have its own set object, you need to
create one for it in the __init__ method, e.g.

class Foo():
x : set

def __init__(self, s):
self.x = set()
if s:
self.x.add(s)

--
Greg
--
https://mail.python.org/mailman/listinfo/python-list
Re: Type annotation pitfall [ In reply to ]
I don't think this has anything to do with typing or providing type hints.
The type hint is the `: set` part, not the `= set()` part.
You can declare the type without assigning to the variable.
Indeed, as you already said, `x` is a class property here, and is shared
amongst instances of the class.
It might be a good idea to move the attribute assignment to the `__init__`
method.

In the following way, you can safely provide the type hint:

```python
class Foo:
x: set

def __init__(self, s):
self.x = set()
if s:
self.x.add(s)
```

Or, even shorter:

```python
class Foo:
def __init__(self, s: str):
self.x: set[str] = {s} if s else set()

print(reveal_type(Foo.x)) # mypy only
```

On Fri, Sep 24, 2021 at 7:58 AM Robert Latest via Python-list <
python-list@python.org> wrote:

> Hi all,
>
> this just caused me several hours of my life until I could whittle it down
> to
> this minimal example. Simple question: Why is the x member of object "foo"
> modified by initializing "bar"?
>
> Obviously, initializing foo with None doesn't set foo.x at all. So I guess
> x
> stays a class property, not an instance property. And instantiating bar
> just
> added an item to the class property but didn't instantiate a new set. So
> basically, neither foo nor bar ever had their "own" x, right?
>
> Oooohh, dangerous! Never use mutable types in type hint, unless it's in
> explicit dataclasses (which automatically add an appropriate __init__()?)
>
> Now I must fine-comb all my code for more of these.
>
> class Foo():
> x : set = set()
>
> def __init__(self, s):
> if s:
> self.x.add(s)
>
> foo = Foo(None)
> print(foo.x) # prints 'set()'
> bar = Foo('abc')
> print(foo.x) # prints '{'abc'}
>
> --
> https://mail.python.org/mailman/listinfo/python-list
>
--
https://mail.python.org/mailman/listinfo/python-list
Re: Type annotation pitfall [ In reply to ]
On Fri, Sep 24, 2021 at 11:43 PM Peter Saalbrink
<petersaalbrink@gmail.com> wrote:
>
> I don't think this has anything to do with typing or providing type hints.
> The type hint is the `: set` part, not the `= set()` part.
> You can declare the type without assigning to the variable.
> Indeed, as you already said, `x` is a class property here, and is shared
> amongst instances of the class.
> It might be a good idea to move the attribute assignment to the `__init__`
> method.
>
> In the following way, you can safely provide the type hint:
>
> ```python
> class Foo:
> x: set
>
> def __init__(self, s):
> self.x = set()
> if s:
> self.x.add(s)
> ```
>

To be clear, this isn't a case of "never use mutables as class
attributes"; often you *want* a single mutable object to be shared
among instances of a class (so they can all find each other, perhaps).
If you want each instance to have its own set, you construct a new set
every object initialization; if you want them to all use the same set,
you construct a single set and attach it to the class. Neither is
wrong, they just have different meanings.

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list