Советы для ограниченного источника в Python

Aug 18 2020

Так же, как code-golf , ограниченный исходный код подталкивает к использованию причуд и скрытых возможностей языка Python. У нас уже есть место для сбора всех этих советов для code-golf , те, что для ограниченного источника, остаются переданными из уст в уста или скрытыми глубоко в документации Python.

Итак, сегодня я хотел бы спросить вас, какие советы по решению проблем с ограниченным исходным кодом в Python?

Пожалуйста, включайте только 1 совет за ответ.


Что дает здесь хорошие чаевые?

Есть несколько критериев, которые, на мой взгляд, должны быть у хороших чаевых:

  1. Это должно быть (отчасти) неочевидным.

    Подобно советам по игре в гольф, это должно быть то, о чем тот, кто немного поиграл в гольф на питоне и прочитал страницу советов, не сразу подумал бы. Например, «Заменить a + bна, a+bчтобы избежать использования пробелов» очевидно любому игроку в гольф, поскольку это уже способ сделать ваш код короче и, следовательно, не является хорошим советом.

  2. Это не должно быть слишком конкретным.

    Поскольку существует много разных типов ограничений источника, ответы здесь должны быть, по крайней мере, в какой-то мере применимы к ограничениям нескольких источников или ограничению одного общего источника. Например, подсказки в форме « Как сделать X без использования символа (ов) Y» обычно полезны, поскольку запрещенные символы являются общим ограничением источника. То, что помогает сделать ваш совет, также должно быть несколько общим. Например , полезны подсказки формы Как создавать числа с ограничением X , поскольку многие программы используют числа независимо от задачи. Подсказки формы Как реализовать алгоритм Шора с ограничением по X - это в основном просто ответы на задачу, которую вы только что придумали, и не очень полезны для людей, решающих другие задачи.

Ответы

24 LuisMendo Aug 18 2020 at 21:45

Избегайте "обычных" букв

Идентификаторы нормализуются парсером Python 3. Это означает, что курсивные (Unicode) буквы, такие как 𝓪𝓫𝓬𝓓𝓔𝓕, интерпретируются как их ASCII-совместимые эквиваленты abcDEF. Итак, следующий код работает (как здесь использовалось ):

𝓝=123
𝓹𝓻𝓲𝓷𝓽(𝓝)

Версии Python, в которых подтверждается такое поведение:

  • Работает: 3.4, 3.5, 3.6, 3.7, 3.8
  • Не работает: 2.7

Пример ограничения источника:

  • Не используйте никаких символов abc···xyz, ABC···XYZ.
21 AdHocGarfHunter Aug 18 2020 at 21:02

Избегайте чисел с логическими значениями

При выполнении арифметических операций с логическими значениями Python обрабатывает их, как если бы они были числами 1 и 0. Так, например,

>>> True+False
1

Вы можете получить все положительные числа, просто добавив друг к другу логические значения.

Вы также можете заменить логические значения логическими значениями, например []>[]is Falseи [[]]>[]is Trueso

>>> ([]>[])+([[]]>[])
1

В некоторых случаях логические значения могут даже использоваться вместо числа без необходимости использовать арифметические операции для его приведения. Например, вы можете индексировать списки / кортежи / строки с помощью логических значений, поэтому:

>>> ['a','b'][True]
'b'

Пример ограничений источника:

  • Не используйте цифры ( 0123456789)

  • Не используйте буквенно-цифровые символы

  • Не используйте ifусловия

19 AdHocGarfHunter Aug 18 2020 at 21:12

Избегайте родителей с индексированием списка

Скобки очень полезны для создания правильного приоритета операторов, поэтому когда их забанят, это неприятно. Однако, если []они еще доступны, мы можем использовать их вместо них. Просто замените

(...)

с участием

[...][0]

Это создает список и индексирует его, чтобы получить единственный элемент. Список заставляет сначала оценить внутреннюю часть, решив вашу проблему с приоритетом.

В приведенном выше примере для этого используются символы []0, однако есть и другие третьи символы, которые можно использовать в этом случае при необходимости.

  • Используя символы []>пишите[...][[]>[]]
  • Используя символы []<пишите[...][[]<[]]
  • Используя символы []=пишите[...][[[]]==[]]

