标题界面过渡指南

备注

本指南最初包含在PyFITS 3.1的发行版中,在许多地方仍然引用了PyFITS,尽管示例已针对更新 astropy.io.fits . 尽管Astropy一直使用PyFITS 3.1头接口,但它在这里仍然可以用于提供信息。

PyFITS v3.1包含了对 Header 接口。尽管新接口与旧接口在很大程度上是兼容的(无论是由于设计上的相似性,还是由于向后兼容性支持),但是仍然存在足够的差异,因此有必要对新接口进行完整的解释。

背景

在3.1之前,PyFITS用户通过三个不同的类与FITS头进行交互: CardCardListHeader .

Card类表示具有关键字、值和注释的单头卡。它还包含解析FITS头卡的所有机制,给定从头部读取的80个字符串或“card image”。

CardList类实际上是Python的一个子类 list 内置的。它是用来表示组成标题的卡片的实际列表。也就是说,它表示按卡在标题中出现的物理顺序排列的卡片的有序列表。它支持在列表中插入和追加新卡片的常用列表方法。它还支持 dict -比如关键字访问,在哪里 cardlist['KEYWORD'] 将返回具有给定关键字的列表中的第一张卡。

CardList类中实际上隐藏了许多用于处理标题的功能。Header类更像是CardList的包装器,添加了一点抽象。它还实现了一个类似dict的部分接口,不过对于Headers,关键字查找返回与该关键字相关联的header值,而不是Card对象,而且header类上的几乎每个方法都只是在底层CardList上执行一些操作。

问题是用户可以做某些事情 only 直接访问卡片列表,例如查找卡片上的注释或具有重复关键字(如历史记录)的访问卡。另一个长期存在的错误特性是,切片Header对象实际上返回了CardList对象,而不是新的Header。除了最简单的用例外,使用CardList对象在很大程度上是不可避免的。

但是我们意识到CardList实际上是一个实现细节,它不代表FITS文件中与头本身不同的任何元素。熟悉FITS格式的用户知道什么是header,但不清楚“card list”如何与之区别,也不清楚为什么操作要经过header对象,而有些操作必须通过CardList执行。

