PyFuck (shirAko)
- Not to be confused with PyFuck (kuangkzh).
PyFuck, also known as PyStr, is an esoteric subset of the Python language discovered in 2022 that uses only 8 symbols in the source code. They are: [
, ]
, (
, )
, +
, ==
, str
and eval
. A symbol is an indivisible unit of code, meaning that you can write ==
but never =
alone. You can't either write for example an s without being part of str
, etc...
It name comes from the similarity to JSFuck, as both are esoteric languages that can be run in non-esoteric ones (Esoteric subsets).
Introduction
PyFuck works because every Python program can be written as a string that gets evaluated. You create a string of characters with the 8 symbols, and that can get evaluated with the eval
symbol. For more complicated programs, you need the exec
function to execute all your Python code. You can use it by creating the string 'exec' and then evaluating it. We can prove that this code is able to behave like any Python code, and is therefore Turing complete, and that there isn't any smallest subset of these symbols that is able to do so. Although we can theorically create another language by replacing the str
, [
and ]
symbols by chr
, the command to output unicode, it is extremely repetitive and isn't as interesting.
To understand how it works, let's look at a code example to see how Python treats boolean values.
[]==[]
The preceding example's output is obviously True
. But now,
([]==[])+([]==[])
gives us the output 2
. This is because the first ()
parenthesis evaluates True
and so does the second parenthesis, and because Python's bool-to-int conversion, True+True
is evaluated as 1+1
. It will be noted that the parenthesis are mandatory since without them, Python's operation precedence would still give the same boring True
. Because it can be repeated indifinitely (3 is ([]==[])+([]==[])+([]==[])
, 4 is ...), it can represent any number, and thus this bool to int conversion will be the building block of our code.
Now, noting that in Python, empty tuple and empty list are different, ()==[]
evaluates to False
, which, by the conversion, can also sometimes be interpreted as 0
. We can then create basic strings like 'T', let's see how it's constructed from pure Python to PyFuck:
'T'
: from string'True'
, first character (index 0 because we're counting from 0)'True'[0]
:'True'
, a string, can be made fromstr(True)
. Here, the True inside the parenthesis is a boolean.str(True)[0]
: we write True as[]==[]
str([]==[])[0]
: 0 is an index number, which here can be written asFalse
str([]==[])[False]
: since False is()==[]
, we can ultimately writestr([]==[])[()==[]]
<<< that's it!
Of course, instead of []==[]
we could have written ()==()
and instead of ()==[]
we could have written []==()
with no impact whatsoever.
Likewise, using str(False) and the fact that True+True=2:
- 'r' is written as
'True'[1]
:str([]==[])[[]==[]]
- 'e' is written as
'True'[3]
:str([]==[])[([]==[])+([]==[])+([]==[])]
- 'F' is written as
'False'[0]
:str(()==[])[()==[]]
- 'a' is written as
'False'[1]
:str(()==[])[[]==[]]
- 's' is written as
'False'[4]
:str(()==[])[([]==[])+([]==[])+([]==[])]
etc...
We can also use str(())
and str([])
to get the ( , ) , [ , ] characters.
Now because str applied to a function or a class will give you it's nature, we have
str(str)
gives us"<class 'str'>"
str(eval)
gives us"<built-in function eval>"
From which we can get the characters c, f, l , o, t , < , > , ' , [space] etc...
We then can create longer strings by concatenating them with the +
symbol, as in Python 'a'+'b' will return 'ab'. For example, if we want to write 'FasT' we will need to write 'F'+'a'+'s'+'T':
str(()==[])[()==[]] + str(()==[])[[]==[]] + str(()==[])[([]==[])+([]==[])+([]==[])] + str([]==[])[()==[]] F a s T
Writing any Python program
To write any Python program, we must be able to
- Have a function that, given a string, executes it as a program
- Get every character possible.
Luckily in Python we already have a function for those, exec
, which can execute any code as a string argument, and chr
, which converts a number to an unicode character. It will be noted that, since chr
can get any character, us getting it implies that we can also write 'exec'. So the only real condition is 2: create chr
. However, since we said in the intro we wouldn't be using chr
as a symbol, we must first write the string 'chr', and then we can use eval on it. As an example, to write 'x', which isn't in our "easy-to-write" character list we just got from basic operations, we should write chr(120), since 'x' is represented as 120 in Unicode. Our idea is to write it as eval('chr')(120)
, with eval effectively turning the string 'chr' into the function chr
. Writing 120 will be easy, since we can always brute-force our way into any number by successively adding []==[]
to itself.
While it is easy to write 'c' as str(str)[[]==[]]
and 'r' as str([]==[])[[]==[]]
, it turns out that writing 'h' is not an easy task.
Our final task: writing 'h'
After trying countless tries of combinations to write 'h' in our language, it seems like the easiest way to write it is as:
str(eval('[].clear'))[13]
for reasons we will see. We already know how to write c, l, e, a, r
. However, for this we will still need to know how to write .
, which isn't in our "easy-to-write" list, and that is not trivial at all. We still have another sub-task!
Fortunately, in Python, running float()
will give you 0.0
, which we can stringify and then take the index 1 to get .
. But wait, we already have the characters f, l, o, a, t
. We are lucky!
So, to write .
:
str(float())[1]
str(eval('float()'))[[]==[]]
We can simplify this for later by observing that eval('float')
returns the float
function, that can be called later. In other words, we can put the parenthesis outside the string and write:
str(eval('float')())[[]==[]]
Using the + method we find out that 'float'
is written as 'f'+'l'+'o'+'a'+'t'
, which is:
str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str(()==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]
Finally, to write '.', we replace the 'float' by this long text:
str(eval(str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str(()==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])])())[[]==[]]
Done!
Now since str(eval('[].clear'))
gives us '<built-in method clear of list object at adress>'
, we can take the h from 'method', so h is str(eval('[].clear'))[13]
since we know how to write everything else, we find that h is:
str(eval(str([])+str(eval(str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str(()==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])])())[[]==[]]+str(str)[[]==[]]+str(str)[([]==[])+([]==[])]+str([]==[])[([]==[])+([]==[])+([]==[])]+str(()==[])[[]==[]]+str([]==[])[[]==[]]))[eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[])+([]==[])))]
And so we can finaly write our promised chr
function as eval('chr')
, which is eval('c'+'h'+'r')
:
eval(str(str)[[]==[]]+str(eval(str([])+str(eval(str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str(()==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])])())[[]==[]]+str(str)[[]==[]]+str(str)[([]==[])+([]==[])]+str([]==[])[([]==[])+([]==[])+([]==[])]+str(()==[])[[]==[]]+str([]==[])[[]==[]]))[eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[])+([]==[])))]+str([]==[])[[]==[]])
And of course we can write exec as eval('e'+chr(120)+'e'+'c')
:
eval(str([]==[])[([]==[])+([]==[])+([]==[])]+eval(str(str)[[]==[]]+str(eval(str([])+str(eval(str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str(()==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])])())[[]==[]]+str(str)[[]==[]]+str(str)[([]==[])+([]==[])]+str([]==[])[([]==[])+([]==[])+([]==[])]+str(()==[])[[]==[]]+str([]==[])[[]==[]]))[eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[])+([]==[])))]+str([]==[])[[]==[]])(eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[]))+str((()==[])+(()==[]))))+str([]==[])[([]==[])+([]==[])+([]==[])]+str(str)[[]==[]])
Everything that can be done in Python can therefore be done in PyFuck, our task is complete!
Optimisations
There are several optimisations, for example: writing chr(120) instead of writing ([]==[])
added to itself 120 times, this could be changed to eval('1'+'2'+'0')
, that would be:
chr(eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[]))+str((()==[])+(()==[]))))
which is much shorter, saving 93% of characters.
Translator/Encoder
In this encoder, change the variable o
by the string of the Python program you want to encode in PyFuck. You can use triple quotes strings for multiline code. In the end, you should have a perfectly runnable code.py
code that does exactly what your original code did. Of course, you could re-feed that code into the encoder program, creating an even more complicated program, but for memory issues, it is almost impossible. In fact, for even a small code, you'll get a RecursionError.
def pystr(n): q="" for i in n: if i=='h':q+='str(eval(str([])+str(eval(str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str(()==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])])())[[]==[]]+str(str)[[]==[]]+str(str)[([]==[])+([]==[])]+str([]==[])[([]==[])+([]==[])+([]==[])]+str(()==[])[[]==[]]+str([]==[])[[]==[]]))[eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[])+([]==[])))]' elif i=='T':q+='str([]==[])[()==[]]' elif i=='r':q+='str([]==[])[[]==[]]' elif i=='e':q+='str([]==[])[([]==[])+([]==[])+([]==[])]' elif i=='F':q+='str(()==[])[()==[]]' elif i=='a':q+='str(()==[])[[]==[]]' elif i=='s':q+='str(()==[])[([]==[])+([]==[])+([]==[])]' elif i=='<':q+='str(str)[()==[]]' elif i=='c':q+='str(str)[[]==[]]' elif i=='l':q+='str(str)[([]==[])+([]==[])]' elif i==' ':q+='str(str)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]' elif i=="'":q+='str(str)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]' elif i=='>':q+='str(str)[eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[])))]' elif i=='b':q+='str(eval)[[]==[]]' elif i=='u':q+='str(eval)[([]==[])+([]==[])]' elif i=='i':q+='str(eval)[([]==[])+([]==[])+([]==[])]' elif i=='t':q+='str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]' elif i=='-':q+='str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]' elif i=='f':q+='str(eval)[eval(str(([]==[])+(()==[]))+str((()==[])+(()==[])))]' elif i=='n':q+='str(eval)[eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[])))]' elif i=='o':q+='str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]' elif i=='v':q+='str(eval)[eval(str(([]==[])+([]==[]))+str((()==[])+(()==[])))]' elif i=='(':q+='str(())[()==[]]' elif i==')':q+='str(())[[]==[]]' elif i=='[':q+='str([])[()==[]]' elif i==']':q+='str([])[[]==[]]' elif i=='0':q+='str((()==[])+(()==[]))' elif i=='1':q+='str(([]==[])+(()==[]))' elif i=='2':q+='str(([]==[])+([]==[]))' elif i=='3':q+='str(([]==[])+([]==[])+([]==[]))' elif i=='4':q+='str(([]==[])+([]==[])+([]==[])+([]==[]))' elif i=='5':q+='str(([]==[])+([]==[])+([]==[])+([]==[])+([]==[]))' elif i=='6':q+='str(([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[]))' elif i=='7':q+='str(([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[]))' elif i=='8':q+='str(([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[]))' elif i=='9':q+='str(([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[]))' elif i=='.':q+='str(eval(str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str(()==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])])())[[]==[]]' else: q+='eval(str(str)[[]==[]]+'+pystr('h')+'+str([]==[])[[]==[]])(eval(' r=str(ord(i)) for j in r: q+='str(' if j=='0':q+='(()==[])+(()==[])' elif j=='1':q+='([]==[])+(()==[])' else: for k in range(int(j)):q+='([]==[])+' q=q[:-1] q+=')+' q=q[:-1] q+='))' q+='+' return q[:-1] o="print('Hello, world!')" print('eval('+pystr('exec'),end=')(',file=open('code.py','a')) print(pystr(o),file=open('code.py','a'),end="") print(')',file=open('code.py','a'),end="")
Hello, World!
Using the above translator, we find out that the Hello World! program, print('Hello World!')
can be written in PyFuck in 5,538 characters.
eval(str([]==[])[([]==[])+([]==[])+([]==[])]+eval(str(str)[[]==[]]+str(eval(str([])+str(eval(str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str(()==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])])())[[]==[]]+str(str)[[]==[]]+str(str)[([]==[])+([]==[])]+str([]==[])[([]==[])+([]==[])+([]==[])]+str(()==[])[[]==[]]+str([]==[])[[]==[]]))[eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[])+([]==[])))]+str([]==[])[[]==[]])(eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[]))+str((()==[])+(()==[]))))+str([]==[])[([]==[])+([]==[])+([]==[])]+str(str)[[]==[]])(eval(str(str)[[]==[]]+str(eval(str([])+str(eval(str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str(()==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])])())[[]==[]]+str(str)[[]==[]]+str(str)[([]==[])+([]==[])]+str([]==[])[([]==[])+([]==[])+([]==[])]+str(()==[])[[]==[]]+str([]==[])[[]==[]]))[eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[])+([]==[])))]+str([]==[])[[]==[]])(eval(str(([]==[])+(()==[]))+str(([]==[])+(()==[]))+str(([]==[])+([]==[]))))+str([]==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])]+str(eval)[eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[])))]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(())[()==[]]+str(str)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+eval(str(str)[[]==[]]+str(eval(str([])+str(eval(str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str(()==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])])())[[]==[]]+str(str)[[]==[]]+str(str)[([]==[])+([]==[])]+str([]==[])[([]==[])+([]==[])+([]==[])]+str(()==[])[[]==[]]+str([]==[])[[]==[]]))[eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[])+([]==[])))]+str([]==[])[[]==[]])(eval(str(([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[]))+str(([]==[])+([]==[]))))+str([]==[])[([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+eval(str(str)[[]==[]]+str(eval(str([])+str(eval(str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str(()==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])])())[[]==[]]+str(str)[[]==[]]+str(str)[([]==[])+([]==[])]+str([]==[])[([]==[])+([]==[])+([]==[])]+str(()==[])[[]==[]]+str([]==[])[[]==[]]))[eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[])+([]==[])))]+str([]==[])[[]==[]])(eval(str(([]==[])+([]==[])+([]==[])+([]==[]))+str(([]==[])+([]==[])+([]==[])+([]==[]))))+str(str)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+eval(str(str)[[]==[]]+str(eval(str([])+str(eval(str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str(()==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])])())[[]==[]]+str(str)[[]==[]]+str(str)[([]==[])+([]==[])]+str([]==[])[([]==[])+([]==[])+([]==[])]+str(()==[])[[]==[]]+str([]==[])[[]==[]]))[eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[])+([]==[])))]+str([]==[])[[]==[]])(eval(str(([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[]))+str(([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[]))))+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str([]==[])[[]==[]]+str(str)[([]==[])+([]==[])]+eval(str(str)[[]==[]]+str(eval(str([])+str(eval(str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str(()==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])])())[[]==[]]+str(str)[[]==[]]+str(str)[([]==[])+([]==[])]+str([]==[])[([]==[])+([]==[])+([]==[])]+str(()==[])[[]==[]]+str([]==[])[[]==[]]))[eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[])+([]==[])))]+str([]==[])[[]==[]])(eval(str(([]==[])+(()==[]))+str((()==[])+(()==[]))+str((()==[])+(()==[]))))+eval(str(str)[[]==[]]+str(eval(str([])+str(eval(str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(str)[([]==[])+([]==[])]+str(eval)[eval(str((()==[])+([]==[]))+str((([]==[]))+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])))]+str(()==[])[[]==[]]+str(eval)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])])())[[]==[]]+str(str)[[]==[]]+str(str)[([]==[])+([]==[])]+str([]==[])[([]==[])+([]==[])+([]==[])]+str(()==[])[[]==[]]+str([]==[])[[]==[]]))[eval(str(([]==[])+(()==[]))+str(([]==[])+([]==[])+([]==[])))]+str([]==[])[[]==[]])(eval(str(([]==[])+([]==[])+([]==[]))+str(([]==[])+([]==[])+([]==[]))))+str(str)[([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])+([]==[])]+str(())[[]==[]])
See also
- PyChr, a variant that uses
chr
rather thanstr
and[]