Bir .py dosyasını ayrıştırın, AST'yi okuyun, değiştirin, sonra değiştirilmiş kaynak kodunu geri yazın


168

Programlı olarak python kaynak kodunu düzenlemek istiyorum. Temelde bir .pydosyayı okumak , AST oluşturmak ve daha sonra değiştirilmiş python kaynak kodunu (yani başka bir .pydosya) yazmak istiyorum.

astVeya gibi standart python modüllerini kullanarak python kaynak kodunu ayrıştırmanın / derlemenin yolları vardır compiler. Ancak, bunların hiçbirinin kaynak kodunu değiştirme yollarını desteklediğini düşünmüyorum (örneğin, bu işlev bildirimini silin) ​​ve daha sonra değiştiren python kaynak kodunu geri yazın.

GÜNCELLEME: Bunu yapmak istememin nedeni , çoğunlukla ifadeleri / ifadeleri silerek, testleri yeniden çalıştırarak ve nelerin kırıldığını görerek python için bir Mutasyon test kütüphanesi yazmak istiyorum .


4
2.6 sürümünden beri kullanımdan kaldırıldı: Derleyici paketi Python 3.0'da kaldırıldı.
dfa

1
Kaynağı ne düzenleyemezsiniz? Neden dekoratör yazamıyorsun?
S.Lott

3
Kutsal inek! Aynı tekniği (özellikle burun eklentisi yaratarak) kullanarak python için bir mutasyon test cihazı yapmak istedim, açık kaynak yapmayı planlıyor musunuz?
Ryan

2
@Ryan Evet, oluşturduğum her şeyi açık kaynak yapacağım. Bu konuda temas halinde olmalıyız
Rory

1
Kesinlikle, size Launchpad üzerinden bir e-posta gönderdim.
Ryan

Yanıtlar:


73

Pythoscope bunu , python 2.6 için 2to3 aracı gibi otomatik olarak oluşturduğu test durumlarına yapar (python 2.x kaynağını python 3.x kaynağına dönüştürür).

Her iki araç da , kaynak -> AST -> kaynağından açıldığında kaynaktaki yorumları koruyabilen python ayrıştırıcı / derleyici makinelerinin bir uygulaması olan lib2to3 kütüphanesini kullanır .

Halat projesi Daha fazla dönüşümler gibi üstlenmeden yapmak istiyorsanız ihtiyaçlarınızı karşılayabilir.

Ast modül Diğer seçenek olduğunu ve koduna geri nasıl "unparse" sözdizimi ağaçlar eski bir örneği var (ayrıştırıcı modülü kullanılarak). Ancak astmodül daha sonra bir kod nesnesine dönüştürülen bir AST dönüşümü yaparken daha kullanışlıdır.

Redbaron Proje ayrıca bir uygun olabilecek (ht Xavier Combelle)


5
unparse örneği hala korunuyor, güncellenmiş py3k sürümü: hg.python.org/cpython/log/tip/Tools/parser/unparse.py
Janus Troelsen

2
unparse.pyKomut dosyası ile ilgili olarak - başka bir komut dosyasından kullanmak gerçekten hantal olabilir. Ama, (astunparse adında bir paket var github , pypi üzerinde temelde bir düzgün paketlenmiş sürümüdür) unparse.py.
mbdevpl

Tercih edilen seçenek olarak parso ekleyerek cevabınızı güncelleyebilir misiniz? Çok iyi ve güncel.
kutulu

59

Yerleşik ast modülünün kaynağa geri dönüşümü için bir yöntemi yok gibi görünüyor. Ancak, burada kodgen modülü ast için bunu yapmanızı sağlayacak güzel bir yazıcı sağlar. Örneğin.

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))

Bu yazdırılacak:

def foo():
    return 42

Bunlar korunmadığından biçimlendirmeyi ve yorumları tam olarak kaybedebileceğinizi unutmayın.

Ancak, gerekmeyebilir. İhtiyacınız olan tek şey değiştirilen AST'yi çalıştırmaksa, bunu ast üzerinde compile () öğesini çağırarak ve sonuçta ortaya çıkan kod nesnesini çalıştırarak yapabilirsiniz.


20
Gelecekte bunu kullanan herkes için, kodgen büyük ölçüde güncel değildir ve birkaç hata vardır. Birkaç tanesini düzelttim; Bunu github'da bir
gist olarak görüyorum

