提问人:habrewning 提问时间:7/10/2022 最后编辑:habrewning 更新时间:11/10/2022 访问量:251
Python 中的鸭子打字(为了模仿字符串)
Duck Typing in Python (in order to mimic a String)
问:
鸭子打字的一般解释如下:https://stackoverflow.com/a/4205163/19446851。
Duck Typing 在 Python 中是什么意思?真的有可能让一种类型看起来像另一种类型吗?我可以有一个自己的类,像字符串一样“看起来和嘎嘎”吗?
请参阅以下示例:
from dataclasses import dataclass
@dataclass
class ColoredObject:
color : ...
name : ...
def __str__(self):
return self.color + " " + self.name
x = ColoredObject("red", "circle")
print("I have a " + x + ".")
该代码不起作用,因为无法连接 ColoredObject 类型的字符串和对象。如果在 Python 中实际上可以使 ColoredObject 像字符串一样“看起来嘎嘎嘎”,那么应该有一种方法可以在不显式转换的情况下连接两者。
下面是一个更实际的例子。我尝试使类 MutableText 像字符串一样“看起来和嘎嘎”,以便我可以在 XML 元素树中使用它。
import xml.etree.cElementTree as ET
root = ET.Element("root_node")
class MutableText:
def __init__(self, init_text):
self.text = init_text
mutable_contents = MutableText("ZigZag")
ET.SubElement(root, "child_node").text = mutable_contents
tree = ET.ElementTree(root)
tree.write("filename.xml")
目标是这条线有效。我能做些什么来实现这一目标?ET.SubElement(root, "child_node").text = mutable_contents
我通过代码得到的错误消息是TypeError: cannot serialize <__main__.MutableText object at 0x7fafc0099e20> (type MutableText)
我已经得到了从 str 类继承的建议。但这不是鸭子打字。这是像 C++ 或 Java 一样的静态类型。
另一个建议是好的。但这也不是鸭子打字。这意味着,每当mutable_contents发生变化时,我总是必须更新 ElementTree。(这其实是我的动机,也是我问这个学术问题的原因。我正在尝试找到一种解决方案,因为不必总是进行此更新。ET.SubElement(root, "child_node").text = mutable_contents.text
我还得到评论说,ElementTree 实际上需要一个字符串而不是 MutableString。但是为什么人们会说,Python 使用 Duck Typing?为什么我没有收到错误消息,指出在提供 MutableString 的情况下需要字符串?
显然,为了让 MutableText 像字符串一样,我的代码中缺少一些东西?但是缺少什么?当 Python 尝试从 MutableText 调用缺少的内容时,它不应该给我一条错误消息吗?
答:
真的有可能让一种类型看起来像另一种类型吗?
对于使用静态类型语言来解释鸭子类型的人来说,这是非常典型的,但它忽略了整个交易的一个重要方面:这并不是说你在伪造另一种类型,而是你的代码依赖于行为而不是类型。
假设我们有这个函数:
def example_math_equation(a,b):
return a + 4*b
这并不规定任何关于类型或必须是什么,只是乘以整数应该是有效的,并且可以将其添加到结果中。因此,此代码不仅适用于数字,也适用于序列:a
b
b
a
>>> example_math_equation("foo", "bar")
'foobarbarbarbar'
这就是鸭子类型的想法,你尽可能避免检查数据类型,只是假设它们支持你需要的操作,如果它们不支持,你就会得到错误。然后,如果有人想要创建一种新的数据类型 - 不是为了模仿另一种定义良好的数据类型,而只是为了不同的行为 - 那么可以使用它来代替。
如果你不想做鸭子打字,你只想作弊和模仿 str 类,有一条路由存在:
class MockStr:
def __init__(self, initial_text):
self._TEXT = initial_text
def make_wrapper_method(methodname):
"makes a method that will forward to ._TEXT field"
def wrapper_method(self, *args, **kw):
#print("CALLED WRAPPER", methodname)
return getattr(self._TEXT, methodname)(*args, **kw)
return wrapper_method
for methodname, underlying_method in vars(str).items():
if not callable(underlying_method) or methodname in dir(MockStr):
continue
setattr(MockStr, methodname, make_wrapper_method(methodname))
x = MockStr("hi there")
print(x + " got added to a string")
但是不要走这条路,你会遇到的第一个问题是,因为不能添加到任何其他内置类型中,所以除非你自己这样做,否则它不会费心定义一个所以会失败,但更具体地说,如果你的目标是制作一个可变的字符串,你真的不应该这样做,因为你的对象不会是不可变的str
__radd__
"a" + x
如果一个类定义了可变对象并实现了一个方法,它不应该实现,因为实现 Hashable 集合要求键的哈希值是不可变的
__eq__()
__hash__()
如果你使用的库期望字符串是不可变的,并在此基础上进行某些优化,那么试图实现这一点的整个过程将只是一场疯狂的追逐,你最好了解库所期望的行为(方法),看看你是否能合理地为它们提供一种数据类型,该数据类型也具有你想要的行为。
评论