Consejos para código fuente restringido en Python

Aug 18 2020

Al igual que el código de golf , la fuente restringida empuja a uno a explotar peculiaridades y características ocultas del lenguaje Python. Ya tenemos un lugar para recopilar todos estos consejos para code-golf , los de código restringido permanecen transmitidos de boca en boca o escondidos en lo profundo de la documentación de Python.

Entonces, hoy me gustaría preguntarle cuáles son algunos consejos para resolver desafíos de código restringido en Python.

Incluya solo 1 propina por respuesta.


¿Qué hace un buen consejo aquí?

Hay un par de criterios que creo que debería tener un buen consejo:

  1. Debería ser (algo) no obvio.

    Al igual que los consejos de código de golf , debería ser algo en lo que alguien que haya jugado un poco al golf en Python y haya leído la página de consejos no pensaría de inmediato. Por ejemplo, "Reemplazar a + bcon a+bpara evitar el uso de espacios", es obvio para cualquier golfista, ya que ya es una forma de acortar su código y, por lo tanto, no es un buen consejo.

  2. No debería ser demasiado específico.

    Dado que existen muchos tipos diferentes de restricciones de fuente, las respuestas aquí deberían ser al menos algo aplicables a las restricciones de múltiples fuentes, o una restricción de fuente común. Por ejemplo, las sugerencias de la forma Cómo X sin usar caracteres Y son generalmente útiles ya que los caracteres prohibidos son una restricción de fuente común. Lo que su consejo ayuda a hacer también debe ser algo general. Por ejemplo, los consejos del formulario Cómo crear números con restricción X son útiles ya que muchos programas utilizan números independientemente del desafío. Consejos del formulario Cómo implementar el algoritmo de Shor con restricción X son básicamente solo respuestas a un desafío que acaba de inventar y no son muy útiles para las personas que resuelven otros desafíos.

Respuestas

24 LuisMendo Aug 18 2020 at 21:45

Evite las letras "normales"

Los identificadores son normalizados por el analizador de Python 3. Esto implica que las letras cursivas (Unicode) como las que 𝓪𝓫𝓬𝓓𝓔𝓕se interpretan como equivalentes compatibles con ASCII abcDEF. Entonces, el siguiente código funciona (como se explotó aquí ):

𝓝=123
𝓹𝓻𝓲𝓷𝓽(𝓝)

Versiones de Python en las que se confirma este comportamiento:

  • Funciona: 3.4, 3.5, 3.6, 3.7, 3.8
  • No funciona: 2.7

Ejemplo de restricción de fuente:

  • Utilizar ningún carácter abc···xyz, ABC···XYZ.
21 AdHocGarfHunter Aug 18 2020 at 21:02

Evite los números con booleanos

Al realizar operaciones aritméticas en valores booleanos, Python los trata como si fueran los números 1 y 0. Por ejemplo,

>>> True+False
1

Puede hacer todos los números positivos simplemente agregando valores booleanos entre sí.

También puede sustituir los valores booleanos por valores booleanos, por ejemplo, []>[]es Falsey [[]]>[]es Trueasí

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

En algunos casos, los valores booleanos incluso se pueden usar en lugar de un número sin tener que usar aritmética para convertirlo. Por ejemplo, puede indexar en listas / tuplas / cadenas con booleanos, así:

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

Restricciones de fuente de ejemplo:

  • No use dígitos ( 0123456789)

  • No use caracteres alfanuméricos

  • No use ifcondiciones

19 AdHocGarfHunter Aug 18 2020 at 21:12

Evite Parens con la indexación de listas

Los paréntesis son muy útiles para crear la precedencia correcta del operador, por lo que es un fastidio cuando están prohibidos. Sin embargo, si []todavía están disponibles, podemos usarlos en su lugar. Simplemente reemplace

(...)

con

[...][0]

Esto crea una lista y la indexa para obtener su único elemento. La lista hace que primero se evalúe el interior resolviendo su problema de precedencia.

El ejemplo anterior usa los caracteres []0para hacer esto, sin embargo, hay otros terceros caracteres que se pueden usar en este caso si es necesario.

  • Usando caracteres []>escribir[...][[]>[]]
  • Usando caracteres []<escribir[...][[]<[]]
  • Usando caracteres []=escribir[...][[[]]==[]]

Ejemplo de restricción de fuente:

  • No use paréntesis
16 WheatWizard Aug 18 2020 at 22:16

Llamadas a funciones sin paréntesis

Podemos evitar el uso de paréntesis para la precedencia de los operadores mediante la indexación de listas , pero los paréntesis siguen siendo muy útiles para llamar a funciones.

La indexación de listas también se puede usar aquí para resolver el problema, sin embargo, es mucho más complejo, por lo que he creado su propia respuesta.

