欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

python编译&反编译,你不知道的心机与陷阱

程序员文章站 2022-05-17 21:02:06
...

 谈到python的文件后缀,说眼花缭乱也不为过.来看看你遇到过哪些类型!

.py      

如果这个不知道,呵呵…那请出门左拐,你还是充钱那个少年,没有一丝丝改变。接着打游戏去吧…

.pyc      

这个后缀应该算是除了python的py代码外,遇到最多的一种文件类型了。虽然python被普遍认为是一种解释性语言,但谁说它就不能被编译后执行呢?python通过compile生成的pyc文件,然后由python的虚拟机执行。相对于py文件来说,编译成pyc本质上和py没有太大区别,只是对于这个模块的加载速度提高了,并没有提高代码的执行速度,通常情况下不用主动去编译pyc文件。那pyc文件存在的意义在哪里?
除了略微提高的模块加载速度外,更多的当然是为了一定意义上的保护源码不被泄露了。
当然为什么说一定意义?因为既然有编译,就毕竟存在反编译喽,这个一会儿说…
如何将py文件编译成pyc文件呢?方法有三:

1.使用python直接编译

python -m sample.py

 2. py_compile

import py_compile
py_compile.compile('sample.py')

3. compileall

import compileall
compileall.compile_file('sample.py')
compileall.compile_dir(dirpath)

三种方式一看便知,区别在于compileall可以一次递归编译文件夹下所有的py文件

.pyo      

pyo是源文件优化编译后的文件,pyo文件在大小上,一般小于等于pyc文件。这个优化没有多大作用,只是移除了断言。原文如下:

When the Python interpreter is invoked with the -O flag, optimized code is generated and stored in .pyo files. The optimizer currently doesn’t help much; it only removes assert statements. When -O is used, all bytecode is optimized; .pyc files are ignored and .py files are compiled to optimized bytecode.

使用方式:python -O -m py_compile sample.py

.pyd    

看到了前几种,相信d这个字母大家猜猜就能想到,它是python的动态链接库;动态链接库(DLL)文件是一种可执行文件,允许程序共享执行特殊任务所必需的代码和其他资源;你在python安装目录的DLL文件夹下,就可以看到它们了。

.pyz(w)

从Python 3.5开始,定义了.pyz和.pyzw分别作为“Python Zip应用”和“Windows下Python Zip应用”的扩展名。
新增了内置zipapp模块来进行简单的管理,可以用Zip打包Python程序到一个可执行.pyz文件。
使用也比较简单,但无法想pyinstaller等打包exe工具一样脱离python环境单独运行,而且这类文件往往拿文本编辑器就能看到它的源码信息,总体来说还是比较鸡肋。

.exe      

为什么会再单独提到exe文件,因为python有很多可以将python源码打包成exe的工具,从而脱离python环境单独运行。感兴趣的朋友可以去看看我之前总结的文章:Python打包工具--Pyinstaller详细介绍

python反编译

所谓道高一尺魔高一丈,不管是python、java还是C语言,只要有编译,必然存在反编译的操作。不管再怎么去考虑加密,势必都有人能**,只是成本不同而已,所以编译只可作为一种防君子不防小人的操作。今天主要和大家聊聊pyc文件的反编译,其中有哪些小心机的做法与陷阱。
首先,反编译既然是一种非正常手段,python自然不会原生附带该模块,我们需要安装它:
pip install uncompyle6
操作呢,也比较简单命令行下输入:
uncompyle6 sample.pyc(pyo) > sample.py
这就完了?说好的心机与陷阱呢?哈哈,接着看…

使用注释的心机

我们来创建一个名为BreezePython.py的文件,并将下面的代码进行编译和反编译,来看看反编译后的内容与原始代码有什么差异

# -*- coding: utf-8 -*-
# @Author : 王翔
# @微信号 : King_Uranus
# @公众号 : 清风Python
# @GitHub : https://github.com/BreezePython
# @Date : 2019/12/15 11:16
# @Software : PyCharm
# @version :Python 3.7.3
# @File : tmp.py

import platform

def test():
    """
    这是一个测试方法,用来验证反编译
    :return: None
    """
    # 打印系统详情
    print(platform.platform())

test()

编译:

python -m BreezePython.py

反编译:
uncompyle6 BreezePython.cpython-37.pyc > output.py
下来看看反编译后的文件有什么区别吧:

# uncompyle6 version 3.6.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: C:\Users\Administrator\Desktop\uncompile\BreezePython.py
# Size of source mod 2**32: 453 bytes
import platform

def test():
    """
    这是一个测试方法,用来验证反编译
    :return: None
    """
    print(platform.platform())


test()
# okay decompiling BreezePython.cpython-37.pyc

相比于原始的代码,有什么差别?没错,注释…文档注释默认会保留,但使用# 进行的注释,却在编译+反编译的过程中丢失了。那么如果你是个心机boy,代码注释都用#去写吧,即便反编译了代码,没有注释的情况下,即便我们自己也会被大段没有注释的代码搞疯掉了,哈哈。

反编译的陷阱

反编译就万无一失么?NO…举一个例子吧,这段代码随意而为,只是为了让反编译的时候抛出错误:

class Demo:
    def __init__(self, color):
        self.color = color

    def jduge(self):
        if not self.color:
            return "get None"
        if not self.color == 'red':
            mood = "disappointed"
            return "I'm %s,I like white best" % mood
        return "get None..."


if __name__ == '__main__':
    Main = Demo("red")
    print(Main.jduge())

正常情况这段代码应该返回:get None…
但这段代码反编译后成了什么样子?

# uncompyle6 version 3.6.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: C:\Users\Administrator\Desktop\uncompile\BreezePython.py
# Size of source mod 2**32: 391 bytes

class Demo:
    def __init__(self, color):
        self.color = color
    def jduge(self):
        if not self.color:
            return 'get None'
        else:
            mood = self.color == 'red' or 'disappointed'
            return "I'm %s,I like white best" % mood
        return 'get None...'

if __name__ == '__main__':
    Main = Demo('red')
    print(Main.jduge())
# okay decompiling __pycache__\BreezePython.cpython-37.pyc

编译后的代码返回:I'm True,I like white best
什么鬼?这编译成什么了乱七八糟的了东西了。京剧多年采坑经验,反编译的代码,当遇到多个if not 的时候,最后一个if not就会被所谓的简要写法弄的变了为了,导致最终反编译出错。这个是个坑,同样你是否也可以利用它呢?

今天的文章就到这里,希望通过这篇文章能让你对python的文件类型与编译、反编译操作有所了解。

作者:华为云特约供稿开发者 清风Python

相关标签: 技术交流