字符串内部 API 的 Pythonic 方法

Pythonic approach to internal API for strings

问题

是否有“pythonic”(即规范的、官方的、PEP8 批准的等)方法在 python 内部(和外部)API 中重用字符串文字?


背景

例如,我正在处理一些(不一致的)JSON-处理代码(数千行),其中有各种 JSON “structs” 我们 assemble、parse 等。代码审查期间反复出现的问题之一是不同的 JSON struct 使用相同的内部参数名称,导致混淆并最终导致错误出现,例如:

pathPacket['src'] = "/tmp"
pathPacket['dst'] = "/home/user/out"
urlPacket['src'] = "localhost"
urlPacket['dst'] = "contoso"

这两个(示例)数据包具有数十个同名字段,但它们代表非常不同类型的数据。此实现没有代码重用理由。人们通常使用代码完成引擎来获取 JSON struct 的成员,这最终会导致难以调试的问题,因为错误键入的字符串文字会导致功能问题,并且没有更早触发错误。当我们必须更改这些 API 时,需要花费大量时间来查找字符串文字以找出哪些 JSON struct 使用了哪些字段。


问题 - Redux

python 社区成员是否有更好的通用方法?如果我在 C++ 中这样做,前面的示例将类似于:

const char *JSON_PATH_SRC = "src";
const char *JSON_PATH_DST = "dst";
const char *JSON_URL_SRC = "src";
const char *JSON_URL_DST = "dst";
// Define/allocate JSON structs
pathPacket[JSON_PATH_SRC] = "/tmp";
pathPacket[JSON_PATH_DST] = "/home/user/out";
urlPacket[JSON_URL_SRC] = "localhost";
urlPacket[JSON_URL_SRC] = "contoso";

我最初的做法是:


我提出的解决方案(对advice/criticism开放)

from abc import ABCMeta

class Custom_Structure:
    __metaclass__ = ABCMeta

    @property
    def JSON_PATH_SRC():
        return self._JSON_PATH_SRC

    @property
    def JSON_PATH_DST():
        return self._JSON_PATH_DST

    @property
    def JSON_URL_SRC():
        return self._JSON_URL_SRC

    @property
    def JSON_URL_DST():
        return self._JSON_URL_DST

通常的做法是:

JSON_PATH_SRC = "src"
JSON_PATH_DST = "dst"
JSON_URL_SRC = "src"
JSON_URL_DST = "dst"


pathPacket[JSON_PATH_SRC] = "/tmp"
pathPacket[JSON_PATH_DST] = "/home/user/out"
urlPacket[JSON_URL_SRC] = "localhost"
urlPacket[JSON_URL_SRC] = "contoso"

大写表示 "constants" 就是这样。你会在标准库中看到它,它甚至在 PEP8:

中被推荐

Constants are usually defined on a module level and written in all capital letters with underscores separating words. Examples include MAX_OVERFLOW and TOTAL.

Python 没有真正的常数,而且它似乎没有它们也能存活下来。如果将其包装在使用 ABCmeta 和属性的 class 中会让您感觉更舒服,请继续。事实上,我很确定 abc.ABCmeta 不会 不会 阻止对象初始化。的确,如果做了,你使用property就不行了! property 个对象 属于 class,但要从实例访问。对我来说,它看起来就像是大量的 rigamarole,却收获甚微。

在我看来,创建常量最简单的方法就是将它们设置为模块中的变量(而不是修改它们)。

JSON_PATH_SRC = "src"
JSON_PATH_DST = "dst"
JSON_URL_SRC = "src"
JSON_URL_DST = "dst"

然后,如果您需要从另一个模块引用它们,它们已经为您命名空间。

>>> that_module.JSON_PATH_SRC
'src'
>>> that_module.JSON_PATH_DST
'dst'
>>> that_module.JSON_URL_SRC
'src'
>>> that_module.JSON_URL_DST
'dst'

创建一堆常量的最简单方法是将它们放入模块中,并在必要时导入它们。例如,您可以有一个带有

constants.py 模块
JSON_PATH_SRC = "src"
JSON_PATH_DST = "dst"
JSON_URL_SRC = "src"
JSON_URL_DST = "dst"

你的代码会做类似的事情

from constants import JSON_URL_SRC
...
urlPacket[JSON_URL_SRC] = "localhost"

如果您想要更好地定义常量分组,您可以将它们粘贴到专用包中的单独模块中,允许您像 constants.json.url.DST 一样访问它们,或者您可以使用 Enum秒。 Enum class 允许您将相关的常量集分组到一个命名空间中。你可以这样写一个模块constants.py

from enum import Enum

class JSONPath(Enum):
    SRC = 'src'
    DST = 'dst'

class JSONUrl(Enum):
    SRC = 'src'
    DST = 'dst'

from enum import Enum

class JSON(Enum):
    PATH_SRC = 'src'
    PATH_DST = 'dst'
    URL_SRC = 'src'
    URL_DST = 'dst'

如何精确地分离常量取决于您。您可以有一个巨大的枚举,每个类别一个或介于两者之间。您可以像这样在您的代码中访问:

from constants import JSONURL
...
urlPacket[JSONURL.SRC.value] = "localhost"

from constants import JSON
...
urlPacket[JSON.URL_SRC.value] = "localhost"