Python是解释型语言,没有严格意义上的编译和汇编过程。但是一般可以认为编写好的python源文件,由python解释器翻译成以.pyc为结尾的字节码文件。pyc文件是二进制文件,可以由python虚拟机直接运行。
Python在执行import语句时,将会到已设定的path中寻找对应的模块。并且把对应的模块编译成相应的PyCodeObject中间结果,然后创建pyc文件,并将中间结果写入该文件。然后,Python会import这个pyc文件,实际上也就是将pyc文件中的PyCodeObject重新复制到内存中。而被直接运行的python代码一般不会生成pyc文件。
加载模块时,如果同时存在.py和.pyc,Python会尝试使用.pyc,如果.pyc的编译时间早于.py的修改时间,则重新编译.py并更新.pyc。
1.把原始代码编译成字节码
编译后的字节码是特定于Python的一种表现形式,它不是二进制的机器码,需要进一步编译才能被机器执行。如果Python进程在机器上拥有写入权限,那么它将把程序的字节码保存为一个以.pyc 为扩展名的文件,如果Python无法在机器上写入字节码,那么字节码将会在内存中生成并在程序结束时自动丢弃。
PVM(Python Virtual Machine)是Python的运行引擎,是Python系统的一部分,它是迭代运行字节码指令的一个大循环、一个接一个地完成操作。
原理:分析脚本文件,递归找到所有依赖的模块。如果依赖模块有.pyd文件,即将其复制到disk目录。如果没有.pyd文件,则生成.pyc文件拷贝到disk目录,并压缩为.zip保存。制作一个exe,导入PythonXX.dll(解析器库),并添加exe运行需要的相关运行时库。这就构成了一个不用安装Python的运行包。
Nuitka是一个Python编写的Python解释器,所以Nuitka可以作为Python的扩展库使用。Nuitka会将所有没有生成pyd的模块的.py代码转换成C代码,然后调用gcc/MSVC编译成.pyd。
.pyc:python编译后的二进制文件,是.py文件经过编译产生的字节码,pyc文件是可以由python虚拟机直接执行的程序(PyCodeObject对象在硬盘上的保存形式)。
pyc文件仅在由另一个.py文件或模块导入时从.py文件创建(import)。触发 pyc 文件生成不仅可以通过 import,还可以通过 py_compile 模块手动生成。
PEP 3147:
包含两个32位大端数字,后面跟的序列化的PyCodeObject。32位数字表示一个Magic Num和Timestamp。每当Python改变字节码格式时,Magic Num会改变,例如,通过向其虚拟机添加新的字节码。这确保为以前版本的VM构建的pyc文件不会造成问题。Timestamp用于确保pyc文件与用于创建它的py文件匹配。当Magic Num或Timestamp不匹配时,将重新编译py文件并写入新的pyc文件。
PEP 552:
pyc头文件目前由3个32位的字组成。我们将把它扩大到4个。第一个单词将继续是magic number,对字节码和pyc格式进行版本控制。第二个4byte新增加的字段,将是一个位字段(bit field),对报头其余部分的解释和pyc的失效行为取决于位字段的内容。
如果位字段(bit field)为0,则pyc是传统的基于时间戳的pyc。即第三个和第四个4字节内容分别是时间戳和文件大小,通过比较源文件的元数据和头文件中的元数据来进行无效判断。
如果位字段的最低位被设置,则pyc是基于哈希的pyc。我们将第二个最低位称为check_source标志。位字段之后是源文件的64位散列。我们将使用带有源文件内容硬编码密钥。
typedef struct { unsigned short min; unsigned short max; wchar_t version[MAX_VERSION_SIZE]; } PYC_MAGIC; static PYC_MAGIC magic_values[] = { { 50823, 50823, L"2.0" }, { 60202, 60202, L"2.1" }, { 60717, 60717, L"2.2" }, { 62011, 62021, L"2.3" }, { 62041, 62061, L"2.4" }, { 62071, 62131, L"2.5" }, { 62151, 62161, L"2.6" }, { 62171, 62211, L"2.7" }, { 3000, 3131, L"3.0" }, { 3141, 3151, L"3.1" }, { 3160, 3180, L"3.2" }, { 3190, 3230, L"3.3" }, { 3250, 3310, L"3.4" }, { 3320, 3351, L"3.5" }, { 3360, 3379, L"3.6" }, { 3390, 3399, L"3.7" }, { 3400, 3419, L"3.8" }, { 0 } };
typedef struct { PyObject_HEAD int co_argcount; /* 位置参数个数 */ int co_nlocals; /* 局部变量个数 */ int co_stacksize; /* 栈大小 */ int co_flags; PyObject *co_code; /* 字节码指令序列 */ PyObject *co_consts; /* 所有常量集合 */ PyObject *co_names; /* 所有符号名称集合 */ PyObject *co_varnames; /* 局部变量名称集合 */ PyObject *co_freevars; /* 闭包用的的变量名集合 */ PyObject *co_cellvars; /* 内部嵌套函数引用的变量名集合 */ /* The rest doesn’t count for hash/cmp */ PyObject *co_filename; /* 代码所在文件名 */ PyObject *co_name; /* 模块名|函数名|类名 */ int co_firstlineno; /* 代码块在文件中的起始行号 */ {
在 Python 代码中,每个作用域(或者叫block或者名字空间)对应一个 PyCodeObject 对象, 所以会出现嵌套: 比如 一个 module 类 定义了 N 个 class, 每个 class 内又定义了 M 个方法. 每个 子作用域 对应的 PyCodeObject 会出现在它的 父作用域 对应的 PyCodeObject 的 co_consts 字段里。
pyo文件是源代码文件经过优化编译后生成的文件,是pyc文件的优化版本,由解释器在导入模块时创建。当我们调用Python解释器时,通过添加“-O”标志来启用优化器,生成.pyo文件。在Python3.5之后,不再使用.pyo文件名,而是生成文件名类似“test.opt-n.pyc的文件。
.pyd文件类型是特定于Windows操作系统类平台的,是一个动态链接库,它包含一个或一组Python模块,由其他Python代码调用。要创建.pyd文件,需要创建一个名为example.pyd的模块。在这个模块中,需要创建一个名为PyInit_example()的函数。当程序调用这个库时,它们需要调用import foo, PyInit_example()函数将运行。
PyInstaller中使用了两种存档。一个是ZlibArchive,它能高效地存储Python模块,并通过一些导入钩子直接导入。另一个是CArchive,类似于.zip文件,这是一种打包(或压缩)任意数据块的通用方法。
ZlibArchive中的目录是一个Python字典,它的Key(import语句中给定的成员名)与ZlibArchive中的查找位置和长度相关联。ZlibArchive的所有部分都以编组格式存储,因此与平台无关。
CArchive很像一个.zip文件,可以包含任何类型的文件。可以python创建,也可以从C代码中解包。CArchive可以附加到另一个文件,比如ELF和COFF可执行文件,存档是在文件的末尾用它的目录创建的,后面只跟一个cookie,它告诉目录从哪里开始以及存档本身从哪里开始。CArchive可以嵌入到另一个CArchive中,内部存档可以在适当的地方打开使用,而不需要提取。
每个目录条目都有可变的长度。条目中的第一个字段给出了条目的长度。最后一个字段是相应打包文件的名称。名称以空内容结尾,每一个成员都可以进行压缩。还有一个与每个成员相关联的类型代码供程序使用。
Uncompyle6,是一个Python原生的跨版本反编译器,是decompyle, uncompyle, uncompyle2的后继版本。Uncompyle6能够将Python二进制文件pyc, pyo反编译为对应的源代码。支持 Python 1.0-3.8 版本的反编译,拥有较好的逻辑处理和解析能力。
a = 0 b = 1 c = 2 def fun1 (arg): A= a M= [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] C= [a] * 100 K= [] while arg[A][a] != '12': #print(M) opcode= arg[A][a].lower() opn = arg[A][b:] #print(opn) if opcode== '4': M [opn[a]] = M [opn[b]] + M[opn[c]] elif opcode== '3': M [opn[a]] = M [opn[b]] * M[opn[c]] elif opcode== '5': M [opn[a]] = M [opn[a]] elif opcode== '1': M[opn[a]] = opn[b] elif opcode== '11': M[opn[a]] = C[opn[b]] elif opcode== '6': M[opn[a]] = a elif opcode== '2': C[opn[a]] = input(M[opn[b]]) elif opcode== '10': print(M[opn[a]]) elif opcode== '9': M[7] = a for i in range(len(M[opn[a]])): print( M[opn[a]], M[opn[b]]) if M[opn[a]] != M[opn[b]]: M[7] = b A= M[opn[c]] K.append(A) elif opcode== '7': s= '' for i in range(len(M[opn[a]])): s+= chr(ord(M[opn[a]][i]) ^ M[opn[b]]) print(s) M[opn[a]] = s elif opcode== '8': s= '' for i in range(len(M[opn[a]])): #print(M[opn[a]][i],M[opn[b]]) s+= chr(ord(M[opn[a]][i]) - M[opn[b]]) M[opn[a]] = s A+= b fun1([ [ '1', 0, 'Authentication token: '], [ '2', 0, 0], [ '1', 6, 'á×äÓâæíäàßåÉÛãåäÉÖÓÉäàÓÉÖÓåäÉÓÚÕæïèäßÙÚÉÛÓäàÙÔÉÓâæÉàÓÚÕÓÒÙæäàÉäàßåÉßåÉäàÓÉÚÓáÉ·Ôâ×ÚÕÓÔɳÚÕæïèäßÙÚÉÅä×ÚÔ×æÔÉ×Úïá×ïåÉßÉÔÙÚäÉæÓ×ÜÜïÉà×âÓÉ×ÉÑÙÙÔÉâßÔÉÖãäÉßÉæÓ×ÜÜïÉÓÚÞÙïÉäàßåÉåÙÚÑÉßÉàÙèÓÉïÙãÉáßÜÜÉÓÚÞÙïÉßäÉ×åáÓÜÜ\x97ÉïÙãäãÖÓ\x9aÕÙÛ\x99á×äÕà©â«³£ï²ÕÔÈ·±â¨ë'], [ '1', 2, c ** (3 * c + b) - c ** (c + b)], [ '1', 4, 15], [ '1', 3, b], [ '3', 2, 2, 3], [ '4', c, c, 4], [ '5', a, c], [ '6', 3], [ '7', 6, 3], [ '1', 0, 'Thanks.'], [ '1', 1, 'Authorizing access...'], [ '10', 0], [ '11', a, a], [ '7', a, 2], [ '8', a, 4], [ '1', 5, 19], [ '9', 0, 6, 5], [ '10', 1], [ '12'], [ '1', b, 'Access denied!'], [ '10', 1], [ '12']])
aaa='á×äÓâæíäàßåÉÛãåäÉÖÓÉäàÓÉÖÓåäÉÓÚÕæïèäßÙÚÉÛÓäàÙÔÉÓâæÉàÓÚÕÓÒÙæäàÉäàßåÉßåÉäàÓÉÚÓáÉ·Ôâ×ÚÕÓÔɳÚÕæïèäßÙÚÉÅä×ÚÔ×æÔÉ×Úïá×ïåÉßÉÔÙÚäÉæÓ×ÜÜïÉà×âÓÉ×ÉÑÙÙÔÉâßÔÉÖãäÉßÉæÓ×ÜÜïÉÓÚÞÙïÉäàßåÉåÙÚÑÉßÉàÙèÓÉïÙãÉáßÜÜÉÓÚÞÙïÉßäÉ×åáÓÜÜ\x97ÉïÙãäãÖÓ\x9aÕÙÛ\x99á×äÕà©â«³£ï²ÕÔÈ·±â¨ë' flag='' for i in range(len(aaa)): flag+=chr((ord(aaa[i])+15)^135) print(flag)
watevr{this_must_be_the_best_encryption_method_evr_henceforth_this_is_the_new_Advanced_Encryption_Standard_anyways_i_dont_really_have_a_good_vid_but_i_really_enjoy_this_song_i_hope_you_will_enjoy_it_aswell!_youtube.com/watch?v=E5yFcdPAGv0}
(lambda __operator, __print, __g, __contextlib, __y: [ (lambda __mod: [ [ [ (lambda __items, __after, __sentinel: __y((lambda __this: (lambda : (lambda __i: [ (lambda __out: (lambda __ctx: [__ctx.__enter__(), __ctx.__exit__(None, None, None), __out[0]((lambda : __this()))][2])(__contextlib.nested(type('except', (), {'__enter__': lambda self: None, '__exit__': lambda __self, __exctype, __value, __traceback: __exctype is not None and [ True for __out[0] in [(sys.exit(0), (lambda after: after()))[1]] ][0]})(), type('try', (), {'__enter__': lambda self: None, '__exit__': lambda __self, __exctype, __value, __traceback: [ False for __out[0] in [(v.append(int(word, 16)), (lambda __after: __after()))[1]] ][0]})())))([None]) for __g['word'] in [__i] ][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))))())(iter(p_text.split('_')), (lambda : [ [ [ [ [ [ [ (lambda __after: __y((lambda __this: (lambda : (lambda __target: [ (lambda __target: [ (lambda __target: [ [ __this() for __g['n'] in [__operator.isub(__g['n'], 1)] ][0] for __target.value in [__operator.iadd(__target.value, (y.value << 4) + k[2] ^ y.value + x.value ^ (y.value >> 5) + k[3])] ][0])(z) for __target.value in [__operator.iadd(__target.value, (z.value << 4) + k[0] ^ z.value + x.value ^ (z.value >> 5) + k[1])] ][0])(y) for __target.value in [__operator.iadd(__target.value, u)] ][0])(x) if n > 0 else __after())))())((lambda : [ [ (__print(('').join(map(hex, w)).replace('0x', '').replace('L', '')), None)[1] for w[1] in [z.value] ][0] for w[0] in [y.value] ][0])) for __g['w'] in [[0, 0]] ][0] for __g['n'] in [32] ][0] for __g['u'] in [2654435769] ][0] for __g['x'] in [c_uint32(0)] ][0] for __g['z'] in [c_uint32(v[1])] ][0] for __g['y'] in [c_uint32(v[0])] ][0] for __g['k'] in [[3735928559, 590558003, 19088743, 4275878552]] ][0]), []) for __g['v'] in [[]] ][0] for __g['p_text'] in [raw_input('plain text:\n> ')] ][0] for __g['c_uint32'] in [__mod.c_uint32] ][0])(__import__('ctypes', __g, __g, ('c_uint32', ), 0)) for __g['sys'] in [__import__('sys', __g, __g)] ][0])(__import__('operator', level=0), __import__('__builtin__', level=0).__dict__['print'], globals(), __import__('contextlib', level=0), (lambda f: (lambda x: x(x))((lambda y: f((lambda : y(y)()))))))
打包流程:读取编写好的python项目–>分析其中调用的模块和库,并收集其文件副本(包括python的解释器)–>将副本和python项目文件封装在一个可执行文件中
使用pip安装:pip install pyinstaller
,安装后会在Scripts文件夹下生成pyinstaller.exe"C:\Python311\Scripts\pyinstaller.exe"
,接下来就可以使用该工具将 Python 程序生成 exe 程序了。
def main(): print('hello') print('world') # 增加调用main()函数 if __name__ == '__main__': main()
将pyinstxtractor.py工具与要反编译的main.exe文件放入同一个工作目录下,在当前目录的终端界面输入python pyinstxtractor.py main.exe
,得到main.exe_extracted文件夹。
def main(): print("hellopyz") if __name__ == '__main__': main()
pyd文件类似于DLL, 一般用C/C++语言编译而成, 可用作模块导入Python程序中。pyd文件仅适用于特定版本的Python, 不同版本间互不兼容, 如Python3.8不支持3.7版本的文件。pyd文件用C/C++语言编译而成, 难以被反编译, 在保护Python程序源码上有很好的效果。而且由于使用了C/C++等低级语言, 代码执行效率较高。
#test.py def main(): print("Hello world pyd") #main.py import test test.main() #setup.py:生成pyd文件的主程序 from distutils.core import setup from Cython.Build import cythonize import sys,os,traceback setup( name = 'test', ext_modules = cythonize('test.py'), )
运行后程序目录下会生成一个test.c文件, 包含了pyd文件所用到的C语言代码。将生成的pyd文件重命名为test.pyd(也可以不重命名),运行main.py,就可以看到pyd文件中的结果了。
使用PyInstaller等库打包exe的时候, 先打包main.py作为主程序。即使exe被某个库反编译, 得到的只是主程序main.py, 而pyd文件中的代码是很难被反编译的。
等Visual Studio加载好后,会自动生成pyd文件的初始C语言代码。编写完成后,打开菜单 “生成” -> “生成xxxx”,就可以在项目所在文件夹的\Debug目录下找到生成的pyd文件。使用"调试"菜单中的调试命令,或者按F5键,就可以直接调试pyd文件。
#include <Python.h> /* * Implements an example function. */ PyDoc_STRVAR(pyddd_example_doc, "example(obj, number)\ \ Example function"); PyObject *pyddd_example(PyObject *self, PyObject *args, PyObject *kwargs) { /* Shared references that do not need Py_DECREF before returning. */ PyObject *obj = NULL; int number = 0; /* Parse positional and keyword arguments */ static char* keywords[] = { "obj", "number", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Oi", keywords, &obj, &number)) { return NULL; } /* Function implementation starts here */ if (number < 0) { PyErr_SetObject(PyExc_ValueError, obj); return NULL; /* return NULL indicates error */ } Py_RETURN_NONE; } /* * List of functions to add to pyddd in exec_pyddd(). */ static PyMethodDef pyddd_functions[] = { { "example", (PyCFunction)pyddd_example, METH_VARARGS | METH_KEYWORDS, pyddd_example_doc }, { NULL, NULL, 0, NULL } /* marks end of array */ }; /* * Initialize pyddd. May be called multiple times, so avoid * using static state. */ int exec_pyddd(PyObject *module) { PyModule_AddFunctions(module, pyddd_functions); PyModule_AddStringConstant(module, "__author__", "admin"); PyModule_AddStringConstant(module, "__version__", "1.0.0"); PyModule_AddIntConstant(module, "year", 2023); return 0; /* success */ } /* * Documentation for pyddd. */ PyDoc_STRVAR(pyddd_doc, "The pyddd module"); static PyModuleDef_Slot pyddd_slots[] = { { Py_mod_exec, exec_pyddd }, { 0, NULL } }; static PyModuleDef pyddd_def = { PyModuleDef_HEAD_INIT, "pyddd", pyddd_doc, 0, /* m_size */ NULL, /* m_methods */ pyddd_slots, NULL, /* m_traverse */ NULL, /* m_clear */ NULL, /* m_free */ }; PyMODINIT_FUNC PyInit_pyddd() { return PyModuleDef_Init(&pyddd_def); }
机器码(machine code),学名机器语言指令,有时也被称为原生码(Native Code),是电脑CPU直接读取运行的机器指令(计算机只认识0和1),运行速度最快。
字节码(byte code)是一种包含执行程序、由一序列 OP代码(操作码)/数据对 组成的二进制文件。字节码是一种中间码,它比机器码更抽象,需要直译器转译后才能成为机器码的中间代码。
通常情况下它是已经经过编译,但与特定机器码无关。字节码通常不像源码一样可以让人阅读,而是编码后的数值常量、引用、指令等构成的序列。字节码主要为了实现特定软件运行和软件环境、与硬件环境无关。字节码的实现方式是通过编译器和虚拟机器。编译器将源码编译成字节码,特定平台上的虚拟机器将字节码转译为可以直接执行的指令。
import dis import marshal f=open("printname.pyc","rb") b_data=f.read() f.close() PyCodeObjectData=b_data[8:]#反序列化 #python3版本的需要去除前16个字节,python2版本的需要去除前8个字节 Pyobj=marshal.loads(PyCodeObjectData) dis.dis(Pyobj)
def main(): a = 40 b = 57 b = b + 3 c = a + b print(chr(c)) if __name__ == '__main__': main()
import marshal,dis f=open("kkkk.pyc","rb").read()#注意如果不是在当前目录下时要用\\分隔,如"C:\\Users\\admin\\Desktop\\kkkk.pyc" f code = marshal.loads(f[16:]) dis.dis(code) 1 0 LOAD_CONST 0 (<code object main at 0x0000018C4525A0E0, file "kkkk.py", line 1>) 2 LOAD_CONST 1 ('main') 4 MAKE_FUNCTION 0 6 STORE_NAME 0 (main) 8 8 LOAD_NAME 1 (__name__) 10 LOAD_CONST 2 ('__main__') 12 COMPARE_OP 2 (==) 14 POP_JUMP_IF_FALSE 22 9 16 LOAD_NAME 0 (main) 18 CALL_FUNCTION 0 20 POP_TOP >> 22 LOAD_CONST 3 (None) 24 RETURN_VALUE Disassembly of <code object main at 0x0000018C4525A0E0, file "kkkk.py", line 1>: 2 0 LOAD_CONST 1 (40) 2 STORE_FAST 0 (a) 3 4 LOAD_CONST 2 (57) 6 STORE_FAST 1 (b) 4 8 LOAD_FAST 1 (b) 10 LOAD_CONST 3 (3) 12 BINARY_ADD 14 STORE_FAST 1 (b) 5 16 LOAD_FAST 0 (a) 18 LOAD_FAST 1 (b) 20 BINARY_ADD 22 STORE_FAST 2 (c) 6 24 LOAD_GLOBAL 0 (print) 26 LOAD_GLOBAL 1 (chr) 28 LOAD_FAST 2 (c) 30 CALL_FUNCTION 1 32 CALL_FUNCTION 1 34 POP_TOP 36 LOAD_CONST 0 (None) 38 RETURN_VALUE >>> len(code.co_code) 26
图中的部分是有用的,其中有替换和倒序,这样处理一下最后的密文 =1nb0A3b7AUQwB3b84mQ/E0MvJUb+EXbx5TQwF3bt52bAZncsd9c
#!/usr/bin/env python # visit https://tool.lu/pyc/ for more information # Version: Python 3.8 _map = ...... def maze(): x = 1 y = 1 step = input() for i in range(len(step)): if step[i] == 'w': x -= 1 elif step[i] == 's': x += 1 elif step[i] == 'a': y -= 1 elif step[i] == 'd': y += 1 else: return False if None[x][y] == 1: return False if None == 29 and y == 29: return True return None def main(): print('Welcome To VNCTF2022!!!') print('Hello Mr. X, this time your mission is to get out of this maze this time.(FIND THAT 7!)') print('you are still doing the mission alone, this tape will self-destruct in five seconds.') if maze(): print('Congratulation! flag: VNCTF{md5(your input)}') else: print("Sorry, we won't acknowledge the existence of your squad.") if __name__ == '__main__': main()
#出题人的脚本: map = [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1], [1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1], [1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1], [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1], [1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1], [1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1], [1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1], [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1], [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1], [1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], [1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1], [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1], [1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1], [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1], [1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1], [1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], [1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], [1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], [1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], [1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1], [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1], [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1], [1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] usedmap = [[0 for i in range(len(map))] for i in range(len(map)) ] flag = '' def DFS(x, y): global flag if x == len(map) - 2 and y == len(map) - 2: # 31x31的迷宫 终点也就是数组的(29, 29) print(flag) return if map[x + 1][y] == 0 and usedmap[x + 1][y] == 0: # 如果下一步不是墙 且没走过 usedmap[x][y] = 1 # 标记当前坐标走过(不是下一步) flag += 's' DFS(x + 1, y) # 尝试向下走 flag = flag[:-1] # 回溯到这说明这条路不可行 所以去掉's' usedmap[x][y] = 0 # 再设置当前坐标为0 重新找路 if map[x - 1][y] == 0 and usedmap[x - 1][y] == 0: usedmap[x][y] = 1 flag += 'w' DFS(x - 1, y) flag = flag[:-1] usedmap[x][y] = 0 if map[x][y + 1] == 0 and usedmap[x][y + 1] == 0: usedmap[x][y] = 1 flag += 'd' DFS(x, y + 1) flag = flag[:-1] usedmap[x][y] = 0 if map[x][y - 1] == 0 and usedmap[x][y - 1] == 0: usedmap[x][y] = 1 flag += 'a' DFS(x, y - 1) flag = flag[:-1] usedmap[x][y] = 0 print("path:") x = 1 # 设置起始坐标 y = 1 DFS(x, y)
创建好虚拟环境后,会自动跳转到该环境目录下,路径前面有(py38)
。而且系统会默认为新建的虚拟环境配置电脑中安装好的Python环境(只包括部分执行命令,没有相关第三方模块),当然我们也可以在新虚拟环境中重新安装Python。