python - Python 中的类不变量

标签 python invariants

Class invariants在编码中绝对有用,因为它们可以在检测到明显的编程错误时提供即时反馈,并且它们还提高了代码的可读性,因为它们明确了参数和返回值可以是什么。我相信这也适用于 Python。

但是,通常在 Python 中,参数测试似乎不是“pythonic”做事的方式,因为它有悖于鸭子类型的习语。

我的问题是:

  1. 在代码中使用断言的 Pythonic 方式是什么?

    例如,如果我有以下功能:

    def do_something(name, path, client):
        assert isinstance(name, str)
        assert path.endswith('/')
        assert hasattr(client, "connect")
    
  2. 更一般地说,什么时候断言太多?

我很乐意听取您对此的意见!

最佳答案

简答:

Are assertions Pythonic?

取决于您如何使用它们。一般来说,没有。制作通用的、灵活的代码是最 Pythonic 的事情,但是当你需要检查不变量时:

  1. 使用类型提示帮助您的 IDE 执行类型推断,从而避免潜在的陷阱。

  2. 进行可靠的单元测试

  3. 喜欢 try/except引发更具体异常的子句

  4. 将属性转换为特性,这样您就可以控制它们的 getter 和 setter。

  5. 使用 assert 语句仅用于调试目的。

引用this Stack Overflow discussion有关最佳实践的更多信息。

长答案

你是对的。具有严格的类不变量不被认为是 Pythonic,但是有一种内置的方法来指定参数的首选类型并返回称为 类型提示,如 PEP 484 中所定义。 :

[Type hinting] aims to provide a standard syntax for type annotations, opening up Python code to easier static analysis and refactoring, potential runtime type checking, and (perhaps, in some contexts) code generation utilizing type information.

格式是这样的:

def greeting(name: str) -> str:
    return 'Hello ' + name 

typing库提供了更进一步的功能。然而,有一个巨大的警告......

While these annotations are available at runtime through the usual __annotations__ attribute, no type checking happens at runtime . Instead, the proposal assumes the existence of a separate off-line type checker which users can run over their source code voluntarily. Essentially, such a type checker acts as a very powerful linter.

糟糕。那么,您可以在测试时使用外部工具来检查不变性何时被破坏,但这并不能真正回答您的问题。


属性和try/except

处理错误的最佳方法是确保它从一开始就不会发生。第二个最好的方法是在它发生时制定一个计划。以这样的类为例:

 class Dog(object):
     """Canis lupus familiaris."""

     self.name = str()
     """The name you call it."""


     def __init__(self, name: str):
         """What're you gonna name him?"""

         self.name = name


     def speak(self, repeat=0):
         """Make dog bark. Can optionally be repeated."""

         print("{dog} stares at you blankly.".format(dog=self.name))

         for i in range(repeat):
             print("{dog} says: 'Woof!'".format(dog=self.name)

如果你想让你的狗的名字成为一个不变量,这实际上不会阻止 self.name从被覆盖。它也不会阻止可能崩溃的参数 speak() .但是,如果您制作 self.name一个property ...

 class Dog(object):
     """Canis lupus familiaris."""
     
     self._name = str()
     """The name on the microchip."""

     self.name = property()
     """The name on the collar."""


     def __init__(self, name: str):
         """What're you gonna name him?"""

         if not name and not name.isalpha():
             raise ValueError("Name must exist and be pronouncable.")

         self._name = name


     def speak(self, repeat=0):
         """Make dog bark. Can optionally be repeated."""

         try:
             print("{dog} stares at you blankly".format(dog=self.name))
             
             if repeat < 0:
                 raise ValueError("Cannot negatively bark.")

             for i in range(repeat):
                 print("{dog} says: 'Woof!'".format(dog=self.name))

         except (ValueError, TypeError) as e:
             raise RuntimeError("Dog unable to speak.") from e


     @property
     def name(self):
         """Gets name."""

         return self._name

由于我们的属性(property)没有二传手,self.name本质上是不变的;除非有人知道 self._x,否则该值无法更改.此外,由于我们添加了 try/except处理我们预期的特定错误的子句,我们为我们的程序提供了更简洁的控制流。


那么什么时候使用断言?

可能没有 100% 的“Pythonic”方式来执行断言,因为您应该在单元测试中执行这些操作。但是,如果数据在运行时保持不变很关键,assert语句可用于查明可能的故障点,如 Python wiki 中所述:

Assertions are particularly useful in Python because of Python's powerful and flexible dynamic typing system. In the same example, we might want to make sure that ids are always numeric: this will protect against internal bugs, and also against the likely case of somebody getting confused and calling by_name when they meant by_id.

For example:

from types import *
  class MyDB:
  ...
  def add(self, id, name):
    assert type(id) is IntType, "id is not an integer: %r" % id
    assert type(name) is StringType, "name is not a string: %r" % name

Note that the "types" module is explicitly "safe for import *"; everything it exports ends in "Type".

负责数据类型检查。对于类,您使用 isinstance() ,就像您在示例中所做的那样:

You can also do this for classes, but the syntax is a little different:

class PrintQueueList:
  ...
  def add(self, new_queue):
   assert new_queue not in self._list, \
     "%r is already in %r" % (self, new_queue)
   assert isinstance(new_queue, PrintQueue), \
     "%r is not a print queue" % new_queue

I realize that's not the exact way our function works but you get the idea: we want to protect against being called incorrectly. You can also see how printing the string representation of the objects involved in the error will help with debugging.

为了获得正确的形式,请将消息附加到您的断言中,如上例所示
(例如:assert <statement>, "<message>")会自动将信息附加到生成的 AssertionError 中协助您调试。它还可以深入了解消费者错误报告,了解程序崩溃的原因。

Checking isinstance() should not be overused: if it quacks like a duck, there's perhaps no need to enquire too deeply into whether it really is. Sometimes it can be useful to pass values that were not anticipated by the original programmer.

Places to consider putting assertions:

  • checking parameter types, classes, or values
  • checking data structure invariants
  • checking "can't happen" situations (duplicates in a list, contradictory state variables.)
  • after calling a function, to make sure that its return is reasonable

断言如果使用得当会很有用,但对于不需要显式不变的数据,您不应该依赖它们。如果您希望代码更符合 Pythonic,您可能需要重构代码。

关于python - Python 中的类不变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40641019/

相关文章:

jpa - JPA 实体上的空构造函数和 setter

python - 通过Python实现Oauth2.0授权refresh token(谷歌API服务创建)

Python:测试参数是否为整数

python - 使用 datetime.strptime() 时出现问题

python - 如何从python中具有特定条件的句子中提取数字?

java - 我该如何为这个不变量编写一个循环?

oop - 在聚合根的子实体上强制执行具有范围的不变量 - DDD

python - 如何在 HDFS 中解压多个 zip 文件

c# - DDD - 如何强制执行不变量但特定于客户要求?

java - Java中的类不变量是什么?