编码指南

随着项目规模的增加,代码库和文档的一致性变得更加重要。因此,我们为scikit-bio提供代码和文档指南。我们的目标是创建一个一致的代码库,其中:

  • 很容易找到相关的功能(并确定您要查找的功能何时不存在),

  • 您可以相信您正在使用的代码已经过充分的测试,并且

  • 名称和接口是直观的。

As scikit-bio is in beta, our coding guidelines are presented here as a working draft. These guidelines are requirements for all code submitted to scikit-bio, but at this stage the guidelines themselves are malleable. If you disagree with something, or have a suggestion for something new to include, you should create an issue to initiate a discussion.

命名约定是什么?我应该如何格式化我的代码?

我们坚持 PEP 8 代码和文档标准的python编码指南。在向scikit bio提交任何代码之前,您应该仔细阅读这些代码并在代码中应用这些指导原则。

我应该怎么称呼我的变量?

  • 选择人们最有可能猜到的名字。 使其具有描述性,但不要太长: curr_recordccurrcurrent_genbank_record_from_database .

  • 好名字很难找到。 不要害怕更改名称,除非它们是其他人也在使用的接口的一部分。处理代码可能需要一些时间才能为所有的东西找到合理的名称:如果有单元测试,则很容易更改它们,尤其是使用全局搜索和替换。

  • 对单个事物使用单数名称,对集合使用复数名称。 例如,你会期望 self.name 握住一根绳子之类的东西,但是 self.names 保存一些你可以循环使用的东西,比如列表或字典。有时候这个决定可能很棘手:是 self.index 保存位置的整数,还是保存按名称键入的记录以便于查找的字典?如果你发现自己对这些事情感到疑惑,也许应该改一下名字来避免这个问题:试试看 self.positionself.look_up .

  • 不要将类型作为名称的一部分。 您可能希望稍后更改实现。使用 Records 而不是 RecordDictRecordList ,等等。也不要使用匈牙利符号(例如,在名称前面加上类型)。

  • 尽可能精确地说出名字。 如果变量是输入文件的路径,则调用它 input_fp 不是 inputfile (无论如何你不应该使用,因为它们是关键字),而不是 infile (因为它看起来应该是一个文件对象,而不仅仅是它的名称)。

  • Use result to store the value that will be returned from a method or function. Use data for input in cases where the function or method acts on arbitrary data (e.g. sequence data, or a list of numbers, etc.) unless a more descriptive name is appropriate.

  • One-letter variable names should only occur in math functions or as loop iterators with limited scope. 有限的范围包括 for k in keys: print k 在哪里 k 只有一两条线幸存下来。循环迭代器应该引用它们循环使用的变量: for k in keys, i in itemsfor key in keys, item in items . 如果循环很长或在同一范围内有多个单字母变量处于活动状态,请重命名它们。

  • 限制使用缩略语。 一些著名的缩写是可以的,但是你不想在6个月后回到你的代码中去,然后去弄清楚是什么 sptxck2 是。多花点时间打字是值得的 species_taxon_check_2 ,但这仍然是个可怕的名字:1号支票是什么?最好是和 taxon_is_species_rank 这不需要解释,尤其是如果变量只使用了一两次。

可接受的缩写

下面列出的缩略语可以被认为是众所周知的,并且在混合名称变量中可以不受惩罚地使用,但是有些缩写词不应该单独使用,因为它们会与公共函数、python内置函数冲突,或者引发异常。不要将以下内容单独用作变量名: direxp (一个普通的 math 模块功能), inmaxmin . 然而,它们可以作为名字的一部分,如 matrix_exp .

完整

缩写

alignment

aln

archaeal

arch

auxiliary

aux

bacterial

bact

citation

cite

current

curr

database

db

dictionary

dict

directory

dir

distance matrix

dm

end of file

eof

eukaryotic

euk

filepath

fp

frequency

freq

expected

exp

index

idx

input

in

maximum

max

minimum

min

mitochondrial

mt

number

num

observation

