Skip to content

Implement default values on NamedTuple #2719

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 14 commits into from
Mar 18, 2017

Conversation

JelleZijlstra
Copy link
Member

This implements python/typing#338 in mypy.

Should we have some special handling here to account for the fact that this will work in 3.6.1, but not in 3.6.0?

@@ -1855,8 +1862,14 @@ def add_method(funcname: str,

add_method('_replace', ret=selftype,
args=[Argument(var, var.type, EllipsisExpr(), ARG_NAMED_OPT) for var in vars])

def make_init_arg(var: Var) -> Argument:
default = default_items.get(var.name(), None)
Copy link
Member

Choose a reason for hiding this comment

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

The default value could be itself None. So that I would use sentinel = object() and then default_items.get(var.name(), sentinel) and below if default is sentinel.

Copy link
Member Author

Choose a reason for hiding this comment

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

The Python default value could be None, but as I understand it mypy would use a value of type mypy.types.NoneTyp internally. The previous code also passes None to indicate that the Argument had no default, so I think this is fine.

reveal_type(X(1)) # E: Revealed type is 'Tuple[builtins.int, builtins.int, fallback=__main__.X]'
reveal_type(X(1, 2)) # E: Revealed type is 'Tuple[builtins.int, builtins.int, fallback=__main__.X]'

X(1, 'a') # E: Argument 2 to "X" has incompatible type "str"; expected "int"
Copy link
Member

Choose a reason for hiding this comment

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

I would add few more test cases. For example, the None default value mentioned above, user defined classes as field types, wrong types like x: str = 5, etc.

Copy link
Member

Choose a reason for hiding this comment

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

Also it makes sense to add tests with classes inheriting form a named tuple with default values.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for these suggestions! The x: str = 5 case actually wasn't checked, and I had some trouble coming up with a way to make that throw an error, because the body of the NamedTuple is currently almost completely ignored. I managed to fix it by typechecking the body like a normal class. This has the side effect that some errors (stuff like x[0]: int) produce multiple errors on the same line, but that doesn't seem like a big deal.


class HasNone(NamedTuple):
x: int
y: Optional[int] = None
Copy link
Member

Choose a reason for hiding this comment

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

Thank you for writing more tests. I will be even more happy if you add few more tests for these (also with --strict-optional)

mypy/semanal.py Outdated
self.analyze_base_classes(defn)
self.analyze_metaclass(defn)
self.analyze_base_classes(defn)
self.analyze_metaclass(defn)

for decorator in defn.decorators:
Copy link
Member

Choose a reason for hiding this comment

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

Why do you need to analyze class decorators for named tuple?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't (although it would be nice to support namedtuple decorators in the future). I'll push an update to change this once tests pass.

Also, I think it shouldn't be hard to implement python/typing#352 by further adjusting the behavior of this function—I might try that once this PR is approved.

Copy link
Member

Choose a reason for hiding this comment

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

Also, I think it shouldn't be hard to implement python/typing#352 by further adjusting the behavior of this function—I might try that once this PR is approved.

I would be glad if you will do this.

mypy/semanal.py Outdated

self.bind_class_type_vars(defn)
if not is_named_tuple:
Copy link
Member

Choose a reason for hiding this comment

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

Sorry for coming up with new comments, but I think that this will be cleaner if you refactor this as:

if self.analyze_namedtuple_classdef(defn):
    # quick check
    self.enter_class(defn)
    defn.defs.accept(self)
    self.leave_class()
else:
    # full story
    ...


class Y(X):
def method(self) -> str:
return self.x + cast(str, self.y)
Copy link
Member

Choose a reason for hiding this comment

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

To be honest, I don't like casts of unsafe expressions in tests, but formally this test works as it should.
Maybe you could modify it?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll just change it to access self.y without using it. I did str(self.y) first but that doesn't work because the fixture doesn't have it.

@ilevkivskyi
Copy link
Member

@gvanrossum
Do you have any additional comments for this PR? I think it is almost ready (apart from two minor things).

@gvanrossum
Copy link
Member

gvanrossum commented Jan 23, 2017 via email

Copy link
Collaborator

@JukkaL JukkaL left a comment

Choose a reason for hiding this comment

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

Looks good, just a few test-related ideas.


class Parameterized(NamedTuple):
x: int
y: List[int] = [1] + [2]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add test case where the default value if [] and the type is List[int]. This will verify that the attribute type is used as type context.

y: int = 3

class Y(X):
def method(self) -> str:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Test calling base class __init__ in a subclass of a named tuple.

@JukkaL
Copy link
Collaborator

JukkaL commented Mar 18, 2017

Also can you resolve the merge conflict?

@JukkaL JukkaL merged commit c3b3177 into python:master Mar 18, 2017
@JukkaL
Copy link
Collaborator

JukkaL commented Mar 18, 2017

Thanks for implementing this!

@JelleZijlstra JelleZijlstra deleted the namedtupledefault branch May 4, 2017 04:07
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.

4 participants