所以首先让我指出一个简单的问题,可能会使您的情况变得清晰。让我们分析一下这些代码sn-ps之间发生了什么:
s = sck.socket(sck.AF_INET, sck.SOCK_STREAM) # s defined at the global level
def closecon ():
# python looks for s in the local namespace, doesn't find it
# and uses the global s above.
s.close() # s.close() refers to the socket object's (defined above) .close() method
def con ():
s.connect((host, port)) # Again s not defined locally, so it uses the global s.
在你的函数中使用全局变量或其他任何东西通常被认为是不好的做法,最终会使你的代码难以阅读,甚至对你自己来说也是如此。您遇到的具体问题归结为这个 sn-p:
def sen ():
s.start()
s = snd() # s is now a snd object, no longer a socket
通过重新定义 s 所指的内容,您的类 snd,每当您 run() 时,它都会使用 s 的定义而不是原始套接字。
class snd (threading.Thread):
def run (self):
while True:
msg = input("You: ")
if msg == endprompt:
closecon()
break
else:
s.send(bytes(msg, "UTF-8")) # snd objects (subclassed Thread) has no send() method
sen()
pass
如果你能一直关注我直到现在,你就会意识到,每当你的 snd 类的 run() 中的代码运行时,它就会到达 s.send(bytes(msg, "UTF-8")) 行,并且正在使用 s 的当前全局定义因为在本地范围内没有 s 定义。要解决此问题,您可以使套接字对象和 snd 对象的变量名称彼此不同,并在您想要引用的地方更新代码socket 和带有新名称的 snd 对象。我不建议将此作为您的代码的解决方案,即使它可能有效。
我的推荐
在您的Thread 子类中,您应该覆盖threading.Thread 的__init__() 方法以包括接受套接字对象。您的 snd 类(以及类似的 rec 类)的代码看起来更像:
class snd (threading.Thread):
## Added __init__ override
def __init__(self, sock):
threading.Thread.__init__(self)
self.sock = sock
def run (self):
while True:
msg = input("You: ")
if msg == endprompt:
closecon()
break
else:
self.sock.send(bytes(msg, "UTF-8")) # Now we use the socket specifically given to this object
sen()
pass # You don't need this
当你想使用你的线程时,你只需要稍微修改你的代码:
my_sock = sck.socket(sck.AF_INET, sck.SOCK_STREAM)
sender = snd(my_sock)
sender.start()
请注意,此时您的代码仍无法正常工作,因为您的 con() 和 closecon() 函数需要重新编写:
def closecon(sock):
sock.close()
def con(sock, host, port):
sock.connect((host,port))
最终,您的代码中还有其他几个问题需要重新处理,但我想我已经为您留下了足够的信息来弄清楚如何去做,或者学习如何解决这个问题。如果你不想阅读下面关于 python 的任何更基本的解释,我至少会给你几点:
- 网络真的很复杂。
- 套接字的级别非常低,使联网变得困难。
- 线程是一种高级工具,需要对程序流程有良好的理解才能正确执行。
- 子类化和继承是一个中等高级的概念。
如果您对语言基础知识缺乏扎实的基础知识,则尝试使用上述任何一点进行编码都将是一场复制粘贴之战,并且由于理解上的小漏洞而导致混乱。我最终建议您在尝试使用套接字和线程进行网络连接之前,先获取一本 Python 书籍,或者仔细阅读在线教程。如果您只是试图让某些东西工作,并且更专注于其他代码,我建议您查看其他更高级的 python 网络库。除此之外,祝你的项目好运。
如果您想让您的代码对您和其他人更有意义,了解局部变量和全局变量之间的区别以及 Python 中变量范围的概念将帮助您了解为什么您的几段代码会失败.随着代码变大,它将帮助您更好地组织代码。但是,如果您只想快速运行,请继续阅读。我不是python或编程方面的专家,所以也许这里或那里可能有错误,但我会尽力解释。
作用域
当您的 python 代码运行并遇到变量名时,它会检查该变量的定义。首先它检查本地命名空间,然后从那里向上,一直到最高的命名空间,即全局命名空间。请注意,它从不查看较低的名称空间以进行定义。这意味着您可以在不同的范围级别重用变量名,而不会发生冲突。如果您不知道命名空间是什么,只需将其视为您在代码中所处的位置。例如,
x=1 # Here you are at the global scope/namespace
def func():
x=2 # << Here you are in func's namespace
print(x) # Back out at the global namespace
如果您认为此代码会导致 2 打印到屏幕上,那么您就错了。 x=2 行在 func 范围内,print(x) 行在全局级别。当print(x)被执行时,它会在全局范围内寻找x的值,这在第一行被定义为x=1。当您将变量定义为给定范围内的局部变量时,它们通常会在您离开该范围时被销毁。所以如果你调用func(),它会在本地范围内设置x=2,然后退出函数。通过执行return 或只是完成一个函数,我们将离开它的作用域,并且在作用域中定义的变量 x 被销毁(取消引用和垃圾收集)。由于 func 的内容位于比全局范围低的范围内,因此在全局级别执行的代码永远不会看到其中分配的值。
因此,作用域使函数和类非常隔离,但解决方案是将参数传递给这些构造。通过将参数传递给函数,它们将在其本地范围内定义的变量设置为从另一个范围传递给它们的值。解释一下,用 args 做这段代码的作用:
def func(value):
print(value)
# By passing 100 to func(), there is essentially
# an invisible line in func that does 'value=100'.
func(100)
您必须对全局变量执行此操作:
def func():
value = x
print(value)
# By not using arguments, we have to manually do that invisible
# assignment of value=100 by setting x=100 and then the function setting value=x
x=100
func()
要了解它提供的实用程序,不仅仅是让 python 在全局范围内搜索变量,这两个 sn-ps 都会将2 打印到屏幕上
使用函数参数:
x = 1
def func_args(value):
print(value*2)
func_args(x)
使用全局变量:
x=1
def func_globals():
print(x*2)
func_globals()
但是,请注意func_globals() 只会打印x*2,而func_args(value) 将打印value*2 传递给它的任何值。这意味着要像func_args() 一样使用func_globals(),您必须知道x 在调用func_globals() 时设置为您想要的值,或者手动将其设置为您想要工作的值每次调用它。使用func_args(),您只需要知道您作为value 传递给函数的内容。
这绝不是对变量范围和命名空间的透彻解释,但希望您对什么是范围有一些基本的了解。互联网上有关于 python 中这类事情的很好的信息,我建议熟悉这些概念,因为它们是 python 和其他语言中代码结构的核心。