Parsing file .py, baca AST, modifikasi, lalu tulis kembali kode sumber yang dimodifikasi

168

Saya ingin mengedit kode sumber python secara terprogram. Pada dasarnya saya ingin membaca .pyfile, menghasilkan AST , dan kemudian menulis kembali kode sumber python yang dimodifikasi (yaitu .pyfile lain ).

Ada cara untuk mengurai / mengkompilasi kode sumber python menggunakan modul python standar, seperti astatau compiler. Namun, saya rasa mereka tidak mendukung cara untuk memodifikasi kode sumber (mis. Hapus deklarasi fungsi ini) dan kemudian tulis kembali kode sumber python yang dimodifikasi.

UPDATE: Alasan saya ingin melakukan ini adalah saya ingin menulis perpustakaan pengujian mutasi untuk python, kebanyakan dengan menghapus pernyataan / ekspresi, tes rerunning dan melihat apa yang istirahat.

Rory
sumber
4
Tidak digunakan lagi sejak versi 2.6: Paket compiler telah dihapus dalam Python 3.0.
dfa
1
Apa yang tidak bisa Anda edit sumbernya? Mengapa Anda tidak bisa menulis dekorator?
S.Lott
3
Astaga! Saya ingin membuat penguji mutasi untuk python menggunakan teknik yang sama (khusus membuat plugin hidung), apakah Anda berencana membuka sumbernya?
Ryan
2
@Ryan Ya saya akan membuka sumber apa pun yang saya buat. Kita harus tetap berhubungan dengan ini
Rory
1
Jelas, saya mengirimi Anda email melalui Launchpad.
Ryan

Jawaban:

73

Pythoscope melakukan ini untuk menguji kasus-kasus yang secara otomatis dihasilkan seperti halnya alat 2to3 untuk python 2.6 (itu mengubah sumber python 2.x menjadi sumber python 3.x).

Kedua alat ini menggunakan lib2to3 library yang merupakan implementasi dari mesin python parser / compiler yang dapat menyimpan komentar dalam sumber ketika itu bulat tersandung dari sumber -> AST -> sumber.

Proyek tali dapat memenuhi kebutuhan Anda jika Anda ingin melakukan lebih banyak refactoring seperti transformasi.

The ast modul pilihan lain, dan ada contoh yang lebih tua bagaimana pohon sintaks "unparse" kembali ke dalam kode (menggunakan modul parser). Tetapi astmodul ini lebih berguna ketika melakukan transformasi AST pada kode yang kemudian diubah menjadi objek kode.

Proyek redbaron juga mungkin cocok (ht Xavier Combelle)

Ryan
sumber
5
contoh tidak tetap masih dipertahankan, berikut adalah versi py3k yang diperbarui: hg.python.org/cpython/log/tip/Tools/parser/unparse.py
Janus Troelsen
2
Berkenaan dengan unparse.pyskrip - mungkin sangat sulit untuk menggunakannya dari skrip lain. Tapi, ada paket yang disebut astunparse ( di github , di pypi ) yang pada dasarnya merupakan versi paket yang benar unparse.py.
mbdevpl
Bisakah Anda memperbarui jawaban dengan menambahkan parso sebagai opsi yang disukai? Sangat bagus dan diperbarui.
kotak
59

Modul bawaan sepertinya tidak memiliki metode untuk mengkonversi kembali ke sumber. Namun, modul codegen di sini menyediakan printer cantik untuk ast yang memungkinkan Anda melakukannya. misalnya.

import ast
import codegen

expr="""
def foo():
   print("hello world")
"""
p=ast.parse(expr)

p.body[0].body = [ ast.parse("return 42").body[0] ] # Replace function body with "return 42"

print(codegen.to_source(p))

Ini akan mencetak:

def foo():
    return 42

Perhatikan bahwa Anda mungkin kehilangan format dan komentar yang tepat, karena ini tidak dipertahankan.

