A recent pep solidifies type annotations in python 2 and 3. These type annotations are compatible with current versions of python. They can be ignored altogether, used however you wish in your own program, or used to typecheck your code with mypy. This post will discuss the last option. Later we’ll see how python’s strong introspective powers can be used to leverage these types in conjunction with mypy.
mypy enables static typing in python. The features include defacto case-classes (using named tuples) and Union types. These are known more generally as “product types” and “sum types” respectively. A product type is similar to a class in Java. It has pre-defined members (private or public) of other types. In a sense it is a “product” of these other types. In mypy, one can declare a product type using classes, or more simply using NamedTuple. For example, let’s create a product type for points in a 3D plane.
Point3D = NamedTuple("Point3D", [("x", float), ("y", float), ("z", float)])If we wanted to use a simple tuple instead, we could declare that so:
Point3DTuple = Tuple[float, float, float]Let’s look at what our named tuple can, and more importantly can’t, do within mypy’s type world. All the displayed errors are part of mypy’s output, which provides the line of the error as well as an explanation. Keep in mind these are all erorrs caught before the program, or even any tests, are run. These errors can also be integrated with editors or IDEs to provde real-time feedback.
It can be created and accessed just like collections.namedtuple.
point = Point3D(0, 1.0, 3.98)
x = point.x
y = point[1] # this typechecks, it probably shouldn'tmypy knows how long the tuple is, and what types its members are!
r = point[99]
foo.py:10: error: Tuple index out of rangemypy enforces the safety of common operators. This avoids meaningless comparisons, for example, which are uncaught by python’s runtime:
>>> "foo" > sys.maxint
True # sure, why not?point.x + "Eureka"
foo.py:10: error: Unsupported operand types for + ("float" and "str")
x = point.x # mypy infers the type after assignment
x > "Eureka"
foo.py:10: error: Unsupported operand types for > ("float" and "str")mypy limits attribute access:
sneaky = point.gecko
foo.py:13: error: "Point3D" has no attribute "gecko"mypy supports generics. A generic can be a lot of things; A list, an Iterable, or something equivalent to scala/java 8’s Option type. If a generic is a collection, all elements of the collection must be of the same type. mypy comes equipped with a number of generic types; take for example List, which is an alias for the built-in list.
ListOfInts = List[int]You can also create types by subclassing Generic.
class Option(Generic[T]):
def getOrElse(t: T) -> T:
. . . It’s possible to use multiple type variables within a generic:
E = TypeVar("E")
V = TypeVar("V")
class Either(Generic[E,V]):
. . . . Let’s use List and 3DPoint to create a more complex product type: Robot Legs.
RobotLegs = NamedTuple("RobotLegs", [("leftLeg", List[Point3D]), ("rightLeg", List[Point3D]), ("color", str)])Note that we’ve defined the field color as simply a string, allowing us to create robot legs with nonsense colors. It’s also possible to create robot legs with negative integers for coordinates! We only want pastel colors, and robots which exist in the cartesian plane.
blueRobot = RobotLegs(points, points, "fizbizzle")Of course, we could check for this condition in the functions that use the color:
def getColor(legs: RobotLegs) -> int:
if legs.color not in ["skyblue", "red", "white"]:
raise ValueError("Invalid color %s" % legs.color)
else:
. . . . That’s a hassle, and it’s easy to forget to do these checks in every function. Instead, let’s nip this in the bud. We really want to make it is easy on ourselves and be really really sure that we only have to validate our input once. We can do all the validation–cleaning up data from I/O, verifying it matches a certain shape, creating errors etc.–when we construct the instances of our types. That way all functions which accept those types are relieved from the obligation of checking themselves.
SkyBlue = NamedTuple("SkyBlue", [])
PastelRed = NamedTuple("PastelRed", [])
White = NamedTuple("White", [])
Color = Union[Blue, PastelRed, White]
RobotLegs = NamedTuple("RobotLegs", [("leftLeg", List[Point3D]), ("rightLeg", List[Point3D]), ("color", Color)])Now we can be assured that our color is one of the primaries (always a good starting pint for giant robots), so we don’t have to worry about validating our data again!
def getColor(legs: RobotLegs) -> int:
if legs.color == SkyBlue(): return 0x87CEFA
if isinstance(legs.color, SkyBlue): return 0x87CEFA # this is equivalentWe can even safely use a statically typed dictionary which never raise a KeyErorr:
colors = { SkyBlue() : 0x87CEFA } # type: Dict[Color,int]
. . . . Now, let’s make sure the 3D coordinates are valid. For this, we’ll need something more powerful than a simple NamedTuple. We’ll want to refuse all non-positive input. But note that a traditional python class won’t be safe because python classes are mutable by default! It doesn’t do much good to make a safe 3DPoint if one can simply overwrite its attributes. But we can create more complex immutable objects in python:
class Coordinate(object):
def __new__(self, x: float, y: float, z: float) -> Point3D:
assert x >= 0 and y >= 0 and z >= 0
return Point3D(x, y, z)In fact it’s possible to use this technique to guarantee that our function will only ever get valid input. It’s only possible to construct the sum type of RobotLegs through the union type of Color; Color is by definition one of Blue, Red. . . and points In languages with the concept of private constructors, it’s possible to guarantee that a RobotLegs cannot be created an invalid state–and therefore that getColor can never be passed invalid data–by making the RobotLegs constructor private. Unfortunately, we can only document the make3DCoordinates function as the point of entry for our API–we can’t exclude the constructor as private.
Note that the assurance offered by static typing is significantly stronger than the contract offered by ducked typing. If we simply accepted an object with leftLeg rightLeg and color as a RobotLeg, we’d have no guarantees that these fields were valid, or even that they were the expected type!
Color is a very simple Union type, analogous to the “Enums” of other languages (including python 3), while providing additional safety. Bution union types are more powerful; it’s possible to create a union type out of product types, and model arbitrary complex systems this way. You can think of these types as representing the “set of all possible inputs and outputs” and functions accepting these types as representing the “cobminators” or “all the things I can ever do with my inputs”. Together, these form a sort of “algebra” that represents your domain. In the domain of giant robots:
Rifle = NamedTuple('Rifle', [('ammo' , int), ('model' , str)])
Knife = NamedTuple('Knife', [('shape' , List[Coordinate]), ('thatsNotAKnife', bool)])
weapon = Union[Rifle, Knife]
RobotLegs = NamedTuple("RobotArms", [("leftArm", List[Coordinate]), ("rightArm", List[Coordinate]), ("color", Color)])
GiantRobot = NamedTuple('GiantRobot', [('weapon', Weapon), ('legs' , RobotLegs), ('arms', RobotArms)])
def canFight(robot: GiantRobot) -> bool:
if isinstance(robot.weapon, Rifle):
return robot.weapon.ammo > 0
else:
return not robot.weapon.thatsNotAKnife # this is a knifeThe isinstance check tells mypy that robot.weapon is specifically a rifle, and therefore has an ammo field of type int. Without that check, we get a nifty error from mypy–and find out before testing, running, or deploying:
foo.py: note: In function "canFight":
foo.py:35: error: Some element of union has no attribute "ammo"Great! we’ve created an API that’s clear, self-documenting, and compartively safe. We’ve provided some limited guarantees of correctness; and our domain is well-defined, which will help us reason about our past and future code moving forward. mypy is a growing project; it’s still in an early stage and being actively developed. It’s become an official part of they python flock as the definitive optional typechecker; it’s got the backing and involvement of python’s creator.
Although mypy is still in active development, it can be a profitable tool right now. It’s not a compiler, and it never touches your code, so it can be used without much concern for bugs. It takes some extra time to annotate python with types–I’ve demonstrated some of the strengths of its type inference, but it’s necessary to annotate some things like lambda expressions, for example. It’s well worth the effort to document and verify your code in one way or another–mypy is another excellent tool for this purpose.
Functional programming principles can be productively applied even in small cases. The predictability and compositional capacity of small functions allows for modular, extensible code. First-order functions can be used in a variety of generic higher-order functions or “combinators”. In the future, we will look at how static typing combined with immutability can make this code much safer as an additional benefit.
Let’s use calculating GC-content as an example.
Where seq is some DNA sequence–just a string of characters composed soley of items in the set {'A', 'C', 'G', 'T'}
def gc(seq):
'''(G+C)/(A+T+G+C) * 100'''
return (seq.count('G') + seq.count('C')) / float( len(seq) )
print(gc("ACGTCCA"))
>>> 0.571428571429But reall, we want to model GC-content as a fraction, so at any given time we know the size of the sequence. Python has a fraction class, but it doesn’t allow us to represent the state 0/0, which would be our startin state.
from fractions import Fraction
class Ratio(Fraction):
''' new is for immutable classes.'''
def __new__(cls, num, denom):
'''cls is like self really '''
if denom ==0 and num == 0:
self = super(Fraction, cls).__new__(cls)
else:
self = super(Fraction, cls).__new__(cls)
self.__str__ = Ratio.__str__
self._numerator, self._denominator = num, denom
return self
def __float__(self):
if self._denominator == 0:
return 0.0
else:
return super(Fraction, self).__float__()
def __str__(self):
return "{0}/{1}".format(self._numerator, self._denominator)Now we can update our function:
def gc(seq):
'''(G+C)/(A+T+G+C) * 100'''
return Ratio(seq.count('G') + seq.count('C'), len(seq))
print(gc("ACGTCCA"))
>>> 4/7’This is all well and good, but what if we want to know the GC-content at a certain place in the read?
seq = 'AGCTTAGGCCTTTAAAACCGGGGCCCCCGGAAGCGACTT'
print gc(seq[:10])
>>> 6/10That works, but will get tiresome and inefficient…
gc(seq[0]), gc(seq[:10]), gc(seq[-1])
>>> (Fraction(0, 1), Fraction(6, 10), Fraction(0, 1))What if we want to create a histogram of the gc-content at each position in the read?
N = len(seq)
def gc_hist_(seq):
return [gc_counter(seq[:i]) for i in xrange(1, N)]we have to re-calculate the GC-ratio each time! In terms of runtime, this is equivalent to:
sum(i for i in xrange(N) )
>>> 741Well, this method is O(N^2) (about (N^2)/2, where N=length of sequence). So the runtime is quadratic. We can fix this by writing a new function specifially for constructing a list of GC-content.
Instead, let’s try solving this problem with higher-order functions, and see where that gets us.
Functional programming works well when we compose small functions together. Let’s create the smallest unit we can out of this problem:
def content(ratio, nt):
val = 1 if nt in 'GC' else 0
return Ratio(ratio.numerator + val, ratio.denominator+1)Next, let’s solve our problem of creating a rolling GC-content for a histogram using a higher-order function and content. A higher-order function is just a function which accepts another function as one of its arguments. This feature isn’t available in all programming languages, but it’s available in python. The higher-order function we’ll use is accumulate.
In haskell, accumulate is known as scanl, and it’s corrolary is foldl. accumulate is similar to reduce in python, except it keeps track of every result, rather than throwing them away and only keeping the final result.
def accumulate(iterable, func, start=None):
'''re-implementation of accumulate that allows you to specify a start value
like with reduce.'''
it = iter(iterable)
if start is None:
total = next(it)
yield total
else:
total = start
#could also skip yielding total
for element in it:
total = func(total, element)
yield totalimport operator
print list(accumulate([1,2,3,4,5], operator.mul)) # --> 1 2 6 24 120
>>> [1, 2, 6, 24, 120]
print list(accumulate("ACGCCGT", content, Ratio(0, 0)))
from functools import partial
func_gc_hist = partial(accumulate, func=content, start=Ratio(0,0))
>>> [Fraction(0, 1), Fraction(1, 2), Fraction(2, 3), Fraction(3, 4), Fraction(4, 5), Fraction(5, 6), Fraction(5, 7)]Now we have our histogram! Let’s look more closely at how this works using reduce as an example. Using our function content with reduce will give us the total GC-ratio for a given sequence. reduce emulates recursion by computing a new value for each element in the list and passing it onto the next call with the element as a paramter. Let’s look at a simple example of summing a list:
def _add2(acc, elem):
return elem + acc
print reduce(_add2, [1,2,3,4,5])
>>> 15Let’s break this down, step by step. Here, each line represents an iteration of reduce. . . .
# under the hood, this might look like:
[1, 2, 3], 0
-> [1, 2], 3
-> [1], 5
>>>6In a sense reduce is “substituting” parts of the list for there sum, similar to how one might simplify an algabreic equation. We can see this pattern in accumulate’s output:
print list(accumulate([1,2,3,4,5], _add2))
>>> [1, 3, 6, 10, 15]Another simple example, reversing a list. list reversal has a straightforward recursive solution:
def _reverse(l):
if len(l) == 0: return []
return [l[-1]] + _reverse(l[:-1])
print(_reverse([1, 2, 3, 4]))
>>> [4, 3, 2, 1]But it can be written much more simply using reduce, which handles the details of the recursive call.
def _reverse(acc, elem):
return [elem] + acc
reduce(_reverse, [1,2,3,4], [])
>>> [4, 3, 2, 1]Again, let’s break it down:
[1, 2, 3, 4], []
-> [1, 2, 3], [4]
-> [1, 2], [4, 3]
-> [1], [4, 3, 2]
>>>[4, 3, 2, 1]notice that at any point during the traversal, the accumulated value is correct for the traversed part of the list. Using this model of folding an accumulating paramter over a sequence, we can model GC-content as a “rolling ratio” over a given sequence of nucleotides. At any point during the traversal, The ratio will be correct, and the result of “reducing” the sequence with this model will give us our total GC-content. The following method is not the most efficient nor the simplest (it requires building a “Ratio” subclass), but it closely (and flexibly) models the mathematical formula that defines GC-content in the first place. ‘’’
seq = 'AGCTTAGGCCTTTAAAACCGGGGCCCCCGGAAGCGACTT'
print reduce(content, seq, Ratio(0,0))
>>> 23/39That’s all reasonable, and it’s nice to see GC-content representated as a ratio. But is it practical? Well, it will work, but there is a much more efficient way. Array-wise computations like this–which will become quite large if we get big reads (or god forbid) a whole contig/genome. Additionally, we may want to scale to viewing multiple reads at once, ie, as a matrix.
from random import choice
N = 1000000
alpha = list('AGCT')
seq = ''.join( choice(alpha) for _ in xrange(N) )
from fn.iters import repeatfunc
seq = ''.join(repeatfunc(partial(choice, alpha), N))
%timeit functional = func_gc_hist(seq)
>>> 1000000 loops, best of 3: 663 ns per loopQuite slow. Let’s see how the efficient numpy library can achieve much faster runtimes, and how we can use these same functional principals–recursion, reduction, and accumulation–to get more leverage (and cleaner code) out of numpy.
import numpy as np
np.array([1, 2, 3, 4, 5]).sum()
>>> 15np.sum is actually a specific (read: partial application) of np.reduce! source
npseq = np.array(list(seq))
gccon = ((npseq == 'C') | (npseq == 'G')).sum()/float(len(npseq))
npresult = ((npseq == 'C') | (npseq == 'G')).cumsum()cumulative sum rolls like accumulate!
Now, we’ll divide by the index (starting at one) to simulate the ratio.
gcs = ((npseq == 'C') | (npseq == 'G'))
npres = gcs.cumsum()/np.arange(1,len(npresult)+1, dtype=float)
np_filter_idx = (npres >= .5).nonzero()Putting this together:
def np_gc_hist(seq):
npseq = np.array(list(seq))
gcs = ((npseq == 'C') | (npseq == 'G'))
npres = gcs.cumsum()/np.arange(1,len(gcs)+1, dtype=float)
return npresLet’s plot it:
import matplotlib
from matplotlib import pyplot as plt
%pylab inline
THRESH=0.4
def doplot(result):
fig = plt.figure()
fig.set_size_inches( 20.0, 8.0 )
gs = matplotlib.gridspec.GridSpec(1,2, width_ratios=[20,1])
ax1 = plt.subplot(gs[0])
ax2 = plt.subplot(gs[1])
ax1.plot(result)
'''draw a line for the threshold.'''
ax1.axhline(y=THRESH, color='green')
fig.show()
doplot(npresult)
png
Let’s try timing again. First, our old result:
%timeit old = func_gc_hist(seq)
>>> 1000000 loops, best of 3: 663 ns per loopNow with numpy:
%timeit numpy_sttyle = np_gc_hist(seq)
>>> 10 loops, best of 3: 140 ms per loopI’m currently working on a plugin for vim. Along the way I’ve discovered a lot of intersting and useful productivity tricks.
fun! Foo()
" function body
endfun
function Foo()
" function body
endfunction
Create functions within the command window by putting each statement on a new line. i.e.
:fun! Foo() [<CR>]
: second line
: . . . . .
Execute them from the commandline window:
:call Foo()
Variables have several possible scopes: l (local to function), s (local to individual scripts/files), g (global), a (a function parameter).
v is a global like g, but has been predefined by vim. These cannot be defined by the user.
b (local to current buffer), w (local to the current winow) and t (local to current tab) are less often used.
View defined variables:
:let g: "or v:, b:, etcl
By the way, we can use " for comments
View register contents:
:reg
Variables are defined using let, e.g.:
let l:bar = "red"
Variables defined or accessed within a function are by default in the local scope. Prepending with g: allows access to global variables.
Arrays are much like python lists. They are type-agnostic and can be sliced.
:let l:a = [1, 2, 'foo', 'bar']
:echo a[2:-1]` "prints ['foo', 'bar']
Strings can also be sliced withe same syntax.
String concatenation is php style:
let g:pair = 'eight' . 'eight'
Directly access the command line:
:!echo -e "bash\nis\nfun" | cat > bar.txt
Read files into the buffer:
:r bar.txt
Produces:
bash
is
fun
Under your cursor.
Read command outputs into the buffer:
:r !curl -s icanhazip.com
If your distribution of vim is compiled with python included (and it usually is) you can also access a python interpreter during your editing session.
:py print "ni!"You can access vim from the internal python interpreter.
:py import vim
:py vim.current.buffer[0] = 'Just answer the five (three!) questions'You can go crazy with it if you like . . .
:py import requests, lxml.etree
:root = lxml.etree.HTML(requests.get('https://twitter.com/montypython').text)
:redir @i| exe ':silent py for t in root.xpath("//p[contains(@class, \"ProfileTweet-text\")]/text()"): print t.encode("utf8")' | redir END | $put iHere, :redir redirects output of the commands between :redir and :redir END (which would usually be printed in the command window) to some register. Vim has 26+ register (all alphabetical letters and additional reserved registers). @i accesses the value at register i, telling vim to pipe output there. | stands in place of <CR> to separate commands. exe executes the given command as if it were in the command. We can use it when we need to make a command out of concatenating strings. In this case, it lets us wrap the python code in ’’ so that python does not try to interpret the next |. silent tells vim not to echo the result of the command in the command window. redir END will populate our register i – it won’t get updated until the redirect is finished. Finally, we execute put i, which inserts the contents of register i after the cursor’s current line. The $ (which generally indecates “last”) tells put to instead append to the last line of the buffer.
There are some useful global variables
v:statusmsg "usually holds the last item shown in the command window
v:warningmsg e.g. W10: Warning: Changing a readonly file
v:errmsg e.g. E488: Trailing characters: put v:errmsg
Useful Motions (normal mode)
command goes to
`f<char>` next instance of <char>
`)` end of setnece
`%` matching parens/brace
`$` end of line
`G` end of file
`}` next paragraph
Additionally, we have visual mode. A quick preview of that:
<C-v> "To select columns. Any change propagates to all columns.
:vs [filename] "split vertical window
:split [filename] "split horizontally
<C-w> Is the window command suffix, so <C-w> h moves to the left widnow, <C-w> l to the right, <C-w> k moves to the upper window and <C-w> j moves to the lower one. I have the following mapping which forces this to work straight out of insert mode:
:inoremap <C-w> <Esc> <C-w>
<Esc> exits to nromal mode. So does <C-[>; we can map the tab key to do it as well:
:imap <tab> <Esc>
So what can we do with all this? Well, we have lots of useful variables, lots of registers, and marks, and even [ communication protocol for controlling external processes] (http://vimdoc.sourceforge.net/htmldoc/netbeans.html). My next post will be about the project that most of this study has been devoted to. For now, I’ll go over a few of my personal settings and one trick that is a bit obscure but extremely powerful.
:noremap <C-d> :call search('def\s\s*(' . expand('<cword>')) <CR>
The search function takes a string (remember that . performs string concatenation), searches the buffer for the regular expression, and moves the cursor to the the line of the found string. search returns the line of the found match, or 0 if no match was found (vim counts its buffer line from 1). <cword> is an “Ex special character” (try :h cmdline-special). The expand function returns the special meaning of <cword>, which happens to be the current word under the cursor. So if the word under my cursor is “print_lines”, search will be called on def\s+print_line(. So this obscure little function will take you to the function definiton of whatever function you’re currently hovering over. IF it’s in the file. But what if it’s not? Let’s try a function.
fun! DefSearch()
let l:regex = 'def\s\s*(' . expand('<cword>')
if search(regex) == 0 " search failed
silent 'grep -E "' . regex . '" *.py'
endif
endfun
VimL’s functions execute their code as if from the command-line (in vim, as if you prepended them with :). So silent executes the following command without echoing in the command window. It then execute’s vim’s built-in grep (which is a wrapper around bash’s grep). grep’s E allows you to use quote-wrapped regular expressions; -r forces a recursive search, if you like it. *.py will match any python files. Let’s map it! Food()
:noremap <C-d> :call DefSearch() <CR>
But it’s only useful if we’re editing or reading a python file . . . we probably don’t want to use it otherwise. So in the .vimrc file, we add:
au BufREadPost *.py :noremap <C-d> :call DefSearch() <CR>
Testing all this can be a bit of a drag, but as we are editing a vim script (or our .vimrc file) we can immmediately run it (and gain access to its globally-declared variables and functions) by calling :source. We can take advantage of anther “cmd-line-special” %, which in the command-line has the value of the current buffer name.
:source %
Okay, one last trick. This one is real nifty. Info on it is in vim’s extensive in-house documentation–just try :h g@. This trick allows you to capture user motions and reference them within a function. We do this by setting an ‘operatorfunc’ option. The result is a custom operator. Given a function Foo:
:nmap <silent> <F4> :set opfunc=Echo<CR>g@
:vmap <silent> <F4> :<C-U>call Echo(visualmode(), 1)<CR>
Note: :vmap is the equivalent of :nmap for visual mode. The function defition:
" uses [/] marks along with visual mode to yank a custom selection of text.
fun! Echo(type, ...)
"backticks=' (goto mark), have to avoid out-quoting string
"clear the @q register for use in this function.
"@q is the reserved phonim register.
let @q=""
" see :h g@ for more info and how to save and restore a register, which
" would allow us to use 'q' only temporarily and then restore it
if a:0 " Invoked from Visual mode, use '< and '> marks.
silent exe "normal! `<" . a:type . '`>"qy'
elseif a:type == 'line'
silent exe 'normal! `[V`]"qy'
elseif a:type == 'block' " column ('block') selection.
silent exe 'normal! `[\<C-V>`]"qy'
"v -> visual mode but stay in-line
else " Stay in-line
silent exe 'normal! `[v`]"qy'
endif
echo @q
endfun
This function echos into the command-window any text that the user selects. It saves the user’s selection in the q register (could be any letter). It is possible to store and restore this register so as not to lose its original contents. Now :normal executes normal-mode commands, and will use any mappings that we defined by nmap. :normal! overrides this use of mapping. The opfunc works by setting [ and ] marks to the beginning and end of the user motiton (i.e. to the next whitespace if it is W or the next like if it is j). The Echo function simply yanks the text between these two marks and stores them in the q register. We can then do whatever we like with the text, in this case echo it. The if statements gaurantee that the selection will work the same in visual and normal mode (v is for inline visual selection; V is for column or “block” selection).
In my next post I’ll talk about an interesting application of this last technique–and VimL in general, and the reasoning behind all this research.