Python how to iterate over StrEnum without linter (PyCharm inspections) unresolved references - python

I have implemented a StrEnum (aka enforcing Enum members to be str) per the instructions within the Python 3.6 enum docs, section 8.13.13.4 Others.
from enum import Enum
class StrEnum(str, Enum):
"""Enum where members are strings."""
I have a subclass of StrEnum where I use a for loop to iterate through it.
class TestStrEnum(StrEnum):
"""Test string enum."""
A = "Apple"
B = "Bananas"
for enum_ in TestStrEnum:
print(f"name = {enum_.name}, value = {enum_.value}.")
This works fine at runtime. However, my linter complains about an unresolved reference when referring to the name and value attributes:
How can I get my linter to not complain?
The problem seems to be that the linter is not properly inspecting within the StrEnum._member_map_. I think the solution may be to override a dunder method within StrEnum, but am not sure which one to use.
Workaround Solution
Here is a workaround I figured out by using the __members__ attribute to access the members in an explicit manner.
for enum_member_name, enum_member in TestStrEnum.__members__.items():
print(f"name = {enum_member.name}, value = {enum_member.value}.")
My linter is PyCharm 2019.2.6 CE's code inspections.
**EDIT**
Per #user2235698's answer, it seems this is a known issue with PyCharm: PY-36205, raised in May 2019. Since this issue only currently has 6 upvotes (as of April 11th, 2020), I am not sure it will be fixed anytime soon.
If anyone has a good solution that can be used until the issue is resolved, I am still interested.

It is a known issue, please vote for https://youtrack.jetbrains.com/issue/PY-36205 (thumbs up near the issue title)

Related

Where are the Type annotation constraints (`ValueRange`/`MinLen` etc) in python 3.9?

After seeing the (awesome) new Annotated type annotation in python 3.9 (varaidic type constraints!), I rushed to upgrade so I could check them out.
(https://docs.python.org/3/library/typing.html?highlight=valuerange)
But when I tried using ValueRange[min,max] or MaxLen[n] - I couldn't seem to find them anywhere..
PyCharm didn't offer me any help, and they don't seem to be in the typing module where I might expect them.
The docs feature them but googling I can't find any reference online as to how to actually import them.
Are they not in the language yet? or just in some new module I'm not aware of?
As others have said, these classes are just an example of what can be annotated. Annotation just grabs a certain variable and adds some "hints" to them via the class you created (MaxLen, ValueRange, etc).
You can then obtain which "hints" are related to every parameter using get_type_hints, and crawl one per one the parameters with its hits and do the checks you want (not the idea Annotation was intended, though I also found it interesting). You have a full example in this other post.
You could define it using dataclasses.dataclass:
from dataclasses import dataclass
#dataclass
class ValueRange:
min: float
max: float
or alternatively using collections.namedtuple:
from collections import namedtuple
ValueRange = namedtuple("ValueRange", ["min", "max"])

Linting classes created at runtime in Python

For context, I am using the Python ctypes library to interface with a C library. It isn't necessary to be familiar with C or ctypes to answer this question however. All of this is taking place in the context of a python module I am creating.
In short, my question is: how can I allow Python linters (e.g. PyCharm or plugin for neovim) to lint objects that are created at runtime? "You can't" is not an answer ;). Of course there is always a way, with scripting and the like. I want to know what I would be looking at for the easiest way.
First I introduce my problem and the current approach I am taking. Second, I will describe what I want to do, and ask how.
Within this C library, a whole bunch of error codes are defined. I translated this information from the .h header file into a Python enum:
# CustomErrors.py
from enum import Enum
class CustomErrors(Enum):
ERROR_BROKEN = 1
ERROR_KAPUTT = 2
ERROR_BORKED = 3
Initially, my approach is to have a single exception class containing a type field which described the specific error:
# CustomException.py
from CustomErrors import CustomErrors
class CustomException(Exception):
def __init__(self, customErr):
assert type(customErr) is CustomError
self.type = customErr
super().__init__()
Then, as needed I can raise CustomException(CustomErrors.ERROR_KAPUTT).
Now, what I want to do is create a separate exception class corresponding to each of the enum items in CustomErrors. I believe it is possible to create types at runtime with MyException = type('MyException', (Exception,), {'__doc__' : 'Docstring for ABC class.'}).
I can create the exception classes at runtime like so:
#CustomException.py
from CustomErrors import CustomErrors
...
for ce in CustomErrors:
n = ce.name
vars()[n] = type(n, (Exception,), {'__doc__' : 'Docstring for {0:s} class.'.format(n)})
Note: the reason I want to create these at runtime is to avoid hard-coding of an Exception list that change in the future. I already have the problem of extracting the C enum automatically on the backburner.
This is all well and good, but I have a problem: static analysis cannot resolve the names of these exceptions defined in CustomException. This means PyCharm and other editors for Python will not be able to automatically resolve the names of the exceptions as a suggested autocomplete list when the user types CustomException.. This is not acceptable, as this is code for the end user, who will need to access the exception names for use in try-except constructs.
Here is the only solution I have been able to think of: writing a script which generates the .py files containing the exception names. I can do this using bash. Maybe people will tell me this is really the only option. But I would like to know what other approaches are suggested for solving this problem. Thanks for reading.
You can add a comment to tell mypy to ignore dynamically defined attribute errors. Perhaps the linters that you use share a similar way to silence such errors.
mypy docs on silencing errors based on error codes
This example shows how to ignore an error about an imported name mypy thinks is undefined:
# 'foo' is defined in 'foolib', even though mypy can't see the
# definition.
from foolib import foo # type: ignore[attr-defined]

