1

Edit:

My confusion has been cleared, thank you.
Instead of retreading my confusion of a very simple concept buried in the complexity of threading and multiprocess, I will just state the source of my confusion, and the simple answer to it.
I thought: self was created BY __init__(), so that self is INSIDE the scope of __init__().
In reality: self was created before the calling of __init__() and was created in the 'parent' scope of __init__(). So, self is actually a variable passed to __init__(). In conclusion, self is not protected and it is not special in anyway.

the code I posted below is a study in variable scoping involving threads ran by another process. while it is not related to the question anymore, it does challenge your understanding of python scoping a bit at the part: self=10 # comment out this assignment and see what happens in def thread_WITHOUT_SelfPassedToIt():. Thanks again.

import threading
import multiprocessing
from time import sleep

class exe_classBased(multiprocessing.Process):
    def __init__(self):
        super().__init__()
        self.aaa = 'aaa'

        self = 10


    def run(self):

        print(
            '===================================================\n'
            '<Round 0> self is NOT alterred in the scope of run()\n'
            '==================================================='
        )

        print('self in the start of run() ==>',type(self))

        def thread_WITHOUT_SelfPassedToIt():
            try:
                print('in a thread WITHOUT self passed to it, self==>', type(self))
            except Exception as e:
                print(e)
            try:
                print('self.aaa==',self.aaa)
            except Exception as e:
                print(e)

            self=10 # comment out this assignment and see what happens
            

        def thread_WITH_SelfPassedToIt(self):
            print('in a thread WITH self passed to it, self==>', type(self))
            try:
                print('self.aaa==',self.aaa)
            except Exception as e:
                print(e)

        t = threading.Thread(
            target=thread_WITHOUT_SelfPassedToIt,
            daemon=1,
        )
        t.start()

        t = threading.Thread(
            target=thread_WITH_SelfPassedToIt,
            args=(self,),
            daemon=1,
        )
        t.start()

        print(
            '===================================================\n'
            '<Round 1> self is ALTERRED in the scope of run()\n'
            '==================================================='
        )

        self=10


        print('in the immidiate start of run() after self=10, self==>', type(self))

        def thread_WITHOUT_SelfPassedToIt1():
            nonlocal self
            try:
                print('in a thread WITHOUT self passed to it, self==>', type(self))
            except Exception as e:
                print(e)

            self=11

        def thread_WITH_SelfPassedToIt1(self):
            print('in a thread WITH self passed to it, self==', self)
            try:
                print('self.aaa==', self.aaa)
            except Exception as e:
                print(e)

        t = threading.Thread(
            target=thread_WITHOUT_SelfPassedToIt1,
            daemon=1,
        )
        t.start()

        sleep(1)
        # give the thread_WITHOUT_SelfPassedToIt enough time to have self=11 excecuted

        t = threading.Thread(
            target=thread_WITH_SelfPassedToIt1,
            args=(self,),
            daemon=1,
        )
        t.start()

        sleep(5)

if __name__ == '__main__':
    multiprocessing.freeze_support()
    e = exe_classBased()
    e.daemon = 1
    e.start()
    sleep(5)

'''
output:
===================================================
<Round 0> self is NOT alterred in the scope of run()
===================================================
self in the start of run() ==> <class '__mp_main__.exe_classBased'>
local variable 'self' referenced before assignment
local variable 'self' referenced before assignment
in a thread WITH self passed to it, self==> <class '__mp_main__.exe_classBased'>
self.aaa== aaa
===================================================
<Round 1> self is ALTERRED in the scope of run()
===================================================
in the immidiate start of run() after self=10, self==> <class 'int'>
in a thread WITHOUT self passed to it, self==> <class 'int'>
in a thread WITH self passed to it, self== 11
'int' object has no attribute 'aaa'
'''
Community
  • 1
  • 1
