在python中当你的函数接受相同的参数时如何使用类?

105 阅读4分钟

启发式的问题

如果你有接受相同参数集的函数,考虑使用一个类。

在其最基本的形式中,类是当你将数据与对这些数据进行操作的函数组合在一起时;它不一定要代表一个真实的("业务")对象,它可以是一个抽象的对象,其存在只是为了使事情更容易使用/理解。

正如维基百科所说,"启发式是解决一个问题的实用方法。 它比机会好,但并不总是有效。 一个人通过使用智慧、经验和常识来发展启发式。"

因此,这并不是 一直都是正确的事情,甚至在大多数时候都是如此。

相反,我希望这个和其他启发式方法能够帮助人们在从 "我知道类的语法,现在怎么办?"到 "正确的 "面向对象设计的路上建立正确的直觉

例子 HighlightedString

我的feed阅读器库允许支持对文章进行全文搜索。 结果包括文章片段,以及片段中实际匹配的部分。

为了突出匹配的部分(比如说,在一个网页上),我们写一个函数,接收一个字符串和一个片断列表1,并在片断内的部分添加前后标记。

>>> value = 'water on mars'
>>> highlights = [slice(9, 13)]
>>> apply_highlights(value, highlights, '<b>', '</b>')
'water on <b>mars</b>'

在写这个函数的时候,我们把部分逻辑拉到一个辅助程序中,这个辅助程序可以分割字符串,使高亮部分总是有奇数索引。 我们不必这样做,但一次推理一个问题会更容易。

>>> list(split_highlights(value, highlights))
['water on ', 'mars', '']

为了使事情更简单,我们只允许有正的开始/停止和没有步骤的非重叠片断。 我们把这个逻辑拉到另一个函数中,对坏片断提出一个异常。

>>> validate_highlights(value, highlights)  # no exception
>>> validate_highlights(value, [slice(6, 10), slice(9, 13)])
Traceback (most recent call last):
  ...
ValueError: highlights must not overlap: slice(6, 10, None), slice(9, 13, None)

测验。哪个函数应该调用validate_highlights() ?两个都要?还是用户?


我们可以写一个HighlightedString类来代替单独的函数。

  • value 和 作为属性highlights
  • apply() 和 作为方法split()
  • 的验证发生在__init__
>>> string = HighlightedString('water on mars', [slice(9, 13)])
>>> string.value
'water on mars'
>>> string.highlights
(slice(9, 13, None),)
>>>
>>> string.apply('<b>', '</b>')
'water on <b>mars</b>'
>>> list(string.split())
['water on ', 'mars', '']
>>>
>>> HighlightedString('water on mars', [slice(13, 9)])
Traceback (most recent call last):
  ...
ValueError: invalid highlight: start must be not be greater than stop: slice(13, 9, None)

这基本上是将数据行为捆绑在一起。

你可能会问:我可以用一个字符串和一些片断来做任何事情,为什么要专门用这种行为呢? 因为在这种情况下,这种行为通常是有用的

除了使用起来更短之外,一个类。

  • 显示意图:这不仅仅是一个字符串和一些片断,它是一个突出的字符串
  • 使得更容易发现哪些动作是可能的(help(),代码完成)。
  • 使代码更简洁__init__ 验证确保无效的对象存在;因此,方法本身不需要验证任何东西。

注意事项:属性变化令人困惑

假设我们把一个突出显示的字符串传递给一个函数,该函数将结果写入一个文本文件,之后我们对它做一些其他的事情。

如果发生这种情况,你会怎么想?

>>> string.apply('<b>', '</b>')
'water on <b>mars</b>'
>>> render_results_page('output.txt', titles=[string])
>>> string.apply('<b>', '</b>')
'<b>water</b> on mars'

你可能会认为这很出乎意料;我知道我会这样想。无论是有意还是无意,render_results_page() 似乎改变了我们的高亮部分,而它本来应该只是渲染结果。

这没关系,错误是会发生的。 但我们怎样才能防止它在将来发生呢?

解决方案:让这个类变得不可变

好吧,在真正的实现中,这个错误不可能发生

HighlightedString是一个冻结的数据类,所以它的属性是只读的;另外,highlights ,作为一个元组存储,这也是不可变的。

>>> string.highlights = [slice(0, 5)]
Traceback (most recent call last):
  ...
dataclasses.FrozenInstanceError: cannot assign to field 'highlights'
>>> string.highlights[:] = [slice(0, 5)]
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

你可以在werkzeug.datastructures 中找到这种模式,它包含常见 Python 对象的 HTTP 味的子类。 例如,Accept2是一个不可变的列表

>>> accept = Accept([('image/png', 1)])
>>> accept[0]
('image/png', 1)
>>> accept.append(('image/gif', 1))
Traceback (most recent call last):
  ...
TypeError: 'Accept' objects are immutable