Monday, December 22, 2014

Retrieve a single file from a git repo.

The answer to this was more difficult to find than it should have been. The best answer is on stackoverflow here.

In short, use the git archive command:

git archive --remote=git@github.com:foo/bar.git --prefix=path/to/ HEAD:path/to/ |  tar xvf -


  • For this to work the owner of the git server needs to have enabled upload-archive (git config daemon.uploadarch true)
  • You need tar -xvf because the output is in tar form

Thursday, December 4, 2014

Using user classes with Python's set operations

Python has an efficient, and built in, implementation of set operations. With a tiny bit of work you can make your user defined classes work nicely with set operations (as well as dictionaries).

Say you have a class:

class A:
  def __init__(self, x, s):
    self.x, self.s = x, s

Python actually doesn't have a way of knowing if the objects are the same or not:

a1 = A(1, 'Hi')
a2 = A(1, 'Hi')
a = set([a1, a2])
-> a1, a2

and by default assumes they are different.

All you have have to do, for things to work properly, is to define a hash function (__hash__()) and an equality (__eq__()) operator for your class. The two functions need to be congruent i.e. if the hash value for two objects is the same, they should also register as equal. It is possible to define the functions differently but this will lead to funny things happening with set operations.

The hash value is an integer. If your class has several attributes that you consider important for distinguishing between objects you can hash each attribute separately and then XOR the individual hashes.

class A:
  def __init__(self, x, s):
    self.x, self.s = x, s

  def __hash__(self):
    return self.x ^ self.s.__hash__()


  def __eq__(self, other):
    return self.__hash__() == other.__hash__()

And now

a1 = A(1, 'Hi')
a2 = A(1, 'Hi')
a = set([a1, a2])
-> just a1 as expected

Tuesday, December 2, 2014

Sphinx + Cython

It's pretty awesome that Sphinx will work with Cython code files (.pyx) but there are currently a few wrinkles:
  1. You need to rerun the cython compiler before the documentation changes take since sphinx reads this off the compiled library
  2. Function signatures are omitted in the documentation and the compiler directive # cython: embedsignature=True does not work either. The workaround is to repeat the signature as the first line of the docstring
def merge_variants(c1, c2):
  """
  merge_variants(c1, c2)
  ... rest of docstring ...
  """

Monday, November 24, 2014

A small caution when using virtualenvs

Virtualenvs are a great way to test and develop code in insulated containers. My main use case is to have an insulated environment where I can mock install a package I'm developing and ensure that I've taken care of all the dependencies so that a stranger can use the package as is. The virtualenvwrapper package is a great utility that simplifies managing multiple virtual environments.

One caution I have to observe is that packages installed out side the virtual environment can interfere in ways that make behaviors inside the virtual env very mysterious. For example, before I started using virtual environments seriously I had installed the nose and tox modules in my base python install. A month or so afterwards I had created a new test environment and was doing pip install -e . to test whether a package I was writing would install correctly on a fresh environment.

Everything installed fine, including an external package A my code needed. But, when I went to run nosetests or tox my code errored out, claiming package A had not been installed. BUT I CAN $%#@ SEE IT. LOOK, I TYPE pip list AND THERE IT IS!

What was happening, is that I was picking up both nose and tox from the base environment, which did not have A installed. I lost some more hair over this. People who know me will confirm that I can't afford such hair loss.

As a rule, if you want to use virtual envs, it is best to have a bare install of Python and then not install anything on top of that outside of a virtual environment.

git merge

I'm so used to git merge doing the right thing re: merging files - and possibly used to working mostly by myself - that when git merge fails I always expect something messy has happened. However, just now, I got a merge complain that amounted to this:

from setuptools import setup, find_packages

setup(
      ... some things ...

<<<<<<< HEAD
    ext_modules=cythonize('lib/*.pyx'),
    entry_points={
      # Register the built in plugins
              ....
      # Command line scripts
              ....
    }
)
=======
    install_requires=
             ....
    cython_ext='lib/*.pyx'
)
>>>>>>> origin/fix_setup

I was surprised since the changes I made and my colleague made were simple and non-overlapping. They just need to come in sequence.

I took a look at the git merge documentation which said

When both sides made changes to the same area, however, Git cannot randomly pick one side over the other, and asks you to resolve it by leaving what both sides did to that area.

What happened here was that there were no intervening lines. We both appended our changes below "... some things ..." and above ")".

git merge's algorithm can't assume (rightly so) that these are tandem changes. Which should come first? Should they both be kept? And so on. It's not a mess, but it still takes humans - actually people working on the code - to figure out that, in this case, both should be kept, and it does not matter which comes first.


Wednesday, November 12, 2014

cdef vs cpdef

In a .pyx file if you want a function that you declared as cdef (e.g. because you want inlining) to be visible to the Python interpreter, you need to use cpdef instead. However, it seems the cpdef version is a lot less efficient than cdef, and it might be better to have a wrapper of some sort instead.

Monday, September 8, 2014

An annoying thing with Python slices

You of course know that Python slices are awesome:

a = 'ABCDEFG'
a[:3] -> 'ABC'
a[2:5] -> 'CDE'

And more interestingly:

a[-3:] -> 'EFG'

and

a[6:4:-1] -> 'GF'

But you can see that the reverse slicing is starting to stretch the fence-post we are familiar with. Python uses zero based, inclusive-exclusive indexing. This corresponds to a C syntax of (for i = n; i < m; i++). When you reverse it the slice goes (for i = m - 1; i > n - 1; i--).

As you can imagine this starts to get ugly and at one point it gets to be wrong:

Say, as is often the case, you are not taking static, pre-determined slices but rather slices determined at runtime. Say you are taking slices between n and m or [n, m).

The forward slice is a[n:m]
The backward slice is a[m-1:n-1:-1] right? Because of the fence posts?

Well yes, except what happens when n = 0? The forward slice is fine but the reverse slice resolves to a[m-1:-1:-1]

This is where Python becomes a little too clever. As you will recall from our earlier examples, negative indices indicate offsets from the end of the object. So, the last slice returns empty.

The correct slice is a[m-1:None:-1] or a[m-1::-1] and the logic for this is cumbersome:

a[m-1:n-1 if n > 0 else None:-1]

The simpler way is to do a[n:m][::-1].





Saturday, September 6, 2014

Mac OS + 'cat' + 'sed' + \n = half-assed

You guys all know how Mac OS darwin does everything JUST a little differently from the *nixes. It's close enough to draw you in, and different enough to stab you in the back. Today's case sed and \n

I needed to cat some files together but I needed a newline between them. I asked my colleague Wan-Ping for a command that would do this and she suggested

cat 1.fa 2.fa 3.fa | sed 's/^>/\n>/g'

So I did this and the sucker added the character 'n' wherever I expected a newline.

It turns out that Macs are special little snow flakes and need a special little command:

cat chr*.fa | sed 's/^>/\'$'\n>/g' > hg38.fa

The magic sauce is the '$' that escapes the '\n'

Whatever.






Wednesday, September 3, 2014

Fixing the big mess with git and case insensitive filesystems

Mac OS X by default is a case insensitive file system. But Mac OS, as in a lot of other things, makes a half-assed job of this. In addition to causing various bits of confusion when creating directories it also leads to a potentially messy situation with git. This is how things happen:

1. You create a directory in your source tree called, say, Plugins with a capital "P".
2. After a few commits you decide that it's better to change this to a lower case "p": plugins
3. When you go to commit this rename (perhaps with a few other changes you implemented) git throws a hissy fit.

After a bit of searching on stack overflow it turns out that this is all related to Mac OS's case-insensitivity.

The cleanest fix I found on stack overflow was:

git mv Plugins temp00
git mv temp00 plugins
git commit

Apparently this fools git's index into doing the change, where as git mv Plugins plugins - because the underlying file system does not recognize the difference - tells git nothing has changed but it has and leaves it in some sort of half way state that messes it up.

Friday, August 29, 2014

A funny thing with Python __getitem__()

So, Python has the 'in' operator which we are encouraged to use to test membership of a key in a dictionary, for example. Like

a = {'A': 22, 'B': 45}
if 'A' in a:
  print 'Yes' 

--> 'Yes'

Python has a index/slice/key notation which consists of square brackets ([]):
a['A']

--> 22

Because Python is so awesome it's slice notation is available to us for any classes we define.
class A():
  def __getitem__(self, item):
    print item
    return item

a = A()
a[23] --> 23
b['hello']  --> 'hello'

(In our real class, of course, we would be doing something interesting in the __getitem__ method, like returning a data structure indexed by the key)

What happens when we put the two together, you know, use the in idiom with a user defined class?

It's so awesome, it needs its own screencast.


So Python goes through indexes sequentially testing membership! Returning None does not help either. You have to return True or 1 to indicate membership or raise IndexError for non-mmebership. Otherwise this madness will never stop!





Monday, August 25, 2014

Big numbers in Python

Numbers in Python can get arbitrarily large. This is pretty fun for things like doing factorials

>>> def fac(n): return reduce(lambda x,y: x*y, range(1,n+1))
... 
>>> fac(1)
1
>>> fac(2)
2
>>> fac(3)
6
>>> fac(4)
24
>>> fac(5)
120
>>> fac(500)
1220136825991110068701238785423046926253574342803192842192413588385845373153881997605496447502203281863013616477148203584163378722078177200480785205159329285477907571939330603772960859086270429174547882424912726344305670173270769461062802310452644218878789465754777149863494367781037644274033827365397471386477878495438489595537537990423241061271326984327745715546309977202781014561081188373709531016356324432987029563896628911658974769572087926928871281780070265174507768410719624390394322536422605234945850129918571501248706961568141625359056693423813008856249246891564126775654481886506593847951775360894005745238940335798476363944905313062323749066445048824665075946735862074637925184200459369692981022263971952597190945217823331756934581508552332820762820023402626907898342451712006207714640979456116127629145951237229913340169552363850942885592018727433795173014586357570828355780158735432768888680120399882384702151467605445407663535984174430480128938313896881639487469658817504506926365338175055478128640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000L
You're like Ooooh! I'm a gonna break Python!

>>> fac(5000)

And Python calmly gives you