En son codegen yukarıdaki yorumdan sonra olan 2012 yılında güncellenen dikkat edin, bu yüzden codegen güncellendi sanırım. @mattbasta
zjffdu


20

Farklı bir cevapta astorpaketi kullanmayı önerdim, ancak o zamandan beri daha güncel bir AST çözümleme paketi buldum astunparse:

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


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

Bunu Python 3.5 üzerinde test ettim.


19

Kaynak kodu yeniden oluşturmanız gerekmeyebilir. Tabii ki, benim için biraz tehlikeli çünkü kodla neden bir .py dosyası oluşturmanız gerektiğini düşündüğünüzü açıklamıyorsunuz; fakat:

  • İnsanların gerçekten kullanacakları bir .py dosyası oluşturmak istiyorsanız, belki bir formu doldurabilir ve projelerine eklemek için yararlı bir .py dosyası alabilirler, o zaman bir AST'ye değiştirmek istemezsiniz ve çünkü tüm biçimlendirmeyi kaybedersiniz (ilgili satır kümelerini gruplandırarak Python'u bu kadar okunabilir yapan boş satırları düşünün) ( ast düğümleri linenove col_offsetöznitelikleri vardır ) yorumlar. Bunun yerine, muhtemelen .py dosyasını özelleştirmek için şablonlama motoru ( örneğin, Django şablon dili , metin dosyalarını bile şablonlamayı kolaylaştırmak için tasarlanmıştır) kullanmak veya Rick Copeland'ın MetaPython uzantısını kullanmak isteyeceksiniz .

  • Bir modülün derlenmesi sırasında bir değişiklik yapmaya çalışıyorsanız, metne kadar geri gitmeniz gerekmediğini unutmayın; AST'yi bir .py dosyasına dönüştürmek yerine doğrudan derleyebilirsiniz.

  • Ancak hemen hemen her durumda, muhtemelen yeni .py dosyaları yazmadan Python gibi bir dilin gerçekten çok kolaylaştığı dinamik bir şey yapmaya çalışıyorsunuz! Sorunuzu, aslında neyi başarmak istediğinizi bize bildirecek şekilde genişletirseniz, yeni .py dosyaları büyük olasılıkla cevaba katılmayacaktır; Yüzlerce Python projesinin yüzlerce gerçek dünya işi yaptığını gördüm ve bir .py dosyası yazmak için tek bir tane bile gerekmedi. Yani, itiraf etmeliyim ki, ilk iyi kullanım durumunu bulduğunuza dair bir şüpheciyim. :-)

Güncelleme: Şimdi ne yapmaya çalıştığınızı açıkladığınıza göre, zaten AST üzerinde çalışmaya karar verirdim. Bir dosyanın satırlarını değil, (yalnızca bir SyntaxError ile ölen yarım ifadelere neden olabilir), ancak tüm ifadeleri kaldırarak mutasyon yapmak isteyeceksiniz ve bunu yapmak için AST'den daha iyi bir yer var mı?


Olası çözüme ve olası alternatiflere iyi bir genel bakış.
Ryan

1
Kod üretimi için gerçek dünya kullanım durumu: Kid ve Genshi (sanırım) dinamik sayfaların hızlı bir şekilde oluşturulması için XML şablonlarından Python oluşturuyor.
Rick Copeland

10

astModül yardımıyla kod yapısının ayrıştırılması ve değiştirilmesi kesinlikle mümkündür ve bir örnekte bir örnekte göstereceğim. Ancak, değiştirilen kaynak kodunu astyalnızca modülle yazmak mümkün değildir . Bu iş için burada bulunan gibi başka modüller de var .

NOT: Aşağıdaki örnek, astmodülün kullanımı ile ilgili giriş niteliğinde bir öğretici olarak ele alınabilir ancak modül kullanımıyla ilgili daha kapsamlı bir kılavuz astburada Green Tree yılanları öğreticisinde ve modülle ilgili resmi belgelerde bulunabilirast .

Giriş ast:

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

Sadece API'yı çağırarak python kodunu (dizede temsil edilen) ayrıştırabilirsiniz ast.parse(). Bu, tutamacı Soyut Sözdizimi Ağacı (AST) yapısına döndürür. İlginçtir, bu yapıyı derleyebilir ve yukarıda gösterildiği gibi yürütebilirsiniz.

