談到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