422857792660554352220106420023358440539078667462664674884978240218135805270810820069089904787170638753708474665730068544587848606668381273633721089377278763127939036305846216064390447898698223987192970889621161265296832177550039924219683703146907264472878789790404754884162215226671928410969236910449565971736352948400223840381120644820230857671104502306174894755428309761781724040805324809927809328784055486199364548291211876258248802189173977900050213212598043639244626460770511358846595108675470585833924655225589035474435988347383178988034633008458631510209091509935653820010933047965742556741930917055172805200236075085991197635228755907902043369743123506916831211924495971556267407521462198986233088625998302859864857578749445963115286970886710046268423648178989905454690861391613218344174148807186234448114831209490361196546872767755617886828720269104814092456410341835975604276458161513178575901661071782544156980883359372729995603371371200471049437656291142488605335299499642300699972204918120100819059439140675053265004775533850899097945101551091486907004407119572336026243368132330218709287699196806656569752790422258267841561083376425781032629202687211070274681394351128601502326190649959171897364176378436491219709109840944514895358959103804176941956657834822071749105512752639148381172052604826965162642710094919393332661030104360530459117014557209584714353721948246686793467375904872268133410207860903657108806376616249749507413107077401682180585945526445171409277469230062697511346044174567946735828782261629584248675157379172942724178783105429858245117575511884506574424827574660800238588378492396247368761507015767725898321128632295537044902516387925127590841791744640466913531047347984464996154595542013996317357476301740036796192919942190762895445656261767041799538161133387312823511534152581309087915883638351664797225912944270653557142511737323807232632958121797916679692329687096923901003255574789055099807487061047230646195984955239657612208673866514171699307557691897902675157342075864796345338446835085965490727326321910504064289713096224505162064669468098869917122127404504020684923266241760132910227866687270305284709452526825496617772499645206699836925910690894082637401043498371591126455822280606361394115344316771769934353664284928294436414769615881993661388255577487709937004594753907845149034434521174560594039916268444697661821387470705325559577933196460996662145377564935474169708562389214773222865507182490430016186142192760452307670621142961767274704123616107220009743758647492753665149532164780849075146330071016691313420662882562618283865836983632108760710427516073348347788414796732427080410860761841281888307115098982135338406610652147087046874760995427473673509451553599769040367353385551052571682650317682405743993414862392331981432579182193321898940450865013610998098383993110996355981328001049731588596312131853801205046787642910669365600437305633431984879048998524701293300789344532868156679762880495532846386020133480265279836946393384995675049993707814746561543438930431384237878981847802886009971088695632988347711863122382785963653115132377931373647397429369411499028751972227999545182615488298951151926682112451355318472209990435355949887299922035062039816011086376236539782172380237846650673624510635034423187315338308212043804710999419227821039747552717416043890169723961305549371844836119803565896062025009093664399360172007383613354405094329072476518909502507724675841989412224659392163116352038147362479528539732089309533421910635702805576629720156556510767780805933453631121829561792887673002802450932122778852968418208261778476955644980385691275787372678040959158711733971103165232678060798127609246173504120182666874262805385275843979167609007743380748420751185119102921960339376280986753665085212869255321536787932521882574101866137054328973735862725370178558806639851350386944039604928258820180419178073649693885802597758398892014389747165465973510852605706234402069637065660129535734043582961473427275805630839510667375349259659518575646939723218275780003250593895303820539697558870511543073920827422440516299708739599768461206246629098112368012579891284802505094028916959765079395437191311379314427405135599630375642214527294341797246187597964074239147838993541565834716156858499036773056611353833367087548900413091981676330749041510337597307246885839246941715548295730750618505881581959528992660225626903439573313450666972952115230668696227920947779974336574472673471408928071411283888082693377378077293104110767513639476200610858040596019639058015761002337463869352228385801434957178125581445862930042479404065736859862007914604590255413929950088044710384758990326548097338166940500085452723713571394902463820308668541802838317527668064278489561005755859991718966786449154063570014497194249878920859731254275567514575206399118150736397483102490793841725653421894276769116598153430084637087769510295415136551734675054015239706042571746001089968440498845985477977905031632568489156557231006499726498721480800181770357701502983008879487243887718884416833034708723239505377642232944095773219137582371673924704216723002256883135779230394688900662466182532658490724406767024939579697217467485562998183149665611743997680482094166257463879660305171274925119226367615337524381656217330771650129520988754856467131862602387619964334867961514408328902061082833180891221325853682856469916007952105166960451695430614212305743006877217407155473217957577017595967640563812729153867513698712395570542350999228605975469962186195531354132139126436676900465429996811680550737866770665988027062972502001882845886145344368771455361304414465613369092862748276981946836480550952968681758714859972973082332924094777085275279923304892719633314751563311192746150389219290616780607901383451137066300684376267199885515143681266137319912103235469786756421210624899005553564022924345831264231038363416781719908354140411717740185950606674198348143345444247191436828225654380047860390575922417071802670646875454211626958746795398540784464654140381751149965273621123540880166990280149033225139460832668170930713868826549977374286127789417784752681328371818759103642140881783220739808059714203285309721443041845459183002833408705783138284973283761286182927136745161897366207237396132790944984014154408304074405393067540767126182547597130843470311389815695365971788564022750674237400323621850094765267521941901241387478279883426470873616812485384444012772521050072279315853096279121131160167772077952572613800240684421885453537121341902236379684012385255288607189967725694227433323948595075570839061877450159652184414998155476107548008054192318436948191732631430603548399790783307267636729090980772827355854348032260067472537097785464567761181807367424367391769863758072145859791485033700592994963793369100283444558089838054017635403737133019311293080958287612107380374800660269767842888358265737486556785868822015143046249655995760379768685318192365806469199584071845493606922169776137542662239658644989770921478134709127917460871630220821981434654245065731262683089579031012893360788644107230184805400373136014216229159146992019884148290014414312800903102107833305090238435726779416177246873411503598700003151092815700331081727415624680432977205070450456683898626301702989301145364477416856732512330376477881749036052572605520684370616116539755132541369303867783267208227323664249206432363089268768826650939691861683271739757479552993242406186992420363781929485368098035256331092448215269276219116259145886393677034653480367887126133367116968226450914997055448521259751870084720025674658752403932061045903070039438252019383102480929019684602472171298321628237994627125366359971898374425099120673688383738299653892030662843074547559074235345274029211606091346327684749522046010409575607348155101677203187580089224494752922031093841661588823584993931745149914395557357641584185479831702428523965451087525425464777294595230360946416541997797947136806344915998772409176443137371178542210740572121166868692153240490080384205921192622875440898261478908123698956367080804687628524499897440855677969456909042340530355943524640751677873953113928698614347227572144946891896093294375476741234907792754338349412323060078767610089949156126934038921148370217193387617823370358925817112869563450001367619897145400986643461922197676975930010555225198913002123021780831934330880446592954552165911855939202579781122952065357362914478404946474565003115498072056580360667380889572746464375428055819322299305089287806874537401327100274428317925355003451536693172112088227603942809788645727306979971285649576934354004030728440581746648376658498039958964243370183454151720285337810904113124462432903353964296651109482836884580127588701293156099225044518125460113274986014470437757313881001319276124676116614833528935557503106018449788994378274613854651708241613167681463911870000812845144341406739985430072772303758111613511094355614896323929750846383152930263582535361784837558519666949972251935515953807207838615142130284450051795239760968433198292598921623223582396390262548856855875458198371559008447860086745945709118128793228222051767509371866110013193625845223493949829511199280837860523506412769337548130609594264463425077601147334209139128541628183172262143783062962408149391997187528106367348876678481602342743230027158192404187686545826519361990687336892886715133840245486110982482004482721799496658712257174429044916781194824165631560303473833317665121218052780795958220298330611945164019413315550379662980215357680731124530585915969709973988055743550083279071844959752353594644354789680372126344509423070253995102864458237454677761013556916212309752286152053213998745673034127676503369636682306665552051562491132528926155863868503100849180920507680658265915276163719928694258350604859732273949286080260640627521341007801815105623787926212039424781833439433877206395801115809084190794320195178235740190546595990289617711776195270354051193727229722248442080440098750369411277686593022133010625031862085145076421052980508837197986052557750303949606158442838846866137510968441567309838079394349570013029265177957120625555851951313574029897589283475525334409858911400694449308432874005015554332587793895080241128538758725945136400838324944471346436826148195406004114845870234072926697740631325878634790667698266181501256117692275715291249164821702372884416357600996851100939411444677628186007072278522314941048564396255796808221289935799262208553889221164765220850367706476491496133789353761537391569177822237744837614120253342622508007300513473422771427333106345971803240244226950458090539326689103619381998838844036231795282435495362489670734155948067688515321073064476077859628627852283657244564306449096277517172656954238392941958409527253281659572534531428389629894005886539486824117113929627356938973482935854650278689437014798383826002058208853517073216288725214522205265969614962147884840129004507737252424605074339660818182960296019196314124998538422017695110361380561701016357743542531148669369994130940908368220071936435111978592782493491477052187226546109199597269439152400467901173602521030051886080337084840114810246351288263986170081804888380750203521448348740849154718714478857809574515499505005070789428842888410027877777455981132319940624176532148686316581736774410084063436959989519288310869124517866342559353458242589411390516469440377562665821577845936829909679754548350510473633770839151033854639602753486401635204633884342346714935641429160856846724874244782055113759168236472297793612971080302530934478115527737540458968990354808058309381267323593563098546564376209385371052808344607189076003388781618019853273759498566916704703448438363503416368325266403224174519476678140428319327482851882140344319384445475456765253419659194332132585432270070759038565239668227171300009189122050845185261514627937717597528852978637931711212529529443323757910072909001703558763798612480281463093944391916950129336315045285163539312868586427437296109446101235604877439863299611899755965996608749049271167685268675335991297583209089553296409523640116060078495005377892783750147344122123777907727134146647404489837589487675423294546899354220341669961366698976529978580795899055864050388507083137333076839766882463680992355219727241831735127646189112380485883115569477888101759708977682149644340317924443085170303692214137621194388641989508360339306459037361842937028710758321966607546113761076362543928614316242890754021082233620012309384737312220374269033838579928678572939434168287053763374091938184632261131740934278117918891642447513543478446040549455379834556163353815868441692054518698919434175386663900335756765603264363767906721626620330878425545157208117246381251512669846685887209013144861632560461019513371814585249988176629925142145014710206193190373671380347663431029705222414785030188275106347446241258707937339085095757724316735066885094208761536164440443755860160625837091300574162065273670941888667964570550744724714137001968165215954380698515999483361357521322106131884771926641942395351412233546746461491743013475866037338176532604557402925472279360288926189385899695656876783017186873988763887625972743976062813263446647679413679726184933395074665820441677989806604203937116666336696282569349097348391155869004856032512219241534268522369316036765491047702733521540143168338872968405443296967684036073182435336224865433823598123544167514608340781166661858781733980624199254577853462678039039937557802759942957205281043775666979396838109341118959475766220191217535093638985465283078692370662512323684390235587636228324657161183714078807661162179517887972801841572019639084400269037450381192797170314489871815031319992111563908303017288012610642062005359240278277393918026391717720136125984776933980647063763022608885359937595079088789081791802195768033381968605120487107610874898411568740159953020639098138993261095538868264084012160831040525974539251576403732889086736948366404734622708560040891610782221943405179794550155347682966855320097501905581419914591124181501062255627411231571377358697194374130822027383843815940638571387913337592362330440453487233047240668784133333047898995255221468847973813568083995644533005222551320155267768895412770329278670827490041172076663112783638152343547681663121189086864991380236281775275946061211813342054791801619220346912760381900528012343973598270461499814511324618195658528232044658270082064934680251556511272822083811563192256509945201222666603226059396247019707668580396286975551115189973049085051758765306785758000660424066894170620303846785860257370634352599586885088679654004465187790208942935153217316750113738031466034642429489076322228133763299919641336502028627289268087560036613770607463575515079087982099722660130472907825746908175451952405573791313113170617323191598673971588373108168916968657704150695512947652386134815766967580364762005289060222744531744305498402863048850869557761528650326080941160688570698894762046478500884303973107412774191961697450517110329082815201273888663422631492147090220016940636504812047036016738602290671629816411198202268607961324739550057567564568204754619040423011062371367395995678940884705976859514505017241517746017351430990972615509378334720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000L

