Самоисправляющийся HelloWorld("print")
В честь приближающегося 1 сентября предлагаю соревнование по написанию самоисправляющегося Hello world
.
Сразу оговорюсь, что задание придумано на Python
, но можно написать ответ на любом языке, где есть схожее с Python поведение:
- в стандартную функцию вывода в консоль передается строка через указание последней внутри круглых скобок
- ЯП позволяет запустить программу, заведомо написанную с ошибкой
Требуется написать код, в конце которого будет указана обратная команда HelloWorld (допустимо дописать обязательные для работы конструкции - например, end
, скобки и т.п.):
# Любой Ваш код
HelloWorld("print")
После запуска в консоль выведется исправленный текст приложения Hello world:
print("HelloWorld")
При этом текст, который "отправляется на печать" (часть HelloWorld
), можно менять - код должен аналогично работать и с такими командами:
hello_world("print") >>> print("hello_world")
test("print") >>> print("test")
Текст для вывода прописывается одним словом - как функция.
После него в скобках и кавычках указывается команда вывода - своя для ЯП.
Обеспечивать возможность изменения ("print")
(или аналогичной в другом ЯП) не нужно.
Аналогично не обязательно обеспечивать работу приложения после вывода текста правильного кода HelloWorld.
Пример на JavaScript:
# Любой Ваш код
HelloWorld('console.log')
Пример на Pascal:
program Hello;
begin
# Любой Ваш код
HelloWorld('writeln')
end.
Критерии победы (по убыванию для случаев равенства голосов)
- Самое большое количество голосов на момент подсчета
- Наименьшее число символов кода (включается всё - и ваш код, и последняя строка с HelloWorld)
- Наиболее ранний ответ
Подведение итогов - 1 сентября в 21:00 МСК
По итогам соревнования больше всего голосов получил ответ @CrazyElf - решение через перехват исключения NameError
.
Но в ответах есть не менее интересные и неожиданные решения!
Спасибо всем за участие! Отдельное спасибо за решения на языках помимо Python!
Так самое короткое решение вышло у @extrn на Perl - 61 символ!
Изначальное моё решение задачи также придумано через перехват исключения NameError
, только в размере вышло 111 символов:
import sys;sys.excepthook=lambda*a:print(f'''print("{str(a[1]).split()[1].strip("'")}")''')
HelloWorld("print")
Ответы (16 шт):
Можно в самом начале прочитать исполняемый файл, вывести все исправленные принты и завершить процесс чтобы не наткнуться на NameError ошибку.
Итого получилось 105 символов моего кода + строчка hello world
_,*f=open(__file__);[l[:-1]and(j:=l.strip().split("("),print(j[1][1:-2]+f"('{j[0]}')"))for l in f];exit()
helloworld("print")
Код просто свайпает название функции и аргумент в любой непустой строчке, так что например type_age("input")
точно так же переведется в input("type_age")
Если же мы уверены что будем работать только с принтами то это можно захардкодить и получить 86 символов
_,*f=open(__file__);[l[:-1]and(print(f"print('{l.split("(")[0]}')"))for l in f];exit()
helloworld("print")
Скорее всего можно оптимизировать и еще но лучше всё таки понимать более четкие критерии задачи
Решение (не костыль) 136 символов!
Код для конкурса:
import re
for f in re.findall(r'\w+(?=\(\"pr)',open(__file__).read()):locals()[f]=lambda x,f=f:print(f'{x}("{f}")')
Hello_world("print")
Этот код не допускает исключения NameError
!
Читаемый вариант:
import re
def create_function(name):
def func(param):
print(f'{param}("{name}")')
return func
for f in re.findall(r'\w+(?=\(\"print\"\))', open(__file__).read()):
locals()[f] = create_function(f)
Hello_world("print")
It_is_not_print("print")
output:
(.venv) PS C:\KWORK> & c:/KWORK/.venv/Scripts/python.exe c:/KWORK/not_print.py
print("Hello_world")
print("It_is_not_print")
(.venv) PS C:\KWORK>
with open('path/to/file', 'r') as f:
cod=f.read()
with open('lib.py', 'w') as f:
cod=f.write('''
def helloworld(a:str) -> str:
print(f'{a}("helloworld")')
''')
with open('path/to/file', 'w') as f:
f.write(f'from lib import helloworld\n{cod}')
Mожно через перехват исключения NameError
.
Но в интерактивном питоне это не работает (например, в Jupyter Notebook
),
работает только при запуске из командной строки:
import traceback as tb
import sys
import re
def my_except_hook(exctype, value, traceback):
rx = re.compile(r'([^\(]+)\(\"([^\"]*)\"\)')
line = next(iter(tb.extract_tb(traceback))).line
text, command = rx.findall(line)[0]
print(f'{command}("{text}")')
sys.excepthook = my_except_hook
hello_world("print")
Вывод:
print("hello_world")
Символов 320, оптимизировать по их количеству пока лениво.
И сам разбор пока не очень красивый.
Мне интересно было концепт попробовать.
UPD:
для конкурса, 182 символа:
import re,sys,traceback as t
def h(d,o,b):print('{1}("{0}")'.format(*re.findall(r'([^\(]+)\(\"([^\"]*)\"\)',next(iter(t.extract_tb(b))).line)[0]))
sys.excepthook=h
hello_world("print")
Python, 85 символов
_,s=open(__file__);a,b,_=s.split('"');print(f'{b}("{a}\b")')and\
HelloWorld("print")
Этот же код в более читабельном виде:
_, s = open(__file__); a, b, _ = s.split('"'); print(f'{b}("{a}\b")') and \
HelloWorld("print")
print
в конце первой строки возвращает None
. \
там же склеивает первую и вторую строки. Выражение None and ...
никогда не вычисляет второй аргумент. Последняя строка файла не вычисляется и к ошибке не приводит.
Программа читает собственный код, предполагая что в нём ровно две строки (_, s = open(__file__)
), разбирает вторую строку (a, b, _ = s.split('"')
), печатает нужный результат. Переменная a
содержит лишнюю открывающую скобку, её удаляет символ \b
при печати. Итог выглядит правильно, но если перенаправить его в поток, можно увидеть и отрывающую скобку и backspace, который её удаляет:
$ python temp.py print("HelloWorld") $ python temp.py | python -c "print(repr(input()))" 'print("HelloWorld(\x08")'
Если "print"
всегда один и тот же, то 83 символа:
_,s=open(__file__);a,_=s.split('(');print(f'print("{a}")')and\
HelloWorld("print")
Perl, 61 символ
Перечитал условие, и только сейчас обратил внимание, что одинарные кавычки допустимы
AUTOLOAD{$AUTOLOAD=~/::/;print"@_('$'')"}
HelloWorld('print')
В читабельном виде
use strict;
use warnings;
# функция, выполняемая при вызове неизвестного имени.
# sub в ее случае указывать необязательно
sub AUTOLOAD {
# аргументы записываются в массив @_
my @args = @_;
# а в переменную $AUTOLOAD - полное имя функции, например "main::HelloWorld"
our $AUTOLOAD;
# с помощью регулярного выражения, ищем в ней ::
$AUTOLOAD =~ /::/;
# не попавшая под регулярное выражение часть строки записывается в переменную $'
my $name = $';
# при интерполяции, массив отображается в виде разделенной пробелами строки,
# но аргумент у нас один, так что пробелы не появятся
print("@args('$name')")
}
HelloWorld("print")
Python, 120 символов
import inspect as i;*_,f=i.currentframe().f_code.co_names;locals()[f]=lambda s:print(f'{s}("{f}")')
HelloWorld("print")
В читабельном виде:
import inspect
fname = inspect.currentframe().f_code.co_names[-1]
locals()[fname] = lambda s: print(f'{s}("{fname}")')
HelloWorld("print")
inspect.currentframe().f_code
извлекает код, который интерпретатор Питона исполняет прямо сейчас. .co_names
- список всех идентификаторов упомянутых в текущем коде. Имена в списке перечислены по порядку первого появления. Нам нужно последнее имя.
В следующей строке этому имени присваивается функция печати.
Когда интепретатор доходит до последней строки, функция в ней уже определена и печатает, то что нужно.
P.S. Решение обходится без чтения файла исходного кода с диска.
Во всех подсчётах:
- перевод строки считается за 1 символ, но только если он обязателен (несогласные могут удалить или заменить на
;
по необходимости) console.log
в браузерных версиях перед подсчётом надо заменить наalert
Во всех решениях предполагается, что команда вывода в файле одна (хотя некоторые могут быть улучшены для нескольких) и она будет единственным кодом в файле помимо обёртки.
HTML + JS, 128 символов
Ограничения на выводимый текст: запрещены все ключевые слова и несколько глобальных свойств.
eval(document.currentScript.textContent.replace(/.*?(\w+)\("(.*)"\).*/s,"$1=()=>$2('$1')"))
HelloWorld("console.log")
Минимальный рабочий (невалидный) html-файл с заменой console.log
на print
будет выглядеть так:
<script>eval(document.currentScript.textContent.replace(/.*?(\w+)\("(.*)"\).*/s,"$1=()=>$2('$1')"))
HelloWorld("alert")</script>
NodeJS, 104 символа
Ограничения на выводимый текст: запрещены все ключевые слова и несколько глобальных свойств (например, undefined
).
eval((""+arguments.callee).replace(/.*?(\w+)\("(.*)"\).*/s,"$1=()=>$2('$1')"))
HelloWorld("console.log")
NodeJS, 111 символов
Ограничения на выводимый текст: запрещены все ключевые слова.
console.log((""+arguments.callee).replace(/.*?(\w+)\("(.*)"\).*/s,"$2('$1')"))
return
HelloWorld("console.log")
Браузерный JS, 93-6*3 = 75 символов
Ограничения на выводимый текст: запрещены не все ключевые слова.
На выведенную ошибку обращать внимания не надо - это сниппет так работает. Если смотреть в консоли браузера, то ошибки там нет.
После вывода исполнение скрипта прекратится.
Аргумент (имя функции для вывода) игнорируется (вроде по условию так можно?).
onerror=e=>!console.log('console.log("'+e.match(/: (\w+)/)[1]+'")')
HelloWorld("console.log")
PS: console.log
-> alert
при подсчёте.
Javascript, 92 символа (для браузера 86)
Ограничения на выводимый текст: запрещены все ключевые слова и больше ничего.
Перевод строки необязателен, в браузере можно alert
.
with(new Proxy({},{has:t=>1,get:(t,s)=>f=>eval(f)(f+'("'+s+'")')}))
HelloWorld("console.log")
PS: Кажется, самое чистое решение из моих js'ных.
Perl, 63 байт
$_=<DATA>;/\("/;print"print$&$`\")"
__END__
HelloWorld("print")
Python 145 - 164 символов (с последней строчкой и без)
Хотелось придумать что-то оригинальное :)
exec('#'+open(__file__).read(),type('',(dict,),{'__getitem__':lambda s,i:s.get(i,lambda x:print(f"{x}({i!r})"))})(__builtins__.__dict__));exit()
helloworld("print")
Более читабельная версия
class DefaultDict(dict): # пользовательский класс в котором изменён дандер получения элемента
def __getitem__(self, key): # в случае если элемента нет в словаре возвращаем лямбду
return self.get(key, lambda arg: print(f"{arg}({key!r})"))
# создаём новый экземпляр нашего класса передавая в него словарь всех built-in имен чтобы не сломать логику их получения
env_globals = DefaultDict(__builtins__.__dict__)
# читаем исходный код этого файла и обрезаем первые n строчек в которых лежит наш код чтобы недопустить рекурсии
source_code = "".join(open(__file__).readlines()[14:])
# запускаем весь оставшийся код передавая в качестве словаря глобальных переменных экземпляр нашего класса
exec(source_code, env_globals)
# выходим из программы чтобы не выполнять весь код ниже ещё раз и не получить NameError
exit()
helloworld("print") # теперь эта и любая другая строчка ниже обработается как надо
Никаких костылей, честно, схавает даже такой код:
exec('#'+open(__file__).read(),type('',(dict,),{'__getitem__':lambda s,i:s.get(i,lambda x:print(f"{x}({i!r})"))})(__builtins__.__dict__));exit()
import turtle
helloworld("print")
turtle.tracer(-1)
turtle.pensize(40)
turtle.teleport(-50, -80)
turtle.dot(40)
turtle.teleport(-50, 80)
turtle.dot(40)
turtle.teleport(60, -186)
turtle.left(50)
for i in range(81):
turtle.forward(5)
turtle.left(1)
print(":)")
Ohh_HelloWorld("print")
turtle.mainloop()
А ещё, в отличии от других решений на Python, мой код ловит любые вызовы функций в рантайме, так что такой код тоже сработает:
exec('#'+open(__file__).read(),type('',(dict,),{'__getitem__':lambda s,i:s.get(i,lambda x:print(f"{x}({i!r})"))})(__builtins__.__dict__));exit()
computed_str = "hello_" + "world"
globals()[computed_str]("print")
Python (156 символов)
Вот моё решение. С re
:
import re
g = re.search(r'(\w+)\("(\w+)"\)', open(__file__).read().splitlines()[-1]).groups()
print(g[1] + '("' + g[0] + '")')
exit()
HelloWorld("print")
Вот пример кода на ассемблере x86 MASM, который решает поставленную задачу:
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
outputString db "print(""HelloWorld"")", 0 ; Исправленная строка для вывода
.code
start:
; Получаем хендл стандартного вывода (консоли)
invoke GetStdHandle, STD_OUTPUT_HANDLE
; Выводим строку в консоль
mov ecx, outputString
invoke lstrlen, ecx
invoke WriteConsole, eax, addr outputString, eax, NULL, NULL
; Завершение программы
invoke ExitProcess, 0
HelloWorld MACRO cmd
outputString db cmd, '("HelloWorld")', 0
ENDM
HelloWorld("print") ; Последняя строка кода
end start
Объяснение:
- outputString: Строка, которая будет выведена в консоль. В данном примере она формируется макросом
HelloWorld
. - start: Основная точка входа в программу, где сначала получаем хендл стандартного вывода (консоли) с помощью
GetStdHandle
, затем выводим строкуoutputString
в консоль с помощьюWriteConsole
. - HelloWorld MACRO: Этот макрос принимает команду (например,
"print"
) и формирует строку в форматеcmd("HelloWorld")
. - Invoke WriteConsole: Функция
WriteConsole
используется для вывода строки в консоль. Она требует указания хендла консоли, строки для вывода, её длины и других параметров. - Invoke ExitProcess: Завершение программы.
Как это работает:
- Макрос
HelloWorld
генерирует строку, которая представляет собой команду для вывода текста, например,print("HelloWorld")
. - Эта строка затем выводится в консоль через стандартный вывод с помощью функций Windows API.
Этот код полностью соответствует требованиям задачи и выводит исправленный текст (например, print("HelloWorld")
) непосредственно в консоль.
Ограничения
- Этот код работает только на Windows и только на архитектуре x86. Для другой ос или другой архитектуры нужен совсем другой код
- Этот код работает только с Hello world, допустим с test("print") это работать не будет. Правда вы можете добавить макрос test, чтоб это заработало.
- В этом коде слишком много символов, поэтому его автор не претендует на звания победителя конкурса.
Примечание
Ответ не стоит рассматривать, как серъёзное решение.
Костыль на 147 символов!
import sys,contextlib as c
@c.contextmanager
def e():
try:yield
except:print(f'print("{sys.exc_info()[1].name}")')
with e():\
Hello_world("print")
Используется контекстный менеджер.
output:
print("Hello_world")
Читабельный код:
import sys
from contextlib import contextmanager
@contextmanager
def try_except():
try:
yield
except NameError:
print(f'print("{sys.exc_info()[1].name}")')
with try_except():
Hello_world("print")
Этот костыль, побочный продукт попыток использовать модуль pdb
.
Костыль на 156 символов!
import sys,traceback as t
def h(о,o,k):print('{1}("{0}")'.format(*next(iter(t.extract_tb(k))).line[:-2].split('("')))
sys.excepthook=h
hello_world("print")
Это решение @CrazyElf
, только без модуля re
.