Typing NamedTuple as a class to include argument hints in PyCharm

EDIT: Type[X] does type hint to the class itself. The repl now has an example of that, too. PyCharm just doesn't use the __init__ information within the class to type hint an initialization elsewhere. If anyone knows why that is, I'll accept that answer. The below question is now just for context:
Let's say I have the following code:
from typing import NamedTuple, Type
class SubTuple(NamedTuple):
a: str
b: int
def get_tuple_type() -> Type[SubTuple]:
return SubTuple
test_tuple = get_tuple_type()
test_tuple_instance = test_tuple('test', 1)
Is there a way to type hint so that PyCharm will give me argument hints? Right now it gives me this:
which is rather useless. Hilariously, it does know the arguments, because when I type:
test_tuple.
it recommends all of the fields in the SubTuple. It just won't recommend them to me when I go to create an instance of the thing.
Also, https://repl.it/repls/BurdensomeInnocentKnowledge just so that you can see that the above code actually does work. The repl IDE also has no idea what to do with this thing, and is recommending that I call .a and .b on the class itself, which leads me to believe this might just be a general issue with what I'm trying to do in Python?
EDIT: I'm using Python 3.7; updated code to include what I've tried. It has the same result. Will update the repl, too.
This is a bug in PyCharm's static type checker, there are a few open bugs about NamedTuple on JetBrains bugtracker. The most up-to-date thread on the NamedTuple datatype is PY-18246. Fixes have been rolled out during 2020 but this specific case of correctly inferring the NamedTuple members after typing a class object has neither been reported nor solved.
The warning has also changed, if you try the same code with PyCharm 2020.3.3 Pro you'll get a different hint.
I tried checking the code with Mypy and it works correctly without any warnings. If you try causing an error by changing the types:
variable = test_tuple(1, 'test')
Mypy warns and correctly hints you about it:
error: Argument 1 to "SubTuple" has incompatible type "int"; expected "str"
error: Argument 2 to "SubTuple" has incompatible type "str"; expected "int"
Found 2 errors in 1 file (checked 1 source file)
Is there a way to type hint so that PyCharm will give me argument hints?
You are already type hinting correctly as PEP 484 recommends. If you use the Mypy static type checker it works, as for PyCharm it's a matter of waiting for a bug fix. There's nothing wrong with your type hinting and it shouldn't be changed in this case.

Should namedtuples follow constant name conventions in python?

