What can a line of Python do?

Life is short. I use Python.

Brevity is a great feature of Python. When you first learned Python, one of the most popular topics about Python’s brevity was “what a line of Python can do.” This topic actually covers quite a wide range of concepts, but as a beginner, the most striking thing is naturally the one I can understand, that is, some gorgeous operations around print. For example, print the multiplication table with one line of code:

 print('\n'.join([' '.join(['%s*%s=%-2s' % (y,x,x*y) for y in range(1,x+1)]) for x in range(1.10)))Copy the code

One line of code to print the heart

print('\n'.join([' '.join([('PYTHON'[(x-y)%6]if((x*0.05) * *2+(y*0.1) * *2-1) * *3-(x*0.05) * *2*(y*0.1) * *3< =0 else' ')for x in range(-30.30)])for y in range(15, -15, -1)))Copy the code

Even fractals:

print('\n'.join([' '.join([The '*'if abs((lambda a:lambda z,c,n:a(a,z,c,n))(lambda s,z,c,n:z if n==0else s(s,z*z+c,c,n-1(a))0.0.02*x+0.05 j.*y,40))"2 else' 'for x in range(-80.20)])for y in range(-20.20)))Copy the code

The code is a bit gimmicky, but it’s undeniably impressive. However, there’s nothing mysterious about these statements when you break them down. With two things: list parsing and lambda statements, you can write your own one-line print XXX.

How can loop and conditional statements be combined on a single line?

Before thinking about that, think about why there is such a need. For example, let’s look at the first example, which is the multiplication table. Following general logic, the table should be printed like this:

lines = []
for x in range(1.10):
    formulas = []
    for y in range(1, x+1):
        formulas.append("%s*%s=%-2s" % (y, x, x * y))
    lines.append("".join(formulas))
print("\n".join(lines))
Copy the code

The overall structure is a two-layer nested loop, each iteration of the external, generating a line; Each iteration inside generates an expression. Join the list of calculations into rows, and then print the list of rows into the entire multiplication table. The idea was clear.

But here we use seven lines, not counting the final print, and the resulting string uses two row list initializations, two for loops, and two join lines. So… No need to ask, after all, the code above shows us that these statements can be combined into a single line. This is Python’s List Comprehension mechanism. It makes our code much cleaner, and it also improves performance.

The basic format for list parsing

The basic syntax for list parsing is:

[< expression >(I)for i in< iterator >if> < conditions]Copy the code

This sentence will iterate over the iterator for I, and if so, insert I into the expression at the end of the list. Rewrite it in pseudocode to understand:

def generate_list() :
	List = []
    for i in< iterator >:if> < conditions:List.append(< expression >(I))return List
Copy the code

A few examples

The simplest way to get a feel for list parsing is from a list of 1 to 10:

a = [i for i in range(1.11)]
print(a)
Print [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Copy the code

This is nothing special, and to generate a list of 1 to 10, you don’t need to use the list parser at all. But if we want to write a list of squares, the value of list parsing comes into play:

a = [i*i for i in range(1.11)]
print(a)
# output: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Copy the code

Expressions can contain more complex things, such as functions:

def factorial(n) :
    # Very classic recursion textbook: factorial function.
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

print([factorial(i) for i in range(1.11)])
# output: [1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
Copy the code

Even objects.

In addition, list parsing supports logical operations. If, when inserting a list, we want the list to contain only items that satisfy a certain condition, we use the following if. For example, following the factorial calculation above, we can write as follows:

print([factorial(i) for i in range(1.11) if i % 2= =1])
Copy the code

But the items that don’t meet the criteria we want to be able to process in a different way and then add to the list, in other words, can we add else to list parsing? The answer is yes.

But if I write it like this:

print([factorial(i) for i in range(1.11) if i % 2= =1 else i*i])
Copy the code

Python will report an error and refuse to execute.

The correct way to do this is to put the if and else at the beginning of the loop:

print([factorial(i) if i % 2= =1 else i * i for i in range(1.11)])
Copy the code

That’s how you get a list of odd factorial and even squared.

Finally, list parsing is fully nested, meaning that the < expression > at the beginning of list parsing can contain another list parser. That’s how the multiplication table works. Returning to the original two-layer loop, we wrap one layer around the other and see how list parsing simplifies expression:

# Original code
lines = []
for x in range(1.10):
    formulas = []
    for y in range(1, x+1):
        formulas.append("%s*%s=%-2s" % (y, x, x * y))
    lines.append("".join(formulas))
print("\n".join(lines))

# Internal list parsing simplifications:
lines = []
for x in range(1.10):
    lines.append("".join(["%s*%s=%-2s" % (y, x, x * y) for y in range (1, x+1)))print("\n".join(lines))

# Simplify the external iteration:
print("\n".join(["".join(["%s*%s=%-2s" % (y, x, x*y) for y in range(1, x+1)]) for x in range(1.10)))Copy the code

It will output three sets of identical multiplication tables, and that’s OK!

For printing hearts, just use if else, and that’s not a difficult problem.

The magic of lambda

List parsing alone can solve the multiplication table, but for Manderbrot sets, which require constant iteration to determine whether or not a pattern is in the set, list parsing alone is a bit weak. We could have externally defined a function to determine whether each point is in the set, but what if we wanted to write it on a single line? Lambda, which appears four times in the code at the beginning, is our answer.

What exactly is lambda?

Lambda is the defining flag of an anonymous function. An anonymous function is a function without a name. The basic syntax is:

lambda argument_list: expression
Copy the code

Note: The above expression will return a function whose input argument list and return value are preceded by a colon. Let’s take the simplest index as an example:

L = [1.2.3.4]
f = lambda x : x[0]
print(f(L))
print((lambda x : x[0])(L))
Both statements will print 1, the first element of L.
Copy the code

Lambda defines a function with an input of x and an output of x[0], equivalent to the following function definition:

def f(x) :
    return x[0]
Copy the code

OK, let’s look at the long string of lambda statements in the case:

(lambda a:lambda z,c,n:a(a,z,c,n))(lambda s,z,c,n:z if n==0else s(s,z*z+c,c,n-1(a))0.0.02*x+0.05 j.*y,40)
Copy the code

This one-line statement encapsulates many of lambda’s tricks, some of which I’ve never seen before myself. Troubling as it may seem, it’s still worth savoring.

Lambda nested

It’s got three parentheses. It’s a pain to look at. Let’s take it from the inside out, one by one. First, there are two lambdas in the first parentheses. This is lambda nesting, where one lambda expression is referenced within another lambda expression.

First look at the inner layer:

lambda z,c,n:a(a,z,c,n)
Copy the code

Where z, c, n are arguments and the return value is a(a, z, c, n), so this function can be written in the non-anonymous form:

def f1(z, c, n) :
    return a(a, z, c, n)
Copy the code

But there’s an extra “A” here! Let’s look at what a is. Notice lambda a: is actually the input argument to the outer function.

def f2(a) :
    return f1
If you want to execute it, you should write a in the parameter of f1, but this is only pseudocode, so it is not so serious :-3
Copy the code

Lambda s,z,c,n:z if n==0else s(s,z*z+c,c,n-1)). Why is there a parenthesis here? Trust your understanding of Python by first excluding multiplication. The parentheses here are actually function calls. This might be a little hard to understand, but what about replacing the lambda expression with f2 above and writing it again?

f2(lambda s,z,c,n:z if n==0 else s(s,z*z+c,c,n-1))
Copy the code

This form is a function in terms of its internal arguments, which also happens to be the form in which a is used. Let’s say the top function is f3(s, z, c, n), so the whole function can be rewritten in a non-anonymous way:

f2(f3):return f1: return f3(f3, 0.0.02*x+0.05 j.*y, 40)
Copy the code

Repeat the relationship between the functions: f2 takes f3 as input and returns f1, which takes the three arguments in the last parenthesis as input and returns the value.

Lambda implements recursion

When I studied the role of S in F3 and finally found that it realized recursion as an intermediate quantity, I was quite shocked.

As we all know, a recursive function is a function that calls itself. But what’s the problem with lambda? Yeah, it doesn’t have a name at all, so how do you call it until you define it? Do I have to write it all over again?

lambda x: lambda x: lambda x: ...
Copy the code

I don’t think so… Change your mind. Although lambda definitions are anonymous, arguments passed in lambda functions have names. Using this, and with the help of other lambda functions, recursion can be achieved.

To illustrate this, let’s write down the definition of F3:

def f3(s, z, c, n) :
    if n == 0:
        return z
    else:
        return s(s, z*z+c, c, n-1)
Copy the code

Here s is called as a function and passed in as an argument. This is where s is both the function and the argument it needs. Looking back, F3 is passed into F2, where it affects the internal arithmetic of F1. Yeah, actually the s here is the A in the first bracket.

Now, you might notice that all of these functions, all of these layers of nesting, are just trying to write this recurrence. F3 defines the structure of the recursion, while F1 and F2 receive the initial recursive value and the recursive body function, respectively. So if F3 gets to F2 and it has a name, it can plug itself into its first argument, and it returns a function that is F1, which is a normal function with recursive energy.

In summary, this structure is:

# lambda for recursion, if it's too long I'll use \ for line folding
(lambda f, arg_list: lambda arg_list: f(f, arg_list))\
(lambda g, arg_list: <terminate_value> if <terminate_condition> \
else g(g, processed_arguments))(arguments)

# correspond to
def func(arg_list) :
    if <terminate_condition>:
        return <terminate_value>
    else:
        return f(processed_arguments)
func(arguments)
Copy the code

So, what this verbose lambda does is for a given set of (x, y) in range, according to


z 0 = 0 . c = 0.02 x + 0.05 j y . z k + 1 = z k 2 + c Z_0 = 0, \ quad c = 0.02 + 0.05 x jy, \ quad z_ {k + 1} = z_ {k} + c ^ 2

Relationship of iteration to z40z_ {40} z40 if ∥ z40 ∥ < 2 \ | z_ {40} \ | < 2 ∥ z40 ∥ < 2 to fill the position into *, otherwise will this position space for space. This leads to the familiar fractals.

Details of the Mandelbrot set operations are shown here.

Now you can start the show operation too!

Now that you have a better understanding of list parsing and lambda expressions, it shouldn’t be too difficult to print the pattern you want with a line of Python. To sum it up:

  1. Define a function to determine what those coordinates should print and compress it into a line with lambda;
  2. Use list parsing to compress coordinate traversal into a single line.

While this kind of excessive compression often doesn’t make code readable, the two main ideas: list parsing and lambda expressions, are things that every Python developer needs to know or master at some point.

Finally, I have to mention the most classic one-liner, which embodies the sentiments of Tim Peters about Python. This is the Zen of Python, and for those who are new to it, try it:

import this
Copy the code

reference

  • What can a line of Python code do? – zhihu (zhihu.com)