一些有趣的Python特性----阅读《wtfPython》记录

一些有趣的Python特性—-阅读《wtfPython》记录

181214

  1. 看这道题
1
2
3
4
5
>>> row = ['']*3
>>> board = [row]*3
>>> board[0][0] = 'x'
>>> board
???会输出什么
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
>>> row = ['']*3
>>> id(row)
2511596760840
>>> board = [row]*3
>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
# 像最初创建时,row、board中每个元素指向地址都是一样的
>>> id(board[0])
2511596760840
>>> id(board[1])
2511596760840
>>> id(board[2])
2511596760840
>>> id(row[0])
2511562308272
>>> id(row[1])
2511562308272
>>> id(row[2])
2511562308272
# 一旦给其中某个元素赋了值,该元素指向地址就发生了改变
>>> row[0]='x'
>>> row
['x', '', '']
>>> id(row[0])
2511566106048
# 但board中每个元素仍旧指向row对象
>>> board
[['x', '', ''], ['x', '', ''], ['x', '', '']]
# 这个同上面row[0]修改一样
>>> board[0]=['y','z','x']
>>> board
[['y', 'z', 'x'], ['x', '', ''], ['x', '', '']]

这个关键是board是通过一个对象row来创建的,而row对象也是通过一个对象来创建的,所以board[0],board[1],board[2],row都指向了同一列表对象

要是想只改变board[0][0],那就不要通过一个row对象来创建就好了

1
2
3
4
5
6
7
8
9
10
>>> board = [['']*3 for _ in range(3)]
>>> id(board[0])
2511596761672
>>> id(board[1])
2511596761416
>>> id(board[2])
2511596761032
>>> board[0][0] = 'x'
>>> board
[['x', '', ''], ['', '', ''], ['', '', '']]
  1. is not… 和 is (not …)…不同

我想这个不难理解,对非空对象都能判断,主要是None对象的理解。记住下面这个就OK了

1
2
3
4
>>> 1 is (not None)
False
>>> 1 is not None
True
  1. 字符串末尾的反斜杠
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> 'asdf\'
File "<stdin>", line 1
'asdf\'
^
SyntaxError: EOL while scanning string literal
>>> 'asdf\\'
'asdf\\'
>>> r'asdf\'
File "<stdin>", line 1
r'asdf\'
^
SyntaxError: EOL while scanning string literal
>>> r'asdf\\'
'asdf\\\\'
>>> r'as\df\\'
'as\\df\\\\'
>>> r'as\'df\\'
"as\\'df\\\\"
  1. == 优先级高于 not

看下这个会输出什么

1
2
3
4
>>> True == False
???
>>> True == not False
???
1
2
3
4
5
6
7
>>> True == not False
File "<stdin>", line 1
True == not False
^
SyntaxError: invalid syntax
>>> not True == False
True

== 优先级高于not ,所以 True == not False 等同于· (True == not) False ,自然语法错误

== 和 not in 优先级同级

  1. 字符串的隐式连接

看下文会输出什么

