The purpose of NewType is purely for static type checking, but for dynamic purposes it produces the wrapped type. It does not make a new type at all, it returns a callable that the static type checker can see, that's all.
When you do:
x = MyStr("Hello World")
it doesn't produce a new instance of MyStr, it returns "Hello World" entirely unchanged, it's still the original str passed in, even down to identity:
>>> s = ' '.join(["a", "b", "c"]) # Make a new string in a way that foils interning, just to rule out any weirdness from caches
>>> ms = MyStr(s) # "Convert" it to MyStr
>>> type(ms) # It's just a str
str
>>> s is ms # It's even the *exact* same object you passed in
True
The point is, what NewType(...) returns is effectively a callable that:
- Acts as the identity function (it returns whatever you give it unchanged); in modern Python,
NewTypeitself is a class that begins with__call__ = _idfunc, that's literally just saying when you make a call with an instance, return the argument unchanged. - (In modern Python) Has some useful features like overloading
|to produceUnions like othertyping-friendly things.
but you can't use it usefully for isinstance, not because it's not producing instances of anything.
As other answers have mentioned, if you need runtime, dynamic checking, subclassing is the way to go. The other answers are doing both more (unnecessarily implementing __new__) and less (allowing arbitrary attributes, bloating instances of the subclass for a benefit you won't use) than necessary, so here's what you want for something that:
- Is considered a subclass of
strfor both static and runtime checking purposes - Does not bloat the memory usage per-instance any more than absolutely necessary
class MyStr(str): # Inherit all behaviors of str
__slots__ = () # Prevent subclass from having __dict__ and __weakref__ slots, saving 16 bytes
# per instance on 64 bit CPython builds, and avoiding weirdness like allowing
# instance attributes on a logical str
That's it, just two lines (technically, a one-liner like class MyStr(str): __slots__ = () is syntactically legal, but it's bad style, so I avoid it), and you've got what you need.
The purpose of NewType is purely for static type checking, but for dynamic purposes it produces the wrapped type. It does not make a new type at all, it returns a callable that the static type checker can see, that's all.
When you do:
x = MyStr("Hello World")
it doesn't produce a new instance of MyStr, it returns "Hello World" entirely unchanged, it's still the original str passed in, even down to identity:
>>> s = ' '.join(["a", "b", "c"]) # Make a new string in a way that foils interning, just to rule out any weirdness from caches
>>> ms = MyStr(s) # "Convert" it to MyStr
>>> type(ms) # It's just a str
str
>>> s is ms # It's even the *exact* same object you passed in
True
The point is, what NewType(...) returns is effectively a callable that:
- Acts as the identity function (it returns whatever you give it unchanged); in modern Python,
NewTypeitself is a class that begins with__call__ = _idfunc, that's literally just saying when you make a call with an instance, return the argument unchanged. - (In modern Python) Has some useful features like overloading
|to produceUnions like othertyping-friendly things.
but you can't use it usefully for isinstance, not because it's not producing instances of anything.
As other answers have mentioned, if you need runtime, dynamic checking, subclassing is the way to go. The other answers are doing both more (unnecessarily implementing __new__) and less (allowing arbitrary attributes, bloating instances of the subclass for a benefit you won't use) than necessary, so here's what you want for something that:
- Is considered a subclass of
strfor both static and runtime checking purposes - Does not bloat the memory usage per-instance any more than absolutely necessary
class MyStr(str): # Inherit all behaviors of str
__slots__ = () # Prevent subclass from having __dict__ and __weakref__ slots, saving 16 bytes
# per instance on 64 bit CPython builds, and avoiding weirdness like allowing
# instance attributes on a logical str
That's it, just two lines (technically, a one-liner like class MyStr(str): __slots__ = () is syntactically legal, but it's bad style, so I avoid it), and you've got what you need.
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
UserId you created with NewType only works for type annotations. Type annotations are only for static analysis (at least up till now), so IDEs like pycharm and tools like mypy use it to implement auto complete or error checking, etc.
If you want a "real" UserId type (a type at runtime), you can subclass int by yourself.
class UserId(int):
pass
print(UserId(3))
# By the way, UserId('Hello world!') will raise ValueError
Python is a dynamically typed language. Variable types don't have to be declared before assigning a value to a variable in Python. Annotations and type hints are used by some IDEs to warn about type mismatches.
To actually check the variable type in a function you can use isinstance:
def NewType(UserId: int)
if not isinstance(UserId, int):
raise TypeError("userid must be an integer")
# your function code here
return result
You can check multiple types simultaneously grouping them as a tuple:
isinstance(variable, (int, float))