6

I write the test code with 3 classes , and using Chain of Responsibility design pattern , the code below

and I print print(c._abc is b._abc), the answer is True , but my original think is that the two are different.

Then , Round 2 , I uncomment self._abc = kwargs and comment other 3 lines , the answer become False .

Why is it so?

import abc

class A:
    __metaclass__ = abc.ABCMeta

    _abc = {}

    def __init__(self,successor=None,**kwargs):
        self._successor = successor

    @abc.abstractmethod
    def handlerRequest(self):
        pass

class B(A):

    def __init__(self,successor=None,**kwargs):
        self._successor = successor
        print(kwargs)
        # self._abc = kwargs                 # round 2<---uncomment here
        self._abc['a'] = kwargs['a']         # round 2<---comment here
        self._abc['b'] = kwargs['b']         # round 2<---comment here
        self._abc['Hello'] = 'World'         # round 2<---comment here

    def handlerRequest(self):
        if (self._successor is not None):
            self._successor.handlerRequest()

        print(self._abc)

class C(A):

    def handlerRequest(self):
        if (self._successor is not None):
            self._successor.handlerRequest()
        print(self._abc)

list = {'a':1,'b':2}
b = B(**list)
c = C(b)
print(c._abc is b._abc)
c.handlerRequest()
Amit Tripathi
  • 7,003
  • 6
  • 32
  • 58
Relax ZeroC
  • 651
  • 1
  • 12
  • 27

1 Answers1

3

First just to make it clear I would like to state that _abc is a class variable, not an instance variable so its address space is shared across parent and child class. Any change in the original _abc object affects all the class's _abc object. Read about class and instance variable (here at SO and here at DO)

To check that _abc share the same address space we can use python built-in id():

id() Return the “identity” of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value.

To check this in the first round we can do:

In [33]: id(c._abc)
Out[33]: 4454841440 

In [34]: id(b._abc)
Out[34]: 4454841440

In [36]: id(A._abc)
Out[36]: 4454841440

In [38]: id(B._abc)
Out[38]: 4454841440

In [39]: id(C._abc)
Out[39]: 4454841440

All of them gives the same value for id(). In round 2, when you uncomment self._abc = kwargs see what happens to the value of id():

In [8]: id(b._abc)
Out[8]: 4585625712 # its different from A._abc and c._abc why?

In [9]: id(c._abc)
Out[9]: 4585627152 # same as A._abc

In [10]: id(A._abc)
Out[10]: 4585627152 # this is same as c._abc

Value of b._abc gets changed but the value of c._abc and A._abc remains the same. So, what is actually happening here?

When in round 1 you do:

# self._abc = kwargs                 
self._abc['a'] = kwargs['a']
self._abc['b'] = kwargs['b']
self._abc['Hello'] = 'World'

You are actually modifying the shared class variable _abc. Your code here is not creating a new self._abc object it is modifying the original self._abc class variable object but in round 2 when you do:

self._abc = kwargs

Here, the code is assigning kwargs, which is a dictionary with its own address space, to self._abc. self._abc becomes class B's instance variable and it is only available to the class B's objects.

To verify this point you can modify your class B to print id's as:

In [11]: class B(A):
    ...:
    ...:     def __init__(self,successor=None,**kwargs):
    ...:         self._successor = successor
    ...:         print(id(self._abc))
    ...:         print(id(kwargs))
    ...:         self._abc = kwargs
    ...:         print(id(self._abc))

In [12]: b = B(**list)
4585627152 # original self._abc id
4583538904 # kwargs object's id
4583538904 # New self._abc id

As you can see originally self._abc had address 4585627152 and kwargs had 4583538904 but after self._abc= kwargs self._abc's new id is 4583538904.

Amit Tripathi
  • 7,003
  • 6
  • 32
  • 58
  • Thank you for your reply , I found that if I create B or C instances in same py file , they share same _abc , is that python mechanism ? – Relax ZeroC Dec 03 '17 at 06:37
  • and if I want to shared object like dict , is this a good pattern , or maybe occur some problem ? – Relax ZeroC Dec 03 '17 at 06:40
  • @RelaxZeroC Glad that it helped. No, actually when you do `class A: _abc = {}` you are creating a class variable and when you do `self.abc = {}` you are creating class instance variable: In case of class variable classes share same _abc variable and in the case of instance variable classes create their own _abc varibale. I will edit tha answer. – Amit Tripathi Dec 03 '17 at 06:40
  • 1
    @RelaxZeroC Generally speaking, if _abc is a constant that you want to share across class and do not modify it it's a good pattern but if you are modifying it in your classes it's better to create instance variables as you might not know which class modified it. – Amit Tripathi Dec 03 '17 at 06:42
  • you mean B,C has it own _abc ? I will modify _abc and I just hope only have one _abc in there , but if B,C create it's own _abc , then _abc will different . Or I should create other class(ex.SaveABC) to save _abc and let B,C to get _abc from SaveABC ? – Relax ZeroC Dec 03 '17 at 06:48
  • @RelaxZeroC do you want _abc to be shared across the instances of A,B and C class objects in such a way that changing _abc will affect it for all three class objects? – Amit Tripathi Dec 03 '17 at 06:55
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/160372/discussion-between-relax-zeroc-and-amit-tripathi). – Relax ZeroC Dec 03 '17 at 10:12