1
2
3
4
>>> 'hello'''
???
>>> 'hello' ''
???
1
2
3
4
5
6
7
8
9
10
11
>>> 'hello'''
'hello'
>>> 'hello' ''
'hello'
# 隐式连接,没有空格
>>> 'hello' 'python'
'hellopython'
# 但是三个引号在前,反而会报错
>>> print('''hello')
# 解释器先遇到三引号会认为是多行字符串,就会默认去寻找三个终止的引号,找不到报错
# 像命令行下python解释器不到三引号结尾根本不会打印

补充

  • 列表推导式中,for循环部分居然可以用 _ 来占位

181215

  • 布尔类型是int类型的子类

看下下面会输出什么

1
2
3
4
5
6
>>> dict = {}
>>> dict[True] = "python"
>>> dict[1] = "scala"
>>> dict[1.0] = "spark"
>>> dict[True]
???

结果是spark。布尔类型是int类型的子类,所以True的整数值就是1,False是0。其次,字典中是通过判断key的哈希值是否相等来判断键是否相同的,所以1、1.0、True都是1

See this:Why is bool a subclass of int?

1
2
3
4
5
6
7
8
9
10
>>> some_bool = True
>>> some_bool*"wtf"
'wtf'
>>> some_bool = False
>>> some_bool*"wtf"
# isinstance(object,classinfo)判断object对象是否是已知的classinfo类型
>>> isinstance(True,int)
True
>>> isinstance(False,int)
True

关于isinstance()和type()用法见补充

  • 类变量和类实例变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>> class A:
x = 1

>> class B(A):
pass

>> class C(A):
pass
>>> A.x,B.x,C.x
(1, 1, 1)
>>> C.x =2
>>> A.x,B.x,C.x
(1, 1, 2)
>>> A.x = 3
>>> A.x,B.x,C.x
(3, 3, 2)
>>> a = A()
>>> a.x
3
>>> a.x = 4
>>> a.x,A.x
(4, 3)

首先类B、C都是A的子类,a是A的实例对象,其次要知道一点,类变量和实例变量在内部是通过类对象的 __dict__ 属性来处理. 如果在当前类的字典中找不到的话就去它的父类中寻找

所以,C类给x赋值2后,C中已经有了该实例变量,但B中仍旧是用的父类A的变量。实例a也是如此

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
>>> class SomeClass:
... some_var = 15
... some_list = [5]
... another_list = [5]
... def __init__(self, x):
... self.some_var = x + 1
... self.some_list = self.some_list + [x]
... self.another_list += [x]
...
>>> some = SomeClass(10)
>>> some.some_var
11
>>> some.some_list
[5, 10]
>>> some.another_list
[5, 10]
# 注意这两个
>>> some.some_list is SomeClass.some_list
False
>>> some.another_list is SomeClass.another_list
True
>>> another = SomeClass(15)
>>> another.some_list
[5, 15]
>>> another.another_list
[5, 10, 15]

+= 回原地修改可变对象,并不改变对象,所以some的some_list不是原属性对象,another_list仍是原对象

  • tuple到底可不可变

看下下面这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> some_tuple = ([1],[2],[3])
>>> some_tuple[0] += [2]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> some_tuple
([1, 2], [2], [3])

>>> some_tuple[0] = [2]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> some_tuple
([1, 2], [2], [3])

不可变序列 不可变序列的对象一旦创建就不能再改变. (如果对象包含对其他对象的引用,则这些其他对象可能是可变的并且可能会被修改; 但是,由不可变对象直接引用的对象集合不能更改.)

没错,不可变的tuple确实报了错(tuple不支持修改),但是使用+=时,确实改变了tuple

像上面说的+=回原地修改可变对象,而tuple中+=是两个过程,先+=,所以+操作时已经修改了原对象,但是再次赋值就触犯了元组不能修改规则,抛出异常

首先需要重温+=这个运算符,如a+=b:

  • 对于可变对象(mutable object)如list, +=操作的结果会直接在a对应的变量进行修改,而a对应的地址不变.
  • 对于不可变对象(imutable object)如tuple, +=则是等价于a = a+b 会产生新的变量,然后绑定到a上而已.

参考这个:Python中tuple+=赋值的四个问题

1
2
3
4
5
6
>>> a = some_tuple[0]
>>> a += [3]
>>> a
[1, 2, 3]
>>> some_tuple
([1, 2, 3], [2], [3])

所以元组到底可不可变?从上面可以看到tuple不支持=这种assign操作,但是对其中可变元素的原地修改是可以的

+=, -=, =, /=, //=, %=, *=, <<=, >>=, &=, ^=, |=这些操作符都是原地修改可变对象,不会改变对象

所以,tuple元组的不可变是指元素对象的引用不可变,不能对其再次赋值,但是在其中可变元素对象的引用不被修改前提下,仍旧可以对可变元素对象修改

部分参考如下:

补充

  • isinstance和type方法的异同

二者都可用来判断一个对象的类型,type是直接返回类型。但是isinstance方法会考虑继承的关系,会认为子类是一种父类类型,就像上面提到的True是int类型

1
2
3
4
5
6
7
>>> isinstance(True,int)
True
# 差异
>>> type(True) == int
False
>>> type(True) == bool
True

isinstance(object,classinfo) ,classinfo 可以是直接或间接类名、基本类型或者由它们组成的元组

推荐使用isinstance

  • +=, -=, =, /=, //=, %=, *=, <<=, >>=, &=, ^=, |=这些操作符都是原地修改可变对象,不会改变对象
觉得有帮助的话,不妨加个鸡腿,O(∩_∩)O哈哈~