Para llamar a una función, comenzamos creando una nueva clase que tiene su indexación definida como función. Entonces, si queremos llamar a printesto, podría verse como

class c:__class_getitem__=print

Luego, para llamar a la función, simplemente la indexamos con el argumento que queremos. Por ejemplo para imprimir "Hello World"hacemos

c["Hello World"]

Esto tiene algunas deficiencias desafortunadas:

  • Solo se puede usar para llamar a funciones con un parámetro.
  • Hay bastantes personajes que se requieren para realizar este truco. ( :=[]_acegilmst)
  • Utiliza muchos caracteres si estás haciendo code-golf

Pero a veces puede ser su única opción.


Ejemplo de restricción de fuente:

  • No use paréntesis

Aquí hay un ejemplo de cómo se usa.

14 null Aug 19 2020 at 07:59

Utilice <<y |para generar constante sin+

Dato curioso: puedes obtener cualquier constante positiva solo usando []<|. El camino a seguir es desplazar a la izquierda un booleano. []<[[]]es 1, por lo que []<[[]]<<[]<[[]]debería desplazarse a la izquierda 1 con 1, que es 2.

¿Funciona?

>>> []<[[]]<<[]<[[]]

Traceback (most recent call last):
  File "<pyshell#29>", line 1, in <module>
    []<[[]]<<[]<[[]]
TypeError: unsupported operand type(s) for <<: 'list' and 'list'

...No.

La precedencia es incorrecta. Afortunadamente, podemos resolver esto con "Ad Hoc Garf Hunter Parenthesis (TM)":

>>> [[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]
2

¡Ajá!

Para obtener números distintos a la potencia de dos, todavía necesita +... o no. |o [bitwise or][[]<[]]* lo hará por ti.

>>> [[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]|[[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]
10

Para obtener números negativos sin -, es posible que desee utilizar ~.

* Esa parte se incluyó en un "Paréntesis Ad Hoc Garf Hunter (TM)" para indicar la precedencia.

12 water_ghosts Aug 19 2020 at 06:45

Acceda a métodos y funciones integradas a través de __dict__

Las clases contienen un __dict__atributo, que asigna los nombres de sus métodos a los métodos mismos. Si no puede escribir el nombre de un método directamente, puede obtenerlos de aquí __dict__.

Por ejemplo, digamos que necesita agregar algo a una lista, pero no puede usar los caracteres p,n,+, etc. Dado que append()es el método número 26 en una lista __dict__, puede llamar al método así:

a = [1,2,3]
list(a.__class__.__dict__.values())[26](a, 4)
print(a)  # prints [1,2,3,4]

¡Pruébelo en línea!

También se puede utilizar con __builtins__para acceder a funciones integradas. Incluso si alguien prohíbe al personaje xpara bloquear la execfunción, aún puede llamar execasí:

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

¡Pruébelo en línea!

Esto funciona mejor en versiones más recientes de Python que garantizan el orden del diccionario, pero probablemente hay otras formas de usar esto en versiones anteriores, como iterar __dict__con una expresión regular o una coincidencia de subcadena.

12 Arnauld Aug 18 2020 at 21:59

Utilice ord()o cadenas binarias para evitar dígitos

La mayoría de los números enteros en los rangos [32..47]y [58..126]se pueden obtener fácilmente del código ASCII de un solo carácter con:

x=ord('A')

# or, if parentheses are not allowed:

y=b'A'[False]

¡Pruébelo en línea!

También se pueden producir números enteros más grandes utilizando sus puntos Unicode:

>>>print (ord("±"))
177
>>> print (ord("π"))
960

Si puede usar una asignación, o necesita evitar los corchetes, puede descomprimir los valores. Tenga en cuenta que esto no funcionará en línea, incluso con el operador de morsa.

x,*_=b'A'
y,_=b'A_'

¡Pruébelo en línea!

Restricciones de fuente de ejemplo:

  • No use dígitos
  • No use dígitos / sin paréntesis / sin corchetes
9 Noname Aug 18 2020 at 21:31

Usar --para evitar +

Ej. Para hacer a+b:

a--b

Ejemplo de restricción de fuente:

  • Evita al +operador
6 water_ghosts Aug 21 2020 at 12:19

Alternativas a evalyexec

¿Necesita tratar una cadena como código, pero no puede usar evalo exec? Hay al menos otras tres formas de ejecutar una cadena:

1) timeit.timeit

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

¡Pruébelo en línea!

timeitejecuta numbertiempos y devuelve un promedio de cuánto tiempo tomó. De forma predeterminada, se ejecuta 1 millón de veces, por lo que es casi seguro que desee establecer number=1o generar una excepción para romper (por ejemplo "print('hello'); 0/0").

Gracias a Ethan White por mostrarme este enfoque.

2) sistema operativo

import os
c='echo "import math;print(math.pi)" | python3'
_=os.system(c)  # Prints 3.141592653589793

¡Pruébelo en línea!

os.systemejecuta un comando de shell arbitrario y devuelve su código de salida. Si solo necesita imprimir algo, puede seguir echo, pero también puede ejecutar código arbitrario llamándose a python3sí mismo.

3) código.InteractiveInterpreter (). Código de ejecución

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