I've written a small python module where I use a couple of namedtuples to pass info around because I find them very expressive. I considered these types and named them following the PEP8 convention for class names (CamelCased). However pylint sees the line:
PersonData = collections.namedtuple('PersonData', 'name surname age')
at the module's global scope and goes: Invalid constant name 'PersonData'.
Am I misusing namedtuples? What's the pythonic recommendation? I can only think of suppressing the warning, renaming the structure to PERSON_DATA, or making it a full class. Note that, in my case, it wouldn't make sense for it to have methods though.
If the answer is to suppress the warning. Wouldn't this be a recurring problem with pylint vs named tuples?
(using pylint-0.26.0, python-2.7.4)
This issue has been solved in newer versions of pylint.
My system was picking the version from the ubuntu (13.04) repositories (pylint-0.26.0).
Using a virtualenv I could pip install pylint-1.0.0 which recognizes named tuples and will actually enforce using the same format as classes.
Note that it won't pick it up if you're doing string manipulation on the fields string. For example:
PersonData = collections.namedtuple('PersonData', 'name ' + 'surname age')
will still cause pylint-1.0.0 to spit out the error code for invalid constant name. In this case the only workaround is to disable the warning as per Martijn Pieters suggestion.
You can ignore pylint here, you are using the camel case naming convention exactly right.
You can suppress the warning:
PersonData = collections.namedtuple('PersonData', 'name surname age') # pylint: disable-msg=C0103
namedtuple is a class factory, so use the naming conventions for a class.
This has been fixed in pylint-1.0.0 (see Pylint's Changelog):
For toplevel name assignment, the class name regex will be used if pylint can detect that value on the right-hand side is a class (like collections.namedtuple()).
Note that the namedtuple must be defined on the toplevel, if defined e.g. within a function pylint will still give a invalid-name warning.
in such case pylint should detect the name is assigned to a class an use the class name regexp.
Please submit a ticket on http://bitbucket.org/logilab/pylint

How can I tell PyCharm what type a parameter is expected to be?

When it comes to constructors, and assignments, and method calls, the PyCharm IDE is pretty good at analyzing my source code and figuring out what type each variable should be. I like it when it's right, because it gives me good code-completion and parameter info, and it gives me warnings if I try to access an attribute that doesn't exist.
But when it comes to parameters, it knows nothing. The code-completion dropdowns can't show anything, because they don't know what type the parameter will be. The code analysis can't look for warnings.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
peasant = Person("Dennis", 37)
# PyCharm knows that the "peasant" variable is of type Person
peasant.dig_filth() # shows warning -- Person doesn't have a dig_filth method
class King:
def repress(self, peasant):
# PyCharm has no idea what type the "peasant" parameter should be
peasant.knock_over() # no warning even though knock_over doesn't exist
King().repress(peasant)
# Even if I call the method once with a Person instance, PyCharm doesn't
# consider that to mean that the "peasant" parameter should always be a Person
This makes a certain amount of sense. Other call sites could pass anything for that parameter. But if my method expects a parameter to be of type, say, pygame.Surface, I'd like to be able to indicate that to PyCharm somehow, so it can show me all of Surface's attributes in its code-completion dropdown, and highlight warnings if I call the wrong method, and so on.
Is there a way I can give PyCharm a hint, and say "psst, this parameter is supposed to be of type X"? (Or perhaps, in the spirit of dynamic languages, "this parameter is supposed to quack like an X"? I'd be fine with that.)
EDIT: CrazyCoder's answer, below, does the trick. For any newcomers like me who want the quick summary, here it is:
class King:
def repress(self, peasant):
"""
Exploit the workers by hanging on to outdated imperialist dogma which
perpetuates the economic and social differences in our society.
#type peasant: Person
#param peasant: Person to repress.
"""
peasant.knock_over() # Shows a warning. And there was much rejoicing.
The relevant part is the #type peasant: Person line of the docstring.
If you also go to File > Settings > Python Integrated Tools and set "Docstring format" to "Epytext", then PyCharm's View > Quick Documentation Lookup will pretty-print the parameter information instead of just printing all the #-lines as-is.
Yes, you can use special documentation format for methods and their parameters so that PyCharm can know the type. Recent PyCharm version supports most common doc formats.
For example, PyCharm extracts types from #param style comments.
See also reStructuredText and docstring conventions (PEP 257).
Another option is Python 3 annotations.
Please refer to the PyCharm documentation section for more details and samples.
If you are using Python 3.0 or later, you can also use annotations on functions and parameters. PyCharm will interpret these as the type the arguments or return values are expected to have:
class King:
def repress(self, peasant: Person) -> bool:
peasant.knock_over() # Shows a warning. And there was much rejoicing.
return peasant.badly_hurt() # Lets say, its not known from here that this method will always return a bool
Sometimes this is useful for non-public methods, that do not need a docstring. As an added benefit, those annotations can be accessed by code:
>>> King.repress.__annotations__
{'peasant': <class '__main__.Person'>, 'return': <class 'bool'>}
Update: As of PEP 484, which has been accepted for Python 3.5, it is also the official convention to specify argument and return types using annotations.
PyCharm extracts types from a #type pydoc string. See PyCharm docs here and here, and Epydoc docs. It's in the 'legacy' section of PyCharm, perhaps it lacks some functionality.
class King:
def repress(self, peasant):
"""
Exploit the workers by hanging on to outdated imperialist dogma which
perpetuates the economic and social differences in our society.
#type peasant: Person
#param peasant: Person to repress.
"""
peasant.knock_over() # Shows a warning. And there was much rejoicing.
The relevant part is the #type peasant: Person line of the docstring.
My intention is not to steal points from CrazyCoder or the original questioner, by all means give them their points. I just thought the simple answer should be in an 'answer' slot.
I'm using PyCharm Professional 2016.1 writing py2.6-2.7 code, and I found that using reStructuredText I can express types in a more succint way:
class Replicant(object):
pass
class Hunter(object):
def retire(self, replicant):
""" Retire the rogue or non-functional replicant.
:param Replicant replicant: the replicant to retire.
"""
replicant.knock_over() # Shows a warning.
See: https://www.jetbrains.com/help/pycharm/2016.1/type-hinting-in-pycharm.html#legacy
You can also assert for a type and Pycharm will infer it:
def my_function(an_int):
assert isinstance(an_int, int)
# Pycharm now knows that an_int is of type int
pass

Categories