Why isn't Python NewType compatible with isinstance and type?

Viewed 367

This doesn't seem to work:

from typing import NewType

MyStr = NewType("MyStr", str)
x = MyStr("Hello World")

isinstance(x, MyStr)

I don't even get False, but TypeError: isinstance() arg 2 must be a type or tuple of types because MyStr is a function and isinstance wants one or more type.

Even assert type(x) == MyStr or is MyStr fails.

What am I doing wrong?

5 Answers

Cross-reference: inheritance from str or int

Even more detailed in the same question: https://stackoverflow.com/a/2673802/1091677


If you would like to subclass Python's str, you would need to do the following way:

class MyStr(str):
  # Class instances construction in Python follows this two-step call:
  # First __new__, which allocates the immutable structure,
  # Then __init__, to set up the mutable part.
  # Since str in python is immutable, it has no __init__ method.
  # All data for str must be set at __new__ execution, so instead
  # of overriding __init__, we override __new__:
  def __new__(cls, *args, **kwargs):
    return str.__new__(cls, *args, **kwargs)

Then:

x = MyStr("Hello World")

isinstance(x, MyStr)

returns True as expected

looks might have been "fixed" in python 3.10:

https://docs.python.org/3/library/typing.html?highlight=typing#newtype

says:

Changed in version 3.10: NewType is now a class rather than a function. There is some additional runtime cost when calling NewType over a regular function. However, this cost will be reduced in 3.11.0.

I don't have 3.10 handy as I write this to try your example.

As at python 3.10...

I'd speculate that the answer to "Why isn't Python NewType compatible with isinstance and type?" ... is "It is a limitation of NewType".

I'd speculate that the answer to "What am I doing wrong?" ... is "nothing". You are assuming NewType makes a new runtime type, it appears that it doesn't.

And for what it's worth, I wish it did work.

maybe you want a type that is methods like str does but is not a str?

A simple way to get that effect is just:

class MyStr:
    value:str
    def __init__(self,value:str):
       self.value=value
    pass

... but that means using all the string methods is "manual", e.g.

x=MyStr('fred')
x.value.startswith('fr')

... you could use @dataclass to add compare etc.

This is not a one-size-fits-all answer, but it might suit your application.

then make that simple

class MyStr:
    value:str
    def __init__(self,value:str):
       self.value=value

...generic like Str in (incomplete) https://github.com/urnest/urnest/blob/master/xju/newtype.py

... and you can write:

class MyStrType:pass; class MyOtherStrType:pass
class MyStr(Str[MyStrType]):
    pass
class MyOtherStr(Str[MyOtherStrType]):
    pass

x=MyStr('fred')
y=MyOtherStr('fred')
x < y # ! mypy error distinct types MyStr and MyOtherStr

That's what I was after, which might be what you were after? I have to provide Int,Str separately but in my experience distinct int, str, float, bool, bytes types give a lot of readability and error-rejection leverage. I will add Float, Bool, Bytes to xju.newtype soon and give them all the full set of methods.

Related