Dicas para código-fonte restrito em Python
Assim como o código-golfe , o código-fonte restrito leva a pessoa a explorar peculiaridades e recursos ocultos da linguagem Python. Já temos um local para coletar todas essas dicas para código -fonte , aquelas para código-fonte restrito permanecem transmitidas boca a boca ou escondidas profundamente na documentação do python.
Então, hoje eu gostaria de perguntar a você quais são algumas dicas para resolver desafios de código restrito em Python.
Inclua apenas 1 dica por resposta.
O que é uma boa dica aqui?
Existem alguns critérios que acho que uma boa dica deveria ter:
Não deve ser (um pouco) óbvio.
Semelhante às dicas de código-golfe , deve ser algo em que alguém que tenha jogado um pouco em python e tenha lido a página de dicas não pensaria imediatamente. Por exemplo, "Substitua
a + b
pora+b
para evitar o uso de espaços" é óbvio para qualquer jogador de golfe, pois já é uma maneira de tornar seu código mais curto e, portanto, não é uma boa dica.Não deve ser muito específico.
Como existem muitos tipos diferentes de restrições de fonte, as respostas aqui devem ser pelo menos um pouco aplicáveis a várias restrições de fonte ou uma restrição de fonte comum. Por exemplo, dicas do formulário Como fazer X sem usar o (s) caractere (s) Y geralmente são úteis, pois caracteres banidos são uma restrição de fonte comum. O que sua dica ajuda a fazer também deve ser um tanto geral. Por exemplo, dicas do formulário Como criar números com restrição X são úteis, pois muitos programas utilizam números independentemente do desafio. Dicas do formulário Como implementar o algoritmo de Shor com restrição X são basicamente apenas respostas para um desafio que você acabou de inventar e não são muito úteis para as pessoas que estão resolvendo outros desafios.
Respostas
Evite letras "normais"
Os identificadores são normalizados pelo analisador Python 3. Isso implica que as letras cursivas (Unicode), como 𝓪𝓫𝓬𝓓𝓔𝓕
são interpretadas como seus equivalentes compatíveis com ASCII abcDEF
. Portanto, o seguinte código funciona (como foi explorado aqui ):
𝓝=123
𝓹𝓻𝓲𝓷𝓽(𝓝)
Versões Python em que esse comportamento é confirmado:
- Funciona: 3.4, 3.5, 3.6, 3.7, 3.8
- Não funciona: 2.7
Exemplo de restrição de fonte:
- Não use caracteres
abc···xyz
,ABC···XYZ
.
Evite números com booleanos
Ao realizar operações aritméticas em Booleanos, Python os trata como se fossem os números 1 e 0. Então, por exemplo
>>> True+False
1
Você pode fazer todos os números positivos apenas adicionando booleanos uns aos outros.
Você também pode substituir os booleanos por valores booleanos, por exemplo []>[]
é False
e [[]]>[]
é True
assim
>>> ([]>[])+([[]]>[])
1
Em alguns casos, os booleanos podem até ser usados no lugar de um número sem a necessidade de usar aritmética para lançá-lo. Por exemplo, você pode indexar em listas / tuplas / strings com booleanos, então:
>>> ['a','b'][True]
'b'
Restrições de fonte de exemplo:
Não use dígitos (
0123456789
)Não use caracteres alfanuméricos
Não use
if
condições
Evite parênteses com indexação de lista
Os parênteses são muito úteis para criar a precedência de operador correta, por isso é uma chatice quando eles são banidos. No entanto, se []
ainda estiverem disponíveis, podemos usá-los. Simplesmente substitua
(...)
com
[...][0]
Isso cria uma lista e a indexa para obter seu único elemento. A lista faz com que o interior seja avaliado primeiro, resolvendo seu problema de precedência.
O exemplo acima usa os caracteres []0
para fazer isso, entretanto, existem outros terceiros caracteres que podem ser usados neste caso, se necessário.
- Usando caracteres para
[]>
escrever[...][[]>[]]
- Usando caracteres para
[]<
escrever[...][[]<[]]
- Usando caracteres para
[]=
escrever[...][[[]]==[]]
Exemplo de restrição de fonte:
- Não use parênteses
Chamadas de função sem parênteses
Podemos evitar o uso de parênteses para precedência de operador usando indexação de lista , mas os parênteses ainda são muito úteis para chamar funções.
A indexação de lista também pode ser usada aqui para resolver o problema; no entanto, é muito mais complexa, portanto, criei sua própria resposta.
Para chamar uma função, começamos por fazer uma nova classe que tem sua indexação definida para ser a função. Então, se quisermos chamar print
isso, pode parecer
class c:__class_getitem__=print
Então, para chamar a função, simplesmente a indexamos com o argumento que queremos. Por exemplo, para imprimir "Hello World"
nós fazemos
c["Hello World"]
Isso tem algumas desvantagens infelizes:
- Só pode ser usado para chamar funções com um parâmetro.
- Existem alguns personagens que são necessários para realizar esse truque. (
:=[]_acegilmst
) - Usa muitos caracteres se você estiver jogando golfe de código
Mas às vezes pode ser sua única opção.
Exemplo de restrição de fonte:
- Não use parênteses
Aqui está um exemplo de como ele está sendo usado.
Use <<
e |
para gerar constante sem+
Curiosidade: você pode obter qualquer constante positiva apenas usando []<|
. O caminho a percorrer é mudar para a esquerda um booleano. []<[[]]
é 1, então []<[[]]<<[]<[[]]
deve deslocar para a esquerda 1 com 1, que é 2.
Funciona?
>>> []<[[]]<<[]<[[]]
Traceback (most recent call last):
File "<pyshell#29>", line 1, in <module>
[]<[[]]<<[]<[[]]
TypeError: unsupported operand type(s) for <<: 'list' and 'list'
...Não.
A precedência está errada. Felizmente, podemos resolver isso com "Ad Hoc Garf Hunter Parenthesis (TM)":
>>> [[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]
2
Aha!
Para obter outros números além da potência de dois, você ainda precisa +
... ou não. |
ou [bitwise or][[]<[]]
* fará isso por você.
>>> [[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]|[[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]
10
Para obter números negativos sem -
, você pode usar ~
.
* Essa parte foi incluída em um "Ad Hoc Garf Hunter Parenthesis (TM)" para indicar precedência.
Métodos de acesso e funções integradas por meio de __dict__
As classes contêm um __dict__
atributo, que mapeia seus nomes de métodos para os próprios métodos. Se você não puder digitar o nome de um método diretamente, poderá obtê-los aqui __dict__
.
Por exemplo, digamos que você precise acrescentar algo a uma lista, mas não pode usar os caracteres p,n,+
, etc. Como append()
é o 26º método em uma lista __dict__
, você pode chamar o método assim:
a = [1,2,3]
list(a.__class__.__dict__.values())[26](a, 4)
print(a) # prints [1,2,3,4]
Isso também pode ser usado __builtins__
para acessar funções integradas. Mesmo se alguém banir o caractere x
para bloquear a exec
função, você ainda pode chamar exec
assim:
list(__builtins__.__dict__.values())[20]("print('Hello, World!')")
Isso funciona melhor em versões mais recentes do Python que garantem a ordem do dicionário, mas provavelmente há outras maneiras de usar isso em versões mais antigas, como iterar através do __dict__
com uma correspondência de regex ou substring.
Use ord()
ou strings binárias para evitar dígitos
A maioria dos inteiros nos intervalos [32..47]
e [58..126]
podem ser facilmente obtidos a partir do código ASCII de um único caractere com:
x=ord('A')
# or, if parentheses are not allowed:
y=b'A'[False]
Inteiros maiores também podem ser produzidos usando seus pontos Unicode:
>>>print (ord("±"))
177
>>> print (ord("π"))
960
Se você pode usar uma atribuição ou precisa evitar colchetes, você pode desempacotar os valores. Observe que isso não funcionará em linha, mesmo com o operador de morsa.
x,*_=b'A'
y,_=b'A_'
Restrições de fonte de exemplo:
- Não use dígitos
- Use sem dígitos / sem parênteses / sem colchetes
Use --
para evitar +
Por exemplo, para fazer a+b
:
a--b
Exemplo de restrição de fonte:
- Evite o
+
operador
Alternativas para eval
eexec
Você precisa tratar uma string como código, mas não pode usar eval
ou exec
? Existem pelo menos três outras maneiras de executar uma string:
1) timeit.timeit
import timeit
_=timeit.timeit("print('Hello!')", number=1)
timeit
executa number
tempos e retorna uma média de quanto tempo levou. Por padrão, ele é executado 1 milhão de vezes, então você certamente desejará definir number=1
ou lançar uma exceção para interromper (por exemplo "print('hello'); 0/0"
).
Agradeço a Ethan White por me mostrar essa abordagem.
2) os.system
import os
c='echo "import math;print(math.pi)" | python3'
_=os.system(c) # Prints 3.141592653589793
os.system
executa um comando de shell arbitrário e retorna seu código de saída. Se você só precisa imprimir algo, pode echo
seguir, mas também pode executar código arbitrário chamando a python3
si mesmo.
3) code.InteractiveInterpreter (). Runcode
from code import InteractiveInterpreter as I
i = I()
i.runcode("print('Hello!')")
code
é projetado especificamente para loops de leitura-avaliação-impressão e, embora seja um pouco desajeitado, este é o mais poderoso dos três. timeit
e os.system
isolar seus processos, mas InteractiveInterpreter
pode usar o estado global em vez do seu próprio:
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
Use *
para evitar/
x**-1
é equivalente a 1/x
. Então, fazer y/x
você pode fazer x**-1*y
.
Se você quiser se livrar dele -1
desesperadamente, dê uma olhada na outra dica do Ad Hoc Garf Hunter.
Exemplo de restrição de fonte:
- Evite usar o
/
personagem
Codifique caracteres restritos e use exec()
Como a maioria das linguagens interpretadas, Python pode executar uma string como código com eval
e exec
. eval
é mais limitado, mas exec
pode lidar com importações, definições de funções, loops, exceções, etc.
Quando combinado com algumas das outras dicas sobre codificação de caracteres, isso permite que você escreva seu código normalmente:
import sys
def f(i):
return 1 if i==1 else i*f(i-1)
i=int(sys.argv[1])
print(f(i))
Em seguida, escolha uma codificação e passe a versão codificada para 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')
Substitua operadores por métodos dunder
A maioria dos operadores python são sintáticos para chamadas de métodos específicos (freqüentemente chamados de "métodos mágicos" ou "métodos dunder", com "dunder" sendo a abreviação de "sublinhado duplo"). Por exemplo, +
chamadas __add__()
, ==
chamadas __eq__()
e <<
chamadas__lshift__()
Se os operadores forem restritos, você pode chamar esses métodos diretamente:
a = 1
print(a.__add__(1).__eq__(2)) # True
Para atribuição, você pode usar __setitem__
nos dicionários locals()
ou globals()
, quer a variável já exista ou não:
a = 1
locals().__setitem__('a',2)
locals().__setitem__('b',2)
print(a.__add__(b).__eq__(4)) # True
Observe que você terá que adicionar parênteses ao redor dos números para evitar um erro de sintaxe. 4.__eq__(4)
não vai funcionar, mas (4).__eq__(4)
vai.
Como criar caracteres apenas com números, aspas e barras invertidas
Strings podem ser compostos com \ooo
onde ooo
é o valor octal do caractere.
Por exemplo:
'\141'=='a'
Você também pode usar hex, à custa de um x
(e a
, b
, c
, d
, e
e / ou f
se eles são usados):
'\x61'=='a'
E unicode às custas de um u
(dois pré-Python 3) e caracteres hexadecimais se forem usados:
'\u2713'=='✓'
Use em __import__("module")
vez deimport module
Para evitar espaços em branco no import statement
, ou para construir dinamicamente o nome do módulo para importar como uma string (por exemplo, "RANDOM".lower()
se você não pode usar uma letra minúscula d
). Não é provável que útil porque muitas vezes você não precisa da biblioteca padrão, e você ainda precisa ser capaz de usar _
, i
, m
, p
, o
, r
, t
, (
, e )
.
Editar: ou como Ad Hoc Garf Hunter sugere, você pode usar import<tab>module
(com um caractere de tabulação literal)!
Isso provavelmente não é relevante para a pergunta, mas é muito bobo.
Esta é (talvez) uma maneira mais portátil do método complicado de water_ghost de acessar métodos embutidos.
O índice é 26 apenas no CPython 3. Esta modificação muito pequena e extremamente fácil de entender permite que ele seja executado em CPython 2.7, CPython 3, PyPy 2.7 e PyPy 3 (testado em 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)
Os índices corretos (no meu sistema) são
CPython 2.7 13
CPython 3 26
PyPy 2.7 26
PyPy 3 25
E por uma feliz coincidência ('C'+'2')%4 == 1
, ('C'+'3')%4 == 2
, ('P'+'2') == 2
e ('P'+'3') == 3
. O valor 14 existe para levá-lo a pensar que existe um padrão.
__debug__
é verdade
Muito autoexpl ... expl ...
>>> __debug__
True