Bir başka çok faydalı API, ast.dump()AST'nin tamamını bir dize formunda döker. Ağaç yapısını incelemek için kullanılabilir ve hata ayıklamada çok faydalıdır. Örneğin,

Python 2.7'de:

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

Python 3.5'te:

>>> 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=[]))])"

Python 2.7 ile Python 3.5 arasındaki baskı ifadesi için sözdizimindeki farka ve ilgili ağaçlardaki AST düğümü tipindeki farka dikkat edin.


Aşağıdakileri kullanarak kod nasıl değiştirilir ast:

Şimdi, python kodunun astmodüle göre değiştirilmesine bir örnek verelim . AST yapısını değiştirmek için ana araç ast.NodeTransformersınıftır. AST'yi değiştirmek gerektiğinde, alt sınıftan alması ve buna göre Düğüm Dönüşümlerini yazması gerekir.

Örneğimiz için, Python 2, print ifadelerini Python 3 fonksiyon çağrılarına dönüştüren basit bir yardımcı program yazmaya çalışalım.

Fun call dönüştürücü yardımcı programına deyimi yazdır: 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])

Bu yardımcı program, aşağıdaki gibi küçük bir örnek dosyada denenebilir ve iyi çalışmalıdır.

Test Giriş dosyası: 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()

Yukarıdaki dönüşümün yalnızca asteğitim amaçlı olduğunu ve gerçek durumda senaryo gibi tüm farklı senaryolara bakılması gerektiğini lütfen unutmayın print " x is %s" % ("Hello Python").


6

Son zamanlarda oldukça kararlı (çekirdek gerçekten iyi test edilmiştir) ve astağaçtan kod üreten genişletilebilir kod parçası oluşturdum : https://github.com/paluh/code-formatter .

Projemi küçük bir vim eklentisi (her gün kullanıyorum) için bir temel olarak kullanıyorum, bu yüzden hedefim gerçekten güzel ve okunabilir python kodu oluşturmak.

PS Uzatmaya çalıştım codegenama mimarisi ast.NodeVisitorarayüze dayanıyor , bu yüzden formatlayıcılar ( visitor_yöntemler) sadece işlevler. Bu yapıyı oldukça sınırlayıcı ve optimize edilmesi zor buldum (uzun ve iç içe ifadeler durumunda, nesneleri ağaç tutmak ve bazı kısmi sonuçları önbelleğe almak daha kolaydır - başka bir şekilde, en iyi düzeni aramak istiyorsanız üstel karmaşıklığı vurabilirsiniz). AMA codegen mitsuhiko'nun (okuduğum) her parçası çok iyi yazılmış ve özlü.


4

Diğer yanıtlardan biri önerir codegentarafından yeri alınmış bir tavır sergileyen astor. Sürümü astorPyPI üzerinde (bu yazının yazıldığı gibi sürümü 0.5) Eğer geliştirme sürümünü yüklemek böylece biraz yanı modası geçmiş gibi görünüyor astoraşağıdaki gibi.

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

Daha sonra astor.to_sourcebir Python AST'yi insan tarafından okunabilir Python kaynak koduna dönüştürmek için kullanabilirsiniz :

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

Bunu Python 3.5 üzerinde test ettim.


4

Buna 2019 yılında bakıyorsanız, bu libcst'i kullanabilirsiniz. paketini . Ast'a benzer bir sözdizimi vardır. Bu bir cazibe gibi çalışır ve kod yapısını korur. Yorumları, boşlukları, yeni satırları vb. Korumanız gereken proje için temel olarak yararlıdır.

Yorumları, boşlukları ve diğerlerini korumakla ilgilenmiyorsanız, ast ve astor kombinasyonu iyi çalışır.


2

Benzer bir ihtiyacımız vardı, bu da diğer cevaplar tarafından çözülmedi. Bunun için ast veya astroid ile üretilen bir AST ağacını alan ASTTokens için bir kütüphane oluşturduk modülleriyle alan ve orijinal kaynak kodundaki metin aralıklarıyla işaretleyen .

