将Python 2代码转换为Python 3

4

我正在尝试将以下Python 2代码转换为Python 3。这段Python代码用于TCP端口转发。它来自于这个页面:http://code.activestate.com/recipes/483730-port-forwarding/

import socket
import sys
import thread

def main(setup, error):
    sys.stderr = file(error, 'a')
    for settings in parse(setup):
        thread.start_new_thread(server, settings)
    lock = thread.allocate_lock()
    lock.acquire()
    lock.acquire()

def parse(setup):
    settings = list()
    for line in file(setup):
        parts = line.split()
        settings.append((parts[0], int(parts[1]), int(parts[2])))
    return settings

def server(*settings):
    try:
        dock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        dock_socket.bind(('', settings[2]))
        dock_socket.listen(5)
        while True:
            client_socket = dock_socket.accept()[0]
            server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            server_socket.connect((settings[0], settings[1]))
            thread.start_new_thread(forward, (client_socket, server_socket))
            thread.start_new_thread(forward, (server_socket, client_socket))
    finally:
        thread.start_new_thread(server, settings)

def forward(source, destination):
    string = ' '
    while string:
        string = source.recv(1024)
        if string:
            destination.sendall(string)
        else:
            source.shutdown(socket.SHUT_RD)
            destination.shutdown(socket.SHUT_WR)

if __name__ == '__main__':
    main('proxy.ini', 'error.log')

这是我拥有的:

import socket
import sys
import threading
import time

def main(setup, error):
    # open file for error messages
    sys.stderr = open(error, 'a')
    # read settings for port forwarding

    threads = []

    for settings in parse(setup):
        #thread.start_new_thread(server, settings)
        t = threading.Thread(target=server, args=(settings))
        t.start()
        threads.append(t)

    for t in threads:
        t.join()

    # wait for <ctrl-c>
    while True:
        time.sleep(60)

def parse(setup):
    settings = list()
    file = open(setup)

    for line in iter(file):
        parts = line.split()
        settings.append((int(parts[0]), parts[1], int(parts[2])))

    return settings

def server(*settings):

    try:
        dock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        dock_socket.bind(('', settings[0]))
        dock_socket.listen(5)
        while True:
            client_socket = dock_socket.accept()[0]
            server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            server_socket.connect((settings[1], settings[2]))

            Thread1 = threading.Thread(target=forward, args=(client_socket, server_socket))
            Thread1.start()

            Thread2 = threading.Thread(target=forward, args=(server_socket, client_socket))
            Thread2.start()

    finally:
        thread = threading.Thread(targer=server, args=settings)
        thread.start()

def forward(source, destination):
    string = ' '
    while string:
        string = source.recv(1024)
        if string:
            destination.sendall(string)
        else:
            source.shutdown(socket.SHUT_RD)
            destination.shutdown(socket.SHUT_WR)

if __name__ == '__main__':
    main('port-forward.config', 'error.log')

Python 3版本似乎可以正常工作,但我不确定它是否编写正确。我不清楚代码中的线程部分。Python 2版本使用线程模块,而Python 3版本使用线程模块。看看Python 2版本,它在主函数中使用锁。我需要在Python 3版本中使用锁吗?另一个问题是,在Python 3代码的服务器函数中,我应该加入两个线程(线程1和线程2)吗?
无论Python版本如何,我还有一个问题是关于服务器函数的参数。我理解“settings”变量是一个列表。为什么在“settings”参数之前需要有一个星号?我确实查看了Python文档中的以下页面 https://docs.python.org/3/tutorial/controlflow.html#arbitrary-argument-lists,但我不明白为什么没有星号传递设置列表就不能正常工作。
我还有一个问题是关于Python 2代码中的线程锁。为什么锁被获取两次?我尝试删除一个锁,程序立即在启动后结束。

我已经编辑了帖子,添加了另一个我想问的问题。 - user695752
4个回答

6

如果您使用命令行工具2to3将代码转换为Python 3,您可以减少一些工作量。以下是使用方法(从bash或Windows命令行):

2to3 -w myscript.py

但让我们来回答你的实际问题:为什么定义中有星号?
def server(*settings):
    ...

函数thread.start_new_thread会启动你的server()函数,就像这样:

server(arg1, arg2, arg3)

arg1arg2等来自start_new_thread的第二个参数。声明def server(* settings)将这些参数收集回单个列表settings中,然后您的函数继续使用它们。如果您编写def server(settings)(没有星号),则表明您的函数只接受一个参数,但实际上会接受多个参数。


感谢Alexis解释星号参数。 - user695752

3
您可以使用Python 3自带的2to3工具作为标准模块。它会为您完成大部分转换工作。
$2to3 youpythonfile.py

在Python 3中,您可以使用与Python 2中的thread相同的_thread模块。


1

1
作为Python cookbook中所提及代码的作者,我很荣幸能够为Python 3重新编写这个程序并向您介绍另一个有用的配方。端口转发(Port Forwarding)是这两个配方中较旧的版本,但它是一个完整的程序,我在第一所大学时就用过。运行简单代理的模块(Module For Running Simple Proxies)是原始代码的改进版,被设计用于其他程序而不是作为一个完整的程序。下面的代码将端口转发(Port Forwarding)的概念与运行简单代理的模块(Module For Running Simple Proxies)以及后来学到的内容相结合,为您提供一个完整且易于阅读的程序,供您使用和学习:
import argparse
import re
import select
import socket
import sys
import threading


SETUP_PATTERN = r'^(?P<server_name>\S+)' \
                r'\s+(?P<server_port>\d+)' \
                r'\s+(?P<proxy_port>\d+)'
SETUP_TYPES = dict(server_name=str, server_port=int, proxy_port=int)


def main():
    arguments = parse_arguments()
    sys.stderr = open(arguments.error, 'a')
    for settings in parse_configuration(arguments.setup):
        threading.Thread(target=handle_connections, kwargs=settings).start()


def parse_arguments():
    parser = argparse.ArgumentParser(description='Forward TCP traffic.')
    parser.add_argument('setup', type=str, help='file with forwarding rules')
    parser.add_argument('error', type=str, help='location for error reports')
    arguments = parser.parse_args()
    return arguments


def parse_configuration(setup):
    with open(setup) as file:
        for line in file:
            match = re.search(SETUP_PATTERN, line)
            if match:
                yield {key: SETUP_TYPES[key](value)
                       for key, value in match.groupdict().items()}


def handle_connections(server_name, server_port, proxy_port):
    proxy = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    proxy.bind((socket.gethostname(), proxy_port))
    proxy.listen(5)
    while True:
        client, address = proxy.accept()
        server = socket.create_connection((server_name, server_port))
        threading.Thread(target=handle_traffic, args=(client, server)).start()


def handle_traffic(client, server):
    pairs, empty = {client: server, server: client}, ()
    while pairs:
        read, write, error = select.select(pairs.keys(), empty, empty)
        for connection in read:
            try:
                data = connection.recv(1 << 12)
            except ConnectionResetError:
                data = None
            if data:
                pairs[connection].sendall(data)
            else:
                connection.shutdown(socket.SHUT_RD)
                pairs.pop(connection).shutdown(socket.SHUT_WR)
    client.close()
    server.close()


if __name__ == '__main__':
    main()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接