Consejos para código fuente restringido en Python
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:
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 + b
cona+b
para 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.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
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
.
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 False
y [[]]>[]
es True
así
>>> ([]>[])+([[]]>[])
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
if
condiciones
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 []0
para 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
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 print
esto, 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.
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.
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 x
para bloquear la exec
función, aún puede llamar exec
así:
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.
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
Usar --
para evitar +
Ej. Para hacer a+b
:
a--b
Ejemplo de restricción de fuente:
- Evita al
+
operador
Alternativas a eval
yexec
¿Necesita tratar una cadena como código, pero no puede usar eval
o 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!
timeit
ejecuta number
tiempos 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=1
o 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.system
ejecuta 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 python3
sí 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!
code
está 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. timeit
y os.system
aislar sus procesos, pero InteractiveInterpreter
puede 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!
Usar *
para evitar/
x**-1
es equivalente a 1/x
. Así y/x
que 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
Codificar caracteres restringidos y uso exec()
Como la mayoría de los lenguajes interpretados, Python puede ejecutar una cadena como código con eval
y exec
. eval
es más limitado, pero exec
puede 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!
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á.
Cómo crear personajes solo con números, comillas y barras invertidas
Las cadenas se pueden componer con \ooo
donde ooo
es 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
, e
y / o f
si se utilizan):
'\x61'=='a'
Y unicode a expensas de u
(dos pre-Python 3) y caracteres hexadecimales si se usan:
'\u2713'=='✓'
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)!
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') == 2
y ('P'+'3') == 3
. El valor 14 está ahí para engañarte y hacerte pensar que hay un patrón.
__debug__
es verdad
Bastante autoexpl ... expl ...
>>> __debug__
True