0

We have a bigger Python project and the run-time errors became more and more annoying, like mistypings, that would be avoidable using a compiled language. I started to use the typing feaures of Python to catch many errors in code "inspection" time instead of runtime, like in C++ or ObjectPascal. Using type annotation and PyCharm is much better, though I have discovered a serious problem:

class TNormalClass():
    def __init__(self):
        self.x : int = 1

c1 = TNormalClass()
c1.x = 2
c1.y = 5  # no error signalized here, but should have been!

After searching on the Internet i've found a solution with __slots__:

class TStrictClass():
    __slots__ = ['x']
    def __init__(self):
        self.x : int = 1

c1 = TStrictClass()
c1.x = 2
c1.y = 5  # PyCharm code inpection warning + runtime error: "object has no attribute 'y'"

That's what I wanted, but e.g. we have an object with 40 long named members. Duplicating every name in the __slots__ is far from elegant in the year 2022.

I've tried the library "autoslot", the c1.y = 5 resulted in runtime error, but this error was not detected inspection time with the PyCharm.

Is there some feature already in Python or maybe planned something in 3.10+ which would help me to create my "strict" objects

  • without duplicating every member in the __slots__
  • and also works with the PyCharm code inspection?

br, nvitya

  • I don't think you should think of using `__slots__` as "duplicating every name". What would you expect the behavior to be if someone mistyped the `__init__` method and wrote `self.y: int = 1`? That error wouldn't be caught unless `__slots__` is your "source of truth" for which attributes are valid. – larsks Oct 18 '22 at 15:35
  • I note that if `TStrictClass` is a [dataclass](https://docs.python.org/3/library/dataclasses.html) the error checking seems to be better, even without using `__slots__` (at least, pyright catches the error). – larsks Oct 18 '22 at 15:38
  • 1
    I'd also suggest using `dataclasses` or maybe the `attrs` package if you want to set `slots` before Python 3.10 – Sam Mason Oct 18 '22 at 15:53
  • @larsks: Thank you for your notes. Maintaining a class with two screens of member definitions would be a pain with ```__slots__``` so I would like to avoid it. The ```dataclass``` without slots does not give runtime error for ```c1.y = 5```. PyCharm unfortunately does not catches it, even with ```slots```. Unfortunately I did not succeed to install ```pyright```, it seems it requires some node.js environment. – Viktor Nagy Oct 19 '22 at 09:44

2 Answers2

0

This has been answered here: Prevent creating new attributes outside __init__

As the second answer there says, __slots__ is the pythonic way.

jprebys
  • 2,469
  • 1
  • 11
  • 16
  • Thank you for your answer. I think I understand the concept of ```__dict__``` and ```__slots__```. But it is still much more complicated than in C++/Java/ObjectPascal. And one half of the identifiers are even String literals. I've seen good progress in the Python in the years towards the usability. I think this has to be solved too, and best without special decorators. – Viktor Nagy Oct 19 '22 at 09:33
0

As suggested in the comments, you probably want to try using dataclasses that were introduced in Python 3.7. If you want more features or need to support older versions of Python you could try using the attrs package that dataclasses was based on.

I'd write your code as:

from dataclasses import dataclass

@dataclass(slots=True)
class TNormalClass:
    x: int = 1

Note that slots was only introduced in Python 3.10, hence my references to the attrs package.

These declarations are exposed to Python's typing support, so will be picked by tools like mypy and by editors like PyCharm.

Sam Mason
  • 15,216
  • 1
  • 41
  • 60
  • Thank you for your answer. I've just tried the ```@dataclass(slots=True)``` with Python 3.10 and PyCharm 2022.2.3. ```c1.y = 5``` is a runtime error, mypy catches it at inspection time, but the PyCharm unfortunately not. Unfortunately the mypy gives too much false positive output at our current project (e.g. ```Skipping analyzing "wx": module is installed, but missing library stubs or py.typed marker```), and more complicated to use it. It seems, that I need to wait for some PyCharm upgrade then... – Viktor Nagy Oct 19 '22 at 09:18
  • you can tell mypy to ignore specific packages: https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-library-stubs-or-py-typed-marker. it's nice having mypy run as part of a CI system on commits to check for significant regressions. can take some work to keep types annotated, but for some projects it's certainly worth it – Sam Mason Oct 19 '22 at 10:59
  • Just thought to try and I get a diagnostic error as expected, saying `"Foo" has no attribute "y"`. I'm personally using Helix, which in turn uses LSP for diagnostics. I assumed PyCharm would also be using LSP as it's become the conventional way to add language support to tooling – Sam Mason Oct 19 '22 at 11:33
  • One thing bothers me a little with the `dataclass` the field definitions are outside of `__init__` and so without `self`. Every other usage of the fields/members require the `self` in the code. I feel here a bit inconsistency, but I can live with it. For now I'll migrate to `@dataclass(slots=True)`, it seems that this is the best option for now. – Viktor Nagy Oct 20 '22 at 06:53
  • See [PEP-526](https://peps.python.org/pep-0526/#class-and-instance-variable-annotations) for the rationale behind this. The syntax feels natural to me, but I've been using it for a while now... – Sam Mason Oct 20 '22 at 21:00