Reputation: 615
I am requesting better insights on the author's comments for a particular section of code. To go much in detail, I will illustrate with an example.
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]}
The above example is picked from a book and the author comments 'Built-in behavior is a violation of a basic rule of object-oriented programming: the search for methods should always start from the class of the target instance (self), even when the call happens inside a method implemented in a superclass'.
I believe the author is trying to convey since python ignores special methods overridden by user-defined class, it is a violation of OOP. I wanted to know whether my explanation is correct?. Do you have any other explanation to author comments?.
Upvotes: 4
Views: 222
Reputation: 51643
This is an implemetation detail problem - it highly depends on what the super-called contructor of the base class does - in this case it does not call __setitem__
.
You can fix it though:
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]}
In pythons dict-case it does not use __setitem__
.
You can have the same problem in "fully" OOP languages, for example in 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;
}
You can test this using
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();
}
and commenting either one of the Base()
-ctor implementations.
You either get (not using SetItem(..)
)
a=1,b=3
a=1,b=3
or (using SetItem(..)
)
a=1,b=3
a=1,b=6
as outputs.
Upvotes: 2
Reputation: 4158
I can't really comment on the "Built-in behavior is a violation of a basic rule of object-oriented programming:". But in your code there are two separate and very different things happening.
When you do
dd = DoppelDict(one=1)
This looks for __init__
in the MRO
and since your class didn't override __init__
and so __init__
method of the super
class which is dict
is called.
However when you do
dd['two'] = 2
python looks for __setitem__
method in the MRO
which you have overriden and hence it is called and you get the expected result.
Its all related to super
and MRO
. You can easily look at the MRO
for any class by simply checking __mro__
attribute.
In[5]: a = 100
In[6]: a.__class__.__mro__
Out[6]: (int, object)
The above example is just for an builtin class but the same applies to any other custom class as well.
Upvotes: 5