Another fun thing about such arbitrary length integers in Python is that you can do weird, and fast, bit shifts.

>>> n = 1 << int(250e6)
You just created a 250 million bit array with the last bit set to one! And it's fast too:
In [407]: %timeit n = 1 << int(250e6)
100 loops, best of 3: 12.4 ms per loop

In [408]: %%timeit n = 1 << int(250e6)
   .....: n &= 1
   .....: 
10000000 loops, best of 3: 159 ns per loop

In [409]: %%timeit n = 1 << int(250e6)
n &= 1 << int(200e6)
   .....: 
100 loops, best of 3: 10.5 ms per loop



Numpy: a note on slicing efficiency

In my application I have three arrays of the same length (and datatype) (say p1, p2 and p3) that have a one to one correspondence with each other. My code works on the arrays in blocks and has to concatenate fragments of these three arrays together. In the rest of my code I take matched slices through these arrays (say p1[0:100], p2[0:100], p3[0:100]) and pass them onto functions for processing,

My first instinct was to create a two-dimensional array since this encapsulates the data nicely, even though the data do not, semantically, constitute a matrix. It was at this point I came across a fact that I had not considered before: taking slices from a 2D array is more time expensive than taking slices from a 1D array.

For example:

In [397]: %%timeit p = numpy.empty((3, 1e6))
   .....: x = p[1, 0:2]
   .....: 
100000 loops, best of 3: 2.18 µs per loop

Vs

In [398]: %%timeit p = numpy.empty(1e6)
   .....: x = p[0:2]
   .....: 
1000000 loops, best of 3: 512 ns per loop

Row-column shape does not make a difference:

In [399]: %%timeit p = numpy.empty((1e6, 3))
x = p[0:2, 1]
   .....: 
100000 loops, best of 3: 1.97 µs per loop

Additionally, A bit of parametric investigation reveals that, interestingly, the size of the slice does not matter:

In [402]: %%timeit p = numpy.empty((3, 1e6))
x = p[1, 0:100000]
   .....: 
100000 loops, best of 3: 2.17 µs per loop

Vs

In [401]: %%timeit p = numpy.empty(1e6)
x = p[0:100000]
   .....: 
1000000 loops, best of 3: 507 ns per loop

I think the most likely reason for this is that we are no longer pulling contiguous memory locations but rather having to pull together fragments from different memory locations and do proper offset computations while we do this. I can't understand why the time is independent of size for the 2D array, though!

So these consideration led me to the following formulation. I first verified that a concat of 2D fragments was not faster than repeated concats of separate 1-D fragments:

In [404]: %%timeit p1 = numpy.empty((3, 1e6)); p2=numpy.empty((3,1e2))
   .....: p = numpy.concatenate((p1, p2),axis=1)
   .....: 
100 loops, best of 3: 8.15 ms per loop

Vs

In [405]: %%timeit p1 = [numpy.empty(1e6) for _ in range(3)]; p2=[numpy.empty(1e2) for _ in range(3)]
   .....: p = [numpy.concatenate((pa, pb)) for pa, pb in zip(p1, p2)]
   .....: 
100 loops, best of 3: 9.34 ms per loop

And then used a list of arrays instead of a 2D array. This works for me because I do a lot of slicing of the resultant arrays and I never need to do any matrix operations with the three rows arranged as a matrix.

Saturday, August 16, 2014

Python blist.sortedlist

The blist package is pretty good. The documentation and care the author has put in is very impressive. It's a shame that it wasn't included in the standard Python distribution, and the reasons given seem to indicate to me that there are not enough people working on large datasets on the Python committee. Any how, I wanted to try out for myself how the sorted list stacks up against a regular python list that is sorted every time after and insertion:


As can be seen, blist.sorted insertion is O(1) and compares quite favorably to the regular Python list.

Timing code follows:

import pylab
from timeit import timeit

l = [1,2,3,4,5,6,7,8,9,10]

def time_things():
  n_loops = 1000
  s = [10, 100, 1000, 10000, 100000]
  blist_time = [timeit("a.add(100)", "import blist; a=blist.sortedlist(range({:d},0,-1))".format(n), number=n_loops)/n_loops for n in s]
  list_time = [timeit("a.append(100); a.sort()", "a=range({:d})".format(n), number=n_loops)/n_loops for n in s]
  return list_time, blist_time

def plot_things(list_time, blist_time):
  s = [10, 100, 1000, 10000, 100000]
  pylab.plot(s, list_time, label='Python list')
  pylab.plot(s, blist_time, label='blist.sortedlist')
  pylab.setp(pylab.gca(), xlabel='List size', ylabel='Time (s)', xscale='log', yscale='log') #, ylim=[1e-9, 1e-3])
  pylab.legend(loc='lower right')

Thursday, August 14, 2014

bcolz, numpy and plain Python

So we have these big data sets that people say are the code of life. In a simple representation they add up to about 3 GB of space. While it is quite possible to load up the whole human genome into RAM and have capacity to spare, I was wondering if there were painless ways to access compressed forms of the data. This lead me to Mikhail Korobov's blog and to bcolz. Here's a quick shoot out between plain Python strings, numpy and bcolz as used to store and access human chromosome 11 which is about 135 million base pairs long:




My take away, for my particular application is that I will stick to plain old Pythons strings - I tend to take short slices of the data (about 100 basepairs long) and that puts me in the range where Python strings are faster than numpy arrays. Memory is cheap.

Code follows:

"""Access times for single characters along the length of the string and for 100 item slices along the length of the
string."""
import pylab
import numpy
import bcolz

chrom_11_str = None  # Ugh, globals for timeit
chrom_11_bcolz = None
chrom_11_numpy = None


def load_data():
  global chrom_11_str, chrom_11_bcolz, chrom_11_numpy
  with open('chr11.fa', 'r') as fp:
    _ = fp.readline()  # The header
    chrom_11_str = ''.join([ln.strip() for ln in fp.readlines()])
  chrom_11_numpy = numpy.fromstring(chrom_11_str, dtype='c')
  chrom_11_bcolz = bcolz.carray(chrom_11_numpy)


def time_things():
  from timeit import timeit
  n_loops = 1000
  return [[[timeit("a = {:s}[0:{:d}]".format(dta, n), "from __main__ import {:s}".format(dta), number=n_loops) / n_loops
           for n in [10 ** m for m in range(6)]],
          [timeit("a = {:s}[100000000:100000000+{:d}]".format(dta, n), "from __main__ import {:s}".format(dta), number=n_loops) / n_loops
           for n in [10 ** m for m in range(6)]]] for dta in ['chrom_11_str', 'chrom_11_bcolz', 'chrom_11_numpy']]


def plot_things(t):
  #t_str, t_bcolz, t_numpy = t
  sl_size = [10 ** m for m in range(6)]
  location = ['(start)', '(end)']
  for n in [0, 1]:
    pylab.subplot(1,2,n+1)
    for t_value, t_name in zip(t, ['str', 'bcolz', 'numpy']):
      pylab.plot(sl_size, t_value[n], label=t_name + location[n])
      #pylab.plot(t_value[1], 'b', label=t_name + '(finish)')
    pylab.setp(pylab.gca(), xlabel='Slice size', ylabel='Time (s)', xscale='log', yscale='log', ylim=[1e-9, 1e-3])
    pylab.legend(loc='lower right')

load_data()
t = time_things()
plot_things(t)

Wednesday, August 13, 2014

Looping over a list of files in Bash

Really, bash was meant for hackers:

FILES=*.txt  # What ever filter you want
for f in ${FILES}
do
  # Your commands here
done

Saturday, August 9, 2014

Nosetests and pdb

I sat there looking at a testing error and thought "Can't we automatically invoke the debugger when a test fails". This is Python, of course someone has a solution for that.

nosetests --pdb

will invoke the debugger .when one of your tests goes south.

Tuesday, July 29, 2014

Nose and test generators