Namun, Anda mungkin tidak perlu melakukannya. Jika semua yang Anda butuhkan adalah menjalankan AST yang diganti, Anda dapat melakukannya hanya dengan memanggil kompilasi () pada ast, dan mengeksekusi objek kode yang dihasilkan.

Brian
sumber
20
Hanya untuk siapa saja yang menggunakan ini di masa depan, codegen sebagian besar sudah ketinggalan zaman dan memiliki beberapa bug. Saya sudah memperbaiki beberapa dari mereka; Saya memiliki ini sebagai intisari pada github: gist.github.com/791312
mattbasta
Perhatikan codegen terbaru diperbarui pada 2012 yang setelah komentar di atas, jadi saya kira codegen diperbarui. @mattbasta
zjffdu
4
astor tampaknya merupakan penerus yang dipertahankan untuk codegen
medmund
20

Dalam jawaban yang berbeda saya menyarankan menggunakan astorpaket, tetapi sejak itu saya menemukan paket AST un-parsing yang lebih mutakhir bernama astunparse:

>>> import ast
>>> import astunparse
>>> print(astunparse.unparse(ast.parse('def foo(x): return 2 * x')))


def foo(x):
    return (2 * x)

Saya telah menguji ini di Python 3.5.

argentpepper
sumber
19

Anda mungkin tidak perlu membuat ulang kode sumber. Itu agak berbahaya bagi saya untuk mengatakan, tentu saja, karena Anda belum benar-benar menjelaskan mengapa Anda pikir Anda perlu menghasilkan file .py penuh kode; tapi:

  • Jika Anda ingin membuat file .py yang sebenarnya akan digunakan orang, mungkin sehingga mereka dapat mengisi formulir dan mendapatkan file .py yang berguna untuk dimasukkan ke dalam proyek mereka, maka Anda tidak ingin mengubahnya menjadi AST dan kembali karena Anda akan kehilangan semua pemformatan (pikirkan baris kosong yang membuat Python begitu mudah dibaca dengan mengelompokkan kumpulan garis yang terkait bersama-sama) ( komentar yang dimiliki node linenodan col_offsetatribut ). Sebagai gantinya, Anda mungkin ingin menggunakan mesin templating ( bahasa template Django , misalnya, dirancang untuk membuat templating bahkan file teks mudah) untuk menyesuaikan file .py, atau menggunakan ekstensi MetaPython Rick Copeland .

  • Jika Anda mencoba membuat perubahan selama kompilasi modul, perhatikan bahwa Anda tidak harus kembali ke teks; Anda bisa langsung mengkompilasi AST alih-alih mengubahnya kembali menjadi file .py.

  • Tetapi di hampir semua kasus, Anda mungkin mencoba melakukan sesuatu yang dinamis yang membuat bahasa seperti Python menjadi sangat mudah, tanpa menulis file .py baru! Jika Anda memperluas pertanyaan untuk memberi tahu kami apa yang sebenarnya ingin Anda capai, file .py baru mungkin tidak akan terlibat dalam jawaban sama sekali; Saya telah melihat ratusan proyek Python melakukan ratusan hal di dunia nyata, dan tidak satu pun dari mereka yang perlu membuat file .py. Jadi, saya harus akui, saya agak skeptis bahwa Anda telah menemukan kasus penggunaan pertama yang bagus. :-)

Perbarui: sekarang setelah Anda menjelaskan apa yang Anda coba lakukan, saya akan tergoda untuk hanya beroperasi di AST. Anda ingin bermutasi dengan menghapus, bukan baris file (yang bisa menghasilkan setengah pernyataan yang mati dengan SyntaxError), tetapi seluruh pernyataan - dan tempat apa yang lebih baik untuk melakukan itu daripada di AST?

Brandon Rhodes
sumber
Tinjauan yang baik tentang kemungkinan solusi dan kemungkinan alternatif.
Ryan
1
Kasus penggunaan dunia nyata untuk pembuatan kode: Kid dan Genshi (saya percaya) menghasilkan Python dari templat XML untuk rendering cepat halaman dinamis.
Rick Copeland
10