obs

observed

obs

original

orig

output

out

parameter

param

phylogeny

phylo

previous

prev

probability

prob

protein

prot

record

rec

reference

ref

sequence

seq

standard deviation

stdev

statistics

stats

string

str

structure

struct

temporary

temp

taxa

tax

taxon

tax

taxonomic

tax

taxonomy

tax

variance

var

如何组织模块(源文件)?

  • 有一个docstring,其中包含对模块功能的描述 . 如果描述很长,那么第一行应该是一个简短的摘要,它本身是有意义的,与其余部分之间用新行隔开。

  • 所有代码,包括import语句,都应该跟在docstring后面。 否则,解释器将无法识别docstring,并且您将无法在交互式会话中访问它(即通过 obj.__doc__ )或者使用自动化工具生成文档时。

  • Import built-in modules first, followed by third-party modules, followed by any changes to the path and your own modules. 尤其是,添加到您的模块的路径和名称很可能会快速更改:将它们放在一个位置会使它们更容易找到。

  • 不要使用 from module import *, instead use from module import Name, Name2, Name3... 或者可能 import module . 这就成功了 much 更容易看到名称冲突和替换实现。

  • 如果您要导入 NumPyMatplotlib ,或另一个鼓励为其import语句使用标准样式的包,例如:

import numpy as np
import numpy.testing as npt
import pandas as pd

from matplotlib import pyplot as plt

模块结构示例

模块的结构应该与下面的示例类似。scikit-bio使用 NumPy doc 文件标准。我们的 doc/README.md 说明如何使用 NumPy doc scikit-bio 标准:

r"""
Numbers (:mod:`skbio.numbers`)
==============================

.. currentmodule:: skbio.numbers

Numbers holds a sequence of numbers, and defines several statistical
operations (mean, stdev, etc.) FrequencyDistribution holds a mapping from
items (not necessarily numbers) to counts, and defines operations such as
Shannon entropy and frequency normalization.


Classes
-------

.. autosummary::
   :toctree: generated/

   Numbers

"""

# ----------------------------------------------------------------------------
# Copyright (c) 2013--, scikit-bio development team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE.txt, distributed with this software.
# ----------------------------------------------------------------------------

from random import choice, random

import numpy as np
from utils import indices


class Numbers(list):
    pass


class FrequencyDistribution(dict):
    pass