Doğrudan kod değişiklikleri yapmaz, ancak üstüne eklemeniz zor değildir, çünkü değiştirmeniz gereken metin aralığını size bildirir.

Örneğin, WRAP(...)yorum ve diğer her şeyi koruyarak bir işlev çağrısını içine alır :

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:])

üretir:

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

Bu yardımcı olur umarım!


1

Bir Program Dönüşüm Sistemi , bir araç olduğunu ayrıştırır kaynak metindir, FBDTÖ inşa siz ( "Bu model görüp, o deseni ile değiştirin") kaynak-kaynağı dönüşümleri kullanarak bunları değiştirmek için izin verir. Bu tür araçlar, sadece "bu modeli görürseniz, bir model varyantı ile değiştir" olan mevcut kaynak kodlarının mutasyonunu yapmak için idealdir.

Tabii ki, ilgilendiğiniz dili ayrıştırabilen ve yine de desene yönelik dönüşümleri yapabilen bir program dönüştürme motoruna ihtiyacınız var. Bizim DMS Yazılım Değişim Mühendisliği Toolkit bunu yapabilir bir sistemdir ve Python ve diğer dillerde çeşitli işler.

Python yorumları doğru şekilde yakalamak için DMS tarafından ayrıştırılmış AST örneği için bu SO cevabına bakınız . DMS, AST'de değişiklik yapabilir ve yorumlar da dahil olmak üzere geçerli metni yeniden oluşturabilir. AST'yi kendi biçimlendirme kurallarını kullanarak yeniden yazdırmasını isteyebilirsiniz (bunları değiştirebilirsiniz) veya orijinal mizanpajı maksimum düzeyde korumak için orijinal satır ve sütun bilgilerini kullanan "aslına uygun yazdırma" yapabilirsiniz (yeni kodun düzeninde bazı değişiklikler) yerleştirilmesi kaçınılmazdır).

DMS ile Python için bir "mutasyon" kuralı uygulamak için aşağıdakileri yazabilirsiniz:

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

Bu kural "+" yerine "-" ifadesini sözdizimsel olarak doğru bir şekilde değiştirir; AST üzerinde çalışır ve böylece doğru görünen dizelere veya yorumlara dokunmaz. "Mutate_this_place" deki ek koşul, bunun ne sıklıkta gerçekleştiğini kontrol etmenizi sağlamaktır; programdaki her yeri değiştirmek istemezsiniz .

Açıkçası çeşitli kod yapılarını algılayan ve bunları mutasyona uğramış sürümlerle değiştiren bir sürü kural daha istersiniz. DMS bir dizi kuralı uygulamaktan mutluluk duyar. Mutasyona uğramış AST daha sonra oldukça basılır.


Bu cevaba 4 yıldır bakmadım. Vay be, birkaç kez reddedildi. Bu gerçekten çarpıcı, çünkü OP'nin sorusunu doğrudan cevaplıyor ve hatta yapmak istediği mutasyonların nasıl yapılacağını gösteriyor. Sanırım hiçbir downvoter neden aşağı indirdiklerini açıklamak isterdi.
Ira Baxter

4
Çünkü çok pahalı, kapalı kaynak bir aracı teşvik ediyor.
Zoran Pavlovic

@ZoranPavlovic: Yani herhangi bir teknik doğruluğuna ya da faydasına itiraz etmiyor musunuz?
Ira Baxter

2
@Zoran: Açık kaynaklı bir kütüphanesi olduğunu söylemedi. Python kaynak kodunu değiştirmek istediğini (AST kullanarak) söyledi ve bulabileceği çözümler bunu yapmadı. Bu böyle bir çözüm. İnsanların Java'da Python gibi dillerde yazılmış programlarda ticari araçlar kullandığını düşünmüyor musunuz?
Ira Baxter

1
Ben aşağı seçmen değilim, ancak yazı biraz reklam gibi okuyor. Cevabı geliştirmek için, ürüne bağlı olduğunuzu açıklayabilirsiniz
wim

0

Bunun için baron kullanırdım, ama şimdi parso'ya geçtim çünkü modern python ile güncel. Harika çalışıyor.

Ayrıca bir mutasyon test cihazı için buna ihtiyacım vardı. Parso ile bir tane yapmak gerçekten çok basit, https://github.com/boxed/mutmut adresindeki koduma göz atın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.