Mem-parsing dan memodifikasi struktur kode tentu dimungkinkan dengan bantuan astmodul dan saya akan menunjukkannya dalam sebuah contoh sebentar lagi. Namun, menulis kembali kode sumber yang dimodifikasi tidak dimungkinkan hanya dengan astmodul. Ada modul lain yang tersedia untuk pekerjaan ini seperti yang ada di sini .

CATATAN: Contoh di bawah ini dapat diperlakukan sebagai tutorial pengantar tentang penggunaan astmodul tetapi panduan yang lebih komprehensif tentang penggunaan astmodul tersedia di sini di tutorial ular Pohon Hijau dan dokumentasi resmi tentang astmodul .

Pengantar ast:

>>> import ast
>>> tree = ast.parse("print 'Hello Python!!'")
>>> exec(compile(tree, filename="<ast>", mode="exec"))
Hello Python!!

Anda dapat menguraikan kode python (direpresentasikan dalam string) hanya dengan memanggil API ast.parse(). Ini mengembalikan pegangan ke struktur Pohon Sintaksis Abstrak (AST). Menariknya Anda dapat mengkompilasi kembali struktur ini dan menjalankannya seperti yang ditunjukkan di atas.

API lain yang sangat berguna adalah ast.dump()yang membuang seluruh AST dalam bentuk string. Ini dapat digunakan untuk memeriksa struktur pohon dan sangat membantu dalam debugging. Sebagai contoh,

Pada Python 2.7:

>>> import ast
>>> tree = ast.parse("print 'Hello Python!!'")
>>> ast.dump(tree)
"Module(body=[Print(dest=None, values=[Str(s='Hello Python!!')], nl=True)])"

Pada Python 3.5:

>>> import ast
>>> tree = ast.parse("print ('Hello Python!!')")
>>> ast.dump(tree)
"Module(body=[Expr(value=Call(func=Name(id='print', ctx=Load()), args=[Str(s='Hello Python!!')], keywords=[]))])"

Perhatikan perbedaan sintaks untuk pernyataan cetak dalam Python 2.7 vs Python 3.5 dan perbedaan jenis AST node di pohon masing-masing.


Cara memodifikasi kode menggunakan ast:

Sekarang, mari kita lihat contoh modifikasi kode python oleh astmodul. Alat utama untuk memodifikasi struktur AST adalah ast.NodeTransformerkelas. Setiap kali seseorang perlu memodifikasi AST, dia perlu subkelas darinya dan menulis Node Transformation (s) yang sesuai.

Sebagai contoh kita, mari kita coba menulis sebuah utilitas sederhana yang mengubah Python 2, mencetak pernyataan ke panggilan fungsi Python 3.

Pernyataan cetak ke Utilitas konverter panggilan menyenangkan: print2to3.py:

#!/usr/bin/env python
'''
This utility converts the python (2.7) statements to Python 3 alike function calls before running the code.

USAGE:
     python print2to3.py <filename>
'''
import ast
import sys

class P2to3(ast.NodeTransformer):
    def visit_Print(self, node):
        new_node = ast.Expr(value=ast.Call(func=ast.Name(id='print', ctx=ast.Load()),
            args=node.values,
            keywords=[], starargs=None, kwargs=None))
        ast.copy_location(new_node, node)
        return new_node

def main(filename=None):
    if not filename:
        return

    with open(filename, 'r') as fp:
        data = fp.readlines()
    data = ''.join(data)
    tree = ast.parse(data)

    print "Converting python 2 print statements to Python 3 function calls"
    print "-" * 35
    P2to3().visit(tree)
    ast.fix_missing_locations(tree)
    # print ast.dump(tree)

    exec(compile(tree, filename="p23", mode="exec"))

if __name__ == '__main__':
    if len(sys.argv) <=1:
        print ("\nUSAGE:\n\t print2to3.py <filename>")
        sys.exit(1)
    else:
        main(sys.argv[1])

Utilitas ini dapat dicoba pada file contoh kecil, seperti yang di bawah ini, dan itu akan berfungsi dengan baik.

