编码指南¶
本节描述了核心包和协调包都应遵循的要求和指南,这些要求和指南也推荐给附属包。
接口和依赖项¶
所有代码都必须与
python_requires
关键在 setup.cfg 核心包的文件。用法
six
,__future__
和2to3
不再被接受。f-strings 如果可能的话,应该使用python3格式(即。
"{{0:s}}".format("spam")
)而不是%
操作人员 ("%s" % "spam"
)核心包应该是可导入的,除了Astropy核心中已经存在的组件之外,没有依赖关系 Python Standard Library 和 NumPy 1.18 或者以后。
其他依赖项-例如 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
模块。
文件和测试¶
数据和配置¶
包可以在名为
data
在子包源目录中,只要它小于大约100KB。这些数据应始终通过get_pkg_data_fileobj()
或get_pkg_data_filename()
功能。如果数据超过此大小,则应将其托管在源代码存储库之外,或者位于internet上的第三方位置或 astropy data server . 无论哪种情况,都应该使用get_pkg_data_fileobj()
或get_pkg_data_filename()
功能。如果需要数据文件的特定版本,则astropy.utils.data
应该使用。所有持久性配置都应该使用 配置系统 (astropy.config ) 机制。这样的配置项应该放在使用它们的模块或包的顶部,并提供足够的说明,以便用户了解设置的更改。
标准输出、警告和错误¶
内置的 print(...)
例如,函数只能用于用户显式请求的输出 print_header(...)
或 list_catalogs(...)
. 任何其他标准输出、警告和错误都应遵循以下规则:
对于错误/异常,应始终使用
raise
使用一个内置异常类或自定义异常类。不伦不类的Exception
类应该尽可能避免使用,而应该使用更具体的异常 (IOError
,ValueError
等)。对于警告,应始终使用
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_output
是False
,它只能包含7位字符。如果astropy.conf.unicode_output
是True
,它可能包含非ASCII字符(如果适用)。__format__
:返回“字符串实例”。如果astropy.conf.unicode_output
是False
,它只能包含7位字符。如果astropy.conf.unicode_output
是True
,它可能包含非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接口准则。
使用 Cython 强烈建议用于C扩展。 Cython 扩展名应存储
.pyx
源代码存储库中的文件,而不是生成的.c
文件。如果一个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()
它不知道它是从继承自两者的子类调用的 B
和 C
那 B.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 *
只进口 foo
和 AClass
,但不是 numpy.array
或 numpy.linspace
.