Python class 范围和线程
Python class scope and threading
我对 python class 范围有点困惑。还有关于它与线程的关系。下面是一个最小的工作示例。我创建了一个 B
的实例并将其传递给 A
.
的实例
按我的理解A
应该是制作自己的本地副本B_instance
。显然这并没有发生,因为每次我通过任何可以想象的方式修改 B_instance
的属性时,我都会看到 A_instance
和 B_instance
的变化(打印 1 -6)。这是否意味着 A_instance.other_class
被视为全局?有没有办法让 A_instance.other_class
本地化?这样修改它就不会改变 B_instance itself
.
问题的第二部分与线程有关。我知道访问 class 属性不是线程安全的,所以我使用了锁。但是,如果您查看打印语句,我希望 "lock released" 在 "modified by main thread" 之前打印。我错过了什么?两个锁似乎都在锁定其他东西,因为显然可以获取主线程中的锁,即使 A_instance
中的锁仍然有效。我觉得这与我在第一部分的发现相矛盾。
是否可以为整个对象获取锁,而不仅仅是它的属性或方法?
class A:
def __init__(self, other_class):
self.lock = threading.Lock()
self.other_class = other_class
def increment_B(self):
self.other_class.B_attr_1 += 1
def set_B(self):
self.other_class.B_attr_1 = 10
def thread_modify_attr(self):
self.lock.acquire()
self.other_class.B_attr_1 = "modified by class"
time.sleep(10)
self.lock.release()
print("lock released")
class B:
def __init__(self):
self.B_attr_1 = 0
if __name__ == "__main__":
B_instance = B()
A_instance = A(B_instance)
print("1:", B_instance.B_attr_1)
A_instance.other_class.B_attr_1 += 1
print("2:", B_instance.B_attr_1)
A_instance.other_class.B_attr_1 = 10
print("3:", B_instance.B_attr_1)
A_instance.increment_B()
print("4:", B_instance.B_attr_1)
A_instance.set_B()
print("5:", B_instance.B_attr_1)
B_instance.B_attr_1 = 0
print("6:", A_instance.other_class.B_attr_1)
lock = threading.Lock()
t = threading.Thread(target=A_instance.thread_modify_attr)
t.start()
print("thread started")
print(B_instance.B_attr_1)
lock.acquire()
B_instance.B_attr_1 = "modified by main thread"
lock.release()
print(B_instance.B_attr_1)
print("done")
t.join()
结果:
1: 0
2: 1
3: 10
4: 11
5: 10
6: 0
thread started
modified by class
modified by main thread
done
lock released
如果有人知道可以阅读有关 python 范围界定详细信息的好地方,我将不胜感激。
没有。当您创建 B 的实例并将其传递给 A 构造函数时,将传递对同一 B 实例的 reference 。没有复制。如果你想要那个,你必须自己复制一份。一种方法是使用 copy.deepcopy
(但请注意,某些类型无法复制——threading.Lock
实例就是其中之一)。本质上,新引用(您存储在 A 的 self.other_class
中)是同一实例的另一个名称。
您的两个代码块能够同时执行的原因是您创建了两个不同的锁。您在 class A 构造函数中创建了一个——那个被 thread_modify_attr
locked/unlocked。另一个是在子线程创建之前在主线程代码中创建的,并由主线程锁定和解锁。如果您希望两者使用相同的锁,请将锁作为参数传递(引用)到线程函数中。因为它们不是同一个锁,所以没有什么可以阻止两个线程并发执行。
锁可以保护任何你想保护的东西。您的代码有责任确定锁保护的内容。也就是说,您 可以 在 class 的构造函数中创建一个锁。然后,无论何时任何引用 class 实例,您都可以使用与该实例关联的锁来同步访问。一种常见的模式是在 class 内集中控制对象资源。因此,外部代理只需要求对象做某事(通过调用其方法之一)。然后该对象在内部使用锁来保护其数据结构。
Python 描述了范围规则 here
只是@Gil 上面的回答的扩展
- 您可以在我为您创建的 this PythonTutor snippet 上看到 Python 如何处理您的 类、它们的实例以及对它们的引用的直观解释。
Lock 可以作为上下文管理器运行,因此您可以在 with 语句中使用它,并且当 with 块因任何原因(例如异常)退出时它会自动释放。
def thread_modify_attr(self):
with self.lock:
self.other_class.B_attr_1 = "modified by class"
time.sleep(10)
print("lock released")
注意: 如果上面的 link 已经失效,只需直接转到 PythonTutor.com 并复制并粘贴此修改后的代码版本。唯一的变化是我删除了与线程有关的所有内容,因为站点不支持它。
class A:
def __init__(self, other_class):
self.other_class = other_class
def increment_B(self):
self.other_class.B_attr_1 += 1
def set_B(self):
self.other_class.B_attr_1 = 10
class B:
def __init__(self):
self.B_attr_1 = 0
if __name__ == "__main__":
B_instance = B()
A_instance = A(B_instance)
print("1:", B_instance.B_attr_1)
A_instance.other_class.B_attr_1 += 1
print("2:", B_instance.B_attr_1)
A_instance.other_class.B_attr_1 = 10
print("3:", B_instance.B_attr_1)
A_instance.increment_B()
print("4:", B_instance.B_attr_1)
A_instance.set_B()
print("5:", B_instance.B_attr_1)
B_instance.B_attr_1 = 0
print("6:", A_instance.other_class.B_attr_1)
print(B_instance.B_attr_1)
print("done")
我对 python class 范围有点困惑。还有关于它与线程的关系。下面是一个最小的工作示例。我创建了一个 B
的实例并将其传递给 A
.
按我的理解
A
应该是制作自己的本地副本B_instance
。显然这并没有发生,因为每次我通过任何可以想象的方式修改B_instance
的属性时,我都会看到A_instance
和B_instance
的变化(打印 1 -6)。这是否意味着A_instance.other_class
被视为全局?有没有办法让A_instance.other_class
本地化?这样修改它就不会改变B_instance itself
.问题的第二部分与线程有关。我知道访问 class 属性不是线程安全的,所以我使用了锁。但是,如果您查看打印语句,我希望 "lock released" 在 "modified by main thread" 之前打印。我错过了什么?两个锁似乎都在锁定其他东西,因为显然可以获取主线程中的锁,即使
A_instance
中的锁仍然有效。我觉得这与我在第一部分的发现相矛盾。是否可以为整个对象获取锁,而不仅仅是它的属性或方法?
class A:
def __init__(self, other_class):
self.lock = threading.Lock()
self.other_class = other_class
def increment_B(self):
self.other_class.B_attr_1 += 1
def set_B(self):
self.other_class.B_attr_1 = 10
def thread_modify_attr(self):
self.lock.acquire()
self.other_class.B_attr_1 = "modified by class"
time.sleep(10)
self.lock.release()
print("lock released")
class B:
def __init__(self):
self.B_attr_1 = 0
if __name__ == "__main__":
B_instance = B()
A_instance = A(B_instance)
print("1:", B_instance.B_attr_1)
A_instance.other_class.B_attr_1 += 1
print("2:", B_instance.B_attr_1)
A_instance.other_class.B_attr_1 = 10
print("3:", B_instance.B_attr_1)
A_instance.increment_B()
print("4:", B_instance.B_attr_1)
A_instance.set_B()
print("5:", B_instance.B_attr_1)
B_instance.B_attr_1 = 0
print("6:", A_instance.other_class.B_attr_1)
lock = threading.Lock()
t = threading.Thread(target=A_instance.thread_modify_attr)
t.start()
print("thread started")
print(B_instance.B_attr_1)
lock.acquire()
B_instance.B_attr_1 = "modified by main thread"
lock.release()
print(B_instance.B_attr_1)
print("done")
t.join()
结果:
1: 0
2: 1
3: 10
4: 11
5: 10
6: 0
thread started
modified by class
modified by main thread
done
lock released
如果有人知道可以阅读有关 python 范围界定详细信息的好地方,我将不胜感激。
没有。当您创建 B 的实例并将其传递给 A 构造函数时,将传递对同一 B 实例的 reference 。没有复制。如果你想要那个,你必须自己复制一份。一种方法是使用
copy.deepcopy
(但请注意,某些类型无法复制——threading.Lock
实例就是其中之一)。本质上,新引用(您存储在 A 的self.other_class
中)是同一实例的另一个名称。您的两个代码块能够同时执行的原因是您创建了两个不同的锁。您在 class A 构造函数中创建了一个——那个被
thread_modify_attr
locked/unlocked。另一个是在子线程创建之前在主线程代码中创建的,并由主线程锁定和解锁。如果您希望两者使用相同的锁,请将锁作为参数传递(引用)到线程函数中。因为它们不是同一个锁,所以没有什么可以阻止两个线程并发执行。锁可以保护任何你想保护的东西。您的代码有责任确定锁保护的内容。也就是说,您 可以 在 class 的构造函数中创建一个锁。然后,无论何时任何引用 class 实例,您都可以使用与该实例关联的锁来同步访问。一种常见的模式是在 class 内集中控制对象资源。因此,外部代理只需要求对象做某事(通过调用其方法之一)。然后该对象在内部使用锁来保护其数据结构。
Python 描述了范围规则 here
只是@Gil 上面的回答的扩展
- 您可以在我为您创建的 this PythonTutor snippet 上看到 Python 如何处理您的 类、它们的实例以及对它们的引用的直观解释。
Lock 可以作为上下文管理器运行,因此您可以在 with 语句中使用它,并且当 with 块因任何原因(例如异常)退出时它会自动释放。
def thread_modify_attr(self):
with self.lock:
self.other_class.B_attr_1 = "modified by class"
time.sleep(10)
print("lock released")
注意: 如果上面的 link 已经失效,只需直接转到 PythonTutor.com 并复制并粘贴此修改后的代码版本。唯一的变化是我删除了与线程有关的所有内容,因为站点不支持它。
class A:
def __init__(self, other_class):
self.other_class = other_class
def increment_B(self):
self.other_class.B_attr_1 += 1
def set_B(self):
self.other_class.B_attr_1 = 10
class B:
def __init__(self):
self.B_attr_1 = 0
if __name__ == "__main__":
B_instance = B()
A_instance = A(B_instance)
print("1:", B_instance.B_attr_1)
A_instance.other_class.B_attr_1 += 1
print("2:", B_instance.B_attr_1)
A_instance.other_class.B_attr_1 = 10
print("3:", B_instance.B_attr_1)
A_instance.increment_B()
print("4:", B_instance.B_attr_1)
A_instance.set_B()
print("5:", B_instance.B_attr_1)
B_instance.B_attr_1 = 0
print("6:", A_instance.other_class.B_attr_1)
print(B_instance.B_attr_1)
print("done")