File Input Uji: py2.py

class A(object):
    def __init__(self):
        pass

def good():
    print "I am good"

main = good

if __name__ == '__main__':
    print "I am in main"
    main()

Harap dicatat bahwa transformasi di atas hanya untuk asttujuan tutorial dan dalam skenario nyata seseorang harus melihat semua skenario yang berbeda seperti print " x is %s" % ("Hello Python").

ViFI
sumber
6

Saya telah membuat baru-baru ini cukup stabil (inti diuji dengan sangat baik) dan sepotong kode yang dapat dikembangkan yang menghasilkan kode dari astpohon: https://github.com/paluh/code-formatter .

Saya menggunakan proyek saya sebagai basis untuk plugin vim kecil (yang saya gunakan setiap hari), jadi tujuan saya adalah untuk menghasilkan kode python yang benar-benar bagus dan dapat dibaca.

PS Saya sudah mencoba untuk memperluas codegentetapi arsitekturnya didasarkan pada ast.NodeVisitorantarmuka, jadi formatters ( visitor_metode) hanyalah fungsi. Saya telah menemukan struktur ini sangat terbatas dan sulit untuk dioptimalkan (dalam kasus ekspresi panjang dan bersarang lebih mudah untuk menjaga pohon objek dan cache beberapa hasil parsial - dengan cara lain Anda dapat menekan kompleksitas eksponensial jika Anda ingin mencari tata letak terbaik). TETAPI codegen karena setiap karya mitsuhiko (yang saya baca) ditulis dengan sangat baik dan ringkas.

paluh
sumber
4

Salah satu jawaban lain merekomendasikan codegen, yang tampaknya telah digantikan oleh astor. Versi astorpada PyPI (versi 0.5 pada tulisan ini) tampaknya agak ketinggalan jaman juga, sehingga Anda dapat menginstal versi pengembangan astorsebagai berikut.

pip install git+https://github.com/berkerpeksag/astor.git#egg=astor

Kemudian Anda dapat menggunakan astor.to_sourceuntuk mengonversi Python AST ke kode sumber Python yang dapat dibaca manusia:

>>> import ast
>>> import astor
>>> print(astor.to_source(ast.parse('def foo(x): return 2 * x')))
def foo(x):
    return 2 * x

Saya telah menguji ini di Python 3.5.

argentpepper
sumber
4

Jika Anda melihat ini pada 2019, maka Anda dapat menggunakan paket libcst ini . Ini memiliki sintaks mirip dengan ast. Ini berfungsi seperti pesona, dan melestarikan struktur kode. Ini pada dasarnya bermanfaat untuk proyek di mana Anda harus menyimpan komentar, spasi, baris baru dll.

Jika Anda tidak perlu peduli dengan komentar yang melestarikan, spasi putih, dan lainnya, maka kombinasi ast dan astor berfungsi dengan baik.

Saurav Gharti
sumber
2

Kami memiliki kebutuhan serupa, yang tidak diselesaikan oleh jawaban lain di sini. Jadi kami membuat perpustakaan untuk ini, ASTTokens , yang mengambil pohon AST yang diproduksi dengan modul ast atau astroid , dan menandainya dengan rentang teks dalam kode sumber asli.

Itu tidak melakukan modifikasi kode secara langsung, tetapi itu tidak sulit untuk ditambahkan di atas, karena itu memberi tahu Anda kisaran teks yang perlu Anda modifikasi.

Misalnya, ini membungkus panggilan fungsi WRAP(...), menjaga komentar dan yang lainnya:

example = """
def foo(): # Test
  '''My func'''
  log("hello world")  # Print
"""

import ast, asttokens
atok = asttokens.ASTTokens(example, parse=True)

call = next(n for n in ast.walk(atok.tree) if isinstance(n, ast.Call))
start, end = atok.get_text_range(call)
print(atok.text[:start] + ('WRAP(%s)' % atok.text[start:end])  + atok.text[end:])

Menghasilkan:

def foo(): # Test
  '''My func'''
  WRAP(log("hello world"))  # Print

