Package: newertype
newertypeAn Implementation of the NewType Pattern for Python that works in dynamic contexts.What is it?NewerTypeis a package that provides a semi-transparent wrapper to an existing type that allows it to be used
mostly as if it's just the wrapped type, but which allows type checking as if it's a distinct type at runtime.With the addition to Python ofPEP 483,PEP 484, & thetypingpackage, Python added support for type
hints. That included an implementation of the Haskellnewtypewhich was
cleverly calledNewType.
As explained inthe documentation,
Python'sNewTypeis, like most of the
typing library, meant for use by static type checkers. This means that, when the code is running, theNewnessof
the type is erased, leaving just the wrapped type & no way to tell that there was ever aNewtype, either by
the code or by Python itself.NewerTypeprovides the same kind of wrapper asNewType, but allows (& enforces) type checking at runtime.
this means, for example, that if you wrap anintin aNewerType, you can do all of the arithmetic &
comparison operations on an instance of that type that you could with a normalintwith either different
instances of that type, orints. But you will not be able to mixdifferentNewerTypes, even if they
all wrapints.This allows you to never have to worry if you are addingMilestoKilometers, or mixing up aUserNamewith aPassword.Main FeaturesA wrapper that allows dynamic type checking while mostly not getting in the wayCarries type information with the object so you can always useisinstace()ortype()to know what it isForwards the magic methods from the wrapped object so things like arithmetic or indexing workAllows you to customize what methods are forwardedNo dependencies!InstallationCurrent stable version:pipinstallnewertypeNewest thing on GitHub:pipinstallgit+https://github.com/evanjpw/newertype.gitUsageBasic usage:fromnewertypeimportNewerTypeAType=NewerType("AType",int)# `AType` is a new type that wraps an inta_type=AType(14)# Make an instance of this new typeisinstance(a_type,AType)# `a_type` is an `AType`# Returns: Trueisinstance(a_type,int)# `a_type` is _NOT_ an `int`# Returns: Falsestr(a_type.__class__.__name__)=="AType"# Returns: TrueYou can use the new type as if it's the wrapped type:AType=NewerType("AType",int)# Let's make some types!a_type_1=AType(7)a_type_2=AType(7)# Two different instances with the same classa_type_1==a_type_2# You can compare them as if they were just `int`s# Returns: TrueEType=NewerType("EType",int)e_type_1=EType(7)e_type_2=EType(14)e_type_2>e_type_1# All of the `int` operations work# Returns: Truea_type_1==e_type_1# But different types are not equal, even if the wrapped value isReturns:FalseIType=NewerType("IType",int)JType=NewerType("JType",int)i_type_1=IType(7)i_type_2=IType(14)i_type_1+i_type_2# Arithmetic works!# Returns: 21j_type_1=JType(7)i_type_1+j_type_1# But not if you try to mix `NewerType`s# "TypeError: unsupported operand type(s) for +: 'IType' and 'JType'"int(i_type_1)<int(i_type_2)# Conversions that work for the inner type work also# Returns: TrueAccessing the wrapped data directly:a_type=AType(14)a_type.inner# the `inner` property gets the contained value# Returns: 14a_type.inner=27# `inner` can also be used to modify the valuea_type.inner# Returns: 27The "truthiness" & string representations are sensible:SType=NewerType("SType",float)s_type=SType(2.71828182845904523536028747135266249775724709369995)str(s_type)# Returns: "SType(2.718281828459045)"repr(s_type)# Returns: "SType(2.718281828459045)"bool(s_type)# Returns: Truebytes(s_type)# `bytes()` only works if it works with the wrapped type# "TypeError: cannot convert 'float' object to bytes"s_type.inner=0.0bool(s_type)# Returns: FalseWhat about forwarding your own methods on your own classes? NewerType can handle that:# First, define a class. It can have the standard indexing methods, but also some unique ones:classForwardable(UserDict):defforwarded(self,value):returnvaluedefalso_forwarded(self,key):returnself[key]def__getitem__(self,item):returnsuper().__getitem__(item)def__setitem__(self,key,value):super().__setitem__(key,value)# The normal behavior is for NewerType to forward the standard methods but ignore the custom ones:FO1Type=NewerType("FO1Type",Forwardable)fo1_type_1=FO1Type(Forwardable())fo1_type_1["a"]=5# `__setitem__` is a standard method, so it's forwardedfo1_type_1["a"]# So is `__getitem__`# Returns: 5fo1_type_1.forwarded(5)# But unique methods are not forwarded# "AttributeError: FO1Type' object has no attribute 'forwarded'"# We can use "extra_forwards" to specify the additional methods we'd like to forward:FO2Type=NewerType("FO2Type",Forwardable,extra_forwards=["forwarded","also_forwarded"])fo2_type_1=FO2Type(Forwardable())fo2_type_1["e"]=7# This continues to workfo2_type_1["e"]# As does this# Returns: 7fo2_type_1.also_forwarded("e")# But now this works also!# Returns: 7# But what if we _don't_ want to forward the standard methods? Use "no_def_forwards":FO3Type=NewerType("FO3Type",Forwardable,extra_forwards=["also_forwarded"],no_def_forwards=True)fo3_type_1=FO3Type(Forwardable())fo3_type_1.inner["g"]=8fo3_type_1.also_forwarded("g")# The extra methods continue to work# Returns: 8fo3_type_1["g"]# But the standard ones don't (unless we specifically mention them in "extra_forwards")# "TypeError: 'FO3Type' object is not subscriptable"TBDThebytes()built-in currently just forces all wrappedstrobjects to "utf-8" as an encoding.
If you need adifferentencoding, usebytes()of.inner.There are abunchmore methods that should be in the whitelist for forwarding. That's a work in progress.Project ResourcesDocumentation - TBDIssue tracker - TBDSource code - TBDChange log - TBDLicenseLicensed under theMIT LICENSE