Пример ограничения источника:

  • Без скобок
16 WheatWizard Aug 18 2020 at 22:16

Вызов функций без скобок

Мы можем избежать использования круглых скобок для определения приоритета операторов, используя индексирование списка , но круглые скобки по-прежнему очень полезны для вызова функций.

Для решения проблемы здесь также можно использовать индексирование списков, однако это намного сложнее, поэтому я сделал для него отдельный ответ.

Чтобы вызвать функцию, мы начинаем с создания нового класса, индексирование которого определено как функция. Итак, если мы хотим позвонить, printэто может выглядеть как

class c:__class_getitem__=print

Затем, чтобы вызвать функцию, мы просто индексируем ее с нужным аргументом. Например, для печати "Hello World"делаем

c["Hello World"]

У этого есть несколько досадных недостатков:

  • Его можно использовать только для вызова функций с одним параметром.
  • Для выполнения этой уловки требуется немало персонажей. ( :=[]_acegilmst)
  • Он использует много символов, если вы занимаетесь кодовым гольфом.

Но иногда это может быть ваш единственный выход.


Пример ограничения источника:

  • Без скобок

Вот пример его использования.

14 null Aug 19 2020 at 07:59

Используйте <<и |для создания константы без+

Интересный факт: любую положительную константу можно получить, только используя []<|. Выход - сдвиг логического значения влево. []<[[]]равно 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)» для обозначения приоритета.

12 water_ghosts Aug 19 2020 at 06:45

Доступ к методам и встроенным функциям через __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__регулярное выражение или совпадение подстроки.

12 Arnauld Aug 18 2020 at 21:59

Используйте 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_'

Попробуйте онлайн!

Пример ограничений источника:

  • Не используйте цифры
  • Не используйте цифры / круглые скобки / скобки
9 Noname Aug 18 2020 at 21:31

Используйте, --чтобы избежать +

Например, сделать a+b:

a--b

Пример ограничения источника:

  • Избегайте +оператора
6 water_ghosts Aug 21 2020 at 12:19

Альтернативы 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

Попробуйте онлайн!

4 Noname Aug 18 2020 at 21:41

Используйте, *чтобы избежать/

x**-1эквивалентно 1/x. Так что делать y/xвы можете x**-1*y.

Если вы отчаянно хотите избавиться от этого -1, вы можете ознакомиться с другим советом Ad Hoc Garf Hunter.

Пример ограничения источника:

  • Избегайте использования /персонажа
4 water_ghosts Aug 19 2020 at 06:19

Кодируйте запрещенные символы и используйте 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')

Попробуйте онлайн!

4 water_ghosts Aug 19 2020 at 13:38

Замените операторы 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)будет.

3 Noodle9 Aug 18 2020 at 21:26

Как создавать символы только с помощью цифр, кавычек и обратной косой черты

Строки могут быть составлены с , \oooгде oooэто восьмеричное значение символа.

Например:

'\141'=='a'

Вы можете также использовать шестигранник, за счет xa, b, c, d, eи / или fесли они используются):

'\x61'=='a'  

И юникод за счет u(двух до Python 3) и шестнадцатеричных символов, если они используются:

'\u2713'=='✓'
3 pxeger Aug 21 2020 at 20:11

Используйте __import__("module")вместоimport module

Чтобы избежать пробелов в import statementили динамически создать имя модуля для импорта в виде строки (например, "RANDOM".lower()если вы не можете использовать строчные буквы d). Не вероятно , что полезно , потому что вы не часто нуждаются в стандартную библиотеку, и вы все равно должны быть в состоянии использовать _, i, m, p, o, r, t, (, и ).

Изменить: или, как предлагает Ad Hoc Garf Hunter, вы можете использовать import<tab>module(с буквальным символом табуляции)!

2 OskarSkog Aug 21 2020 at 22:57

Скорее всего, это не имеет отношения к вопросу, но это очень глупо.

Это (возможно) более переносимый способ запутанного метода 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 предназначено для того, чтобы заставить вас думать, что существует закономерность.

2 null Aug 27 2020 at 18:30

__debug__ правда

Довольно самообучения ... объяснять ...

>>> __debug__
True