Python 中的鸭子打字(为了模仿字符串)

Duck Typing in Python (in order to mimic a String)

提问人:habrewning 提问时间:7/10/2022 最后编辑:habrewning 更新时间:11/10/2022 访问量:251

问:

鸭子打字的一般解释如下: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 调用缺少的内容时,它不应该给我一条错误消息吗?

python string elementtree 鸭子类型

评论

0赞 PaulMcG 7/10/2022
为了写出你的 XML,你可能必须遍历你构建的树并将所有 MutableString 转换为 strs - 然后调用 write()
1赞 Thomas Weller 7/10/2022
“write() 参数必须是 str,而不是 MutableText”——那里有什么难理解的?它想要一个字符串,一个真正的字符串,而不是 MutableText。
0赞 habrewning 7/11/2022
但是,write函数怎么可能想要一个字符串呢?是否可以在 Python 中定义具有类型的参数?我们不是在谈论 C++。类型提示是可选的,对执行没有影响?
0赞 mzjn 11/6/2022
为什么需要模仿字符串?重点是什么?
0赞 habrewning 11/7/2022
我有一个数据结构,我希望能够随时以 XML 格式写出该数据结构。该数据结构中的某些值经常更改。但是,我不想总是在数据结构的某些值发生变化时,必须遍历整个 XML 树并对其进行更新。执行此类操作的常用方法是使数据本身成为 XML ElementTree(例如,从它继承)。但是我的程序与XML没有太大关系(仅用于导出数据)。使其成为 XML ElementTree 将是一个不合适的设计决策。

答:

1赞 Tadhg McDonald-Jensen 11/10/2022 #1

真的有可能让一种类型看起来像另一种类型吗?

对于使用静态类型语言来解释鸭子类型的人来说,这是非常典型的,但它忽略了整个交易的一个重要方面:这并不是说你在伪造另一种类型,而是你的代码依赖于行为而不是类型

假设我们有这个函数:

def example_math_equation(a,b):
    return a + 4*b

这并不规定任何关于类型或必须是什么,只是乘以整数应该是有效的,并且可以将其添加到结果中。因此,此代码不仅适用于数字,也适用于序列:abba

>>> 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__()

如果你使用的库期望字符串是不可变的,并在此基础上进行某些优化,那么试图实现这一点的整个过程将只是一场疯狂的追逐,你最好了解库所期望的行为(方法),看看你是否能合理地为它们提供一种数据类型,该数据类型也具有你想要的行为。

评论

1赞 toraman 4/8/2023
我们能不能花点时间来体会一下这个答案有多好。如果您不确定提问者这样做的原因是否正确,那么回答问题并不容易。大多数人只是认为这是一个糟糕的问题,忽略它并提出其他建议。这个答案就是那些人应该做的。用适当而简短的解释来解决可能的误解,提供答案并解释为什么它可能没有达到预期的效果。完善。