In my code I have a directory of small python modules that act as plugins. They can be called by a main program as directed. Each plugin, therefore, has a standard interface and needs to work with the main program. I wanted to setup an automated test that would run the main program, load each plugin in turn and run to make sure there was no crash. It turns out that nose has a very elegant way of doing this.

My test module for this turns out to be (with a little paraphrasing and removal of details):

from nose.plugins.skip import SkipTest


def find_plugin_models():
  return a_list_of_the_modules_loaded

def check_plugin(model):
  if an_important_precondition_is_not_met:
    #http://stackoverflow.com/questions/1120148/disabling-python-nosetests
    raise SkipTest('I can not run this test')
  run_a_test()
  assert thing_is_ok()


#http://stackoverflow.com/questions/19071601/how-do-i-run-multiple-python-test-cases-in-a-loop
def test_all_found_plugins():
  """Integration test on automatically found mutation plugin"""
  for model in find_plugin_models():
    check_plugin.description = 'Testing {:s} automatically'.format(model.__name__)
    yield check_plugin, model

Note the cool ability to set a .description attribute that allows you to print what you like when the test runs.

Friday, July 25, 2014

numpy and testing

Numpy has some great builtins for running unit tests under
numpy.testing
. There are routines for comparing arrays that come in handy for tests. Many of these have verbose settings that give useful details in a thoughtful format for chasing down why a test failed.

Making docopt and Sphinx work together

I use docopt to build simple, self-documenting command line interfaces to scripts. I was delighted when sphinx's outdoc features seemed to interpret docopt's options well:

But it is apparent that the formatting of the usage part is a bit off and any option that does not begin with -- or - messes up the options formatting completely.

It turns out that Sphinx has a bunch of plugins for documenting command line options with sophisticated ones able to run the code or analyze the argparse object itself but nothing for docopt. One code sample I found wrote out the command line description in restructured text and then stripped out parts of it to make docopt parse it properly

The real solution is, of course, to write a Sphinx extension for docopt, but I ended up using a slight modification that doesn't confuse docopt and lets SPhinx make decent (not great) autodoc out of it.

My docstring looks like this:

Command line::

  Usage:
    fasta2wg  --index=IDX  --wg=WG  [--fa=FA] [-v]
    fasta2wg  describe  --wg=WG

  Options:
    --index=IDX       Index file (.json) listing fasta files to be inserted into whole genome
    --wg=WG           Name of whole genome file
    --fa=FA           If set, will also dump a fa.gz file (by simply concatenating the files together) for use by BWA
    -v                Be verbose when you do things
    describe          Take the indicated whole genome file as input and print a summary of what it contains

And the Sphinx documentation looks like:



Wednesday, July 23, 2014

Some notes on Python imports and project organization

One majorly annoying thing for Pythonistas is the search path that the import command uses. When I was a beginner and wanted to get cracking I'd simply add whatever I was working on to PYTHONPATH, not wanting to get bogged down in silly details (that's what we were using Python for, right?).

I learned my lesson when I "forked" one of my projects (by duplicating the directory - give me a break, I wasn't born learning version control systems) and spent several hours trying to figure out why changes to a file I was making did not show up when I ran the code - Python, of course, was picking up the old version of the module in the original directory.

My current strategies, in somewhat random order, for project organization and testing are:

Project organization


ProjectName
   |--------->Readme
   |--------->setup.py
   |--------->docs
   |--------->mainmodule
   |             |------->__init__.py  (empty)
   |             |-------> root level modules 
   |             |-------> submodule1
   |             |    |---------------> __init__.py (empty)
   |             |    |----> sub modules
   |             |-------> submodule2
   |                  |---------------> __init__.py (empty)
   |                  |----> sub modules
   |
   |--------->tests
                 |------->__init__.py  import sys; sys.path.append('../mainmodule')
                 |-------> root level module tests 
                 |-------> submodule1
                 |    |---------------> __init__.py (empty)
                 |    |----> sub module tests
                 |-------> submodule2
                      |---------------> __init__.py (empty)
                      |----> sub module tests

An important lesson is a) not to forget the __init__.py files and b) to add relevant paths to sys.path in __init__.py when needed.

Using this directory structure allows me to use sphinx for the documentation (the source root goes in conf.py in docs) and allows me to invoke nosetests by doing

nosetests tests

and the path appending going on in tests/__init__.py allows me to import any part of the module in any test script just as I would from a different application after the application has been installed using setup.py

Tuesday, July 22, 2014

Use numpydoc + sphinx

An ideal code documentation system should allow you to write documentation once (when you are writing the code) and then allow you to display the documentation in different contexts, such as project manuals, inline help, command line help and so on. You shouldn't need to duplicate docs - it is a waste of effort and leads to errors when the code is updated. The documentation you write should be easily readable by human users as they peruse the code, as well as by users as they run your code.

Sphinx is an awesome documentation system for Python. Sphinx can take descriptions in docstrings and embed them in documentation so that we can approach the goals of an ideal documentation system.

However, Sphinx violates the human readability part when it comes to function parameter descriptions. Take the following function, for example.


def _repeat_sequence(seq_len=100, subseq=None, subseq_len=10, base_sel_rng=None, alphabet=['A', 'C', 'T', 'G']):
"""
Create a sequence by repeating a sub-sequence
"""
subseq = base_sel_rng.choice(alphabet, size=subseq_len, replace=True, p=[.3, .2, .2, .3]).tostring()
return subseq * (seq_len / subseq_len) + subseq[:seq_len % subseq_len]


The Sphinx-parsable way of writing the docstring is:

def _repeat_sequence(seq_len=100, subseq=None, subseq_len=10, base_sel_rng=None, alphabet=['A', 'C', 'T', 'G']):
  """Create a sequence by repeating a sub-sequence

  :param seq_len: Length of sequence.
  :type seq_len: int
  :param subseq: Sub-sequence to use as repeat block. Omit to generate a random sub-sequence.
  :type subseq: str
  :param subseq_len: If subseq is omitted this must be provided to indicate desired length of random sub-sequence
  :type subseq_len: Length of random sub-sequence
  :param base_sel_rng: Random number generator e.g. numpy.random
  :param alphabet: List of characters constituting the alphabet
  :type alphabet: list

  :returns:  str -- the sequence.
  """
  subseq = base_sel_rng.choice(alphabet, size=subseq_len, replace=True, p=[.3, .2, .2, .3]).tostring()
  return subseq * (seq_len / subseq_len) + subseq[:seq_len % subseq_len]

It does not matter that the Sphinx output is well formatted: for humans it's rather yucky to look at which defeats the purpose of docstrings.

It turns out that, as is common in Python, this problem has been noted and rectified. In this case the correction comes from the great folks at numpy who have a nice extension called numpydoc. The numpydoc version of this docstring is:

def repeat_sequence(seq_len=100, subseq=None, subseq_len=10, base_sel_rng=None, alphabet=['A', 'C', 'T', 'G']):
  """Create a sequence by repeating a sub-sequence

  Parameters
  ----------
  seq_len : int
                              Length of sequence.
  subseq : str, optional
                              Sub-sequence to use as repeat block. Omit to generate a random sub-sequence.
  subseq_len : int, optional
                              If subseq is omitted this must be provided to indicate desired length of random
                              sub-sequence
  subseq_len : int
                              Length of random sub-sequence
  base_sel_rng : object
                              Random number generator e.g. numpy.random
  alphabet : list, optional
                              List of characters constituting the alphabet

  Returns
  -------
  str
      The sequence.

  .. note:: This is meant to be used internally

  """
  subseq = base_sel_rng.choice(alphabet, size=subseq_len, replace=True, p=[.3, .2, .2, .3]).tostring()
  return subseq * (seq_len / subseq_len) + subseq[:seq_len % subseq_len]

Note: The only glitchy thing is that when you add numpydoc to the list of extensions in Sphinx's conf.py it can't be ahead of standard sphinx extensions - the order matters. For example, my extensions list is:

extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.doctest',
    'sphinx.ext.todo',
    'sphinx.ext.coverage',
    'sphinx.ext.pngmath',
    'sphinx.ext.mathjax',
    'sphinx.ext.viewcode',
    'numpydoc.numpydoc'
]

Putting numpydoc first leads to the error:
Extension error:
Unknown event name: autodoc-process-docstring

Thursday, July 10, 2014

Recap: Python list indexing is O(1)

Just in case any of you, hearing about how slow and ponderous Python is, were worried about indexing into a list - it's a O(1) operation:

