Советы для ограниченного источника в Python
Так же, как code-golf , ограниченный исходный код подталкивает к использованию причуд и скрытых возможностей языка Python. У нас уже есть место для сбора всех этих советов для code-golf , те, что для ограниченного источника, остаются переданными из уст в уста или скрытыми глубоко в документации Python.
Итак, сегодня я хотел бы спросить вас, какие советы по решению проблем с ограниченным исходным кодом в Python?
Пожалуйста, включайте только 1 совет за ответ.
Что дает здесь хорошие чаевые?
Есть несколько критериев, которые, на мой взгляд, должны быть у хороших чаевых:
Это должно быть (отчасти) неочевидным.
Подобно советам по игре в гольф, это должно быть то, о чем тот, кто немного поиграл в гольф на питоне и прочитал страницу советов, не сразу подумал бы. Например, «Заменить
a + b
на,a+b
чтобы избежать использования пробелов» очевидно любому игроку в гольф, поскольку это уже способ сделать ваш код короче и, следовательно, не является хорошим советом.Это не должно быть слишком конкретным.
Поскольку существует много разных типов ограничений источника, ответы здесь должны быть, по крайней мере, в какой-то мере применимы к ограничениям нескольких источников или ограничению одного общего источника. Например, подсказки в форме « Как сделать X без использования символа (ов) Y» обычно полезны, поскольку запрещенные символы являются общим ограничением источника. То, что помогает сделать ваш совет, также должно быть несколько общим. Например , полезны подсказки формы Как создавать числа с ограничением X , поскольку многие программы используют числа независимо от задачи. Подсказки формы Как реализовать алгоритм Шора с ограничением по X - это в основном просто ответы на задачу, которую вы только что придумали, и не очень полезны для людей, решающих другие задачи.
Ответы
Избегайте "обычных" букв
Идентификаторы нормализуются парсером Python 3. Это означает, что курсивные (Unicode) буквы, такие как 𝓪𝓫𝓬𝓓𝓔𝓕
, интерпретируются как их ASCII-совместимые эквиваленты abcDEF
. Итак, следующий код работает (как здесь использовалось ):
𝓝=123
𝓹𝓻𝓲𝓷𝓽(𝓝)
Версии Python, в которых подтверждается такое поведение:
- Работает: 3.4, 3.5, 3.6, 3.7, 3.8
- Не работает: 2.7
Пример ограничения источника:
- Не используйте никаких символов
abc···xyz
,ABC···XYZ
.
Избегайте чисел с логическими значениями
При выполнении арифметических операций с логическими значениями Python обрабатывает их, как если бы они были числами 1 и 0. Так, например,
>>> True+False
1
Вы можете получить все положительные числа, просто добавив друг к другу логические значения.
Вы также можете заменить логические значения логическими значениями, например []>[]
is False
и [[]]>[]
is True
so
>>> ([]>[])+([[]]>[])
1
В некоторых случаях логические значения могут даже использоваться вместо числа без необходимости использовать арифметические операции для его приведения. Например, вы можете индексировать списки / кортежи / строки с помощью логических значений, поэтому:
>>> ['a','b'][True]
'b'
Пример ограничений источника:
Не используйте цифры (
0123456789
)Не используйте буквенно-цифровые символы
Не используйте
if
условия
Избегайте родителей с индексированием списка
Скобки очень полезны для создания правильного приоритета операторов, поэтому когда их забанят, это неприятно. Однако, если []
они еще доступны, мы можем использовать их вместо них. Просто замените
(...)
с участием
[...][0]
Это создает список и индексирует его, чтобы получить единственный элемент. Список заставляет сначала оценить внутреннюю часть, решив вашу проблему с приоритетом.
В приведенном выше примере для этого используются символы []0
, однако есть и другие третьи символы, которые можно использовать в этом случае при необходимости.
- Используя символы
[]>
пишите[...][[]>[]]
- Используя символы
[]<
пишите[...][[]<[]]
- Используя символы
[]=
пишите[...][[[]]==[]]
Пример ограничения источника:
- Без скобок
Вызов функций без скобок
Мы можем избежать использования круглых скобок для определения приоритета операторов, используя индексирование списка , но круглые скобки по-прежнему очень полезны для вызова функций.
Для решения проблемы здесь также можно использовать индексирование списков, однако это намного сложнее, поэтому я сделал для него отдельный ответ.
Чтобы вызвать функцию, мы начинаем с создания нового класса, индексирование которого определено как функция. Итак, если мы хотим позвонить, print
это может выглядеть как
class c:__class_getitem__=print
Затем, чтобы вызвать функцию, мы просто индексируем ее с нужным аргументом. Например, для печати "Hello World"
делаем
c["Hello World"]
У этого есть несколько досадных недостатков:
- Его можно использовать только для вызова функций с одним параметром.
- Для выполнения этой уловки требуется немало персонажей. (
:=[]_acegilmst
) - Он использует много символов, если вы занимаетесь кодовым гольфом.
Но иногда это может быть ваш единственный выход.
Пример ограничения источника:
- Без скобок
Вот пример его использования.
Используйте <<
и |
для создания константы без+
Интересный факт: любую положительную константу можно получить, только используя []<|
. Выход - сдвиг логического значения влево. []<[[]]
равно 1, поэтому []<[[]]<<[]<[[]]
следует сдвинуть 1 влево на 1, то есть 2.
Это работает?
>>> []<[[]]<<[]<[[]]
Traceback (most recent call last):
File "<pyshell#29>", line 1, in <module>
[]<[[]]<<[]<[[]]
TypeError: unsupported operand type(s) for <<: 'list' and 'list'
... Нет.
Приоритет неправильный. К счастью, мы можем решить эту проблему с помощью «Специальной скобки для охотников на гарфов (TM)»:
>>> [[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]
2
Ага!
Чтобы получить числа, отличные от степени двойки, вам все равно нужно +
... или нет. |
или [bitwise or][[]<[]]
* сделает это за вас.
>>> [[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]|[[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]
10
Чтобы получить отрицательные числа без -
, вы можете использовать ~
.
* Эта часть была заключена в «Специальную скобку для охотников на гарфов (TM)» для обозначения приоритета.
Доступ к методам и встроенным функциям через __dict__
Классы содержат __dict__
атрибут, который сопоставляет их имена методов с самими методами. Если вы не можете ввести имя метода напрямую, вы можете получить его отсюда __dict__
.
Например, предположим, что вам нужно добавить что-то в список, но вы не можете использовать символы p,n,+
и т. Д. Поскольку append()
это 26-й метод в списке __dict__
, вы можете вызвать метод следующим образом:
a = [1,2,3]
list(a.__class__.__dict__.values())[26](a, 4)
print(a) # prints [1,2,3,4]
Это также можно использовать с __builtins__
для доступа к встроенным функциям. Даже если кто-то запретит персонажу x
блокировать exec
функцию, вы все равно можете вызвать exec
это так:
list(__builtins__.__dict__.values())[20]("print('Hello, World!')")
Это лучше всего работает в более поздних версиях Python, которые гарантируют порядок словаря, но, вероятно, есть другие способы использовать это в более старых версиях, например, повторение через __dict__
регулярное выражение или совпадение подстроки.
Используйте ord()
или двоичные строки, чтобы избежать цифр
Большинство целых чисел находятся в диапазонах [32..47]
и [58..126]
могут быть легко получены из кода ASCII одного символа с помощью:
x=ord('A')
# or, if parentheses are not allowed:
y=b'A'[False]
Целые числа большего размера также могут быть получены с использованием их точек Юникода:
>>>print (ord("±"))
177
>>> print (ord("π"))
960
Если вы можете использовать присваивание или вам нужно избегать скобок, вместо этого вы можете распаковать значения. Обратите внимание, что это не будет работать встроенно, даже с оператором моржа.
x,*_=b'A'
y,_=b'A_'
Пример ограничений источника:
- Не используйте цифры
- Не используйте цифры / круглые скобки / скобки
Используйте, --
чтобы избежать +
Например, сделать a+b
:
a--b
Пример ограничения источника:
- Избегайте
+
оператора
Альтернативы eval
иexec
Вам нужно рассматривать строку как код, но вы не можете использовать eval
или exec
? Есть как минимум три других способа выполнить строку:
1) timeit.timeit
import timeit
_=timeit.timeit("print('Hello!')", number=1)
timeit
проходит number
раз и возвращается в среднем на сколько времени потребовалось. По умолчанию он запускается 1 миллион раз, поэтому вы почти наверняка захотите установить number=1
или вызвать исключение для выхода (например "print('hello'); 0/0"
).
Спасибо Итану Уайту за то, что показал мне этот подход.
2) os.system
import os
c='echo "import math;print(math.pi)" | python3'
_=os.system(c) # Prints 3.141592653589793
os.system
запускает произвольную команду оболочки и возвращает ее код выхода. Если вам просто нужно что-то напечатать, вы можете придерживаться этого правила echo
, но вы также можете выполнить произвольный код, вызвав python3
себя.
3) code.InteractiveInterpreter (). Runcode
from code import InteractiveInterpreter as I
i = I()
i.runcode("print('Hello!')")
code
специально разработан для циклов чтения-оценки-печати, и, хотя он немного неуклюж, это самый мощный из трех. timeit
и os.system
изолировать их процессы, но InteractiveInterpreter
может использовать глобальное состояние вместо своего собственного:
from code import InteractiveInterpreter as I
a = 64
i = I(globals())
i.runcode("import math; a=math.log2(a)")
print(a) # a = 6.0
print(math.pi) # math is imported globally
Используйте, *
чтобы избежать/
x**-1
эквивалентно 1/x
. Так что делать y/x
вы можете x**-1*y
.
Если вы отчаянно хотите избавиться от этого -1
, вы можете ознакомиться с другим советом Ad Hoc Garf Hunter.
Пример ограничения источника:
- Избегайте использования
/
персонажа
Кодируйте запрещенные символы и используйте exec()
Как и большинство интерпретируемых языков, Python может запускать строку как код с помощью eval
и exec
. eval
более ограничен, но exec
может обрабатывать импорт, определения функций, циклы, исключения и т. д.
В сочетании с некоторыми другими советами по кодированию символов это позволяет вам писать код как обычно:
import sys
def f(i):
return 1 if i==1 else i*f(i-1)
i=int(sys.argv[1])
print(f(i))
Затем выберите кодировку и передайте закодированную версию в exec
:
exec('\x69\x6d\x70\x6f\x72\x74\x20\x73\x79\x73\x0a\x0a\x64\x65\x66\x20\x66\x28\x69\x29\x3a\x0a\x20\x72\x65\x74\x75\x72\x6e\x20\x31\x20\x69\x66\x20\x69\x3d\x3d\x31\x20\x65\x6c\x73\x65\x20\x69\x2a\x66\x28\x69\x2d\x31\x29\x0a\x0a\x69\x3d\x69\x6e\x74\x28\x73\x79\x73\x2e\x61\x72\x67\x76\x5b\x31\x5d\x29\x0a\x70\x72\x69\x6e\x74\x28\x66\x28\x69\x29\x29\x0a')
Замените операторы dunder-методами
Большинство операторов python являются синтаксическим сахаром для вызовов определенных методов (часто называемых «магическими методами» или «dunder-методами», где «dunder» означает «двойное подчеркивание»). Например, +
звонки __add__()
, ==
звонки __eq__()
и <<
звонки__lshift__()
Если операторы ограничены, вы можете вызвать эти методы напрямую:
a = 1
print(a.__add__(1).__eq__(2)) # True
Для задания, вы можете использовать __setitem__
на locals()
или globals()
словари, или нет уже существует переменная:
a = 1
locals().__setitem__('a',2)
locals().__setitem__('b',2)
print(a.__add__(b).__eq__(4)) # True
Обратите внимание, что вам придется заключать числа в круглые скобки, чтобы избежать синтаксической ошибки. 4.__eq__(4)
не сработает, но (4).__eq__(4)
будет.
Как создавать символы только с помощью цифр, кавычек и обратной косой черты
Строки могут быть составлены с , \ooo
где ooo
это восьмеричное значение символа.
Например:
'\141'=='a'
Вы можете также использовать шестигранник, за счет x
(и a
, b
, c
, d
, e
и / или f
если они используются):
'\x61'=='a'
И юникод за счет u
(двух до Python 3) и шестнадцатеричных символов, если они используются:
'\u2713'=='✓'
Используйте __import__("module")
вместоimport module
Чтобы избежать пробелов в import statement
или динамически создать имя модуля для импорта в виде строки (например, "RANDOM".lower()
если вы не можете использовать строчные буквы d
). Не вероятно , что полезно , потому что вы не часто нуждаются в стандартную библиотеку, и вы все равно должны быть в состоянии использовать _
, i
, m
, p
, o
, r
, t
, (
, и )
.
Изменить: или, как предлагает Ad Hoc Garf Hunter, вы можете использовать import<tab>module
(с буквальным символом табуляции)!
Скорее всего, это не имеет отношения к вопросу, но это очень глупо.
Это (возможно) более переносимый способ запутанного метода water_ghost для доступа к встроенным методам.
Индекс равен 26 только для CPython 3. Эта очень небольшая и чрезвычайно простая для понимания модификация позволяет ему работать на CPython 2.7, CPython 3, PyPy 2.7 и PyPy 3 (проверено на Debian 10 amd64).
a = [1, 2, 3]
list(a.__class__.__dict__.values())[[14,13,26,25][sum(map(ord,{__import__("sys").version[0],__import__("platform").python_implementation()[0]}))&3]](a, 4)
print(a)
Правильные индексы (в моей системе):
CPython 2.7 13
CPython 3 26
PyPy 2.7 26
PyPy 3 25
И по счастливой случайности ('C'+'2')%4 == 1
, ('C'+'3')%4 == 2
, ('P'+'2') == 2
и ('P'+'3') == 3
. Значение 14 предназначено для того, чтобы заставить вас думать, что существует закономерность.
__debug__
правда
Довольно самообучения ... объяснять ...
>>> __debug__
True