In order to build the modular typecasting callable cast
, we start by outlining the data types and default conversion behavior of python, before extending it to wrap non-iterable objects in the desired iterable, and convert the elements of iterable objects to the desired non-iterable type (shown by examples).
data = Union[None,int,float,list,tuple,str,dict,set,np.ndarray]
trycast(0.5,int) #works like int(0.5)
trycast(0,list) #just return 0 as list(0) gives an error - we want an actual list!
The default typecasting, as in the above, has some obvious limitations which can be extended with a basic consideration of whether the object is to be treated as an iterable or a non-iterable. To that end,
nonitr2itr(0,list)
itr2nonitr([0.5,1.5],int)
itr2itr({0:1},list) #simply treats dict -> itrtype as dict.items() -> itrtype, as list(dict) returns dict.keys()
itr2itr([0,1],np.ndarray) #makes sure the datatype np.ndarray yields the callable np.array()
Now, we combine these conversion cases into a single object, the Caster
class, which instantiates as a callable dictionary of partially evaluated functions, that typecast according to the type of the object they are called on, and the type to be casted to. The ruleset is stored as a dictionary which can be modified in place to treat objects differently as needed.
cast=Caster()
cast
wraps non-iterables in iterables:
cast(0,list)
cast
converts the elements of an iterable into the desired non-iterable:
cast([0.5,1.5],int)
cast
accepts multiple arguments for sequential conversion:
cast([0,1.1,1.5,2],int,set,list) #convert to int, get unique elements, return as list
By default, we choose to treat strings as non-iterables. This proves useful in later applications of the module.
cast('hi',list)
If we want to change this, we can do so by examining how cast
converts objects:
cast[str]
It is a nested dictionary, keyed by the datatype to be converted, and valued by an inner dict, which is keyed by the datatype to convert to, and valued with a function converting from the outer key to the inner key. To change how the instance evaluates strings to lists for example,
cast[str][list] = lambda s: list(s)
cast('hi',list)
If instead we want to change the default behavior of strings to be treated as an iterable, then we can modify the Caster class iterables
:
Caster.iterables
Caster.iterables.append(str)
Now we re-instantiate a class instance,
cast=Caster()
cast('hi',tuple)
and strings are treated as iterables, as in native python types. This process can be applied to any datatype, include new ones, etc. We can change it back again by:
Caster.iterables.remove(str)
cast=Caster()
Now let's see all the types of data conversions, with a parsing function for easy printing of datatypes:
for n in [0,1.5]:
for t in Caster.noniterables:
print(f'From {n} to {typestr(t)}: {cast(n,t)}')
for n in [1.5,'1.5']:
for t in Caster.iterables:
print(f'From {n} to {typestr(t)}: {cast(n,t)}')
for n in [[0,0.1],(0,0.1),{0:0.1},{0,0.1},np.array([0,0.1])]:
for t in Caster.noniterables:
print(f'From {n} to {typestr(t)}: {cast(n,t)}')
for n in [[0,0.1],(0,0.1),{0:0.1},{0,0.1},np.array([0,0.1])]:
for t in Caster.iterables:
print(f'From {n} to {typestr(t)}: {cast(n,t)}')
In conclusion, cast
can sequentially transition python objects between iterable and noniterable datatypes or any other datatype, with the fallback of using the default python conversions or simply returning the object, and can be updated as a dictionary of callables to include new typecasting behavior.
pad(data=[1,0],bits=5,to=float)
fill([[1],[1,1,1]],fillwith=0,mask=False)
fill([[1],[1,1,1]],fillwith=0,mask=True)
for i in range(11):
print(f"i={i}, nbits({i})={nbits(i)}")
for i in range(11):
print(f"i={i}, num2ar({i})={num2ar(i)}")
num2ar(10,bits=5,to=float)
for i in range(4):
print(f"a={num2ar(i)}, ar2num(a)={ar2num(num2ar(i))}")
ar2hex(num2ar(10))
ar2hex(num2ar(10),prefix=False)
hex2ar('a')
hex2ar('0xa')
str2ar('111')
ar2str([1.5,1.5,1.3],int)
NOT(1)
NOT(0.25)
AND(1,1)
AND(1,0.5)
OR(1,0)
OR(0.5,0.5)
XOR(1,1)
XOR(1,1,1)
XOR(0.5,0.5,0.5)
for x,y in np.ndindex((2,2)):
for z in [AND,OR,XOR]:
print(f"{z.__name__}{x,y}={z(x,y)}")
ar2gr(num2ar(10))
gr2ar(ar2gr(num2ar(10)))
for i in range(11):
print(f"i={i}, num2gr(i)={num2gr(i)}")
for i in [10,10.0,'10','0xa']:
t_in=str(type(i)).split('<')[-1].split('>')[0].split('class')[-1].split('\'')[1]
for j in [int,float,list,str,hex]:#,set,dict]:
t_out='hex' if j==hex else str(j).split('<')[-1].split('>')[0].split('class')[-1].split('\'')[1]
print(f'From {t_in} to {t_out}: convert({i},{t_out})={convert(i,j)}')
for i in [10,10.0,'10','0xa']:#,{0,10},{0:10,'a':11}]:
t_in=str(type(i)).split('<')[-1].split('>')[0].split('class')[-1].split('\'')[1]
for j in [int,float,list,str,hex]:#,set,dict]:
t_out='hex' if j==hex else str(j).split('<')[-1].split('>')[0].split('class')[-1].split('\'')[1]
print(f'Gray Code: From {t_in} to {t_out}: convert({i},{t_out})={convert(i,j,gray=True)}')
rint(np.array([0.5,0.51]))