我应该如何写评论?

  • 总是在代码更改时更新注释。 不正确的评论远比不发表评论更糟糕,因为它们具有误导性。

  • 注释应该比代码本身更能说明问题。 仔细检查你的注释:它们可能表明你最好重写你的代码(尤其是 重命名变量 特别是,不要分散那些必须通过代码解释的神奇数字和其他常量。使用名称是自记录的变量要好得多,尤其是当您多次使用同一常量时。另外,考虑将常量转换为类或实例数据,因为“常量”需要更改或在多个方法中被需要是非常常见的。

    错了

    win_size -= 20        # decrement win_size by 20

    OK

    win_size -= 20        # leave space for the scroll bar

    对吗?

    self._scroll_bar_size = 20

    win_size -= self._scroll_bar_size

  • Use comments starting with #, not strings, inside blocks of code.

  • 每个方法、类和函数都用一个docstring开头,并使用三个双引号(“”)。 确保docstring跟在 NumPy doc 标准。

  • 总是在代码更改时更新docstring。 与过时的注释一样,过时的docstring会浪费大量时间。”正确的例子是无价的,但是错误的例子比没有价值的例子更糟糕。” Jim Fulton .

我应该如何测试我的代码?

在python中测试代码有几种不同的方法: noseunittestnumpy.testing . 它们的目的是相同的,检查给定输入的代码的执行是否会产生指定的输出。这些方法适用的情况各不相同。

不管采用什么方法,总的原则是每一行代码都应该被测试。在从代码产生的结果中得出结论之前,必须对代码进行充分的测试,这一点至关重要。对于科学研究来说,bug不仅仅意味着你永远不会遇到的不开心的用户: 它们可能意味着收回出版物 .

测试是发明所需接口的机会。在编写方法之前先为方法编写测试:通常,这有助于您确定要调用它的内容以及它应该使用哪些参数。一次编写几个方法测试是可以的,并且随着你对接口的想法的改变而改变它们。但是,一旦你告诉别人接口是什么,就不应该改变它们。本着这种精神,您的测试还应该从尽可能短的别名中导入测试的功能。这样,对API的任何更改都会导致测试中断,这是正确的!

永远不要把原型当作生产代码。不需要测试就可以编写原型代码来尝试,但是当你发现了算法和接口之后,你必须重写它 有测试吗 认为它完成了。通常,这有助于您决定实际需要哪些接口和功能,以及可以去掉哪些。

“代码一点测试一点”。对于生产代码,先写几个测试,然后写几个方法,再写几个测试,再写几个方法,然后可能更改一些名称或概括一些功能。如果你有大量的代码,你所要做的就是编写测试,那么你大概完成了30%,而不是90%。自从上一次编写错误的代码后,无论你在测试中花费了多少时间,测试都大大减少了。记住使用python的交互式解释器快速检查语法和思想。

更改时运行测试套件 anything. Even if a change seems trivial, it will only take a couple of seconds to run the tests and then you'll be sure. This can eliminate long and frustrating debugging sessions where the change turned out to have been made long ago, but didn't seem significant at the time. Note that tests are executed using Travis CI, see this document's section 供进一步讨论。

一些提示

  • Use the unittest or the nose framework with tests in a separate file for each module. Name the test file test_module_name.py and include it inside the tests folder of the module. Keeping the tests separate from the code reduces the temptation to change the tests when the code doesn't work, and makes it easy to verify that a completely new implementation presents the same interface (behaves the same) as the old.

  • Always include an __init__.py file in your tests directory. This is required for the module to be included when the package is built and installed via setup.py.

  • 总是从最小深度的API目标导入 . 也就是说你会用 from skbio import DistanceMatrix 而不是 from skbio.stats.distance import DistanceMatrix . 这使我们可以防止API中大多数意外回归的情况。

  • Use numpy.testing if you are doing anything with floating point numbers, arrays or permutations (use numpy.testing.assert_almost_equal). Do not try to compare floating point numbers using assertEqual if you value your sanity.

  • Test the interface of each class in your code by defining at least one TestCase with the name ClassNameTests. This should contain tests for everything in the public interface.

  • 如果类很复杂,您可能需要用名称定义其他测试 ClassNameTests_test_type . 这些可能是子类 ClassNameTests 为了分享 setUp 方法等。

  • Tests of private methods should be in a separate TestCase called ClassNameTests_private. Private methods may change if you change the implementation. It is not required that test cases for private methods pass when you change things (that's why they're private, after all), though it is often useful to have these tests for debugging.

  • Test `all` the methods in your class. You should assume that any method you haven't tested has bugs. The convention for naming tests is test_method_name. Any leading and trailing underscores on the method name can be ignored for the purposes of the test; however, all tests must start with the literal substring test for unittest and nose to find them. If the method is particularly complex, or has several discretely different cases you need to check, use test_method_name_suffix, e.g. test_init_empty, test_init_single, test_init_wrong_type, etc. for testing __init__.

  • 测试方法的docstring应该被认为是可选的 ,而应该在名称本身中包含对该方法所做操作的描述,因此该名称应具有足够的描述性,以便在详细模式下运行测试时,您可以立即看到失败的文件和测试方法。

$ python -c "import skbio; skbio.test(verbose=True)"
skbio.maths.diversity.alpha.tests.test_ace.test_ace ... ok
test_berger_parker_d (skbio.maths.diversity.alpha.tests.test_base.BaseTests) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.1234s

OK
  • Module-level functions should be tested in their own TestCase ,叫做 modulenameTests . 即使这些函数很简单,也要检查它们是否像广告中说的那样工作。

  • 与需要计算器的单个大案相比,测试几个可以手工检查的小案例要重要得多。 不要相信电子表格可以进行数值计算——用R代替!

  • 确保测试了所有的边缘情况:当输入为None、“”、0或负数时会发生什么? 如果某个值导致条件朝一个方向或另一个方向发展,会发生什么情况?不正确的输入是否会引发正确的异常?你的代码能接受它期望的类型的子类或超类吗?如果输入非常大会发生什么?

  • 要测试置换,请检查原始版本和无序版本是否不同,但已排序的原始版本和已排序的无序版本是否相同。 确保你得到 不同的 在重复运行和从不同点开始时的排列。

  • 为了测试随机选择,使用二项式分布或其正态近似,计算出在一个大样本(比如1000个或100万个)中每一个选择的数量。 做几次测试,检查你是否在平均值的3个标准偏差之内。

  • 所有依赖于随机值的测试都应该种子化,例如如果使用NumPy, numpy.random.seed(0) 在任何其他情况下,应该使用适当的API在运行之间创建一致的输出。最好对每个测试用例都这样做,而不是在 setUp 函数/方法(如果存在)。

  • 随机失败发生的次数应该少于1/101000次,否则,您有可能在测试套件的总运行时间上增加大量的时间。

A的例子 nose 测试模块结构

# ----------------------------------------------------------------------------
# Copyright (c) 2013--, scikit-bio development team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE.txt, distributed with this software.
# ----------------------------------------------------------------------------

import numpy as np
from nose.tools import assert_almost_equal, assert_raises

from skbio.math.diversity.alpha.ace import ace


def test_ace():
    assert_almost_equal(ace(np.array([2, 0])), 1.0)
    assert_almost_equal(ace(np.array([12, 0, 9])), 2.0)
    assert_almost_equal(ace(np.array([12, 2, 8])), 3.0)
    assert_almost_equal(ace(np.array([12, 2, 1])), 4.0)
    assert_almost_equal(ace(np.array([12, 1, 2, 1])), 7.0)
    assert_almost_equal(ace(np.array([12, 3, 2, 1])), 4.6)
    assert_almost_equal(ace(np.array([12, 3, 6, 1, 10])), 5.62749672)

    # Just returns the number of OTUs when all are abundant.
    assert_almost_equal(ace(np.array([12, 12, 13, 14])), 4.0)

    # Border case: only singletons and 10-tons, no abundant OTUs.
    assert_almost_equal(ace([0, 1, 1, 0, 0, 10, 10, 1, 0, 0]), 9.35681818182)


def test_ace_only_rare_singletons():
    with assert_raises(ValueError):
        ace([0, 0, 43, 0, 1, 0, 1, 42, 1, 43])


if __name__ == '__main__':
    import nose
    nose.runmodule()

Git指针

提交消息是记录对项目所做更改的有用方法,它还记录了谁在进行这些更改以及何时进行这些更改,所有这些都与追溯问题时相关。

编写提交消息

提交消息中最重要的元数据是(可以说)作者的姓名和作者的电子邮件。GitHub使用此信息将您的贡献属性到项目中,请参见 scikit-bio list of contributors .

跟随 this guide 设置你的系统 make sure the e-mail you use in this step is the same e-mail associated to your GitHub account .

完成此操作后,当您运行以下命令时,您应该会看到您的姓名和电子邮件:

$ git config --global user.name
Yoshiki Vázquez Baeza
$ git config --global user.email
yoshiki89@gmail.com

编写提交消息

一般来说,提交消息的编写应该遵循 NumPy's guidelines 如果遵循正确,这将帮助您更好地组织您的更改,也就是说,错误修复将在提交之后,再提交更新测试套件,最后一次提交将根据需要更新文档。

GitHub提供了一组方便的特性,可以将提交消息链接到问题跟踪器中的票证,这特别有用,因为您可以 close an issue automatically 当更改合并到主存储库中时,这将减少必须完成的工作量,以确保过期问题不会打开。