¡Pruébelo en línea!

codeestá diseñado específicamente para bucles de lectura-evaluación-impresión, y aunque es un poco torpe, este es el más poderoso de los tres. timeity os.systemaislar sus procesos, pero InteractiveInterpreterpuede usar el estado global en lugar del propio:

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

¡Pruébelo en línea!

4 Noname Aug 18 2020 at 21:41

Usar *para evitar/

x**-1es equivalente a 1/x. Así y/xque puedes hacerlo x**-1*y.

Si desea deshacerse de él -1, puede consultar el otro consejo de Ad Hoc Garf Hunter.

Ejemplo de restricción de fuente:

  • Evita usar el /personaje
4 water_ghosts Aug 19 2020 at 06:19

Codificar caracteres restringidos y uso exec()

Como la mayoría de los lenguajes interpretados, Python puede ejecutar una cadena como código con evaly exec. evales más limitado, pero execpuede manejar importaciones, definiciones de funciones, bucles, excepciones, etc.

Cuando se combina con algunos de los otros consejos sobre la codificación de caracteres, esto le permite escribir su 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))
    

Luego, elija una codificación y pase la versión codificada a 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')

¡Pruébelo en línea!

4 water_ghosts Aug 19 2020 at 13:38

Reemplazar operadores con métodos dunder

La mayoría de los operadores de Python son azúcar sintáctico para llamadas a métodos específicos (a menudo llamados "métodos mágicos" o "métodos dunder", siendo "dunder" la abreviatura de "doble subrayado"). Por ejemplo, +llamadas __add__(), ==llamadas __eq__()y <<llamadas__lshift__()

Si los operadores están restringidos, puede llamar a estos métodos directamente:

a = 1
print(a.__add__(1).__eq__(2))  # True

¡Pruébelo en línea!

Para la asignación, puede usar __setitem__en los diccionarios locals()o globals(), ya sea que la variable ya exista o no:

a = 1
locals().__setitem__('a',2)
locals().__setitem__('b',2)
print(a.__add__(b).__eq__(4))  # True

¡Pruébelo en línea!

Tenga en cuenta que deberá agregar paréntesis alrededor de los números para evitar un error de sintaxis. 4.__eq__(4)no funcionará, pero lo (4).__eq__(4)hará.

3 Noodle9 Aug 18 2020 at 21:26

Cómo crear personajes solo con números, comillas y barras invertidas

Las cadenas se pueden componer con \ooodonde oooes el valor octal del carácter.

P.ej:

'\141'=='a'

También puede utilizar hexagonal, a expensas de una x(y a, b, c, d, ey / o fsi se utilizan):

'\x61'=='a'  

Y unicode a expensas de u(dos pre-Python 3) y caracteres hexadecimales si se usan:

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

Usar en __import__("module")lugar deimport module

Para evitar espacios en blanco en import statement, o para construir dinámicamente el nombre del módulo para importar como una cadena (por ejemplo, "RANDOM".lower()si no puede usar minúsculas d). No es probable que útil, ya que no suelen necesita la librería estándar, y usted todavía tiene que ser capaz de uso _, i, m, p, o, r, t, (, y ).

Editar: o como sugiere Ad Hoc Garf Hunter, ¡puedes usar import<tab>module(con un carácter de tabulación literal)!

2 OskarSkog Aug 21 2020 at 22:57

Es probable que esto no sea relevante para la pregunta, pero es muy tonto.

Esta es (quizás) una forma más portátil del complicado método de water_ghost para acceder a los métodos incorporados.
El índice es 26 solo en CPython 3. Esta modificación muy pequeña y extremadamente fácil de entender le permite ejecutarse en CPython 2.7, CPython 3, PyPy 2.7 y PyPy 3 (probado en 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)

Los índices correctos (en mi sistema) son

CPython 2.7    13
CPython 3      26
PyPy 2.7       26
PyPy 3         25

Y por una feliz coincidencia ('C'+'2')%4 == 1, ('C'+'3')%4 == 2, ('P'+'2') == 2y ('P'+'3') == 3. El valor 14 está ahí para engañarte y hacerte pensar que hay un patrón.

2 null Aug 27 2020 at 18:30

__debug__ es verdad

Bastante autoexpl ... expl ...

>>> __debug__
True