在知乎的一则问题有哪些能炫技的代码写法?中看到一个回答:使用python输出”hello world”,

看完回答确实很炫。

注: 原回答中的代码需要申请,如果有侵权会立即删除。

0x00 介绍

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

(lambda _, __, ___, ____, _____, ______, _______, ________:

getattr(

__import__(True.__class__.__name__[_] + [].__class__.__name__[__]),

().__class__.__eq__.__class__.__name__[:__] + ().__iter__().__class__.__name__[_____:________]

)(

_,

(lambda _, __, ___: _(_, __, ___))(

lambda _, __, ___: chr(___ % __) + _(_, __, ___ // __) if ___ else (lambda: _).func_code.co_lnotab,

_ << ________,

(((_____ << _____) + ___) << ((((___ << __) - _) << ____) + (_ << __))) - (

((((___ << __) + _) << ___) - _) << ((((___ << __) - _) << ____) - (___ << _))) + (

((((_ << ____) - _) << ____) + _____) << ((_____ << _____) - _)) - (

((_____ << ___) - _) << ((((_____ << __) - _) << ___) - _)) - (

((_ << ______) - _) << (((((_ << ___) + _)) << ____) - ___)) + (

((_______ << _____) - _____) << ((_ << _______) - _)) - (

((_ << _____) + _) << ((((_ << ____) - _) << ___) - _)) - (

((_______ << ____) - ___) << ((_______ << ____) - ___)) + (

((___ << ____) + _) << ((((___ << __) + _) << ___) - ___)) + (

((___ << ___) + _) << ((((___ << __) - _) << ___) + (_ << _))) + (

((_______ << __) - _) << ((_____ << ____) + (_ << _))) + (

((_______ << ___) + _) << (((((_ << ___) + _)) << ___) + _)) + (

((_______ << ____) - _) << ((_ << ______))) + (

((((_ << ____) - _) << ___) - _) << ((_______ << ___))) + (

_ << ((((___ << __) + _) << __) + _)) + (

((((___ << __) - _) << ___) + _) << ((_____ << ___) - _)) - (

((_ << _____) + _) << ((_ << _____) - _)) - (((_____ << ___) - _) << ((___ << ___) - _)) - (

((_____ << ___) - _) << ((_ << ____) - _)) - (((_______ << __) - _) << ________) + (

(((___ << __) + _)) << ___)

)))(*(lambda _, __, ___: _(_, __, ___))(

(lambda _, __, ___: [__(___[(lambda: _).func_code.co_nlocals])] +

_(_, __, ___[(lambda _: _).func_code.co_nlocals:]) if ___ else []),

lambda _: _.func_code.co_argcount,

(

lambda _: _,

lambda _, __: _,

lambda _, __, ___: _,

lambda _, __, ___, ____: _,

lambda _, __, ___, ____, _____: _,

lambda _, __, ___, ____, _____, ______: _,

lambda _, __, ___, ____, _____, ______, _______: _,

lambda _, __, ___, ____, _____, ______, _______, ________: _

)))

输出结果为:

1
2

hello, world from xlzd.

0X01 涉及的概念

1. lambda表达式

简单来说,编程中提到的 lambda 表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数.python中的lambda表达式具体可参考:Lambdas

Example:

1
2
3
4
5
6

add = lambda x, y: x + y

print(add(3, 5))

# Output: 8

在上面的代码中,就是使用了lambda表达式使得代码成为一行,当然这只是炫技的一部分。其实,只要你功能不是太大,那用lambda都可以包裹到一行。有一个这样的库,可以把你所有python实现,包裹到一行当中,请见csvoss/oneliner · GitHub

2. python 内置函数

1). __class__内置函数

用于获取对象的class

Example:

1
2
3
print(True.__class__)

#Output: <type: 'bool'>

具体可参考官方API

2). __name__内置函数

用于获取当前对象的名字

1
2
3
print(True.__class__.__name__)

# Output: bool

具体可参考官方API

3). ** __import__ 内置函数**
在官方给出的API中:
_import__(name[, globals[, locals[, fromlist[, level]]]])
这个函数被import语句调用,作用是引入名字为name的模块

4). getattr内置函数

在官方给出的API中:

getattr(object, name[, default])
返回所给对象object的名字为name的值

Example:

1
2
3
4

getattr(__import__('os'),'write')(1,b'Hello World')

# Output: Hello World

5). func_code.co_nlocals内置函数
这个函数(python2.x)的作用是获得函数的参数的个数
具体可以查看官方的API
Example:

1
2
3
func = lambda _:_
print (func.func_code.co_nlocals)
#Output: 1

在该代码中多次使用这个技巧来获得整数的值

0x02 炫技

1.炫技一 获取目标类名

在该代码中使用截取一些对象的类的名字获取目标的类名。
首先在最外层的lambda的标的式中一共有8个参数,参数依次以18个_组成,而传入的参数即18的整数,这个将在后面提到。所以_的个数代表整数的大小。通过上面提到的内置函数,我们将_代表的数字带入可以得到

1
2
3
4
print(True.__class__.__name__[1] + [].__class__.__name__[2])
print(().__class__.__eq__.__class__.__name__[:2] + ().__iter__().__class__.__name__[5:8])
#Output: os
# write

通过一系列类的名字截取出我们所需要的os中的write的名字,并通过__import__getattr获得write函数。

炫技二 递归

在该代码中有几处地方使用的递归的方式
其形式为:

1
2
3
4
5
lambda x,y:x(y):((lambda x,y:x(y)),y)
# 传给最外层的lambda的第一个参数是一个lambda表达式是和其形式一样的函数,
# 从而到达自我调用实现递归,其等价于
def func(x,y):
func(x,y)

如果读者非常熟悉递归想必很容易看出来这个形式的。
当然递归中需要有退出的条件,例如在上面获得字符串的递归中,使用了用第___//__(第三个参数//第二个参数),直达为0退出。
注://运算即除法取商 如5//4=1 4//2=2 2//3=0

炫技三 获得字符串

我们所拥有的参数只有1-8,通过1-8的数字使用移位运算和加法等基本的运算得到一个目标值(复杂技巧),然后对该目标值递归的方式依次获得目标字符的ascii通过chr函数转化为字符。这部分代码位于整个代码的中间部分即getattr()获得的write函数包裹的范围。
通过计算这个数为:
249642804582896990794842262078881781526671259605700207976
然后通过递归获得目标字符串,递归退出的条件为第传入的值为0,在每次递归中这个值都要//(1<<8)(_<<________)。在这个递归函数中,第二个参数为256,第三个参数即上面提到的。我们改写这个复杂的函数可以得到

1
2
3
4
5
6
7
8
9
10
11
number = 249642804582896990794842262078881781526671259605700207976
def getStr(coff,number):
# 递归退出条件
if number == 0:
#返回空字符
return ''
#否则继续递归
else:
return chr(number%coff)+getStr(coff,number//coff)
print(getStr(1<<8,number))
#Output: hello, world from xlzd.

炫技四 获得1-8的数字

上面已经提到可以通过co_nlocals的内置函数来获得整型数,如何向最外层的lambda表达式传入1-8的参数呢?这里同样使用了递归的方式。每次生成一个数字的数组并与前面生成的数组相加,递归的退出条件也是当传入的参数为0的时候。最后生成一个包含[1-8]的数组,传入匿名函数中。这部分代码也就是最后的那部分代码,也就是最外层lambda的括号外面。将其转化为常规代码即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def getArray(getNumber,lambdaArray):
# 还存在元素
if lambdaArray:
number = [getNumber(lambdaArray[0])]
# 传入除去第一个元素的剩余数组
return number+getArray(getNumber,lambdaArray[1:])
# 递归退出条件
else:
return []
lambdaArray = (
lambda _: _, #1
lambda _, __: _, #2
lambda _, __, ___: _, #3
lambda _, __, ___, ____: _, #4
lambda _, __, ___, ____, _____: _, #5
lambda _, __, ___, ____, _____, ______: _, #6
lambda _, __, ___, ____, _____, ______, _______: _, #7
lambda _, __, ___, ____, _____, ______, _______, ________: _ #8
)

getNumber = lambda _: _.func_code.co_argcount

print(getArray(getNumber,lambdaArray))

#Output: [1,2,3,4,5,6,7,8]

这样就可以获得了[1-8]的数组,当然使用数组传入函数的8个参数时,需要使用*值来使得正常的传入值。有关这部分的内容可以参考这篇博客-python函数的四种参数传递方式或者官方文档。

0x03 总结

整个代码可以大体分为三大部分:

第一部分: 最外层的匿名函数,接受1-8个参数分别代表1-8的整数

第二部分: 利用递归的形式获取目标字符

第三部分: 利用递归获得[1-8]的数组

每一部分都是用了不同的技巧使得简单的代码复杂化,虽然整个代码初看非常繁琐复杂,但是通过一一解析还是很容易理清其中逻辑的。

如果上面的分析中存在错误请不吝赐教。
(完)