因此,重新设计的主要目标是消除 CardList 类,并使用户可以直接通过 Header 物体。它还试图尽可能类似地将头表示为更熟悉的数据结构—有序映射(或 OrderedDict 对于不太熟悉FITS格式的新用户来说,尽管在处理FITS格式的特性方面还有许多额外的复杂性。

折旧警告

关于 Header 类被标记为已弃用,原因可能是它们已重命名为more PEP 8 -兼容名称,或因为新功能而变得冗余。若要检查代码是否使用任何不推荐使用的方法或功能,请使用运行代码 python -Wd . 这将向控制台输出任何不推荐警告。

与标头相关的两个最常见的不推荐警告是:

  • Header.has_key :自从PyFITS 3.0以来,它就被弃用了,就像Python的一样 dict.has_key 已弃用。检查键在映射对象中的存在,如 dictHeader 使用 key in d 语法。这在Python中一直是首选。

  • Header.ascardlistHeader.ascard :这些用于访问 CardList 对象作为标头的基础。它们仍然可以工作,并返回一个应该支持大部分旧CardList功能的框架CardList实现。但尽量去掉这些。如果直接访问 Card 组成标题的对象是必需的,请使用 Header.cards ,它返回卡上的迭代器。下面是更多信息。

新的收割台设计

新的 Header 类的作用是作为 dict via duck typing . 也就是说,尽管它不是 dict ,它实现了所有相同的方法和接口。特别是,它类似于 OrderedDict 因为插入的顺序被保留了。但是,Header还支持FITS格式特有的许多附加特性和行为。还应该注意的是,虽然旧的头实现也有dict-like接口,但它没有实现 整个的 dict接口和新的头文件一样。

尽管在大多数情况下,新的报头像dict/mapping一样使用,但它也支持 list 接口。list-like接口有点特殊,在某些上下文中,头的行为类似于值的列表,在其他上下文中类似于关键字列表,在少数上下文中类似于 Card 物体。这可能是新设计中最困难的方面,但这是有逻辑的。

与旧的头实现一样,支持整数索引访问: header[0] 返回第一个关键字的值。但是 Header.index() 方法将头视为关键字列表,并返回给定关键字的索引。例如::

>>> header.index('BITPIX')
2

Header.count() 类似于 list.count 并以关键字作为参数:

>>> header.count('HISTORY')
20

一个很好的经验法则是,任何项目访问都使用方括号 [] 收益率 价值 在头中,无论是使用关键字还是索引查找。方法如下 index()count() 处理标题中项目的顺序和数量的方法通常处理关键字。最后,方法是 insert()append() 在卡片的标题上添加新的项目。

除了类似列表的方法之外,对于大多数基本的用例,新的头类的工作方式与旧的实现非常相似,不应该出现太多的意外。但也有不同之处:

  • 与前面一样,Header()初始值设定项可以获取 Card 要填充标头的对象。但是,现在可以使用任何iterable。同样重要的是要注意 any 接受的头方法 Card 对象也可以接受2元组或3元组来代替卡。也就是说,要么 (keyword, value, comment) 元组或 (keyword, value) 元组(comment假定为空)可以在任何地方代替Card对象。这甚至是首选,因为它涉及更少的输入。例如::

    >>> from astropy.io import fits
    >>> header = fits.Header([('A', 1), ('B', 2), ('C', 3, 'A comment')])
    >>> header
    A       =                    1
    B       =                    2
    C       =                    3 / A comment
    
  • 如前一个示例所示 repr() 对于头(即,在Python控制台中以表达式形式输入Header对象时显示的文本),显示头与在FITS文件中显示的一样。这将在每个卡后插入新行,这样无论终端宽度如何,都可以读取。它是 not 必须使用 print header 来看看这个。套房 header 显示文件中显示的标题内容(无结束卡)。

  • len(header) 现在支持(以前必须这样做 len(header.ascard) ). 这将返回页眉中的卡片总数,包括空白卡片,但不包括结束卡片。

  • FITS支持有重复的关键字,尽管除了注释和历史之类的注释关键字之外,它们通常是错误的。PyFITS现在支持读取、更新和删除重复的关键字;使用 (keyword, index) 元组。例如, ('HISTORY', 0) 代表第一张历史卡, ('HISTORY', 1) 表示第二张历史卡,依此类推。事实上,当一个关键字单独使用时,它是 (keyword, 0) . 现在可以删除如下意外重复:

    >>> del header[('NAXIS', 1)]
    

    这将从标题中删除意外复制的NAXIS卡。

  • 即使有重复的关键字,关键字查找 header['NAXIS'] 将始终返回与该关键字的第一个副本相关联的值,但有一个例外:注释和历史等注释关键字应具有重复项。所以 header['HISTORY'] 例如,以正确的顺序返回整个历史值序列。这个值列表可以任意切片。例如,要查看标题中的最后三个历史条目:

    >>> hdulist[0].header['HISTORY'][-3:]
      reference table oref$laf13367o_pct.fits
      reference table oref$laf13369o_apt.fits
    Heliocentric correction = 16.225 km/s
    
  • 现在可以使用下标赋值向标头添加新关键字。就像普通人一样 dictheader['NAXIS'] = 1 将更新已存在的NAXIS关键字,或添加值为的新NAXIS关键字 1 如果它不存在。在旧接口中,这将返回一个 KeyError 如果NAXIS不存在,并且添加新关键字的唯一方法是通过update()方法。

    默认情况下,以这种方式添加的新关键字将添加到标头的末尾,但有一些特殊情况除外:

    • 如果页眉在末尾包含额外的空白卡片,则在空白之前添加新的关键字。

    • 如果标题以注释卡片列表结尾—例如,一系列历史卡片—这些卡片将保留在末尾,并且在注释卡片之前插入新的关键字。

    • 如果关键字是注释或历史(或空白关键字的空字符串)之类的注释关键字,则 new 注释关键字总是添加到同一类型的最后一个注释关键字之后。例如,历史关键字总是放在最后一个历史关键字之后:

      >>> header = fits.Header()
      >>> header['COMMENT'] = 'Comment 1'
      >>> header['HISTORY'] = 'History 1'
      >>> header['COMMENT'] = 'Comment 2'
      >>> header['HISTORY'] = 'History 2'
      >>> header
      COMMENT Comment 1
      COMMENT Comment 2
      HISTORY History 1
      HISTORY History 2
      

    这些行为表示关键字赋值的合理默认行为,与 update() 在旧的头实现中。通过使用其他赋值方法(如 Header.set()Header.append() 后面描述的方法。

  • 现在还可以使用元组将值和注释同时分配给关键字:

    >>> header['NAXIS'] = (2, 'Number of axis')
    

    这将更新现有关键字的值和注释,或添加具有给定值和注释的新关键字。

  • 有一种新的 Header.comments 属性,列出与标题中关键字相关联的所有注释(不要与注释卡混淆)。这允许查看和更新特定卡片上的注释:

    >>> header.comments['NAXIS']
    Number of axis
    >>> header.comments['NAXIS'] = 'Number of axes'
    >>> header.comments['NAXIS']
    Number of axes
    
  • 从标头中删除关键字时,不要假定该关键字已存在。在旧的头实现中,此操作将静默不执行任何操作。为了向后兼容,仍然可以删除不存在的关键字,但会发出警告。将来这个 will 如果试图删除不存在的关键字会引发 KeyError . 这是为了与Python dicts的行为保持一致。所以,除非在删除关键字之前确定关键字存在,否则最好执行以下操作:

    >>> try:
    ...     del header['BITPIX']
    ... except KeyError:
    ...     pass
    

    或者如果你喜欢三思而后行:

    >>> if 'BITPIX' in header:
    ...     del header['BITPIX']
    
  • del header 现在支持切片。例如,要从标题中删除最后三个关键字:

    >>> del header[-3:]
    
  • 现在可以比较两个Header是否相等-以前没有两个Header对象是相同的。现在,如果它们包含完全相同的内容,它们就会被视为相等。也就是说,这需要严格的平等。

  • 现在可以使用“+”运算符添加两个标头,该运算符返回由右标头扩展的左标头的副本 extend() . 也可以添加作业。

  • 这个页眉.update()旧标头API常用的方法已重命名为 Header.set() . 此更改的主要原因非常简单:Header实现了 dict 接口,它已经有一个名为update()的方法,但它的行为与旧的不同页眉.update().

    可以在API文档中读取新update()的详细信息,但它与 dict.update . 它还通过分析传递给它的参数来支持与旧update()的向后兼容性,因此现有代码不会立即中断。然而,这个 will 如果已启用,则导致输出不推荐警告。对于初学者来说,最好将所有update()调用替换为set()。还记得,现在可以通过直接赋值将新关键字添加到头中。所以总的来说,选择使用 Header.set() 它是否能够使用 beforeafter 争论。

  • 使用切片索引对标题进行切片将返回一个新的标头,其中只包含切片中包含的那些卡片。如前所述,对标题进行切片通常会返回一个卡片列表—这是一个错误的特性。通常,支持切片的对象在切片时应该返回相同类型的对象。

    同样,用于返回CardList对象的通配符关键字-现在它们返回一个新的头类似于一个片段。例如::

    >>> header['NAXIS*']
    

    返回一个新的标题,其中只包含原始标题中的纳西和纳西卡。

过渡提示

上面的内容看起来很像,但是使用pyfit来操作头的大多数现有代码不需要更新,至少不应该立即更新。最常见的操作仍然同样工作。

如上所述,使用 python -Wd 启用弃用警告-这应该是一个好主意,在哪里寻找更新您的代码。

如果您的代码需要能够在PyFITS 3.1中同时支持旧版本的PyFITS,那么事情会稍微复杂一些,但不会太多——因为这个原因,在多个版本中不会删除不推荐使用的接口。

  • 在过去几年中,任何PyFITS版本都支持它,第一个值得做的更改是删除 Header.has_key 换成 keyword in header 语法。对于任何一个dict来说,做出这样的改变也是值得的,因为 dict.has_key 已弃用。在代码上运行以下正则表达式可能对大多数(但不是所有)情况有所帮助:

    s/([^ ]+)\.has_key\(([^)]+)\)/\2 in \1/
    
  • 如有可能,将所有呼叫替换为页眉.update()与页眉.set()(但是如果您需要支持旧的PyFITS版本,请不要为此烦恼)。另外,如果您有任何电话页眉.update()可以替换为简单的下标赋值(例如。, header['NAXIS'] = (2, 'Number of axes') )如果可能的话,也这样做。

  • 查找任何使用 header.ascardheader.ascardlist() . 首先确定代码是否真的需要直接作用于Card对象。如果确实是这样的话,那就用 header.cards -那应该不会有太多的麻烦。如果您确实需要支持旧版本,可以继续使用 header.ascard 现在。

  • 如果您有切分头部的任何代码,最好是获取结果并从中创建一个新的header对象。例如::

    >>> new_header = fits.Header(old_header[2:])
    

    这避免了在PyFITS<=3.0中,通过使用结果初始化一个新的Header对象,对头进行切片会返回CardList。这在两种情况下都有效(在PyFITS 3.1中,用现有的头初始化一个头只会复制它 list

  • 如前所述,查找删除关键字的任何代码 del 确保他们在跳跃前先看一眼 (if keyword in header: )或者请求原谅 (try/except KeyError:

其他陷阱

  • 如上所述,无需进入 print header 在交互式Python提示中显示标题。套房 >>> header 它本身就足够了。使用 print 通常会的 not 以可读方式显示页眉,因为页眉卡之间不包含换行符。原因是Python有两种类型的字符串表示。当用户调用时返回一个 str(header) ,当你 print 一个变量。在Header类的情况下,它实际上返回头的字符串值,因为它是按字面方式写入FITS文件的,其中不包括换行符。

    另一种类型的字符串表示在调用 repr(header) . 这个 repr 对象的表示是对象的有用字符串“表示”;在本例中,是标题的内容,但卡片之间有换行符,并且去掉了尾端卡片和尾随的填充。当用户在Python提示下自己输入一个变量而没有 print 打电话。

  • 当前版本的FITS标准(3.0)在第4.2.1节中指出,标题中字符串值的尾随空格不重要,应忽略。PyFITS<3.1 did 将尾随空格视为重要。例如,如果标题包含:

    关键字1='值'

    然后 header['KEYWORD1'] 将返回字符串 'Value    ' 没错,后面的空格完好无损。新的Header接口通过自动剥离尾随空格来修复这个问题,以便 header['KEYWORD1'] 只会回来 'Value' .

    然而,IRAF-CCD马赛克任务使用了一种惯例来表示其TNX世界坐标系和ZPX世界坐标系非标准WCS,它使用了一系列形式的关键字 WATj_nnn ,它存储了非线性失真投影系数的文本描述。它使用自己的微格式将系数作为一个字符串列出,但是字符串很长,因此分成了几个这样的字符串 WATj_nnn 关键词。这些关键字的正确重组需要按字面意思处理所有空白。该公约要么忽略了FITS标准中对空白的规定处理,要么早于FITS标准中对空白的规定处理。

    为了解决这个问题,一个全局变量 fits.STRIP_HEADER_WHITESPACE 介绍。临时设置 fits.STRIP_HEADER_WHITESPACE.set(False) 在读取受此问题影响的关键字之前,将返回它们的值,并且所有尾随空格都保持不变。

    PyFITS的未来版本可能能够在上下文中检测到这种约定的使用,并根据约定进行操作,但在大多数情况下,PyFITS的默认行为是根据FITS标准进行操作。