NewTypes

There are situations where you may want to avoid programming errors bycreating simple derived classes that are only used to distinguishcertain values from base class instances. Example:

  1. class UserId(int):
  2. pass
  3.  
  4. def get_by_user_id(user_id: UserId):
  5. ...

However, this approach introduces some runtime overhead. To avoid this, the typingmodule provides a helper function NewType that creates simple unique types withalmost zero runtime overhead. Mypy will treat the statementDerived = NewType('Derived', Base) as being roughly equivalent to the followingdefinition:

  1. class Derived(Base):
  2. def __init__(self, _x: Base) -> None:
  3. ...

However, at runtime, NewType('Derived', Base) will return a dummy function thatsimply returns its argument:

  1. def Derived(_x):
  2. return _x

Mypy will require explicit casts from int where UserId is expected, whileimplicitly casting from UserId where int is expected. Examples:

  1. from typing import NewType
  2.  
  3. UserId = NewType('UserId', int)
  4.  
  5. def name_by_id(user_id: UserId) -> str:
  6. ...
  7.  
  8. UserId('user') # Fails type check
  9.  
  10. name_by_id(42) # Fails type check
  11. name_by_id(UserId(42)) # OK
  12.  
  13. num = UserId(5) + 1 # type: int

NewType accepts exactly two arguments. The first argument must be a string literalcontaining the name of the new type and must equal the name of the variable to which the newtype is assigned. The second argument must be a properly subclassable class, i.e.,not a type construct like Union, etc.

The function returned by NewType accepts only one argument; this is equivalent tosupporting only one constructor accepting an instance of the base class (see above).Example:

  1. from typing import NewType
  2.  
  3. class PacketId:
  4. def __init__(self, major: int, minor: int) -> None:
  5. self._major = major
  6. self._minor = minor
  7.  
  8. TcpPacketId = NewType('TcpPacketId', PacketId)
  9.  
  10. packet = PacketId(100, 100)
  11. tcp_packet = TcpPacketId(packet) # OK
  12.  
  13. tcp_packet = TcpPacketId(127, 0) # Fails in type checker and at runtime

You cannot use isinstance() or issubclass() on the object returned byNewType(), because function objects don’t support these operations. You cannotcreate subclasses of these objects either.

Note

Unlike type aliases, NewType will create an entirely new andunique type when used. The intended purpose of NewType is to help youdetect cases where you accidentally mixed together the old base type and thenew derived type.

For example, the following will successfully typecheck when using typealiases:

  1. UserId = int
  2.  
  3. def name_by_id(user_id: UserId) -> str:
  4. ...
  5.  
  6. name_by_id(3) # ints and UserId are synonymous

But a similar example using NewType will not typecheck:

  1. from typing import NewType
  2.  
  3. UserId = NewType('UserId', int)
  4.  
  5. def name_by_id(user_id: UserId) -> str:
  6. ...
  7.  
  8. name_by_id(3) # int is not the same as UserId