编码指南

本节描述了核心包和协调包都应遵循的要求和指南,这些要求和指南也推荐给附属包。

接口和依赖项

  • 所有代码都必须与 python_requires 关键在 setup.cfg 核心包的文件。

  • 用法 six__future__2to3 不再被接受。

  • f-strings 如果可能的话,应该使用python3格式(即。 "{{0:s}}".format("spam") )而不是 % 操作人员 ("%s" % "spam"

  • 核心包应该是可导入的,除了Astropy核心中已经存在的组件之外,没有依赖关系 Python Standard LibraryNumPy 1.17.0 或者以后。

  • 其他依赖项-例如 SciPy, Matplotlib, 或其他第三方包-允许用于子模块或函数调用中,但必须在包文档中注明,并且只应影响相关组件。在函数和方法中,可选依赖项应该使用普通 import 声明,它将引发一个 ImportError 如果依赖项不可用。在astropy核心包中,这些可选依赖项应该记录在 setup.cfg 文件中 extras_require 条目。

    在模块级别,可以从一个可选的依赖项中派生类,如下所示:

    try:
        from opdep import Superclass
    except ImportError:
        warn(AstropyWarning('opdep is not present, so <functionality>'
                            'will not work.'))
        class Superclass(object): pass
    
    class Customclass(Superclass):
        ...
    
  • 包或子包所需但不是特定的通用实用程序应放在 packagename.utils 模块(例如。 astropy.utils 对于核心包)。如果实用程序已经存在于 astropy.utils ,包应始终使用该实用程序,而不是在中重新实现它 packagename.utils 模块。

文件和测试

  • 所有公共类/方法/函数都必须存在docstring,并且必须遵循 编写文档 文件。

  • 尽可能在所有类和函数的docstring中编写用法示例。这些示例应该简短且易于复制——用户应该能够逐字复制并运行它们。这些示例应尽可能放在 doctest 格式化并将作为测试套件的一部分执行。

  • 应该为尽可能多的公共方法和函数提供单元测试,并且应该遵循 测试指南 文件。

数据和配置

标准输出、警告和错误

内置的 print(...) 例如,函数只能用于用户显式请求的输出 print_header(...)list_catalogs(...) . 任何其他标准输出、警告和错误都应遵循以下规则:

  • 对于错误/异常,应始终使用 raise 使用一个内置异常类或自定义异常类。不伦不类的 Exception 类应该尽可能避免使用,而应该使用更具体的异常 (IOErrorValueError 等)。

  • 对于警告,应始终使用 warnings.warn(message, warning_class) . 这些被重定向到 log.warning() 默认情况下,但仍可以使用标准警告捕捉机制和自定义警告类。警告级别应该是 AstropyUserWarning 或者从中继承。

  • 对于信息和调试消息,应该始终使用 log.info(message)log.debug(message) .

日志记录系统使用内置的Python logging 模块。可以使用以下命令导入记录器:

from astropy import log

编码风格/惯例

  • 规范应遵循标准 PEP8 Style Guide for Python Code . 特别是,这包括只使用4个空格进行缩进,而从不使用制表符。

  • 我们的测试基础设施目前执行PEP8样式指南的一个子集。您可以通过运行以下命令在本地检查您的更改是否遵循了这些更改 tox 命令:

    tox -e codestyle
    
  • 遵循现有的编码风格 在一个小包装内,避免做出纯粹是风格上的改变。特别是,不同子包(通常为80或100个字符)的最大行长度存在变化。请在添加或修改代码时保持样式。

  • 自动代码格式化程序的使用(例如。, Black )强烈反对对 Astropy 的贡献。

  • 根据PEP8的建议,一般情况下将使用绝对进口量。例外情况是表单的相对导入 from . import modname ,当引用同一子模块中的文件时最好。这使得当前子模块的代码与另一个子模块的代码相比更加清晰。

    注解

    测试PEP8代码符合性有多种选项,请参阅 测试指南 了解更多信息。看到了吗 Emacs设置以遵循编码准则 对于帮助确保符合PEP8的Emacs的一些配置选项。

  • Astropy源代码应该在文件的开头(或者紧跟在 #!/usr/bin env python 命令,如果相关)指向Astropy源代码的许可证。这句话应该说:

    # Licensed under a 3-clause BSD style license - see LICENSE.rst
    
  • 以下命名约定:

    import numpy as np
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    

    应在相关的地方使用。另一方面::

    from packagename import *
    

    不应该被使用,除非作为一个工具来展平一个模块的命名空间。中给出了允许使用的示例 可接受的使用 from module import * 例子。

  • 类要么使用直接变量访问,要么使用Python的属性机制来设置对象实例变量。 get_value/set_value 仅当获取和设置值需要计算开销较大的操作时,才应使用样式方法。这个 属性与。 get_/set_ 下面的例子说明了这一指导原则。

  • 类应该使用内置 super() 函数,除非有特殊原因不调用。 super() 应该在所有子类中一致地使用,因为它在其他情况下不起作用。这个 super()与直接调用 下面的例子说明了为什么这很重要。

  • 在没有充分理由的情况下,一般应避免多重继承。多重继承很难很好地实现,这就是为什么许多面向对象的语言,比如Java,根本不允许它。Python确实通过使用 C3 Linearization 算法,它提供一致的方法解析顺序。如果没有充分的理由,或者没有理解如何使用C3来确定方法解析顺序,就不应该尝试非平凡的多重继承方案。但是,可以使用使用正交基类的简单多重继承,称为“mixin”模式。

  • __init__.py 模块的文件不应包含任何重要的实现代码。 __init__.py 但是,可以包含docstring和用于组织模块布局的代码(例如。 from submodule import * 根据上述指南)。如果一个模块足够小,可以放在一个文件中,那么它应该是一个单独的文件,而不是一个带有 __init__.py 文件。

  • 命令行脚本应遵循 编写命令行脚本 文件。

Unicode准则

为了获得最大的兼容性,我们需要假设将非ASCII字符写入控制台或文件将不起作用。但是,对于那些拥有正确配置的Unicode环境的用户,我们应该允许他们在适当的时候选择使用Unicode输出。因此,有一个全局配置选项, astropy.conf.unicode_output 若要启用值的Unicode输出,请设置为 False 默认情况下。

对于定义标准字符串转换方法的类,应使用以下约定 (__str____repr____bytes____format__ ). 在下面的项目符号中,短语“string instance”用来指代 str ,而“字节实例”是指 bytes .

  • __repr__ :返回仅包含7位字符的“字符串实例”。

  • __bytes__ :返回仅包含7位字符的“字节实例”。

  • __str__ :返回“字符串实例”。如果 astropy.conf.unicode_outputFalse ,它只能包含7位字符。如果 astropy.conf.unicode_outputTrue ,它可能包含非ASCII字符(如果适用)。

  • __format__ :返回“字符串实例”。如果 astropy.conf.unicode_outputFalse ,它只能包含7位字符。如果 astropy.conf.unicode_outputTrue ,它可能包含非ASCII字符(如果适用)。

对于希望通过字符串(unicode或字节)往返的类,解析器必须接受 __str__ . 此外, __repr__ 有意义的话应该往返。

这种设计通常遵循波斯特定律:“接受的东西要自由,发送的东西要保守。”

下面的示例类显示了一种实现方法:

# -*- coding: utf-8 -*-

from astropy import conf

class FloatList(object):
    def __init__(self, init):
        if isinstance(init, str):
            init = init.split('‖')
        elif isinstance(init, bytes):
            init = init.split(b'|')
        self.x = [float(x) for x in init]

    def __repr__(self):
        # Return unicode object containing no non-ASCII characters
        return f'<FloatList [{", ".join(str(x) for x in self.x)}]>'

    def __bytes__(self):
        return b'|'.join(bytes(x) for x in self.x)

    def __str__(self):
        if astropy.conf.unicode_output:
            return '‖'.join(str(x) for x in self.x)
        else:
            return self.__bytes__().decode('ascii')

另外,还有一个测试助手, astropy.test.helper.assert_follows_unicode_guidelines 以确保类遵循上面概述的Unicode准则。下面的示例测试将测试上面的示例类是否兼容:

def test_unicode_guidelines():
    from astropy.test.helper import assert_follows_unicode_guidelines
    assert_follows_unicode_guidelines(FloatList(b'5|4|3|2'), roundtrip=True)

含C代码

  • 只有在C扩展比纯Python提供了显著的性能增强,或者已经存在一个健壮的C库来提供所需的功能时,才允许使用C扩展。当使用C扩展时,Python接口必须满足前面提到的Python接口准则。

  • The use of Cython is strongly recommended for C extensions. Cython extensions should store .pyx files in the source code repository, but not the generated .c files.

  • 如果一个C扩展依赖于一个外部的C库,那么该库的源代码应该与Astropy内核绑定,前提是C库的许可证与Astropy许可证兼容。此外,该软件包必须与使用系统安装的库兼容,而不是使用Astropy中包含的库,安装该软件包的用户应该能够选择使用使用 ASTROPY_USE_SYSTEM_??? 环境变量,其中 ??? 类库的名字是。 ASTROPY_USE_SYSTEM_WCSLIB (也见) 外部C库

  • 如果需要C扩展,但是 Cython 不能使用 PEP 7 Style Guide for C Code 建议使用。

  • C扩充 (Cython 或其他)应提供通过中描述的机制构建扩展所需的信息 C或Cython扩展 .

附属包的特定要求

  • 将不接受实现许多与关联包本身无关的类/函数的关联包(例如前一个包中的剩余代码)-该包只应包括所需的功能和相关扩展。

  • 附属包裹必须在 Python Package Index ,以及用于下载和安装源包的适当元数据。

  • 这个 astropy 根包名称不应被附属包使用-它是保留给核心包使用的。

实例

本节展示了一些示例(并非所有示例都正确!)以说明指南中的要点。

属性与。 get_/set_

此示例显示了一个示例类,说明了与getter/setter方法不同的属性使用准则。

假设您定义了 Star 类并创建如下实例:

>>> s = Star(B=5.48, V=4.83)

始终使用like属性:

>>> s.color = 0.4
>>> print(s.color)
0.4

而不是像这样:

>>> s.set_color(0.4)  # Bad form!
>>> print(s.get_color())  # Bad form!
0.4

使用Python属性,属性语法仍然可以使用get/set方法执行任何可能的操作。但是,对于冗长或复杂的计算,请使用以下方法:

>>> print(s.compute_color(5800, age=5e9))
0.4

super()与直接调用

这个例子说明了为什么使用 super() 与在多重继承情况下手动调用超级类的方法相比,会导致更一致的方法解析顺序:

# This is dangerous and bug-prone!

class A(object):
    def method(self):
        print('Doing A')


class B(A):
    def method(self):
        print('Doing B')
        A.method(self)


class C(A):
    def method(self):
        print('Doing C')
        A.method(self)

class D(C, B):
    def method(self):
        print('Doing D')
        C.method(self)
        B.method(self)

如果你这样做了:

>>> b = B()
>>> b.method()

你会看到:

Doing B
Doing A

这就是你所期望的,C也是一样。但是,如果你这样做了:

>>> d = D()
>>> d.method()

您可能希望看到按D、B、C、A顺序调用的方法,但是您看到的却是:

Doing D
Doing C
Doing A
Doing B
Doing A

因为两者 B.method()C.method() 调用 A.method() 不知道它们被称为层次结构中链的一部分。什么时候? C.method() 它不知道它是从继承自两者的子类调用的 BCB.method() 应该叫下一个。通过打电话 super() 的整个方法解析顺序 D 是预计算的,使每个超类能够协作地确定下一个类中应交给哪个类的控制权 super() 呼叫:

# This is safer

class A(object):
    def method(self):
        print('Doing A')

class B(A):
    def method(self):
        print('Doing B')
        super().method()


class C(A):
    def method(self):
        print('Doing C')
        super().method()

class D(C, B):
    def method(self):
        print('Doing D')
        super().method()
>>> d = D()
>>> d.method()
Doing D
Doing C
Doing B
Doing A

如您所见,每个超类的方法只输入一次。要实现这一点,非常重要的是,类中调用其超类版本的方法的每个方法都使用 super() 而不是直接调用方法。在最常见的单继承情况下,使用 super() 在功能上等同于直接调用超类的方法。但是当一个类在多重继承层次结构中使用时,它必须使用 super() 以便与层次结构中的其他类协作。

注解

有关 super() ,参见https://rhettinger.wordpress.com/2011/05/26/super-consided-super/

可接受的使用 from module import *

from module import * 不鼓励在包含实现代码的模块中使用,因为这会妨碍清晰性并经常导入未使用的变量。但是,它可以用于按以下方式布局的包:

packagename
packagename/__init__.py
packagename/submodule1.py
packagename/submodule2.py

在这种情况下, packagename/__init__.py 可能是::

"""
A docstring describing the package goes here
"""
from submodule1 import *
from submodule2 import *

或允许直接在类中使用子函数 packagename.foo 而不是 packagename.submodule1.foo . 如果使用此选项,强烈建议子模块使用 __all__ 变量指定应导入哪些模块。因此, submodule2.py 可能是:

from numpy import array, linspace

__all__ = ['foo', 'AClass']

def foo(bar):
    # the function would be defined here
    pass

class AClass(object):
    # the class is defined here
    pass

这确保了 from submodule import * 只进口 fooAClass ,但不是 numpy.arraynumpy.linspace .

额外资源

有关编码指南的更多提示和提示如下。