Skip to content
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

Subclasses of NamedTuple #3739

Closed
progval opened this issue Jul 19, 2017 · 5 comments
Closed

Subclasses of NamedTuple #3739

progval opened this issue Jul 19, 2017 · 5 comments

Comments

@progval
Copy link

progval commented Jul 19, 2017

Hi,

I often use a subclass to a namedtuple to add optional arguments (and/or methods) to a namedtuple.

For instance, this gives this kind of code:

     1	from typing import *
     2	
     3	_Foo = NamedTuple('_Foo', [
     4	    ('bar', str),
     5	    ('baz', str),
     6	    ])
     7	
     8	class Foo(_Foo):
     9	    def __new__(cls, bar, baz='z') -> 'Foo':
    10	        return _Foo.__new__(cls, bar, baz)
    11	
    12	def f() -> None:
    13	    print(repr(Foo('r')))
    14	
    15	f() # prints Foo(bar='r', baz='z')

which I think is completely valid Python.

However, Mypy raises the following errors:

foo.py:10: error: Too many arguments for "__new__" of "object"
foo.py:10: error: Argument 1 to "__new__" of "object" has incompatible type Type[Foo]; expected Type[object]
foo.py:13: error: Too few arguments for "Foo"

Is there something I am doing wrong?

Thanks!

@miedzinski
Copy link
Contributor

It seems to work if you use super() instead of referencing _Foo directly. I don't believe it has anything to do with namedtuples.

from typing import NamedTuple

_Foo = NamedTuple('_Foo', [
    ('bar', str),
    ('baz', str),
])

class Foo(_Foo):
    def __new__(cls, bar, baz='z') -> 'Foo':
        return super().__new__(cls, bar, baz)

OTOH I'm not sure whether mypy properly typechecks namedtuples unless they subclass directly from typing.NamedTuple. See

mypy/mypy/semanal.py

Lines 890 to 906 in 8709da5

def analyze_namedtuple_classdef(self, defn: ClassDef) -> Optional[TypeInfo]:
# special case for NamedTuple
for base_expr in defn.base_type_exprs:
if isinstance(base_expr, RefExpr):
base_expr.accept(self)
if base_expr.fullname == 'typing.NamedTuple':
node = self.lookup(defn.name, defn)
if node is not None:
node.kind = GDEF # TODO in process_namedtuple_definition also applies here
items, types, default_items = self.check_namedtuple_classdef(defn)
info = self.build_namedtuple_typeinfo(
defn.name, items, types, default_items)
node.node = info
defn.info = info
defn.analyzed = NamedTupleExpr(info)
return info
return None
.

@progval
Copy link
Author

progval commented Jul 19, 2017

I didn't know I could use super() in __new__, thanks.

I replaced _Foo with super(), and now I am only getting this:

foo.py:13: error: Too few arguments for "Foo"

@ilevkivskyi
Copy link
Member

@progval
By the way, switching to Python 3.6 will give you typing.NamedTuple that supports optional arguments, methods, docstrings, and nice static typing (supported by mypy):

from typing import NamedTuple

class Link(NamedTuple):
    """Represent a link between two nodes."""
    start: int
    end: int
    label: str = '<no name>'
    def weight(self) -> float:
        return abs(self.end - self.start)/2

@Daenyth
Copy link

Daenyth commented Jul 19, 2017

Since you're on python3, you might want to use the syntax instead, like this:

class Foo(NamedTuple):
    bar: str
    baz: str = 'z'

I use this in my project and mypy deals with it just fine. It's also much more readable.

@ilevkivskyi
Copy link
Member

Concerning __new__ we already have #1279 so that this one can be closed

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

No branches or pull requests

4 participants