Python and Spaces and Tabs, Oh My!
Python indentation
The subject of Python source code indentation can be a contentious one. The Python style guide (PEP8) suggests that using four spaces per indentation level is the proper was to indent Python code. However, I have discovered I prefer to use tabs instead of spaces. Could I customize my text editor of choice, Vim, to edit using tabs and save to disk using spaces? Doing so would allow me to adhere to PEP8 when exchanging source code files with other programmers.
Rendering tabs in Vim
Vim can be configured to display a unique, visible, character on screen at the position of each tab character. I have this set to a unicode, vertical bar character. This makes indentation errors easy to see, and indentation easier to follow over large code blocks.
In my case I chose a nice vertical bar U+2502
from the box drawing set of characters (https://en.wikipedia.org/wiki/Box_Drawing). In order to activate this feature, the vim list
configuration options are added to the .vimrc
configuration file.
set list
set listchars=tab:│\ ,eol:¬,extends:❯,precedes:\<
Listchars takes two characters for tab, so an escaped white space character is appended to the unicode character. I have also defined characters to show end-of-line markers and vim-specific line continuation markers extends
and precedes
.
On-the-fly re-indentation
Now tab characters will be rendered in the editor window, but there is still a problem. Some source files use spaces for indentation, not tabs. Any Python source file that uses spaces for indentation will look like this:
However, Vim autocommands can be used to replace spaces with tabs when reading in a Python source file, and reverse the operation when writing it back to disk. This can be achieved by defining two Vim functions and some autocommands in the .vimrc
configuration file.
function! PyExpandTabs()
silent setlocal expandtab
silent %retab!
endfunction
function! PyContractTabs()
silent setlocal noexpandtab
silent %retab!
endfunction
augroup python_files
autocmd!
autocmd FileType python setlocal noexpandtab
autocmd FileType python set tabstop=4
autocmd FileType python set shiftwidth=4
autocmd BufWritePre *.py :call PyExpandTabs()
autocmd BufWritePost *.py :call PyContractTabs()
autocmd BufReadPost *.py :call PyContractTabs()
augroup END
The first 3 autocmd
statements sets up using tabs, equivalent to 4 spaces, when editing a Python source file. The last 3 statements trigger upon reading and writing a Python source file to disk. BufWritePre
calls the user-defined Vim function PyExpandTabs() just before writing an edited buffer to disk. The PyExpandTabs() function replaces all the tabs in the source file being edited with spaces. BufWritePost
triggers after that file has been written to disk to reverse the change and replace the spaces with tabs to allow us to continue editing with our favorite indentation method. BufReadPost
also calls PyContractTabs() right after a file has been loaded into a Vim edit buffer, but before passing control to the user. PyContractTabs() replaces leading spaces with tabs.