[python] 根据递归定理,打印出自身的程序

引言

fzu可计算性理论作业
来源于《计算性理论导引》(第三版、机械工业出版社)一书中p170练习题6.1
该书中递归定理部分在p157页有介绍,但其本质应该是依靠p155-156的自引用定理
思路就是先做自引用定理的SELF,那么递归定理也就同理了

任务

练习6.1:用一个实际的程序设计语言(或它的一个合理的近似)写一个本质上反映递归定理的例子,它将其自身打印出来)

自引用定理·图灵机SELF构造·人话

SELF分为A、B两部分
A运行完能打印出B的描述
B运行,根据A打印出的、自己的描述再构造出A的描述
B将两部分描述合并为SELF的描述并打印出来

思路

上课看到时第一反应就是利用文件读写,读取自身并再写出来,很简单嘛
实际也确实如此,以python为例,文件名为tm.py:

with open('.\tm.py', 'r', encoding='utf-8') as f:
     print(f.read())

完美

但隐约觉得这样似乎并不符合要求

决定照着自引用定理先实现A、B
想法很单纯,
A.py里用个变量存储B的代码,然后将其写入文件中,即为打印了B的描述
然后B.py里读入A写入的文件,再加上A里的文件读写代码就组装成了A的代码,将其写入文件中,即为打印了A的描述
A.py

descriptor_b = f'''with open('[B]', 'r') as f:
    descriptor_b = f.read()
with open('[A]', 'w') as f:
    f.write(f"descriptor_b = f'''{descriptor_b}'''\nwith open('[B]', 'w') as f:\nf.write(descriptor_b)")
'''
with open('[B]', 'w') as f:
    f.write(descriptor_b)

B.py

with open('[B]', 'r') as f:
    descriptor_b = f.read()
with open('[A]', 'w') as f:
    f.write(f"descriptor_b = f'''{descriptor_b}'''\nwith open('[B]', 'w') as f:\nf.write(descriptor_b)")

然而这样走不通,主要是A.py里三引号'''嵌套引起冲突,这是由于B代码里要组装出A,descriptor_B这个字符串比含有A,也就是自己套了自己,还必须得这样套
当然,换引号也没用

想了很多办法,比如不显式写出''',用char(39)*3在里面计算出来:
A.py

descriptor_b = f'''with open('[B]', 'r') as f:
    descriptor_b = f.read()
with open('[A]', 'w') as f:
    descriptor_b = descriptor_b.replace(
        chr(39)*3, "{{chr(39)*3}}"
    ).replace(
        "{chr(39)*3}", "{{{{chr(39)*3}}}}", 1
    ).replace(
        "{{descriptor_b}}", "{{{{descriptor_b}}}}", 1
    ).replace(r"\\n", r"\\\\n")
    f.write(f"descriptor_b = f{chr(39)*3}{{descriptor_b}}{chr(39)*3}\\nwith open('[B]', 'w') as f:\\n    f.write(descriptor_b)\\n")
'''
with open('[B]', 'w') as f:
    f.write(descriptor_b)

可惜这样还是不行,解决了'''又会因为f里面的大括号{}会被转义而不得不陷入了新的套娃
总是差一点点,一度想要放弃

后来洗了个澡,想到书里这个图灵机的描述也没说就是原原本本对应到代码上
于是打算把B的代码整个编码成字节串再写进A里面,A在写文件前再把它解码为字符串就好了
A.py

descriptor_b = b'with open(\'[B]\', \'r\') as f:\n    descriptor_b = f.read()\nwith open(\'[A]\', \'w\') as f:\n    db1, db2 = descriptor_b.rsplit(\'\\n\', 1)\n    descriptor_b = db1 + \'\\n\' + db2.replace(\'\\n\', \'#\')\n    descriptor_b = descriptor_b.encode()\n    f.write(f"descriptor_b = {descriptor_b}\\nwith open(\'[B]\', \'w\') as f:\\n    db1, db2 = descriptor_b.decode().rsplit(\'\\\\n\', 1)\\n    descriptor_b = db1 + \'\\\\n\' + db2.replace(\'#\', r\'\\\\n\')\\n    f.write(descriptor_b)\\n")\n'
with open('[B]', 'w') as f:
    db1, db2 = descriptor_b.decode().rsplit('\n', 1)
    descriptor_b = db1 + '\n' + db2.replace('#', r'\n')
    f.write(descriptor_b)

B.py

with open('[B]', 'r') as f:
    descriptor_b = f.read()
with open('[A]', 'w') as f:
    db1, db2 = descriptor_b.rsplit('\n', 1)
    descriptor_b = db1 + '\n' + db2.replace('\n', '#')
    descriptor_b = descriptor_b.encode()
    f.write(f"descriptor_b = {descriptor_b}\nwith open('[B]', 'w') as f:\n    db1, db2 = descriptor_b.decode().rsplit('\\n', 1)\n    descriptor_b = db1 + '\\n' + db2.replace('#', r'\\n')\n    f.write(descriptor_b)\n")

注意到里面用到 rsplitreplace 是因为编码成字节串后原本字符间的换行符就会被显式转换成'\n',就会跟B里拼装A时使用的'\n'混淆,
于是先将B里需要的这部分换成别的,如'#'再去编码,最后再换回来即可
效果很好

*乎上看到一个使用 Kleene 递归定理的能输出自己的 Python 程序
没有仔细看,大概跟网上另一个使用C里面pritf实现用ascii码代替引号有相通之处

(lambda : (lambda y: (lambda x: (print('('+x+')()'), x)[-1])('lambda : (%s)(%r)' % (y, y)))("lambda y: (lambda x: (print('('+x+')()'), x)[-1])('lambda : (%s)(%r)' % (y, y))"))()

但好像也不符合本题所需递归定理的思想,权作补充
而且既然都单文件了,我还是直接文件读写吧...

posted @ 2021-05-04 16:01  NoNoe  阅读(462)  评论(1编辑  收藏  举报