Dict的子类违反了python中面向对象编程的基本规则

时间:2019-01-29 11:45:49

标签: 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
 print(dd)  # {'one':1,'two':[2,2]}

以上示例摘自一本书,作者评论说:“内置行为违反了面向对象编程的基本规则:对方法的搜索应始终从目标实例(自身)的类开始,即使调用发生在超类中实现的方法内部也是如此。

我相信作者正在尝试传达信息,因为python忽略了用户定义类重写的特殊方法,这违反了OOP。我想知道我的解释是否正确?您对作者的评论还有其他解释吗?

2 个答案:

答案 0 :(得分:5)

我不能真正评论“内置行为违反了面向对象编程的基本规则:”。但是在您的代码中,发生了两个独立且截然不同的事情。

完成时

dd = DoppelDict(one=1)

这会在__init__中查找MRO,因为您的类没有覆盖__init__,所以__init__类的super方法是{ {1}}被调用。

但是什么时候做

dict

python在dd['two'] = 2 中寻找已被覆盖的__setitem__方法,因此该方法被调用并获得预期的结果。

所有与MROsuper有关。您只需检查MRO属性即可轻松查看任何类的MRO

__mro__

以上示例仅适用于内置类,但其他任何自定义类也适用。

答案 1 :(得分:2)

这是一个实现细节问题-很大程度上取决于基类的超级构造函数的作用-在这种情况下,它不会调用__setitem__

您可以通过以下方式解决它:

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]}

在python字典情况下,它不使用__setitem__


您可能在“完全” OOP语言中遇到相同的问题,例如在C#中:

public class Base
{
    public Base(Dictionary<string, int> d)
    {
        // if the base constructor internally uses SetItem(..) it works as expected
        // if you overload SetItem in the Child-Klasses:
        foreach (KeyValuePair<string, int> kvp in d)
            SetItem(kvp); 

        // if the base constructor does _not_ use SetItem(..) it does not work by
        // overloading child classes SetItem() method: 
        // foreach (KeyValuePair<string, int> kvp in d) 
        //    D[kvp.Key] = kvp.Value; 
    }

    public Dictionary<string, int> D { get; } = new Dictionary<string, int>();

    public override string ToString() 
        => string.Join(",", D.Select(kvp => $"{kvp.Key}={kvp.Value}"));

    protected virtual void SetItem(KeyValuePair<string, int> kvp) => D[kvp.Key] = kvp.Value;
}

public class Other : Base
{
    public Other(Dictionary<string, int> d) : base(d) { }

    // overridden implementation doubling the incoming value
    protected override void SetItem(KeyValuePair<string, int> kvp)
        => D[kvp.Key] = 2 * kvp.Value;
}

您可以使用

对此进行测试
public static void Main(string[] args)
{
    Dictionary<string, int> d = new Dictionary<string, int> { ["a"] = 1, ["b"] = 3 };

    Base b = new Base(d);
    Other o = new Other(d);

    Console.WriteLine(b.ToString());
    Console.WriteLine(o.ToString());

    Console.ReadLine();
}

并评论Base()-ctor实现之一。

您要么得到(不使用SetItem(..)

a=1,b=3
a=1,b=3

或(使用SetItem(..)

a=1,b=3
a=1,b=6

作为输出。