启发式的问题
如果你有接受相同参数集的函数,考虑使用一个类。
在其最基本的形式中,类是当你将数据与对这些数据进行操作的函数组合在一起时;它不一定要代表一个真实的("业务")对象,它可以是一个抽象的对象,其存在只是为了使事情更容易使用/理解。
正如维基百科所说,"启发式是解决一个问题的实用方法。 它比机会好,但并不总是有效。 一个人通过使用智慧、经验和常识来发展启发式。"
因此,这并不是 一直都是正确的事情,甚至在大多数时候都是如此。
相反,我希望这个和其他启发式方法能够帮助人们在从 "我知道类的语法,现在怎么办?"到 "正确的 "面向对象设计的路上建立正确的直觉。
例子 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和 作为属性highlightsapply()和 作为方法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