Dict的子类化违反了python中面向对象编程的基本规则
我要求更好地了解作者对特定代码部分的评论。为了更详细,我将用一个例子来说明Dict的子类化违反了python中面向对象编程的基本规则,python,oop,Python,Oop,我要求更好地了解作者对特定代码部分的评论。为了更详细,我将用一个例子来说明 class DoppelDict(dict): def __setitem__(self, key, value): super().__setitem__(key, [value] * 2) # case 1. dd = DoppelDict(one=1) print(dd) # {'one':1} # case 2. dd['two'] = 2 pr
class DoppelDict(dict):
def __setitem__(self, key, value):
super().__setitem__(key, [value] * 2)
# case 1.
dd = DoppelDict(one=1)
print(dd) # {'one':1}
# case 2.
dd['two'] = 2
print(dd) # {'one':1,'two':[2,2]}
上面的例子摘自一本书,作者评论说“内置行为违反了面向对象编程的基本规则:搜索方法应该始终从目标实例(self)的类开始,即使调用发生在超类中实现的方法内部”
我相信作者试图传达的是,由于python忽略了由用户定义类重写的特殊方法,这违反了OOP。我想知道我的解释是否正确?。你对作者的评论还有其他解释吗?我不能真正评论“内置行为违反了面向对象编程的基本规则:”。但是在你的代码中有两种不同的事情发生 当你这样做的时候
dd = DoppelDict(one=1)
dd['two'] = 2
这将在MRO
中查找\uuuuuu init\uuuuu
,并且由于您的类没有重写\uuuuu init\uuuuu
,因此调用超级类的\uuuu init\uuu
方法,即dict
然而当你这样做的时候
dd = DoppelDict(one=1)
dd['two'] = 2
python在您已重写的MRO
中查找\uuuuuuu setitem\uuuuuuu
方法,因此将调用该方法并获得预期结果
它都与super
和MRO
有关。通过简单地检查\uuu MRO\uuu
属性,可以轻松查看任何类的MRO
In[5]: a = 100
In[6]: a.__class__.__mro__
Out[6]: (int, object)
上述示例仅适用于内置类,但也适用于任何其他自定义类。这是一个实现细节问题-这在很大程度上取决于基类的超级构造函数所做的事情-在这种情况下,它不调用\uuu setitem\uuu
您可以通过以下方式进行修复:
class DoppelDict(dict):
# force it to use setitem instead of update
def __init__(self, *kargs, **kwargs):
# probably also should do something with kargs
for k,w in kwargs.items():
super().__setitem__(k,[w]*2) # see Graipher comment - for less code duplication
# one could use self[k] = w .. plus 1 function call
# but less code duplication for the special logic
def __setitem__(self, key, value):
super().__setitem__(key, [value] * 2)
# case 1.
dd = DoppelDict(one=1)
print(dd) # {'one': [1, 1]}
# case 2.
dd['two'] = 2
print(dd) # {'one': [1, 1], 'two': [2, 2]}
在pythons dict的情况下,它不使用\uuuu setitem\uuuu
在“完全”OOP语言中,例如在C#中,您可能会遇到同样的问题:
公共类基
{
公共基础(字典d)
{
//如果基本构造函数在内部使用SetItem(..),它将按预期工作
//如果在子Klass中重载SetItem:
foreach(d中的KeyValuePair kvp)
设置项(kvp);
//如果基本构造函数不使用SetItem(..),则它不工作
//重载子类SetItem()方法:
//foreach(d中的KeyValuePair kvp)
//D[kvp.Key]=kvp.Value;
}
公共字典D{get;}=new Dictionary();
公共重写字符串ToString()
=>string.Join(“,”,D.Select(kvp=>$”{kvp.Key}={kvp.Value}”);
受保护的虚拟void SetItem(KeyValuePair kvp)=>D[kvp.Key]=kvp.Value;
}
公共类其他:基本类
{
公共其他(字典d):基本(d){}
//重写的实现将传入值加倍
受保护的覆盖无效设置项(KeyValuePair kvp)
=>D[kvp.Key]=2*kvp.Value;
}
您可以使用
publicstaticvoidmain(字符串[]args)
{
字典d=新字典{[“a”]=1、[“b”]=3};
基准b=新基准(d);
其他o=新的其他(d);
Console.WriteLine(b.ToString());
Console.WriteLine(o.ToString());
Console.ReadLine();
}
以及注释Base()
-ctor实现中的任何一个
您可以获取(不使用SetItem(..)
)
或(使用设置项(..)
)
作为输出。否。您有责任-如果您更改\uuuuuu setitem\uuuuuu
的行为,您还必须覆盖\uuuuuu init\uuuuuu
。如果不是,您的参数只是传递到super()类上,那么它应该如何“看到”您更改的实现?@Anakhand这不是真的,当您执行DoppelDict(one=1)
时,会调用super类的\uuuu init\uuuuuu
,因为它没有被重写,而且当您实际执行dd['two']
,正在调用在实例类本身中找到的\uuuuuuu setitem\uuuuu
。这一切都与MRO有关。是的,就像帕特里卡特纳和罗希特说的那样。尽管您是orverriden setitem,但也需要重写init方法。我很好奇,你在读什么书?我想这本书的作者希望dict
的\uuuu init\uuuuu
方法在创建dict时会使用\uuuuuu setitem\uuuu
,但事实并非如此。查看源代码(如果我查看了正确的函数…),它是使用更新来完成的。“基本规则”是“不要猜测!源代码总是正确的,如果有疑问,请阅读源代码。”。我理解这个线程更多的是关于你描述的行为是否符合OOP原则。我不认为这是一般的OOP行为,而是python特有的行为。@Rohit,如果你重写init方法,你能实现类似的行为吗#2@s326280当然,我只是强调了背景中发生了什么,显然对于类似DICT的东西,你需要重写这两种方法。如果有帮助的话,请考虑支持和接受答案。在固定的<代码> DoppError字典<代码>中,它不应该是<代码> [K]=W < /代码>,而不是<代码>()。
?由于它确实使用了\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
@Graipher,这取决于在我的重写\uuuuuuuuuuuuuuuuuuuu但是我从没有调用另一个函数中得到了一个更少的stackframe开关(如果你构造了数百万个东西,这可能很好)。。。一般来说,我倾向于你关于减少维护的建议——这里所需的逻辑很简单,而且它可以双向工作。@Graipher将你的建议编辑为代码Common