Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Implement new NamedTuple syntax #2245

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

Merged
merged 5 commits into from
Oct 25, 2016
Merged

Conversation

ilevkivskyi
Copy link
Member

Starting from Python 3.6b1, typing.NamedTuple supports the PEP 526 syntax:

from typing import NamedTuple

class Employee(NamedTuple):
    name: str
    salary: int

manager = Employee('John Doe', 9000)

Here is the support of this feature for mypy. It works only with --fast-parser, since it is required to parse PEP 526 syntax. Roughly half of the tests are simply adapted from tests for function NamedTuple syntax, plus another half are specific for the new syntax.

@gvanrossum Please take a look.

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from the extensive test suite this was actually pretty easy! I have a bunch of suggestions here.

def visit_class_def(self, defn: ClassDef) -> None:
# special case for NamedTuple
for base_expr in defn.base_type_exprs:
if isinstance(base_expr, NameExpr):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, this should probably also check for MemberExpr (similar to what's happening in check_namedtuple()).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

def visit_class_def(self, defn: ClassDef) -> None:
# special case for NamedTuple
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd refactor this into a helper function. Also it probably should be called after clean_up_bases_and_infer_type_variables().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@@ -545,7 +545,57 @@ def check_function_signature(self, fdef: FuncItem) -> None:
elif len(sig.arg_types) > len(fdef.arguments):
self.fail('Type signature has too many arguments', fdef, blocker=True)

def check_namedtuple_classdef(self, defn: ClassDef) -> Tuple[List[str], List[Type]]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Believe it or not, mypy's (loose) convention for helper functions is that they follow the main (or only) call site.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talking about placement, it would be nice to have it closer to other namedtuple-handling functions (~ 1600)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elazarg 1600 is probably too far. I put it in the same place where other ClassDef helpers are (~ 750).

return items, types

def analyze_namedtuple_classdef(self, defn: ClassDef) -> None:
node = self.lookup(defn.name, defn)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Careful, this may return None.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@@ -545,7 +545,57 @@ def check_function_signature(self, fdef: FuncItem) -> None:
elif len(sig.arg_types) > len(fdef.arguments):
self.fail('Type signature has too many arguments', fdef, blocker=True)

def check_namedtuple_classdef(self, defn: ClassDef) -> Tuple[List[str], List[Type]]:
NAMEDTUP_CLASS_ERROR = 'Invalid statement in NamedTuple definition; ' \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use () for the line continuation instead of \.

if len(defn.base_type_exprs) > 1:
self.fail('NamedTuple should be a single base', defn)
items = [] # type: List[str]
types = [] # type: List[Type]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks hard to prove (and preserve in future edits) that these have the same length. Maybe make a list of tuples and separate them at the end using list(zip(*x))?

Copy link
Contributor

@elazarg elazarg Oct 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, and this should be uniform in other places (e.g. parse_namedtuple_fields_with_types). In my PR I did not refactor it, trying to stay somewhat closer to the original implementation, A namedtuple Field would add to readability: instead of returning Tuple[List[str], List[Type], bool] we can return Tuple[List[Field], bool]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list(zip(*x)) will require a special-casing for empty namedtuples, also mypy complains about its own code (probably because of not very precise stub for zip). I refactored this part, so that it is clear that the two lists have always equal length (there are only two appends now that stay next to each other) and added comments.

if (not isinstance(stmt, PassStmt) and
not (isinstance(stmt, ExpressionStmt) and
isinstance(stmt.expr, EllipsisExpr))):
self.fail(NAMEDTUP_CLASS_ERROR, stmt)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't namedtuples have methods? Subclasses can.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because collections.namedtuple (and consequently typing.NamedTuple) does not support this. I was thinking about adding support for this in typing.py, but could not find a good solution (the main problem is support for super() in methods). Another argument is that named tuple is intended to be something simple (like record in some other languages).

# x: int assigns rvalue to TempNode(AnyType())
self.fail('NamedTuple does not support initial values', stmt)
if stmt.type is None:
self.fail('NamedTuple field type must be specified', stmt)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you get this the previous fail() also triggers, right? I think that if someone writes x = 1 they deserve to get a single error that explains why that's not allowed -- they may not even realize you see this as an "initial value".

(Also, what happens when they write x = 1 # type: int ?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I changed this so that a single "generic" ... expected "field_name: field_type" error is displayed (also for x = 1 # type: int, since this will not work in typing.py). A more specific error Right hand side values are not supported in NamedTuple is displayed for x: int = 1 (the generic error could be not completely clear in this case).

@ilevkivskyi
Copy link
Member Author

@gvanrossum I have pushed new commits with response to comments. Please take a look.

@gvanrossum gvanrossum merged commit ec5c476 into python:master Oct 25, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants