Skip to content
Permalink
Fetching contributors…
Cannot retrieve contributors at this time
230 lines (174 sloc) 10.6 KB

不同寻常的 Python 考试

这是一道考察 Python 语言奇技淫巧(各种边界情况)的题,一共包括了 20 个小题,回答任意 10 小题可以拿到第一个 flag,任意 15 小题可以拿到第二个 flag,全部答对可以拿到第三个 flag。对于每个小题,用户的输入由 Python 的 ast.literal_eval 读入。根据文档,这个函数类似 eval,但是只能读入 Python 的基本类型,包括字符串、数值、列表等等,它可以安全地读入 Python 的字面量而无需担心任意代码执行的问题。

服务器上跑的 Python 版本是 3.7,有些人可能因为本地运行的 Python 版本比服务器低从而导致了本地和远程的行为不一致。远程 Python 的版本可以通过测试一些版本之间的细微区别来确定。预期解法在近几个 Python 3 的版本中都可以跑通。

解答

def challenge_1(self, answer):
    if answer == "Hello":
        return True

直接输入 'Hello' 即可过关。

def challenge_2(self, answer):
    a, b, c, d = answer
    if a == b and a is b and c == d and c is not d:
        return True

Python 中 == 表示判断相等关系,而 is 判断是否为同一个对象。相等并且为同一个对象的例子可以是 None。一些整数和字符串也可能满足这个性质,但这与实现有关,Python 并不保证这一点。

相等但是不为同一个对象的例子,找一种可变的数据类型即可,例如列表。

一个例子:1, 1, [1], [1]

def challenge_3(self, answer):
    if answer in answer == answer:
        return True

这里 in== 的优先级是什么呢?其实 Python 中比较运算这样连起来写等价于 answer in answer and answer == answer,正如 1 < x < 2 等价于 1 < x and x < 2

随便一个字符串都可以过关。

def challenge_4(self, answer):
    a1, b1 = answer
    a2, b2 = answer
    if a1 * 2 != a1 and b1 * 2 != b1:
        a1 *= 2
        b1 *= 2
        if a1 == a2 and b1 != b2:
            return True

这个小题需要我们找到两个对象,其中一个 *= 2 之后对象本身会改变(和自己的另一个引用相等),而另一个对象 *= 2 之后引用改变(和原来自己的引用不相等)。找一个 mutable 和一个 immutable 即可,例如 [1], 1

def challenge_5(self, answer):
    r = reversed([1, 2, 3])
    if list(r) == list(r) + answer:
        return True

看上去像是 answer = [],但实际上 reversed([1, 2, 3]) 是一个 iterator,遍历一次之后就空了,所以答案是 [3, 2, 1]

def challenge_6(self, answer):
    a, b = answer
    if max(a, b) != max(b, a):
        return True

max 函数用对象之间的比较运算来取出最大值。我们可以找 {0}, {1} 这两个集合,Python 中集合的比较运算是判断(真)子集关系,所以 {0} > {1}{1} > {0} 都是 False,两次 max 结果不同。

def challenge_7(self, answer):
    a, b, c = answer
    for x in a, b, c:
        if isinstance(x, float) or isinstance(x, complex):
            return False
    if a * (b + c) != a * b + a * c:
        return True

不满足乘法分配律?Python 中的乘法不一定是对数值进行,字符串和列表也可以和整数做乘法,所以很容易找到 2, 'a', 'b' 这样的解,此时等号两边是 ababaabb

def challenge_8(self, answer):
    a, b, c = answer
    for x in a, b, c:
        if isinstance(x, float) or isinstance(x, complex):
            return False
    if a * (b * c) != (a * b) * c:
        return True

不满足乘法结合律?也可以和上一题一样用字符串或者列表来构造。这里可以利用它们乘以负数时总是会得到空串的性质,例如一个解是 [0], -1, -1

def challenge_9(self, answer):
    a, b = answer
    for x in a, b:
        if isinstance(x, float) or isinstance(x, complex):
            return False
    if type(a ** b) != type(b ** a):
        return True

需要 a ** bb ** a 的类型不同。这里想要考察,Python 中相同基本类型的运算结果类型,可能与具体的值相关。

(-1) ** 2 = 1,是 int 类型,而 2 ** (-1) = 0.5,是 float 类型。也可以用 (-1) ** 0.5 来得到 complex 复数类型。

def challenge_10(self, answer):
    a, b = answer
    if a and a.count(b) > len(a):
        return True

a 非空,并且 a 里面对 b 进行无覆盖的计数结果大于 a 的长度,这看起来不可能,其实 b 是空串的时候就可以。一个可能的解是 'a', ''

def challenge_11(self, answer):
    if max(answer) != max(*answer):
        return True

Python 中的函数可以以 max(1, 2, 3) 的形式调用,也可以以 max([1, 2, 3]) 的形式调用。当参数个数为 1 时,就会匹配后一种形式。所以我们只需要让 answer 列表里面只有一个元素,这样 max(*answer) 就也会匹配到第二种形式,从而可以做到两个 max 的结果不相同。一个可能的解是 [[0]]

def challenge_12(self, answer):
    a, b = answer
    if a < b and all(x > y for x, y in zip(a, b)):
        return True

ab 小,但是 a 中的每个元素都比 b 中对应位置的元素大?看起来不大可能,其实 a 是空列表并且 b 非空的时候就可以做到,因为 a 中根本没有元素,对空集计算 all 会得到 True

def challenge_13(self, answer):
    a, b = answer
    if b and not (a ^ b) - a:
        return True

看似 ab 是两个整数,但如果这样的话,我们可以推出 a ^ b == a,从而 b == 0,矛盾,这是不可能的。除了整数以外,集合也支持 ^- 运算,很容易找到满足题目中关系的集合,例如 {1}, {1}

def challenge_14(self, answer):
    backup = deepcopy(answer)
    try:
        answer[0] += answer[1]
    except:
        if backup != answer:
            return True

这里 answer[0] += answer[1] 一行执行时需要产生异常,但是却需要 answer 的值发生变化。这是 Python 官方文档 FAQ 中指出的一种特殊情况,让 answer 是 tuple 并且 answer[0] 是 list 就可能触发这种情况,原因是 list 的 += 运算可以正常进行,但是接下来在 tuple 中更新引用时会失败,tuple 是 immutable 类型。答案可以是 [1], [1]

def challenge_15(self, answer):
    item, l = answer
    if item in l and not min(l) <= item <= max(l):
        return True

看起来似乎需要找一个列表和一个列表中的元素,元素不在列表的最大和最小值之间。但其实 iteml 都可以是字符串,此时 item 可以是一个长度大于等于 2 的字符串,而 min(l)max(l) 都是按单个字符计算的。一个可能的答案是 'aa', 'aaa'

def challenge_16(self, answer):
    item, l = answer
    if item == 233 and item in l and l in l:
        return True

看似不可能。我们可以从 l in l 开始思考,Python 中什么字面量可以 in 它自己呢?可以是字符串或者 bytes。对 bytes 进行遍历时,元素是 0~255 范围的 int,所以 233, b'\xe9' 可以满足条件。

def challenge_17(self, answer):
    item, l = answer
    if l[0] == item and item not in l:
        return True

l 取下标 0 得到的元素并不 in l,这可能吗?可以想想 Python 的各种基本类型,其中 dict 就可以满足我们的要求,因为对 dict 类型取下标拿到的是 value,而判断对象是否 in 一个 dict 时,是根据 key 的集合判断的。一个可能的答案是 1, {0:1}

def challenge_18(self, answer):
    a, b = answer
    if (a - b) != -(b - a):
        return True

这个小题有点 tricky,看起来使用各种 listdictset 之类的东西搞不定了。我们可以拿出杀手锏,浮点数中的特殊值,infnan。这种浮点数并不能直接输入,但经过简单尝试可以发现,输入很大的数,例如 1e999,就可以得到 inf,而 inf - inf 可以得到 nannan 有一个性质就是 nan == nan 并不成立,所以这道题的答案可以是 1e999, 1e999

def challenge_19(self, answer):
    if answer.isdecimal():
        if len(answer) < 5:
            if sum(ord(c) - ord("0") for c in answer) == 23333:
                return True

长度小于 5 的字符串,每个字符都是 decimal,并且取 Unicode 码之后加起来需要等于 23333。我们可以发现,满足 isdecimal() 条件的字符,不只有 0~9 这 10 个数字,还有很多各种各样的 Unicode 字符。我们可以简单写一个程序输出它们:

for i in range(0x110000):
    if chr(i).isdecimal():
        print(i)

在输出的这些 Unicode 码中,找到 5 个加起来是 23333 并不难,可以在适当的范围内手工尝试,也可以写程序搜索解决。例如一个可行的解是 '᱙᱙᱃۰'

def challenge_20(self, answer):
    if len(set(str(x) for x in answer)) == 7 and all(x == 0 for x in answer):
        return True

我们需要找到 7 个对象,它们转化为字符串之后各不相同,但是每个对象却都和 0 相等。比较好想到的例子是 00.0False0j,它们分别是 intfloatboolcomplex 四种 Python 数值类型的对象。可 Python 只有这四种数值类型,另外三个去哪里找呢?

我们可以发现,浮点数标准中,是存在 0.0-0.0 两个表示 0 的值的,它们相等。Python 中,str(-0.0) 可以得到 '-0.0' 这个字符串。我们可以类似构造出复数类型下面的各种包含浮点 0.0-0.0 的组合。最终的 7 个对象是:0, 0.0, -0.0, False, -0.0-0.0j, -0.0j, 0j

其实我作为出题人不知道是否有第 8 种满足条件的对象,我自己尽力找只能找到这 7 种。我非常期待有人可以构造出第 8 种甚至更多,如果你找到了,一定记得告诉我😑

其他

对于所有小题,如果你有其他更巧妙的解法,欢迎补充。

关于 Python 冷知识的一些参考资料:

https://github.com/satwikkansal/wtfpython

https://github.com/cosmologicon/pywat

You can’t perform that action at this time.