-
Notifications
You must be signed in to change notification settings - Fork 190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Type annotations in canopen #358
Comments
I'm fine with and definitely welcome type hinting, especially if it means that structured type info lands in the documentation as well. Regarding In your fork, why do you have the |
Let me give an example: class NmtBase(object):
def __init__(self, node_id: int):
self.id = node_id
self.network: Optional[Network] = None
def send_command(self, code: int):
super(NmtMaster, self).send_command(code)
logger.info(
"Sending NMT command 0x%X to node %d", code, self.id)
assert self.network # <-- This is needed to instruct the type checker to ignore None
self.network.send_message(0, [code, self.id]) # <-- Can't do this without checking self.network Using class NmtSlave(NmtBase):
def __init__(self, node_id: int, local_node: LocalNode): # With __future__ import annotations
def __init__(self, node_id: int, local_node: "LocalNode"): # Without It is optional from py3.7 and is not yet made mandatory. The feature is described in PEP 563. See https://docs.python.org/3/library/__future__.html |
Go for it! Static analysis will definitely find some issues, but hopefully the fixes are fairly easy like asserting that attributes are not None like in your example. |
What is reasonable accept criterias for type annotations in canopen? What is the definition of done? What tools should we aim compliance for (and with what settings)? Based on what I usually do I usually use mypy and pyright (the latter because I'm using VSCode) for type checking and pylint and/or flake8 for general linting. As pointed out in #346 , pylint is a commonly used tool. |
I've been working a lot with type annotating canopen lately as can be found in PR #360 . It has taken a lot more time than I expected, but I hope it can be of use. canopen uses a lot of dynamic types in variables and it was much work to figure that out. It is mostly done, but there is a few remaining errors to deal with. Please review the PR and give comments. Large or small. I've collected a series of comments and questions. I'm sorry for the length of this post, but it would help a lot if we could get some insight into this. 1) Use of multiple typesThis is a general type annotation advice: When using vars that can hold multiple types, e.g. There is a lot of this in canopen, and this is what's what have taken the most time to resolve. Its also harder to code with, because one have to test everything all the time. I'm sure there are lots of ´Optional` uses in canopen that can be removed, but its not always clear if the optional value serves a function or merely a convenience or habit. 2) Type annotating OD variable access is tediousDue to the dynamic nature of (canopen OD) objects (variable, array, record) and # This creates erros in type checkers
var: int = sdo[0x6061].raw
# This is what's needed to do full type checking
obj = sdo[0x6061] # obj might be Variable, Array, Record
assert isinstance(obj, Variable) # Limit type to Variable
data = obj.raw # data might be bool, int, float, str, bytes
assert isinstance(obj, int) # Limit type to int
var = data # Now its guaranteed that var is int Is the current design too flexible? It is impossible for the type checker to know that OD Some quick proposals as food for thought: sdo[0x6061] # Only returns `Variable`. Fails if used on an `Array` or `Record`
sdo.get_container(0x1234) # Returns `Array` or `Record`. Fails on `Variable`
sdo.get_variable(0x6061) # Named methods for getting variable only
sdo.get_array(0x1234)
var.raw_int # Only returns int. Fails if any other types are returned 3) Needed to check variable type when encoding objectsThe encoding of a parameter is stored in 4) Attribute injectionIn 5) Is a node_id value optional or not?Are there any use cases where initalization of 6) How should
|
With reference to the discussion in #339 with regards to type hints, I have started looking at the work needed for type annotating canopen. What is the overall ambitions with regards to implementation of type hints? What level of hinting and static code analysis are we aiming for? What tool? mypy, flake8, pyright/pylance ?
I'm a big proponent of type annotated python. It helps development, navigation in the editor works and it shows the expected types for calls and operations and thereby reducing bugs and misunderstandings. I think adding typing to canopen is the right thing to do. The benefits outweighs the disadvantages.
Having a full compliant type annotated library will reduce the degrees of freedom in the codebase. You can do more "tricks" with python than a linter is able to understand, and thus coding for it requires a degree of code conformity. canopen relies on a lot of duck typing and dynamic types (especially variables that conditionally contain
None
). This results a quite a bit of warnings and/or code constructs to avoid the warnings. - And rightly so, as the code in many cases is not executing any type checks to verify the datatype before invoking an operation. Lastly, many of these linting tools are very opinionated on code structure, so we must expect some functional changes.I've given a stab at implementing type annotation in canopen (see link below). I'm using mypy and pyright. After doing this I think we will have to face a few discussions on the correct and intended behavior on a few details. E.g. the use
None
as optional value.sveinse@4e8925e
The text was updated successfully, but these errors were encountered: