Dicas para código-fonte restrito em Python

Aug 18 2020

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:

  1. 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 + bpor a+bpara 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.

  2. 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

24 LuisMendo Aug 18 2020 at 21:45

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.
21 AdHocGarfHunter Aug 18 2020 at 21:02

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 []>[]é Falsee [[]]>[]é Trueassim

>>> ([]>[])+([[]]>[])
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 ifcondições

19 AdHocGarfHunter Aug 18 2020 at 21:12

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 []0para 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
16 WheatWizard Aug 18 2020 at 22:16

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 printisso, 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.

14 null Aug 19 2020 at 07:59

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.

12 water_ghosts Aug 19 2020 at 06:45

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]

Experimente online!

Isso também pode ser usado __builtins__para acessar funções integradas. Mesmo se alguém banir o caractere xpara bloquear a execfunção, você ainda pode chamar execassim:

list(__builtins__.__dict__.values())[20]("print('Hello, World!')")

Experimente online!

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.

12 Arnauld Aug 18 2020 at 21:59

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]

Experimente online!

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_'

Experimente online!

Restrições de fonte de exemplo:

  • Não use dígitos
  • Use sem dígitos / sem parênteses / sem colchetes
9 Noname Aug 18 2020 at 21:31

Use --para evitar +

Por exemplo, para fazer a+b:

a--b

Exemplo de restrição de fonte:

  • Evite o +operador
6 water_ghosts Aug 21 2020 at 12:19

Alternativas para evaleexec

Você precisa tratar uma string como código, mas não pode usar evalou exec? Existem pelo menos três outras maneiras de executar uma string:

1) timeit.timeit

import timeit
_=timeit.timeit("print('Hello!')", number=1)

Experimente online!

timeitexecuta numbertempos 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=1ou 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

Experimente online!

os.systemexecuta um comando de shell arbitrário e retorna seu código de saída. Se você só precisa imprimir algo, pode echoseguir, mas também pode executar código arbitrário chamando a python3si mesmo.

3) code.InteractiveInterpreter (). Runcode

from code import InteractiveInterpreter as I
i = I()
i.runcode("print('Hello!')")

Experimente online!

codeé projetado especificamente para loops de leitura-avaliação-impressão e, embora seja um pouco desajeitado, este é o mais poderoso dos três. timeite os.systemisolar seus processos, mas InteractiveInterpreterpode 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

Experimente online!

4 Noname Aug 18 2020 at 21:41

Use *para evitar/

x**-1é equivalente a 1/x. Então, fazer y/xvocê pode fazer x**-1*y.

Se você quiser se livrar dele -1desesperadamente, dê uma olhada na outra dica do Ad Hoc Garf Hunter.

Exemplo de restrição de fonte:

  • Evite usar o /personagem
4 water_ghosts Aug 19 2020 at 06:19

Codifique caracteres restritos e use exec()

Como a maioria das linguagens interpretadas, Python pode executar uma string como código com evale exec. evalé mais limitado, mas execpode 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')

Experimente online!

4 water_ghosts Aug 19 2020 at 13:38

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

Experimente online!

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

Experimente online!

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.

3 Noodle9 Aug 18 2020 at 21:26

Como criar caracteres apenas com números, aspas e barras invertidas

Strings podem ser compostos com \oooonde 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, ee / ou fse eles são usados):

'\x61'=='a'  

E unicode às custas de um u(dois pré-Python 3) e caracteres hexadecimais se forem usados:

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

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)!

2 OskarSkog Aug 21 2020 at 22:57

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') == 2e ('P'+'3') == 3. O valor 14 existe para levá-lo a pensar que existe um padrão.

2 null Aug 27 2020 at 18:30

__debug__ é verdade

Muito autoexpl ... expl ...

>>> __debug__
True