【问题标题】:Python convention for variable naming to indicate units用于指示单位的变量命名的 Python 约定
【发布时间】:2014-03-09 15:30:48
【问题描述】:

首先,当我问及单位时,我指的是度量单位,例如英寸、英尺、像素、细胞。我不是指像 int 和 float 这样的数据类型。

Wikipedia 将此称为逻辑数据类型,而不是物理数据类型

我想知道命名变量的最佳方式。

这里有一些代码来完成我的要求:

board_length=8 #in inches
board_length=8*12 #Convert from feet to inches

请注意,它们都是整数(或浮点数,我不在乎),但我已经更改了单位。我也保持变量名相同。我可以建立一个约定,这就是这个问题的目的。如果没有指导,我可能会这样做:

board_length=8
board_length_inches=8*12

我认为这是一种临时的做事方式。或者,我可以建立一个约定:

Fboard_length=8
Iboard_length=8*12

或我同样不喜欢的其他变体。如何以描述性的方式命名变量,同时尽可能接近PEP-08

为了尽可能清楚,变量可能具有不同的数据类型,但单位是相同的(无论是存储为整数还是浮点数,英寸都具有相同的命名)

【问题讨论】:

  • 根据单位对脚本的重要性,您可以考虑根据每个单位制作类型子类。每个“度量单位”(长度、重量等)都有其继承内置类型的 on 类,并且对于各个单位都有自己的子类。然后,这些单元将具有转换为相同类型的所有其他单元的方法。这将是很多工作,所以你需要问问它是否真的值得
  • 或者您可以装饰内置函数以具有跟踪单位的属性或方法
  • 我不会太担心这个。我想不出任何情况下您需要在不同的单位中持续存储相同的值。我会这样做 - board_length = 24 # in inches(几行之后)print(to_feet(board_length)),或FEET = 12; print(board_length*FEET)
  • 我在这种情况下使用board_length_inches。保持简单。
  • 我和@sweeneyrod 在一起。换句话说,您的策略应该是选择一些您将在内部用于所有计算的单位,并且始终如一地使用它。您只需要在交互时(与人类用户,或与需要除您正在使用的单位以外的某些模块)交互时关心不同单位之间的转换,并且您会在这些接口点使用恰当命名的转换函数或方法你的代码。

标签: python naming-conventions pep8


【解决方案1】:

虽然您可以提出一个命名约定,但最好构建一个表示“距离”的对象,该对象具有以不同单位读取/写入的属性。例如:

class Distance(object):

    def __init__(self):
        self._inches = 0

    @property
    def inches(self):
        return self._inches

    @inches.setter
    def inches(self, value):
        self._inches = value

    @property
    def feet(self):
        return self._inches/12

    @feet.setter
    def feet(self, value):
        self._inches = value * 12

您甚至可以使其更通用,以便您可以轻松地扩展新的转换。 (注意:根据 cmets 编辑此内容以进行记忆)

from collections import defaultdict

class Distance(object):

    _conversion_map = defaultdict(lambda: {'to' : None, 'from' : None})

    def __init__(self, **kwargs):
        self._memo = {}
        if kwargs:
            unit, value = kwargs.iteritems().next()
            if unit == 'inches':
                self.inches = value
            else:
                setattr(self, unit, value)
        else:
            self.inches = 0

    def __getattr__(self, name):
        if name in self._conversion_map:
            try:
                return self._memo[name]
            except KeyError:
                converter = self._conversion_map[name]['to']
                if converter is None:
                    raise AttributeError
                converted = converter(self.inches)
                self._memo[name] = converted
                return converted
        else:
            raise AttributeError

    def __setattr__(self, name, value):
        if name == '_memo':
            super(Distance, self).__setattr__(name, value)
        else:
            # Clear memoized values if setting the value of the object
            self._memo = {}
        if name == 'inches':
            super(Distance, self).__setattr__(name, value)
        if name in self._conversion_map:
            converter = self._conversion_map[name]['from']
            if converter is None:
                raise AttributeError
            self._memo[name] = value
            self.inches = converter(value)
        else:
            raise AttributeError

    @classmethod
    def converter(cls, func):
        direction, unit = func.__name__.split('_', 1)
        cls._conversion_map[unit][direction] = func
        return func

@Distance.converter
def to_feet(value):
    return value / 12

@Distance.converter
def from_feet(value):
    return value * 12

board_1_length = Distance(feet=2)
board_2_length = Distance(inches=14)
board_1_length.inches # 24
board_2_length.feet # 1 (integer division)

【讨论】:

  • 对于我的应用程序,我不太倾向于使用这种技术(尽管可能)。缺陷出现在我过于简单的示例中。问题是单位之间的转换很慢,而且本质上是一种方式......在我的特殊情况下,它有时会将形状表示为矢量数据,而在其他情况下则表示为灰度字段(即 SVG 与 BMP)。
  • 你可以将这种模式与缓存系统结合起来,所以完全转换只在第一次完成(虽然仍然是懒惰的),然后返回缓存的转换结构。
【解决方案2】:

我会更进一步,使用单独的对象类型来提供类型安全,而不是简单地依赖命名约定。否则,您可以将表示英寸的变量传递给需要英里的方法。

我认为依赖命名约定难以长期维护,而使用类型将为您提供更大的灵活性和安全性(例如,提供内置于对象类型中的转换等)

【讨论】:

  • 这是合理的建议。如果命名约定的目的是帮助开发人员避免混淆和错误 - 通过让 Python 类系统为您强制执行规则,您将获得更多。如果您只使用命名约定,困惑的开发人员很容易做出 12(英寸)+ 1(厘米)= 13(哎呀)之类的事情。
  • 我会接受这个答案,即使它没有建议命名约定,但它确实建议了一个替代方案,适用于对这个问题感兴趣的其他人。谢谢。
【解决方案3】:

如果您想要更强大的单位支持,您应该查看PyPi's Pint module。它不直接处理您的命名约定问题,但它可能需要大量的工作来处理频繁的转换。您可以在此处找到有关它和其他一些单元模块的信息:

http://www.drdobbs.com/jvm/quantities-and-units-in-python/240161101

【讨论】:

  • 我总体上喜欢这个建议,它似乎是基本上实现@BrianAgnew 建议的好方法。
【解决方案4】:

...六年后...

在我提出问题很久之后,我偶然发现了 python 类型库。它提供了一种(运行时)免费的静态类型检查方法,而不只是“int”或“float”,而是“inches”和“meters”:

from typing import NewType

UserId = NewType('UserId', int)
some_id = UserId(524313)

这让我得到了我所追求的大部分。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-06-19
    • 2015-02-19
    • 2018-09-12
    • 1970-01-01
    • 1970-01-01
    • 2010-09-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多