变量相关面试题

1. 变量相关

1.1 python的内置数据类型都有哪些

难度指数: ★
重要指数: ★

这是一个送分题,死记硬背即可,答案如下

int float  str(字符串) list(列表) tuple(元组) dict(字典) set(集合)

回答到这里,就已经ok了,如果想表现的更完美,还可以把bytes(字节串) 也加上,体现你对基础数据类型的掌握深度,bytes是python3以后才有的数据类型。

1.2 如何在一个函数内部修改全局变量

难度指数: ★★
重要指数: ★

一般回答

在函数内使用global关键字

a = 10

def test():
    global a
    a = 20

test()
print(a)        # 20

更优回答

如果想面试官面前给自己加分,答案就不能止于在函数内部使用global。

如果全局变量是不可变对象,那么使用global就对了,如果全局变量是可变对象,比如是一个字典,那么在函数内使用字典时,无需使用global,直接使用字典即可。

修改全局变量,是一个模糊的表述,是修改变量的指向,还是修改变量所指向对象的内容?我们把问题考虑的全面一点,就区分可变对象与不可变对象来回答。

这个问题,牵扯出了可变对象与不可变对,还有变量的作用于问题,这两部分内容我会在其他面试题里讲解,面试官可能会朝着这两个方向继续提问。

1.3 哪些是可变对象,哪些是不可变对象,他们的区别是什么

难度指数: ★★★
重要指数: ★★★

python的内置数据类型中,可变对象有列表,字典,集合
不可变对象有int,float, str,元组

以上,请死记硬背

接下来,要回答最关键的部分,他们的区别是什么。

想要回答这个问题,你先要指出python的变量是对象的引用。这是关键的得分点,因为涉及到变与不变这个概念是通过变量重新赋值来阐述的。

不论是可变,还是不可变,都是针对变量所指向的对象的值而言的。

a = 5
a = 6

有人会有疑问,a的值最开始是6,后来重新赋值成6,这分明是可变啊。这里理解的误区在于把变量指向的改变理解对象值的改变。5这个对象就在内存中,它没有发生任何改变,发生改变的是a这个变量的指向从5变成了6

a = [1]
a.append(2)

列表是可变对象,列表里的元素可以增加,可以减少,可以发生变化。

如果你认为已经对可变对象和不可变对象理解了,请看下面这个题目

lst = [1, 2, 3]
for item in lst:
    item = item + 1

print(lst)

lst = [[1], [2], [3]]
for item in lst:
    item.append(0)

print(lst)

请思考,两次print语句输出的内容分别是什么,答案是

[1, 2, 3]
[[1, 0], [2, 0], [3, 0]]

第一段代码,遍历列表的过程中,虽然执行了对item加1的操作,但是,item仅仅是指向了列表里的元素,而列表里的元素都是不可变对象,你无法修改它。item = item + 1 这条语句,创建了一个新的对象,然后重新赋值给item,item改变了指向,没有改变列表列的任何数据。

第二段代码,遍历列表的过程中,item指向了列表lst中的元素,而lst中的元素都是列表,item.append(0)执行时,并没有改变item的指向,而是向所指向的列表里添加新的元素。

关于可变对象和不可变对象的理解,你可以参考这两篇文章

  1. 变量引用
  2. 可变对象与不可变对象

1.4 简述python变量的作用域

难度指数: ★★★
重要指数: ★★★

理解变量作用域,是理解闭包,装饰器的关键。

对于这个问题,我们首先要回答出python的变量有几种作用域

  1. 局部作用域
  2. 嵌套作用域
  3. 全局作用域
  4. 内置作用域

对于这些概念,我们用最简单的方法来理解

局部作用域是指函数内的作用域,函数内的变量自然也就是局部变量

嵌套作用域,一个函数的内部又定义了一个函数,那么这样就产生了嵌套作用域,嵌套作用域是一个相对概念

全局作用域,每个模块都是一个全局作用域

内置作用域,系统内固定模块里定义的变量,如预定义在builtin 模块内的变量

如果编码习惯不好,内置作用域会产生一些奇怪的问题

a = 3
b = 5
sum = a + b
print(sum)

lst = [1, 2, 3]
print(sum(lst))

上面的代码会报错

TypeError: 'int' object is not callable

这个错误产生的原因是因为第3行的变量sum 覆盖了内置作用域的sum函数,最后一行代码中,sum是一个int类型的数据,而不是内置函数sum。

1.5 is 和 == 的区别

难度指数: ★★★
重要指数: ★★

is是身份运算符,而 == 是比较关系运算符

对于变量a, b ,如果他们内存地址相同,则a is b 的结果是True

如果a, b 的值相同,a == b 的结果是True

那么,有没有什么例子,是a == b成立,而a is b不成立的呢?

a = [1]
b = [1]

print(a == b)       # True
print(a is b)       # False

print(id(a), id(b))

使用id()可以查看对象的内存地址,a和b的内存地址是不相同的。

在交互式解释器里执行下面的代码

>>> a = 98434
>>> b = 98434
>>> a is b
False
>>> a == b
True
>>> id(a), id(b)
(2138843672464, 2138875607760)

这个例子可以更加有力的解释is 和 == 的区别,这段代码一定要在解释器里执行,如果在pycharm里执行,得到的结果会不一样,因为它运行代码的机制与解释器不同。

这个题目如果继续深挖,就挖到了python的内存池技术,还是在解释器里执行代码

>>> a = 256
>>> b = 256
>>> a is b
True
>>> a = -5
>>> b = -5
>>> a is b
True

为啥a is b的结果是True呢?python认为-5到256的整数是经常被使用的,因此在解释器启动时就创建了这些对象并且放在内存池中,你每次使用这些小的整数时,用的都是内存池里的对象,这样就避免了反复申请创建新对象。

1.6 连接字符串用join还是+

难度指数: ★
重要指数: ★★

一定要用join

lst = ['I', 'like', 'python']
string = ' '.join(lst)
print(string)

string = ''
for item in lst:
    string += item + " "

print(string)

相比于使用+ 对字符串进行连接,join编写代码更容易,当有大量字符串需要连接时,join的速度更快,关键的原因在于使用+连接字符串时,每一次操作都新建一个字符串对象来保存连接后的结果,而join则可以事先申请好一片内存来存储连接后的字符串,避免反复创建字符串对象。

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案