老齐教室

初学者专题:变量和赋值

作者:老齐

对于初学Python者,除了看书(《跟老齐学Python:轻松入门》或者《Python大学实用教程》,均为电子工业出版社出版)、或者看视频(网易云课堂、CSDN上均有老齐的视频课程),还要进行专题性总结。比如本文,就是要帮助学习者,对变量赋值这两个非常基本、几乎无处不在的内容作为一个专题进行总结。

对象

Python语言既能够实现面向过程,也能够实现面向对象编程。而面向对象,是当前软件开发的主流编程思想。在我的两本书中,我都强调了面向对象——不要认为这种编程思想不适用于初学者,更不要认为它有多难。

并且,Python语言中已经有这样一个明确的概念:万物皆对象,不论是数字、整数、函数、类,等等,都是对象。

Python语言中的对象,可以理解为语言中的“实际物体”,它一经创建,存储器中就开辟出一个空间保存它,因而也就有了一个内存地址。

比如:

1
2
>>> id(3.14)
4312572400

这里返回的十进制数字,就代表了存储器为浮点数对象3.14分配的内存地址。

另外,Python中的任何对象都有类型。在Python中,类型就是类。在Python创立之初,这两个没有统一,后来将“类”和“类型”统一了起来,这样我们就可以把每一个具体的数据,看成是某个“类”的实例,而那个“类”就是这个实例的“类型”。

比如:

1
2
>>> type(3.14)
<class 'float'>

返回值显示,3.14float类的实例,也就是float类型。

下面自定义一个类,然后创建实例,再看看那个实例的类型:

1
2
3
4
5
6
>>> class Book:
... book = "跟老齐学Python:轻松入门"
...
>>> my_book = Book()
>>> type(my_book)
<class '__main__.Book'>

除了返回的类的具体内容不同之外,Book()(注意,这是对象,my_book引用了这个对象,所以,也可以说是my_book,对此的详细说明请阅读本文后续内容)和3.14都是所对应类的实例——返回值格式相同,因此它们也都是所对应类的那种“类型”。

变量

在Python中使用变量,非常方便,可以用“召之即来挥之即去”形容,也就是不需要“创建”变量,用的时候信手拈来。

1
2
3
4
5
>>> int a
File "<stdin>", line 1
int a
^
SyntaxError: invalid syntax

在有些语言里面,比如Java,要使用某个变量,必须先创建它。但是,如果把这个习惯搬到Python中,就如同上面操作那样,会报错。另外,还要注意,Python中的变量不是对象,它不能单独存在。

1
2
3
4
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined

上面的演示,就是想用一个变量a,但是,只是把它单独放在那里,就如同在数学上,“假设变量a”,这样做是不行的。在Python中,变量必须和某个对象关联起来。

1
2
3
4
5
>>> a = 2
>>> id(a)
4308314816
>>> id(2)
4308314816

在上面的示例中,a=2就是将变量a与对象2关联了起来。注意,这里的变量a不是像容器那样,将对象2放入其中,而是如同标签那样,贴到了对象2上,于是乎通过这个标签就可以找到那个对象。

id(a)并不是返回变量a的内存地址——它不是对象,而是返回了它所引用的对象2的内存地址。再参考后面的id(2),可以看到两者内存地址一样,也就是说证明了a这个变量引用的对象就是2

但是,如果在交互模式中,做下面的操作,就令人匪夷所思了。

1
2
3
4
5
>>> a = 1.23
>>> id(1.23)
4312572400
>>> id(a)
4311689008

按照前面的说法,这个怎么解释?!

再解释这个之前,先来看下面的操作。

创建一个文件,比如命名为idvalue.py,然后在文件中写入如下代码:

1
2
3
4
5
6
7
8
9
10
11
#coding:utf-8

a = 1.23
id_value1 = id(a)
id_value2 = id(1.23)

print(f"id(a): {id_value1}")
print(f"id(1.23): {id_value2}")

if id_value1 == id_value2:
print("id() 返回值相同")

这段程序,跟前面交互模式里面的一样。但是,请注意接下来的事情。保存文件之后,执行它。

1
2
3
4
% python3 idvalue.py
id(a): 4327795888
id(1.23): 4327795888
id() 返回值相同

看执行结果。这里得到的a1.23的内存地址居然是一样的。

tu

神奇吗?

要解释这种神奇,话就有点长了,要从交互模式的特点开始说起。交互模式,是我们学习和做简单练习的好地方,但是,它有一个缺点,“记忆力太差”,每一行执行完毕,就会将该行“忘记”,下一行又从头开始。当执行了a = 1.23之后,本来内存中已经创建了1.23这个对象,但是,当再次执行id(1.23)时,因为两个不完全一样,交互模式的解析器忘记了前面的1.23,于是乎又在存储器中重新创建了id(1.23)中的1.23对象。

可为什么a = 2不如此呢?因为Python还有一个习惯,把-256~256这些整数,在内存中有“常住户口”。

1
2
3
4
5
6
7
8
9
10
11
>>> b = 256
>>> id(b)
4308322944
>>> id(256)
4308322944
>>>
>>> c = 257
>>> id(c)
4312572688
>>> id(257)
4312572624

但是,如果在一个程序文件里,Python解析器的“记忆力”就正常了,不会重复创建同样的对象——当然,如拷贝等有意为之的除外。

至此,我们已经明确,Python中的变量和对象之间是引用关系,正式因为这种引用关系,让Python中实现某些操作就非常容易了。