In [936]: big_list = range(int(1e8))
len(b
In [937]: len(big_list)
Out[937]: 100000000

In [938]: %timeint big_list[-1]
ERROR: Line magic function `%timeint` not found.

In [939]: %timeit big_list[-1]
10000000 loops, best of 3: 66.1 ns per loop

In [940]: %timeit big_list[0]
10000000 loops, best of 3: 65.2 ns per loop

In [941]: small_list = range(1000)

In [942]: %timeit small_list[0]
10000000 loops, best of 3: 70.6 ns per loop

In [943]: %timeit small_list[-1]
10000000 loops, best of 3: 69.7 ns per loop

In [944]: %timeit big_list[10000]
10000000 loops, best of 3: 68.2 ns per loop

In [945]: %timeit small_list[100]
10000000 loops, best of 3: 70.2 ns per loop

Wednesday, July 9, 2014

Mac OS X: Coding in the dark

So, sometimes you need to work at night and not light up the whole room (e.g. when there is a sleeping baby next to you). The dimmest setting on the mac screen is still annoyingly bright, and indeed after a while, begins to hurt my eyes. One native thing to try is "invert colors" under "screen" in "accessibility". It makes the screen look like a negative image. It looks very cool, a bit annoying if you have images, but for text it is a very good counterpart to bright backgrounds during the day.

Monday, July 7, 2014

Python: To format or to concatenate

A while ago a kindly reader pointed out that Python's string .format method is, after all, a function, and carries with it some over head. I have some inner loop code that I could stand to run a little faster and I was looking for a way to speed things up without losing readability. In one part of the loop I was creating a string using the format statement. I wondered if I could speed things up by changing that to a concat.

So I first tested it out on some toy code:

def str_format(size=int(1e6)):
  for n in range(size):
    a = 'hi {:d} {:d} {:d}'.format(n, n+1, n+2)
  return a


def str_concat(size=int(1e6)):
  for n in range(size):
    a = 'hi ' + str(n) + str(n+1) + str(n+2)
  return a

In [448]: %timeit str_concat()
1 loops, best of 3: 996 ms per loop

In [449]: %timeit str_format()
1 loops, best of 3: 1.26 s per loop

So, the plain python concat is faster than the elegant way. This held with one variable or two variables too. This probably has to do with the complexity of the .format function, which offsets even the extra calls to str







Sunday, July 6, 2014

Doctests or nosetests?

I had a torrid love affair with doctests. It was AMAZING! You mean you can write documentation AND test your code at the same time!? You can write a line of code, put down the answer next to it and the computer WILL CHECK IT FOR YOU?

Well, this went on for several months. Then one morning I woke up next to this:

def handle_variant(variant, f_vseq, f_vseq_pos, strand_no=None):
  """Write the variant to the mutated sequence and fill out the pos array

  Some setup
  >>> import io, vcf, struct
  >>> def add_GT(v, gt):
  ...   md=vcf.model.make_calldata_tuple('GT')(gt)
  ...   call=vcf.model._Call(v,'sample',md)
  ...   v.samples = [call]
  ...   v._sample_indexes = {'sample': 0}

  Test with SNP: ignore zygosity
  >>> ref_seq = 'ACTGACTG'; \
  pos = 2; \
  ref = 'C'; \
  alt = [vcf.model._Substitution('T')]; \
  variant = vcf.model._Record('1', pos, '.', ref, alt, 100, None, None, None, None, None); \
  add_GT(variant, '1/1'); \
  f_vseq = io.BytesIO(); \
  f_vseq_pos = io.BytesIO(); \
  print handle_variant(variant, f_vseq, f_vseq_pos)
  2
  >>> _ = f_vseq.seek(0); print f_vseq.read()
  T
  >>> _ = f_vseq_pos.seek(0); print struct.unpack('I',f_vseq_pos.read(4))
  (2,)

  Test with SNP: homo, strand 0
  >>> f_vseq = io.BytesIO(); \
  f_vseq_pos = io.BytesIO(); \
  print handle_variant(variant, f_vseq, f_vseq_pos, 0)
  2
  >>> _ = f_vseq.seek(0); print f_vseq.read()
  T
  >>> _ = f_vseq_pos.seek(0); print struct.unpack('I',f_vseq_pos.read(4))
  (2,)


  Test with SNP: homo, strand 1
  >>> f_vseq = io.BytesIO(); \
  f_vseq_pos = io.BytesIO(); \
  print handle_variant(variant, f_vseq, f_vseq_pos, 1)
  2
  >>> _ = f_vseq.seek(0); print f_vseq.read()
  T
  >>> _ = f_vseq_pos.seek(0); print struct.unpack('I',f_vseq_pos.read(4))
  (2,)



  Test with SNP: het, strand 0
  >>> add_GT(variant, '0/1'); \
  f_vseq = io.BytesIO(); \
  f_vseq_pos = io.BytesIO(); \
  print handle_variant(variant, f_vseq, f_vseq_pos, 0)
  1
  >>> _ = f_vseq.seek(0); print f_vseq.read()
  
  >>> _ = f_vseq_pos.seek(0,2); print f_vseq.tell()
  0

  Test with SNP: het, strand 1
  >>> f_vseq = io.BytesIO(); \
  f_vseq_pos = io.BytesIO(); \
  print handle_variant(variant, f_vseq, f_vseq_pos, 1)
  2
  >>> _ = f_vseq.seek(0); print f_vseq.read()
  T
  >>> _ = f_vseq_pos.seek(0); print struct.unpack('I',f_vseq_pos.read(4))
  (2,)


  Test with delete: ignore zygosity
  >>> ref_seq = 'ACTGACTG'; \
  pos = 2; \
  ref = 'CTG'; \
  alt = [vcf.model._Substitution('C')]; \
  variant = vcf.model._Record('1', pos, '.', ref, alt, 100, None, None, None, None, None); \
  add_GT(variant, '1/0'); \
  f_vseq = io.BytesIO(); \
  f_vseq_pos = io.BytesIO(); \
  print handle_variant(variant, f_vseq, f_vseq_pos)
  4
  >>> _ = f_vseq.seek(0); print f_vseq.read()
  C
  >>> _ = f_vseq_pos.seek(0); print struct.unpack('I',f_vseq_pos.read(4))
  (2,)

  Test with same delete, strand 1 (same as REF)
  >>> f_vseq = io.BytesIO(); \
  f_vseq_pos = io.BytesIO(); \
  print handle_variant(variant, f_vseq, f_vseq_pos, 1)
  1
  >>> _ = f_vseq.seek(0); print f_vseq.read()
  
  >>> _ = f_vseq_pos.seek(0,2); print f_vseq.tell()
  0


  Test with delete and .
  >>> ref_seq = 'ACTGACTG'; \
  pos = 8; \
  ref = 'G'; \
  alt = [vcf.model._Substitution('.')]; \
  variant = vcf.model._Record('1', pos, '.', ref, alt, 100, None, None, None, None, None); \
  f_vseq = io.BytesIO(); \
  f_vseq_pos = io.BytesIO(); \
  print handle_variant(variant, f_vseq, f_vseq_pos)
  8
  >>> _ = f_vseq.seek(0); print f_vseq.read()
  
  >>> _ = f_vseq_pos.seek(0,2); print f_vseq.tell()
  0


  Test with delete and .
  >>> ref_seq = 'ACTGACTG'; \
  pos = 4; \
  ref = 'GACTG'; \
  alt = [vcf.model._Substitution('.')]; \
  variant = vcf.model._Record('1', pos, '.', ref, alt, 100, None, None, None, None, None); \
  f_vseq = io.BytesIO(); \
  f_vseq_pos = io.BytesIO(); \
  print handle_variant(variant, f_vseq, f_vseq_pos)
  8
  >>> _ = f_vseq.seek(0); print f_vseq.read()
  
  >>> _ = f_vseq_pos.seek(0,2); print f_vseq.tell()
  0


  Test with insert
  >>> ref_seq = 'ACTGACTG'; \
  pos = 2; \
  ref = 'C'; \
  alt = [vcf.model._Substitution('CGGG')]; \
  variant = vcf.model._Record('1', pos, '.', ref, alt, 100, None, None, None, None, None); \
  f_vseq = io.BytesIO(); \
  f_vseq_pos = io.BytesIO(); \
  print handle_variant(variant, f_vseq, f_vseq_pos)
  2
  >>> _ = f_vseq.seek(0); print f_vseq.read()
  CGGG
  >>> _ = f_vseq_pos.seek(0); print struct.unpack('4I',f_vseq_pos.read(4*4))
  (2, 3, 3, 3)


  Test with insert
  >>> ref_seq = 'ACTGACTG'; \
  pos = 8; \
  ref = 'G'; \
  alt = [vcf.model._Substitution('GTTT')]; \
  variant = vcf.model._Record('1', pos, '.', ref, alt, 100, None, None, None, None, None); \
  f_vseq = io.BytesIO(); \
  f_vseq_pos = io.BytesIO(); \
  print handle_variant(variant, f_vseq, f_vseq_pos)
  8
  >>> _ = f_vseq.seek(0); print f_vseq.read()
  GTTT
  >>> _ = f_vseq_pos.seek(0); print struct.unpack('4I',f_vseq_pos.read(4*4))
  (8, 9, 9, 9)

  See Readme.md for details of algorithm
  """
  var_type = '1' if strand_no is None else variant.genotype('sample').data.GT.split('/')[strand_no]
  # 0 means REF 1 means ALT. If we don't specify a strand number it means we don't care about Zygosity and will always
  # take the ALT
  if var_type == '1':
    alt = variant.ALT[0].sequence
    if alt == '.': alt = ''
    ref = variant.REF
    if ref == '.': ref = ''
    f_vseq.write(alt)    # Copy over ALT
    new_ref_pos = len(ref) + variant.POS - 1  # Advance along ref_seq
                                              # -1 because POS is 1-indexed, we are 0-indexed internally
    if len(alt) > 0:
      if len(ref) > 0:
        f_vseq_pos.write(struct.pack('I', variant.POS))  # The original base
      if len(alt) > 1:
        f_vseq_pos.write(struct.pack('{:d}I'.format(len(alt) - len(ref)), *[new_ref_pos + 1] * (len(alt) - len(ref))))
  else:
    new_ref_pos = variant.POS - 1  # Keep us here - we didn't implement this variant and we should keep copying
                                   # -1 because POS is 1-indexed, we are 0-indexed internally
  return new_ref_pos

There were several functions like this. I was refactoring this code and when I would work on these functions I would spend minutes trying to find the actual body of the function, often puzzling over what turned out to be a doctest and not the actual code. I had been warned about this by well meaning relatives.

Doctests are awesome when you can combine a test and example and it is pithy. When you need to setup complex data structures or you need to test lots of use cases, it is better to put tests in a separate file, in a separate directory and run them with nose.

Doctests can still be used in the documentation as examples of using functions with the added benefit that the examples are kept upto date by being tested.







Saturday, July 5, 2014

Python string concatenation

Continuing with our timing and efficiency theme: What is the more efficient way of building up a string?

from cStringIO import StringIO

def string_concat(size=int(1e6)):
  repeat = 'A'
  st = ''
  for n in range(size):
    st += repeat
  return st

def list_concat(size=int(1e6)):
  repeat = 'A'
  st = []
  for n in range(size):
    st += repeat
  return ''.join(st)

def stringIO_concat(size=int(1e6)):
  repeat = 'A'
  buf = StringIO()
  for n in range(size):
    buf.write(repeat)
  return buf.getvalue()

Turns out the simple way is best:

In [91]: %timeit string_concat()
10 loops, best of 3: 121 ms per loop

In [92]: %timeit list_concat()
1 loops, best of 3: 293 ms per loop

In [93]: %timeit stringIO_concat()
1 loops, best of 3: 336 ms per loop

Friday, July 4, 2014

numpy arrays and iterators

Is it better to access a numpy array with an iterator or indexing?

import numpy

# This is how I've always done it - straight python idiomatic usage
def iterate1(m=100000):
  a = numpy.empty(m)
  sum = 0
  for n in a:
    sum += n
  return sum

# Didn't know about this one, but it is the recommended one in the docs
def iterate2(m=100000):
  a = numpy.empty(m)
  sum = 0
  for n in numpy.nditer(a):
    sum += n
  return sum

#This is C-like 
def loop(m=100000):
  a = numpy.empty(m)
  sum = 0
  for n in range(a.size):
    sum += a[n]
  return sum

And the shootout?

>>> %timeit iterate1()
10 loops, best of 3: 35.4 ms per loop
>>> %timeit iterate2()
10 loops, best of 3: 63.7 ms per loop
>>> %timeit loop()
10 loops, best of 3: 45.1 ms per loop


>>> %timeit iterate1(1e6)
1 loops, best of 3: 368 ms per loop
>>> %timeit iterate2(1e6)
1 loops, best of 3: 605 ms per loop
>>> %timeit loop(1e6)
1 loops, best of 3: 443 ms per loop

So the pythonic one seems to be the best

Thursday, July 3, 2014

HDF5 is not for fast access

HDF5 is a good solution for storing large datasets on disk. Python's h5py library makes it possible to pretend that data stored on disk is just like an in memory array. It is important to keep in mind that the data is really stored on disk and is read in every time a slice or index into the data is taken.

import numpy
import h5py


def create_data(length=1e4):
  data = numpy.random.rand(length)
  with h5py.File('test.h5', 'w') as fp:
    fp.create_dataset('test', data=data)
  return data


def access_each_h5():
  y = 0
  with h5py.File('test.h5', 'r') as fp:
    for n in range(fp['test'].size):
      y += fp['test'][n]
  return y

def access_each_array(data):
  y = 0
  for n in range(data.size):
    y += data[n]
  return y


d = create_data()

>>> run test.py
>>> %timeit access_each_array(d)
100 loops, best of 3: 4.14 ms per loop
>>> %timeit access_each_h5()
1 loops, best of 3: 1.9 s per loop
That sobering difference in performance reminds us that we can't - performance wise - equate the two. When processing data from an hdf5 file, it is best to read in as large chunks as your memory will allow and do the heavy lifting in memory.

Friday, June 20, 2014

A note on Python's __exit__() and errors

Python's context managers are a very neat way of handling code that needs a teardown once you are done. Python objects have do have a destructor method (__del__) called right before the last instance of the object is about to be destroyed. You can do a teardown there. However there is a lot of fine print to the __del__ method. A cleaner way of doing tear-downs is through Python's context manager, manifested as the with keyword.

class CrushMe:
  def __init__(self):
    self.f = open('test.txt', 'w')

  def foo(self, a, b):
    self.f.write(str(a - b))

  def __enter__(self):
    return self

  def __exit__(self, exc_type, exc_val, exc_tb):
    self.f.close()
    return True

with CrushMe() as c:
  c.foo(2, 3)

One thing that is important, and that got me just now, is error handling. I made the mistake of ignoring all those 'junk' arguments (exc_type, exc_val, exc_tb). I just skimmed the docs and what popped out is that you need to return True or False depending on whether there was an error. So I wrote my code to return False if there was a problem in saving the file (My actual code is a little more involved, but the same in spirit) and True otherwise.

So what happens now if you do
with CrushMe() as c:
  c.foo('2', '3')

Of course, YOU know that this code will error out - you can't subtract strings. But if you run this code, it will FAIL SILENTLY. This is because I was careless and did not consider what happens if there is an error SOMEWHERE ELSE.

The proper way to do this, as a minimum, is to change the code to

def __exit__(self, exc_type, exc_val, exc_tb):
    self.f.close()
    return True if exc_type is None else False

Another note: defining both a __del__ method and an __exit__ method can lead to tricky situations. The following code, for, instance, calls the close method twice.

class CrushMe:
  def __init__(self):
    self.f = open('test.txt', 'w')

  def foo(self, a, b):
    self.f.write(str(a - b))

  def __del__(self):
    self.close()

  def __enter__(self):
    return self

  def __exit__(self, exc_type, exc_val, exc_tb):
    self.close()
    return True if exc_type is None else False

  def close(self):
    print 'Called!'
    self.f.close()

with CrushMe() as c:
  c.foo(2, 3)

close gets called first by __exit__ when we exit the context and then when we exit the interpreter and the object is deleted.


Sunday, June 1, 2014

Feedburner and the fragmentation of google

Wordpress has a plugin for sending your post to twitter or linkedin (and linkedin has a builtin way of sending your updates to twitter) but I didn't see anything like that for blogger (except a G+ button for publicizing to that thriving community). Whenever I did a search I came up with some third party service. Finally I ran into Feedburner, which turns out to be Google's own service for publicizing things on social media. This just led me to an observation that google has so many services and they CAN talk to each other, but there are complicated layers between them.

Wednesday, May 21, 2014

Python 'or'

I've been trying to write more Pythonic code. I'm sure everyone has their own definition of what Pythonic is, but everyone will agree on things like using list/dict comprehensions where possible and so on and so forth.

I was perusing some code written by some colleagues and found this:

    push_data = {'message': container.message or 'NO COMMIT MESSAGE',

This took me to the basic definition of Python's or keyword and, notably, this clause: "These only evaluate their second argument if needed for their outcome."

Way cool! I would previously have used a construction like

    push_data = {'message': container.message if container.message else 'NO COMMIT MESSAGE',

and patted myself on the back for being Pythonic, but there is an even better way to do it.

I must say, though, that I would not use the corresponding and construction because it is not so intuitive to me.

0 and 56

Tuesday, May 20, 2014

PyVCF and header lines

PyCVF which I use for loading VCF files ignores* all lines of the header (## lines) but needs the column header line - if you omit this than PyVCF will skip the first variant line.

*And by ignores I really meant - interprets such lines as ##key=value pairs which are then found in vcf_reader.metadata.

Tuesday, May 13, 2014

Finder on Mavericks slowing down?

If you find that Finder on Mavericks has gotten obscenely slow you can try resetting the finder's .plist and restarting the finder (From the solution here):

rm ~/Library/Preferences/com.apple.finder.plist&&killall Finder

Tuesday, May 6, 2014

Darwin and case sensitivity in filenames

I normally use a Mac as a pretty interface to a unix (POSIX) system and don't run into problems, but every now and then my whole workflow will come to a stop because Darwin did that one thing slightly different from everyone else and I have to run that down. This time it was case sensitive file names.

POSIX file systems are case sensitive. So a file called Test is different from TEST is different from tesT (Though one is recommended not to torture users in that fashion).

So under a proper POSIX system you can do

mkdir Test
mkdir tesT
mkdir TEST

and end up with three different directories.

Darwin lulls you into a false sense of security by accepting all three forms but behind the scenes it simply maps it all to the same form. So you get a nonsensical complaint as you do this

mkdir Test  # OK
mkdir tesT  # Directory already exists? WTF?
mkdir TEST  # Directory already exists? Whaaa?

Sigh.

Monday, May 5, 2014

Doctesting Python command line scripts

The purists will hate this one, but the pragmatists may not condemn me so much. I'm writing Python because I want compact, easy to understand and maintain code. If I was into writing a ton of complicated boilerplate I would have used C.

In keeping with this philosophy I love doctest for testing Python code. Many a time writing code with an eye to doctesting it has led me to more modular and functional code. Perhaps this code was a little slower than it could be, but boy was it fast to write and debug and that's the reason we are in Python, right?

One bump in the road is how do you doctest whole command line applications? Vital parts of my code consist of Python scripts that are meant to be called as command line tools. The individual parts have been tested with doctest, but there is often a need for a gestalt test, where several tools are chained and the output needs to be tested.

There is something called shell-doctest which seems to do exactly this, but it is yet another third party package and not very recently maintained, or very widely known. I'm not sure I should use it.

The creator of Python has recommended creating a main function that can be then tested with forced arguments. I like this, and may convert my 'main' sections this way, but there is something to be said to maintaing the 'conversational' style of doctest, where you simply plop down python commands as you would normally and have the correct answers as if you were in an interactive programming environment.

My current experiment is to merge the gestalt test with my instruction manual so that I'm writing the command line examples into my Readme.md file which then doubles as a test file which can be executed by running python -m doctest -v Readme.md

In fact, if you are in IPython, the magic function run does exactly what I want - which, is to run a complete shell command, like run process_data.py arg1 arg2 arg3. Unfortunately doctest uses a vanilla python shell which does not have this facility.

My solution is to define a function I call shell early on in the document.

>>> import shlex, subprocess
>>> def shell(command): subprocess.call(shlex.split(command))

Now, in my Readme.md file I have things like:

You can run the data processing program as

>>> shell('python process_data.py infile.txt outfile.txt')

Which will result in this processed data

>>> with open('outfile.txt','r') as f: print f.read()
Output Data

Which serves both as do by example documentation as well as test.

Sunday, May 4, 2014

Storing state in a Python function

This one blew me away. You can store state in a function, just like you would any object. These are called function attributes. As an aside, I also learned that using a try: except clause is ever slightly so faster than an if.

def foo(a):
  try:
    foo.b += a
  except AttributeError:
    foo.b = a
  return foo.b

def foo2(a):
  if hasattr(foo2, 'b'):
    foo2.b += a
  else:
    foo2.b = a
  return foo2.b

if __name__ == '__main__':
  print [foo(x) for x in range(10)]
  print [foo2(x) for x in range(10)]

"""
python -mtimeit -s'import test' '[test.foo(x) for x in range(100)]'
python -mtimeit -s'import test' '[test.foo2(x) for x in range(100)]'
"""

Giving us:

python test.py
[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

python -mtimeit -s'import test' '[test.foo(x) for x in range(100)]'  ->  10000 loops, best of 3: 29.4 usec per loop
python -mtimeit -s'import test' '[test.foo2(x) for x in range(100)]' -> 10000 loops, best of 3: 39.1 usec per loop

Thursday, May 1, 2014

Python: Maps, Comprehensions and Loops

Most of you will have seen this already but:

c = range(1000)
d = range(1,1001)

def foo(a,b):
  return b - a


def map_foo():
  e = map(foo, c, d)
  return e


def comprehend_foo():
  e = [foo(a, b) for (a,b) in zip(c,d)]
  return e


def loop_foo():
  e = []
  for (a,b) in zip(c,d):
    e.append(foo(a, b))
  return e

def bare_loop():
  e = []
  for (a,b) in zip(c,d):
    e.append(b - a)
  return e

def bare_comprehension():
  e = [b - a for (a,b) in zip(c,d)]
  return e

"""
python -mtimeit -s'import test' 'test.map_foo()'
python -mtimeit -s'import test' 'test.comprehend_foo()'
python -mtimeit -s'import test' 'test.loop_foo()'
python -mtimeit -s'import test' 'test.bare_loop()'
python -mtimeit -s'import test' 'test.bare_comprehension()'
"""

In order of speediness:

test.bare_comprehension() -> 10000 loops, best of 3: 97.9 usec per loop
test.map_foo() -> 10000 loops, best of 3: 125 usec per loop
test.bare_loop() -> 10000 loops, best of 3: 135 usec per loop
test.comprehend_foo() -> 10000 loops, best of 3: 159 usec per loop
test.loop_foo() -> 1000 loops, best of 3: 202 usec per loop

So, where-ever you can, use map. The reason map_foo is slower than bare_comprehension is the function overhead.

Python doctest and functions that do file I/O

Some of my code contains functions that read data from files in chunks, process them, and then write data out to other files. I thought it was silly and expensive to first read the data into a list/array, work on them, write out the results to another list and then write the list out to files. But how do you write doctests for such functions?

One way would be to explicitly open files for reading/writing but this is a pain because you have to first create the file, process them, check the answers and then remember to clean up (delete) the files afterwards. You could use the wonderful Python tempfile module, but ...

In my case I was passing file handles to the functions. One convenient way to test such functions without creating physical files on disk is to use io.BytesIO(). This function returns a data structure that has all the characteristics of a file but resides in memory.

For example, suppose you have a cool function that reads in a file and writes out every second byte.

def skip_a_byte(fin, fout):
  b = fin.read(1)
  while b:
    if fin.tell() % 2:
      fout.write(b)
    b = fin.read(1)

We could outfit with a doctest harness as follows:

def skip_a_byte(fin, fout):
  """
  >>> import io; \
  data = 'Haopwf gNqojwj jBlrmowwjnl BCloRwT'; \
  f1 = io.BytesIO(data); \
  f2 = io.BytesIO(); \
  skip_a_byte(f1, f2); \
  _ = f2.seek(0); \
  print f2.read()
  How Now Brown Cow
  """
  b = fin.read(1)
  while b:
    if fin.tell() % 2:
      fout.write(b)
    b = fin.read(1)

if __name__ == "__main__":
  import doctest
  doctest.testmod()

Oh, BTW, I would like to give a shoutout to PyCharm for being very cool: they recognize the code in the docstring, and can do code analysis on it!

Wednesday, April 30, 2014

How to tell if a property is a rental

From a thread here:

  1. The most accurate is to physically go to the place and politely ask the residents - making clear that you are considering buying and are trying to determine how many units are rentals.
  2. Online, if you go to the assessor's database if the owner's mailing address is different from the house address it's most certainly not a primary residence. This may mean it's rented out.
  3. Online, if you can see the property tax amount, many towns in Mass. have a residential exemption which applies to primary residences only. If the tax seems lower, then this is a primary residence.

Tuesday, April 29, 2014

What makes marine grade wire special?

I was in the market for pain old 14 AWG wire for a project when I came across spools of wire being sold as "tinned marine grade wire". I was curious to know what made this wire "marine grade". From this page, it turns out, marine grade wire is

  1. 12% bigger for a given wire gauge. So 14G marine wire is thicker than regular 14G wire
  2. Each strand of the wire is finer and individually coated with tin, making it resistant to corrosion
  3. The insulation on the wire is oil, moisture and heat resistant.

Monday, April 28, 2014

Breaking BAM

It's fun to mess with our bioinformatics tools and laugh at ourselves. The BAM format carries a query name string. In an idle moment I wondered, how long a string can I put here before bad things happen to the BAM?

Generally this string carries information related to the device that produced the read, lane number, run number and so and so forth. It's completely specification free and everyone encodes information here in their own way, so you would think it's an arbitrary length string. Almost.

Try out the short Python snippet below. It generates a dummy fasta file (test.fa) and a dummy BAM file (test.bam) with one 'aligned' read. The only funky thing is that you can make the qname field of the read as long as you want (by varying the lone input parameter to the script).

import sys
import pysam

try:
  qnamelen = int(sys.argv[1])
except:
  qnamelen = 255

with open('test.fa', 'w') as f:
  f.write('>test\n')
  f.write('A'*100)

bam_hdr = {'HD': {'VN': '1.4'},
           'SQ': [{'LN': 100, 'SN': 'test'}]}
bf = pysam.Samfile('test2.bam', 'wb', header=bam_hdr)  # Write binary BAM with header
ar = pysam.AlignedRead()
ar.qname = 'Q'*qnamelen  # Gives Tablet headaches when str len > 254
ar.seq = 'A'*50
ar.qual = '~'*50
ar.mapq = 100
ar.pos = 0
ar.cigarstring = '50M'
bf.write(ar)
bf.close()
pysam.sort('test2.bam', 'test')
pysam.index('test.bam')

Now open the pair of fasta and BAM files in samtools tview, Tablet and IGV (and indeed in anything else). Laugh loudly until your office mates banish you.


Only samtools tview - the least fancy one - loads the alignment without batting an eyelid.

The specs allow an arbitrary length null terminated string.

(Oh, as an aside, try this. Create a .fasta file with this simple corruption: Remove the newline from the sequence id in the fasta file (so that the sequence looks something like 'testAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'. Load the sequence in IGV. Now fix the sequence and try and convince IGV to load the new corrected sequence. Hint: it won't go away until you use igvtools to reindex the fasta file. running bwa index won't help)

Saturday, April 26, 2014

Adjusting sticking doors

Several of the doors in ye old house were sticking at the top at the latch end. The door was angled too high (or the door frame top was drooping). My solution, in each case, was to use a chisel to deepen the lower hinge seat on the door frame. This allowed the bottom of the door to swing towards the hinge side, bringing the top away from the frame. I could have added shims to the top hinge too. Indeed, for one of the doors I added shims to the top hinge, chiseled away the lower hinge AND chiseled away a bit of the top corner.

Wednesday, April 23, 2014

Bash conditionals

#!/bin/bash
set -x
MYVAR=3
if [ ${MYVAR} = 1 ]; then
    : First option
elif [ ${MYVAR} = 2 ]; then
    : Second option
elif [ ${MYVAR} = 3 ]; then
    : Third option
fi

->

+ MYVAR=3
+ '[' 3 = 1 ']'
+ '[' 3 = 2 ']'
+ '[' 3 = 3 ']'
+ : Third option

You need to watch out for whitespace, as bash is sensitive to whitespace

#!/bin/bash
set -x
MYVAR=3
if [${MYVAR} = 1 ]; then
    : First option
elif [ ${MYVAR} = 2 ]; then
    : Second option
elif [ ${MYVAR} = 3 ]; then
    : Third option
fi

->

+ MYVAR=3
+ '[3' = 1 ']'
./test.sh: line 4: [3: command not found
+ '[' 3 = 2 ']'
+ '[' 3 = 3 ']'
+ : Third option


Very, very sensitive to whitespace

#!/bin/bash
set -x
MYVAR=3
if [ ${MYVAR}=1 ]; then
    : First option
elif [ ${MYVAR} = 2 ]; then
    : Second option
elif [ ${MYVAR} = 3 ]; then
    : Third option
fi

->

+ MYVAR=3
+ '[' 3=1 ']'
+ : First option


Monday, April 14, 2014

The magic of mmap

Big data is sometimes described as data whose size is larger than your available RAM. I think that this is a good criterion because once the size of your data (or the size of any results of computing on your data) start to approach your RAM size you have to start worrying about how you are going to manage memory. If you leave it up to your OS you are going to be writing and reading to disk in somewhat unpredictable ways and depending on the software you use, your program might just quit with no warning or with a courtesy 'Out of memory' message. The fun challenge of "Big Data" is, of course, how to keep doing computations regardless of the size of your data and not have your computer quit on you. Some calculations can be done in a blocked fashion but some calculations require you to access different parts of the data all at once.

Python's mmap module is an excellent way to let someone else do the dirty work of handling data files that are comparable or larger than available memory.

import mmap
import numpy


@profile
def load_data():
  fin = open('../Data/human_chrom_11.smalla', 'r+b')
  x = fin.read()
  y = x[numpy.random.randint(0,len(x))]
  print y

@profile
def map_data():
  fin = open('../Data/human_chrom_11.smalla', 'r+b')
  x = mmap.mmap(fin.fileno(), 0)
  y = x[numpy.random.randint(0,len(x))]
  print y

load_data()
map_data()

Here the .smalla data files are simply somewhat large files that can be loaded into memory (which we do for illustration purposes) but which we'd rather not. Running this code with memory_profiler

python -m memory_profiler test.py

tells us:

Filename: test.py

Line #    Mem usage    Increment   Line Contents
================================================
     5   16.922 MiB    0.000 MiB   @profile
     6                             def load_data():
     7   16.926 MiB    0.004 MiB     fin = open('../Data/human_chrom_11.smalla', 'r+b')
     8  145.680 MiB  128.754 MiB     x = fin.read()
     9  145.691 MiB    0.012 MiB     y = x[numpy.random.randint(0,len(x))]
    10  145.691 MiB    0.000 MiB     print y


Filename: test.py

Line #    Mem usage    Increment   Line Contents
================================================
    12   16.941 MiB    0.000 MiB   @profile
    13                             def map_data():
    14   16.941 MiB    0.000 MiB     fin = open('../Data/human_chrom_11.smalla', 'r+b')
    15   16.945 MiB    0.004 MiB     x = mmap.mmap(fin.fileno(), 0)
    16   16.953 MiB    0.008 MiB     y = x[numpy.random.randint(0,len(x))]
    17   16.953 MiB    0.000 MiB     print y

As we can see from the 'increment' column, when we map the data we hardly use any memory at all compared to the 128 MB that we heat up when we load the data into memory at once.

We should keep in mind that we have traded off space for time here. Even with a SSD operating on data from disk is going to take much longer than operating on data that is all in memory, but at least we are able to do it.


Now, if you want both fast access and operability with limited RAM, you need a much larger bag of tricks which I don't have and which often heavily depends on tricks you can do with your particular data.



The logging overhead.

Python makes printing logger messages, and adjusting the logger level (which messages to print when) very easy. However, it seems, that the logger code comes with a higher overhead than if you used simple 'if' statements to control what messages to print.

Logger messages are very, very useful. Two typical uses of logger messages is to debug things when a program goes awry and to print out the progress of a program (if the user wishes it) to reassure us that the program is running and inform us of how much further the program has to go.

Python's logger is a lot of fun because it is very easy to set up logging messages at different levels and then filter which messages you will actually show. For example you can code the program to print out values of some variables at the  'DEBUG' level and print out the progress of the program at the 'INFO' level. Then you can instruct the code to print out only the INFO messages or both the DEBUG and INFO messages.

It's very likely that actually printing logging messages slows down the program, but I began to wonder what kind of overhead even the ignored logger messages were introducing to the program. One could think that placing a debug logger message in a loop is a good debug tool which can be conveniently switched off when in production with little effect, but how true is that?


import logging
logger = logging.getLogger("Meta")


def foo(N):
  sum = 0
  for n in range(N):
    sum += n
    logger.debug('This is message {:d}'.format(n))
  x = sum**2

def bar(N):
  sum = 0
  for n in range(N):
    sum += n
  x = sum**2

def foobar(N, flag=False):
  sum = 0
  for n in range(N):
    sum += n
    if flag:
      logger.debug('This is message {:d}'.format(n))
  x = sum**2


if __name__ == "__main__":
  import timeit
  print 'No logger message: ', timeit.timeit('bar(100)', setup='from __main__ import logging, bar; logging.basicConfig(level=logging.ERROR)', number=1000)
  print 'Suppressed logger message: ', timeit.timeit('foo(100)', setup='from __main__ import logging, foo; logging.basicConfig(level=logging.ERROR)', number=1000)
  print 'Logger message behind false if statement: ', timeit.timeit('foobar(100)', setup='from __main__ import logging, foobar; logging.basicConfig(level=logging.ERROR)', number=1000)
  print 'Logger message behind true if statement: ', timeit.timeit('foobar(100, True)', setup='from __main__ import logging, foobar; logging.basicConfig(level=logging.ERROR)', number=1000)


Which gives us

No logger message:  0.00471091270447
Suppressed logger message:  0.140299081802
Logger message behind false if statement:  0.00491690635681
Logger message behind true if statement:  0.133023023605

This is interesting. It shows that the overhead for the suppressed logging statement is very high, and is in fact higher than that for a simple if statement - that is if you had code where you suppressed printing of a log message using an if statement you would waste less time than if you relied on logger's own logic to handle log levels.

This is a little disappointing.

UPDATE: Following Anon's suggestion to use
logger.debug('This is message %d', n)
I found that it did indeed reduce the logger overhead, but not by much. The new times are:

No logger message:  0.0054669380188
Suppressed logger message:  0.0824329853058
Logger message behind false if statement:  0.00509595870972
Logger message behind true if statement:  0.0940999984741

Tuesday, April 8, 2014

Bash: print commands as they execute

I'm making a demo script that calls programs from a toolchain I am developing. The idea of the script is that it runs a set of commands in sequence and also prints what the command will be doing. Say the base script was:

python do_this.py

I first put in echo to describe the command

echo 'Now we do blah blah'
python do_this.py


But, I wanted to see the commands too. I discovered the set -x command, which starts the debug mode and prints out every command as it is executed.

set -x
echo 'Now we do blah blah'
python do_this.py


But, this now, effectively, printed out the description twice: once when printing the echo statement and then again when actually executing the echo statement. Then I discovered the No OPeration (NOP) character for bash ":".

set -x
: Now we do blah blah
python do_this.py

This prints the line but does not try to execute it.

Thursday, April 3, 2014

Python: passing a mix of keyword arguments and dictionary arguments to a function

So Python is cool because of keyword arguments:

def foo(a=1,b=2,c=3):
  print a,b,c

foo(a=1) # -> 1 2 3

Python is cool because you can pass a dictionary whose keys match the argument names:

def foo(a=1,b=2,c=3):
  print a,b,c

args = {'a': 1, 'b':2}
foo(**args) # -> 1 2 3

But, can you mix the two? Yes, yes you can!

def foo(a=1,b=2,c=3):
  print a,b,c

args = {'a': 1, 'b':2}
foo(c=3, **args) # -> 1 2 3

Hmm, can we screw up the interpreter? What happens if we send the same argument as a keyword AND a dictionary?

def foo(a=1,b=2,c=3):
  print a,b,c

args = {'a': 1, 'b':2}
foo(a=4, **args) # -> TypeError: foo() got multiple values for keyword argument 'a'

Nothing gets past Python, eh?

Monday, March 31, 2014

Washing machine hoses

When our home inspector went through he mentioned to me that I should replace the existing rubber hoses on the washing machine with steel-reinforced ones. I wondered if washing machine hoses were specially prone to fail, perhaps something to do with the intermittent nature of the load (if you look at the hoses when the washing machine runs, they jerk as the machine draws water in phases). I also began to wonder if dishwasher hoses suffered from the same problem.

A quick search brought up two documents, one from a community association underwriter - which I assume insures people with rental properties. The other document I found was from an insurance association serving hotels and inns. The first one claims that steel-reinforced hoses are no better than rubber hoses since the hoses are damaged at the connection. The other one suggests that steel-reinforced hoses may be better than regular rubber hoses but all hoses should be replaced every five years or so.

I could not find reasons why these hoses were more susceptible to rupturing and why the dish washer water connection does not suffer from the same maintenance issue.


execfile and imported modules

I was given to believe that Python's execfile statement simply executed all the commands in a file and continued on its way. This is not entirely true, at least in Python 2.7: imported modules seem not to be handled by the execfile statement, which seems to be rather odd to me.

import numpy


def gen(n=100):
  return numpy.arange(n)

This code does what you expect when you import it as a module:

In [1]: import test

In [2]: test.gen(10)
Out[2]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

And when you run it as a script to incorporate it into your workspace:

In [3]: run test

In [4]: gen(10)
Out[4]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

However, when you use execfile to run the script you run into a snag:


In [5]: pars = {}; execfile('test.py', {}, pars); pars['gen'](10)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-5-b06061c74d2b> in <module>()
----> 1 pars = {}; execfile('test.py', {}, pars); pars['gen'](10)
/Users/kghose/Code/Mitty/Test/test.py in gen(n)
      3 
      4 def gen(n=100):
----> 5   return numpy.arange(n)
NameError: global name 'numpy' is not defined

Whaaaaa?

If, instead, you use the fascinating standard Python imp module, you get:


In [7]: import imp; mod = imp.load_source('test','./test.py', open('test.py','r'))
In [8]: mod
Out[8]: <module 'test' from './test.pyc'>
In [9]: mod.gen(10)
Out[9]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

Things run as they should.

Tuesday, March 25, 2014

Parameter files in Python for Python programs

I often write and use Python programs that require input parameters to tell them what to do. When there are a few parameters I usually pass them through the command line (my current favorite framework for command line argument parsing is the amazing docopt module). When there are many parameters, however, command line arguments can get cumbersome.

In the past I have looked to configuration markup languages (YAML comes to mind) and have also used microsoft excel format files (using the excellent xlrd package). Using proprietary formats makes my stomach upset, but there are no good solutions for the odf counterparts.

Recently I have started to experiment with storing parameter files in python and reading them into my code.

__import__(filename) works and puts the parameters into a separate name space, so you can access them as filename.x, filename.y etc. However, the file needs to be in the module search path, and it feels a bit of a misuse.

A better solution, I find, is to use execfile to read the parameter file into the code. There are no path restrictions, execfile IS meant to execute code (so no misuse there) and the variables from the Python file are loaded into a separate dictionary so they are contained and do not spill all over your code.

#Main file
pars = {}
execfile('params.py', {}, pars)

With params.py looking like

a = 43
b = 'a string'
c = {'d': 23}

We get:

pars --> {'a': 43, 'b': 'a string', 'c': {'d': 23}}

We can put in comments in the file and structure however we need which I find very convenient.

execfile has disappeared in Python 3 but you can use

with open(filename) as f:
    exec(compile(f.read(), filename, "exec"))

UPDATE: However, see a funny issue with execfile here.

Monday, March 24, 2014

git subtree split

Problem: You have a git hub repository with code in several folders. You want to move one of the sub folders into its own separate repository (for example, it was experimental code you were working on, which you now want to spin off into its own life)

Solution: Use git subtree split as detailed here (look for the answer that says The Easy Way™)

Wednesday, March 19, 2014

Another reason to love PyCharm

The latest version of PyCharm can run the pep8.py tool on your code to flag coding style violations on the fly. I thought this was cool, but it can be annoying sometimes. This is because pep8 consists of coding guidelines and some of them are a matter of taste. My personal quirk is that I like to use 2 spaces for indents instead of 4. 

When I turn on PyCharm’s pep8 checking all my code gets underlined because I’m using two spaces for indents. However, I discovered that under Preferences->Inspections->PEP 8 Coding style violation and there is a properties list labelled “Ignore” where you can type in pep8 violations to be ignored.

UPDATE: From this very informative post here and a response from one of PyCharm's programmers there: Go to the error, click on the light bulb icon and then select 'Ignore errors like this'. This will automatically add a proper exception to this list. For those interested the list of error codes is here.

I typed in ‘indentation’ and was delighted to note that this made those annoying flags disappear (This should not have worked). Also, you can change the severity of the violation flag - I changed it from warning to typo, which is less visually distracting.