eliu
  • 2,390
  • 1
  • 17
  • 29
  • 1
    If I make a variable named `x` and store a list in it like `x = [1, 2, 3]` and then afterwards I do `x = 6`, would you expect the list to "blow up"? – Aran-Fey Jul 14 '17 at 23:55
  • but the "self" in a multiprocessing class just inherited everything from multiprocess and it should hold many important information for the OS to start a process for it, right? in the subsequent threads, which run after the __init__, self reemerges as the real self. – eliu Jul 15 '17 at 00:11

3 Answers3

3

self is a local variable in that function. Reassigning it doesn't have any effect on the rest of the program. It's no different from assigning a parameter variable in some other function, e.g.

def add1(x):
    y = x + 1
    x = 10
    return y

foo = 3
bar = add1(foo)
print(foo)

This will print 3, not 10, because the assignment was local to add1.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • I think I found the kink in my head now, this is a pure variable scoping problem. I somehow just somehow assumed self was first created in the init(). In reality, self is created before init, and it is created in a broader scope, and then it is passed to the init() method just like other functions and arguments. – eliu Jul 15 '17 at 00:28
  • Yeah, you seemed to think that the `__init__` method does a `return self` at the end, and that's what gets returned when you create the new object. – Barmar Jul 15 '17 at 04:36
3

With self = 10 you are just resigning your local pointer to the current class instance to something else. Basically you loose the reference to "yourself", but just that.

To mutate self, you need to change some of it's attributes:

self.foo = bar

This modifies the same object self that was passed as parameter.

Joao Delgado
  • 861
  • 4
  • 12
  • what happens inside the threads functions then? some "self' retrained their class instance status, some "self" degraded into an int. – eliu Jul 15 '17 at 00:17
  • The functions executed by `threading.Thread` don't have a reference to the `self` defined in the `run` method. The two `local variable 'self' referenced before assignment` errors you see are caused by the first two print statements. – Joao Delgado Jul 15 '17 at 00:26
  • Overall, there is only one instance of `exe_classBased` which is created on the main method in `e = exe_classBased()`. Everything else are just references to that object. Taking one reference and pointing it to something else (in this case a `10`) doesn't change the object itself neither other references to it. – Joao Delgado Jul 15 '17 at 00:30
  • [This](https://stackoverflow.com/a/986145/1649846) answer to another question on a similar topic has some good information – Joao Delgado Jul 15 '17 at 00:33
  • Thank you for clearing this up, strangely enough, I have very firm grasp of "reference passing" and "scoping". it is a mental "kink" that lead to the confusion. but still, please try this: delete `self=10` in the `thread_WITHOUT_SelfPassedToIt()` function definition, and run the code, the thread clearly has the reference from `self`, but this `self` seem to come from the `run()`'s scope. – eliu Jul 15 '17 at 00:40
  • PyCharm won't even highlight the `self` in the thread function definition, even though it is pointing to the class. Interestingly enough, PyCharm not highlighting was the trigger of this question. (I am referring to the code with `self=10` deleted in the `thread_WITHOUT_SelfPassedToIt() ` function definition – eliu Jul 15 '17 at 00:48
  • Oh I see the problem now, my bad. The functions executed in another thread do have a reference to the `self` passed in `run`. **But**, in round 1 there is a `nonlocal self` in the beginning. This tells python that `self` references the global `self` (from the run method). On the round 0 there is no such thing and python is doing something similar to _hoisting_ in javascript. Since there is a assignment later in the method of a local var `self`, the reference to the _global_ `self` is lost in the whole function scope. – Joao Delgado Jul 15 '17 at 00:49
2

The self variable will not point to the class object itself when used as assignee. Instead, it will rewrite the local variable self.

Try instead:

class B: # We create a class B
 b = None

b = B() # We instantiate B

class A:
 def __init__(self):
  self.__class__ = b.__class__ # Ugly but works

a = A() # We instantiate A
type(a) # What is it exactly?
<class '__main__.B'> # Magic!
Fabien
  • 4,862
  • 2
  • 19
  • 33