1

In my codebase there is some amount of None.__class__. PyCharm marks this as as a warning:

Cannot find reference '__class__' in 'None'

I am using PyCharm 2022.3 (Community Edition) if that matters.

However, when I try it out in REPL I get this sensible output which seems to be consistent across different python versions (including Python 2.7 and Python 3.10) from what I have tried:

>>> None.__class__
<type 'NoneType'>

Is there a hidden danger I am not seeing? The documentation https://docs.python.org/3/library/constants.html#None seems to suggest NoneType is a proper part of the language and not some implementation-quirk.

bad_coder
  • 11,289
  • 20
  • 44
  • 72
julaine
  • 382
  • 3
  • 12
  • 1
    Is there any valid reason to use `None.__class__`? – matszwecja May 11 '23 at 13:46
  • There are some functions that expect a class as a parameter and sometimes these get called with the NoneType. Pretty sure it could be done in another way and I do not know the rationale for why None.__class__ was chosen. – julaine May 11 '23 at 13:49
  • Is the warning part of the standard linter or a PEP 8 one? If standard, you should be able to turn it off. if PEP 8, then there is a very good reason not to use `None.__class__`. – Simon B May 11 '23 at 13:50
  • 2
    Try to avoid accessing dunder attributes where possible. Instead of `None.__class__`, you can use `types.NoneType` or `type(None)` (which is what `types` uses to define `NoneType`). – chepner May 11 '23 at 13:54
  • 1
    @SimonB it is a standard inspection but not specific to this None-situation - it is part of the important 'Unresolved references'-inspection. – julaine May 11 '23 at 13:58
  • @chepner That does seem to be the minimally invasive change that I need. Thank you. – julaine May 11 '23 at 13:59
  • 1
    Having said that, I don't know why PyCharm is issuing a warning (or at least, why it words it that way). The `__class__` attribute is, as far as I know, a defined part of the language, not a CPython-specific implementation detail. – chepner May 11 '23 at 14:03

1 Answers1

-1

which seems to be consistent across different python-versions (including python2.7 and python3.10)

Location of the NoneType has changed across Python versions, see Where is the NoneType located?

The two main ways of getting NoneType from Python 3.10 onward are calling the builtin type(None) or importing it, both are equivalent although using type(None) saves one import:

from types import NoneType
assert type(None) is NoneType

You may need to adjust PyCharm's inspections to be version specific for your project, see Wrong Python version in PyCharm's inspections.

>>> None.__class__
<type 'NoneType'>

Is there a hidden danger I am not seeing?

Using builtin functions, in this case type(), is always preferred. Calling the builtin type() would also be portable across Python versions.

The alternative of calling the __class__ descriptor directly makes things generally more complicated, see Difference between type(obj) and obj.__class__ and also Python __class__(). Lastly, in typeshed the NoneType is remarkably simple.


I do not think that this is a PyCharm linter bug but that the warning follows the same trend as mypy, see Warn when NoneType is used in a type #11288 and Treating NoneType as None and warning against using NoneType. #13153.

In effect, using mypy on the snippet:

from types import NoneType

my_var = None.__class__
the_type: NoneType = my_var

gives:

nonetype_test.py:4: error: NoneType should not be used as a type, please use None instead  [valid-type]
nonetype_test.py:4: error: Incompatible types in assignment (expression has type "Type[None]", variable has type "None")  [assignment]

If instead we use builtins.type with subscripting (see the end note under the deprecated typing.Type):

the_type2: type[None] = type(None)

Mypy doesn't complain (and neither does the PyCharm linter):

Success: no issues found in 1 source file
bad_coder
  • 11,289
  • 20
  • 44
  • 72
  • The second half of your answer gets some things mixed up. Using `NoneType` as a type *annotation* is a mypy error, but there's no indication that the questioner is asking about type annotations. For runtime type checking, you need the real `NoneType` type, not `None` - `isinstance` doesn't do the weird `None` special-casing that static type checkers do. – user2357112 May 13 '23 at 16:37
  • Also, mypy reports no errors about the attempt to access `None.__class__`. Unlike PyCharm, mypy is totally fine with people accessing that. It's reporting errors for the attempt to annotate it with `NoneType`, first because mypy just doesn't like people using `NoneType` as an annotation, and second because it's the wrong type - `NoneType` is the type of `None`, not the type of `None.__class__`. – user2357112 May 13 '23 at 16:37
  • @user2357112 what the OP is asking about is the PyCharm linter warning, they explain in the comments that their use case is indeed type hinting. So the answer addresses the linter warning and the use case. – bad_coder May 13 '23 at 16:43
  • There is nothing in the questioner's comments that suggests they're doing type hints. They're passing `None.__class__` as a parameter to functions that take a class. – user2357112 May 13 '23 at 16:44
  • @user2357112 I've seen you raise objections where you don't fully read the question and this is another one of those cases, here's the quote you missed: *" Pretty sure it could be done in another way and I do not know the rationale for why None.\_\_class\_\_ was chosen."*. – bad_coder May 13 '23 at 16:45
  • There is nothing in that sentence about type hints. – user2357112 May 13 '23 at 16:47
  • @user2357112 that's because you didn't read the sentence before it. But provide a use case (the question doesn't have the MRE you're talking about) and ask a separate question if you want. – bad_coder May 13 '23 at 16:48
  • The sentence before it is "There are some functions that expect a class as a parameter and sometimes these get called with the NoneType." They are *passing* this type to functions that take a type. There is no indication that these functions have any particular annotations. – user2357112 May 13 '23 at 16:51
  • @user2357112 yes there's no MRE, but as far as an MRE was provided and a use case mentioned I answered it. Go ahead and provide a use case for the objection you're raising, I'd like to see it. (Also, 99% of the users coming across the linter warning will be using this as a type hint, I also answered having them in mind.) – bad_coder May 13 '23 at 16:52
  • A use case for... passing `None.__class__` to a function that takes a class? Sure. Say you're registering hooks to handle various types in some JSON processing code you're writing. You call `register(str.__class__, stringhandler)` to register the handler you wrote for strings, `register(list.__class__, listhandler)` to register the handler you wrote for lists, and `register(None.__class__, nullhandler)` to register the handler you wrote for JSON nulls, which get deserialized as Python None. – user2357112 May 13 '23 at 16:56
  • @user2357112 complete overkill, show me a side-by-side comparison where that solution is simpler and more maintainable than the alternatives. – bad_coder May 13 '23 at 17:17
  • It doesn't have to be simpler and more maintainable for people to write it. People write complete overkill all the time. If you want something simpler, though... how about `isinstance(value, (str, bytes, None.__class__))`? Direct quote from the [standard library](https://github.com/python/cpython/blob/46f1c78eebe08e96ed29d364b1804dd37364831d/Lib/multiprocessing/shared_memory.py#L289). – user2357112 May 13 '23 at 17:26
  • @user2357112 according to the data model `__class__` is only used for classes and modules, by registering to `None.__class__` you're registering a virtual subclass to what's essentially a singleton and doesn't need to be thought of as a class. It also seems poor form to not test for `None` or `NoneType` using `is` - PEP 8: *"Comparisons to singletons like None should always be done with is or is not"*. But what's more, if you later want to use nominal subtyping and want a type guard against that subclass inheriting from `NoneType` what do you plan on using for it..? Shot yourself in the foot? – bad_coder May 14 '23 at 00:35