Semoga ini membantu!

DS.
sumber
1

Sebuah Program Transformasi Sistem adalah alat yang mem-parsing teks sumber, membangun AST, memungkinkan Anda untuk mengubah mereka menggunakan sumber-to-sumber transformasi ( "jika melihat pola ini, menggantinya dengan pola yang"). Alat-alat seperti itu ideal untuk melakukan mutasi kode sumber yang ada, yang hanya "jika Anda melihat pola ini, ganti dengan varian pola".

Tentu saja, Anda memerlukan mesin program transformasi yang dapat mengurai bahasa yang menarik bagi Anda, dan masih melakukan transformasi yang diarahkan pola. Perangkat Rekayasa Ulang Perangkat Lunak DMS kami adalah sistem yang dapat melakukan itu, dan menangani Python, serta berbagai bahasa lainnya.

Lihat jawaban SO ini untuk contoh AST parsing DMS untuk Python menangkap komentar secara akurat. DMS dapat membuat perubahan pada AST, dan membuat ulang teks yang valid, termasuk komentar. Anda dapat memintanya untuk mencetak awal AST, menggunakan konvensi pemformatan sendiri (Anda dapat mengubahnya), atau melakukan "pencetakan kesetiaan", yang menggunakan informasi baris dan kolom asli untuk secara maksimal mempertahankan tata letak asli (beberapa perubahan dalam tata letak tempat kode baru dimasukkan tidak dapat dihindari).

Untuk menerapkan aturan "mutasi" untuk Python dengan DMS, Anda bisa menulis yang berikut:

rule mutate_addition(s:sum, p:product):sum->sum =
  " \s + \p " -> " \s - \p"
 if mutate_this_place(s);

Aturan ini menggantikan "+" dengan "-" dengan cara yang benar secara sintaksis; ini beroperasi di AST dan karenanya tidak akan menyentuh string atau komentar yang kelihatannya benar. Kondisi tambahan pada "mutate_this_place" adalah membiarkan Anda mengontrol seberapa sering ini terjadi; Anda tidak ingin bermutasi di setiap tempat dalam program.

Anda tentu ingin lebih banyak aturan seperti ini yang mendeteksi berbagai struktur kode, dan menggantinya dengan versi yang dimutasi. DMS senang menerapkan seperangkat aturan. AST yang termutasi kemudian dicetak ulang.

Ira Baxter
sumber
Saya belum melihat jawaban ini dalam 4 tahun. Wow, sudah beberapa kali diturunkan. Itu benar-benar menakjubkan, karena menjawab pertanyaan OP secara langsung, dan bahkan menunjukkan bagaimana melakukan mutasi yang ingin ia lakukan. Saya kira para downvoters tidak akan mau menjelaskan mengapa mereka downvot.
Ira Baxter
4
Karena mempromosikan alat sumber tertutup yang sangat mahal.
Zoran Pavlovic
@ZoranPavlovic: Jadi Anda tidak keberatan dengan akurasi teknis atau kegunaannya?
Ira Baxter
2
@Zoran: Dia tidak mengatakan dia punya perpustakaan sumber terbuka. Dia mengatakan dia ingin memodifikasi kode sumber Python (menggunakan AST), dan solusi yang bisa dia temukan tidak melakukan itu. Ini adalah solusi. Anda tidak berpikir orang menggunakan alat komersial pada program yang ditulis dalam bahasa seperti Python di Jawa?
Ira Baxter
1
Saya bukan pemilih, tetapi posnya sedikit mirip iklan. Untuk meningkatkan jawabannya, Anda dapat mengungkapkan bahwa Anda berafiliasi dengan produk
wim
0

Saya dulu menggunakan baron untuk ini, tetapi sekarang telah beralih ke parso karena itu up to date dengan python modern. Ini bekerja dengan baik.

Saya juga membutuhkan ini untuk tester mutasi. Sangat sederhana untuk membuatnya dengan parso, periksa kode saya di https://github.com/boxed/mutmut

kemas
sumber