Package: pyqol
PyQoLThis package contains a lot of feature for Quality of Life, functionnal programming, and others.Big titles represent modules, so for imports, you need to usefrom pyqol.MODULE import function.COREcodeditCodedit is a decorator that lets you change the source code of a function using regexes. Sadly the syntax has to be "python-valid" before, but the code doesn't have to mean anything though.frompyqol.COREimportcodedit@codedit("oooooooone","1")defadd_one(x):returnx+oooooooone@codedit(r"_(.+)_more_than\[(.+)\]",r"\2 + \1")defadd_one(x):return_1_more_than[x]Obviously, in that case it's not that useful, but I'm sure you can find some hacks using it :)CodeditsIs a class that contains all known useful codedits..LambdaLets you define a custom lambda operator.frompyqol.COREimportCodedits@Codedits.Lambda(">>")defadd_one(x):ld={a>>a+1}returnld(x)Valid lambdas include:-1>,>,:,>>, ...Bittors(better iterators)Iis the newrangefrompyqol.BittorsimportI,IC,IE,IR,MultiteratorI, and its brothersIR,IC,IEare iteration functions, used to create loops.
It will iterate over any iterable, and transform aintargument into a range from0to thisint. Negativeints lead to a backwards loop.
It will iterate over all arguments at the same time, and zip them.Arguments are:I(*args,revserse=False,enum=False,chunking=False,chunk_size=1)Enum = Trueis the same as zipping withI(None), which returns an infinite loop. It also can be called withIEChunking, also called byICreturns multiple values at once, in a tuple.IC(-8,chunk_size=2)->(7,6),(5,4),(3,2),(1,0)Nostart, end, stephere, all arguments are the iterationsMultiteratorThis lets you handle complex iteration patterns while not suffering from the usual problems caused by having nested loops.
Here, an example speaks for itself:my_iterator=Multiterator()foriinmy_iterator("outer range loop",5):forj,kinmy_iterator("inner zip loop",x,z):if...my_iterator.stop("inner zip loop")if...my_iterator.stop("outer range loop")if...my_iterator.stop(Multiterator.all)Multiterator's call works exactly like aIcall..StructsStructStruct takes any arguments when created, and stores them. It is similar to a JS object.frompyqol.StructsimportStructmy_object=Struct(health=100,strength=20)my_object.sword=Swords.Diamonddef_run(self:Struct):passmy_object.run=_runmy_object.run()sword_type=my_object.get("sword")will_return_none=my_object.get("Flamingo")RegistryYou can create registers of functions (for plugin management, or special scoping), by creating a registry:frompyqol.StructsimportRegistryr=Registry()Then, you can register functions in it, and access them that way.@r.registerdefmy_happy_little_function(x):print(f"happy little{x}")r.registry["my_happy_little_function"]("accident")r["my_happy_little_function"]("programmer")NaVis a substitute for a value, likeNothingin FP, orNaNin JS. It nullifies any operation done to it, and logs them internally. This means that you can have a program fail silently, and still know what happened to that variable.frompyqol.StructsimportNaVdefdiv(a,b):ifb==0:returnNaV("Division by 0")returna/bfoo=div(8,0)bar=foo*8+5print(bar)NotaValue!Message:Divisionby0Logs:Operationmultwith8Operationaddwith5Most external libs won't be compatible with this, it might not always be optimal to use NaV.StreamThis is a big feature, quite hard to explain properly.
A stream works like a queue, for iterables and lazy evaluation. You can add four things to a Stream's queue: Literals, Lists (anything that can turn into a list), Iterable (no ranges) and Generators.
A few examples will speak for themselves.frompyqol.StructsimportStreamforiinStream([1,2,3]):print(i)#1, 2, 3my_stream=Stream()<<1<<2<<3list(my_stream)==[1,2,3]foriinmy_stream:...my_stream<<next_val<<other_valStream.Listmy_stream<<Stream.List([1,2,3,4,5])Stream.Iterablemy_stream<<Stream.Iterable(I(12))my_stream<<other_stream# streamceptionStream.GeneratorFor this, you have multiple options. This lets you generate the next element of the stream depending on the index of the element, and the elements prior.s<<Stream.Generator(3,lambdaidx:idx*2,require_index=True)# 0, 2, 4s<<Stream.Generator(5,lambdaa:a*2)# 4*2, 16, 32, 64, 128Now, let's create the easiest fibonacci iterator ever ! Don't worry about performence, it's autocached !# first define a basecasefib=Stream(cache_size=50)<<1<<1# None means it'll generate foreverfib<<Stream.Generator(None,lambdaa,b:a+b)foriinfib[:20]:print(i)# 1, 1, 2, 3, 5, ....BitStorageThis lets you store custom datatypes, giving you access to bitwise storage. To define thoe datatypes, all you need is a dict listing the properties and their allocated number of bits, then you can generate entities of that type, and use masks to get properties fast.# define the typeChessPiece=BitStorage({"color":1,"kind":3,})white_queen=ChessPiece.new({"color":0b0,"kind":0b111,})color=ChessPiece.get(white_queen,"color")white_queen==0b0111.FPFunctionYou can decorate one of your functions withFunctionto access function composition, and other features.frompyqol.FPimportFuntion,F@Functiondefadd_two(n):returnn+2mult_by_two=Function(lambdax:x*2)add_then_mult=(add_two+mult_by_two)mult_then_add=(add_two*mult_by_two)Bunction(better function)This class is a superset ofFunction, which allows for cool setups. Let's implement the fibonacci function with it, in a very defensive manner:# First, setup the default casefrompyqol.FPimportBunction,B@Bunctiondeffib(n):returnfib(n-1)+fib(n-2)Alone, this function doesn't work, it needs to return1if the input is0or1. We can easily patch this by adding cases, which will overwrite the default.#if x is 0, this case will be executed@fib.case(lambdax:x==0)def_one(x):return1Yes, this is ugly, that's why you can also do this:# if input is 1, return 1fib.case(1)(1)Now, let's do a little defensive programming, and make our function idiot-proof:fib.case(lambdax:x<0)(0)We can even preprocess the inputs, to fit in one of our cases when it couldn't before@fib.preprocess(lambdax:type(x)==str)def_exec(x):ifx.isnumber():# implement your own isnumber, python doesn't have one for floats for some reasonreturnfloat(x)# will then be converted to a floatelse:return0# will input 0 to fib# Then, convert floats to intsfib.preprocess(lambdax:type(x)==float)(lambdax:int(x))Mapfrompyqol.FPimportMapA new tool for iterating, theMapMap.overMap over is a curried function taking in an iterable, then a function, and outputs a newList of the results of the function. You can use it as a decorator, or as a normal function callMap.over([0,1,2,3])(lambdax:x*2)==L(0,2,4,6)@Map.over([0,1,2,3])defnewlist(e):returne*2newlist==L(0,2,4,6)That last functionnality might look extremely wierd, and it does, but it can be practical if used correctly.
If you have a set of objects you iterate over everywhere in the code, that you might change, why not have them all in once place ?agents=L(...)ForAllAgents=Map.over(agents)#...@ForAllAgentsdeftraining_log(agent):#...returnlog_infoprint(training_log)Map.usingIt is the exact same asMap.over, but the argument order is swapped.@Map.usingdefmult_list_by_two(e):returne*2mult_list_by_two([0,1,2,3])==L(0,2,4,6)Helpersfrompyqol.FPimportHelpers.timerA simple decorator for debugging the execution time of a function:@Helpers.timerdeftakes_long():foriinI(None):ifi==int(1e12):break;takes_long()# will print:# Finished takes_long in x secsMLA collection of helpers for making Neural Networks !
For now, most of the supported code is in PyTorch, so feel free to sumbit equivalents in other frameworks !Requires torch :(PyTorchModulesfrompyqol.MLimportPyTorchModules.Flatten.Residual.LowerBound.GDNAllEasier imports (requires the dependencies from all the modules though..)frompyqol.AllimportStruct,Helpers,I,B