1
2
3
4
5
6
7
>>> first = 1
>>> second = 2
>>> first, second = second, first
>>> first
2
>>> second
1

在这个示例中,变量first引用了1second引用了2,如果想把这两个变量引用的对象对调一下,在Python里面使用first, second = second, first。这是多么简单、优雅、直接呀。如果在Java里面,就麻烦了——学过C/C++/Java等语言的同学,试一试吧。

由此,我们可以放胆说:Python简单、优雅

还是因为“引用”,一个对象可以被多个变量引用,就相当于一个物体上可以贴多个标签那样。

1
2
3
4
5
>>> a = b = 3.14
>>> a
3.14
>>> b
3.14

“变量引用对象”这个思想,还可以扩展到函数的参数。有的人习惯把函数的参数弄很多名称,比如形参、实参等,并且进行区分。在Python语言中,它们本质上都是变量,只不过是在函数作用于里面使用的变量。当定义函数的时候,虽然那个变量也没有引用对象,但因为是定义函数,这个函数并没有执行,所以,一个没有引用对象的变量是许可存在的。

1
2
3
4
5
6
7
8
9
>>> book = ['python',]
>>> def my_book(b):
... b.append("feature engineering")
... return b
...
>>> my_book(book)
['python', 'feature engineering']
>>> book
['python', 'feature engineering']

上面代码中,第1行创建了一个列表。第2行开始,创建了一个函数,参数是b——这时候称为“形参”,即形式上的参数。其实,因为这个函数并没有调用,所以,那个变量b可以认为是占位符,可以单独存在,不需要引用任何对象。

后面,调用这个函数my_book(book)。这里我们通常形象而简要地说:“将boo传给函数”。这句话其实不是很严谨,但它因为形象简短,所以被广为使用。如果严谨地说——未免啰嗦了,调用这个函数后,发生的是:b引用了变量book所引用的对象['python',]

从后面的执行结果中可以看出,因为函数内和函数外,不同变量引用的对象都是同一个,所以,会发生同样的变化。

对于变量,最后要强调的就是命名习惯:

  • 非数字开头
  • 字母都小写
  • 用有意的单词或者单词组合,多个单词之间用_连接
  • 避免使用与下列项目冲突的单词:内置类型、内置函数、关键词

以下是列出Python关键词的方法:

1
2
3
>>> import keyword
>>> keyword.kwlist
['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']

或者:

1
2
3
4
5
6
7
8
9
10
11
12
>>> help("keywords")
Here is a list of the Python keywords. Enter any keyword to get more help.

False class from or
None continue global pass
True def if raise
and del import return
as elif in try
assert else is while
async except lambda with
await finally nonlocal yield
break for not

有一种常见情况,如下所示:

1
2
3
4
5
>>> list = [1,2,3]
>>> list("laoqi")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'list' object is not callable

这就是因为本来的内置对象类型list,或者内置函数的名称,已经被>>> list = [1,2,3]占用了,后面再使用list,指的就是这个列表。所以,第二行的操作中会报错。

tu

赋值

在理解变量和对象关系的基础上,理解“赋值”或“赋值语句”就简单多了,从本质上讲,赋值就是变量与某对象建立引用关系,只不过,这里不是如前面所说的某个直接的对象,这里的对象可能是经过计算之后得到的,所以,先要知道“表达式”。

所谓表达式,就是对象间用操作符连接,组成一个有意义的式子。

1
2
3
4
>>> 1 + 2
3
>>> "python" + "lang"
'pythonlang'

以上演示的都是表达式,一般而言,表达式要返回一个对象。如果将这个对象再被变量引用,就成为了“赋值”。

1
2
3
>>> py = "python" + "lang"
>>> py
'pythonlang'

当然,前面出现的诸如a = 2也都是赋值语句。

在赋值语句中,=的作用于数学中的等号有很大区别,它表示将变量与对象之间建立引用关系。

1
2
3
4
>>> n = 1
>>> n = n + 1
>>> n
2

对于表达式n = n + 1,这里的=如果是数学中的意义,那么就会出现0=1

对于n = n + 1这种操作,还可以写成:

1
2
3
>>> n += 1
>>> n
3

这种写法称为自增,同样,减、乘、除也都有类似的操作。

对于赋值,必须要说明的是Python3.8中新增的功能:海象运算符

1
2
3
4
5
>>> n = len('python')
>>> if n < 10:
... print(f"the word length is: {n}")
...
the word length is: 6

在第二行开始的条件语句中,要使用n,必须要要在前面通过赋值语句获得。Python3.8的海象赋值运算符,把上面的赋值那一行和第二行融合起来,让代码更简洁。

1
2
3
4
>>> if (i := len('python')) < 10:
... print(f"the word length is: {n}")
...
the word length is: 6

关于海象运算符,本微信公众号有专门文章阐述,请参阅:《Python3.8新语法:海象运算符》

最后,要强调的是,Python中函数、类也都是对象,它们同样能够用于赋值语句。

1
2
3
4
5
6
>>> lam = lambda x: x+3
>>> lam(4)
7
>>> q = lam
>>> q(4)
7

第一行创建了一个lambda函数,这个函数对象用变量lam引用,也是赋值。注意第三行,只有名称才引用对象,如果是lam(),是试图要执行lam引用的对象,所以,q = lam才是赋值,或者说变量q也引用了这个lambda函数。

本文对变量和赋值做了专题总结,供初学者复习,以便加深对相关知识的理解。


专注于软件和AI的公众号

老齐教室

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

关注微信公众号,读文章、听课程,提升技能