- 利用Python进行数据分析(原书第2版)
- (美)韦斯·麦金尼
- 4406字
- 2023-07-26 14:31:30
2.3 Python语言基础
在本节,我会给出Python编程核心概念以及语言机制方面的概述。下一章,我会更为详细地介绍Python内建数据结构、函数以及其他内建工具。
2.3.1 语言语义
Python语言的设计非常独特,它侧重于可读性、易用性及清晰性。一部分人则认为它是“可执行的伪代码”。
2.3.1.1 缩进,而不是大括号
Python使用缩进(tab或者空格)来组织代码,而不是像其他语言比如R、C++、Java和Perl那样用大括号。考虑使用for循环来实现排序算法:
for x in array: if x < pivot: less.append(x) else: greater.append(x)
一个冒号代表一个缩进代码块的开始,单个代码块中所有的代码必须保持相同的缩进,直到代码块结束。
无论你喜欢或讨厌这种缩进,缩进就是Python编程者的真实生活。个人看法,缩进使Python的代码比我用过的其他语言更具可读性。刚开始看起来会不太适应,但很快你就会习惯。
我强烈推荐使用四个空格作为你的默认缩进,而不是用tab。很多文本编辑器可以设置用空格替代tab(请照做)。有些人使用tab或者其他数量的空格,但使用两个空格的并不常见。对大多数Python编程者来说,四个空格是标准形式,因此在非特殊原因下我推荐使用四个空格来缩进。
目前为止,你见到的Python语句都不是以分号结尾的。然而分号也是可以用于在一行内将多条语句之间进行分隔:
a = 5; b = 6; c = 7
我们不鼓励在Python中将多条语句写成一行,因为这样会使代码可读性下降。
2.3.1.2 一切皆为对象
Python语言的一个重要特征就是对象模型的一致性。每一个数值、字符串、数据结构、函数、类、模块以及所有存在于Python解释器中的事物,都是Python对象。每个对象都会关联到一种类型(例如字符串、函数)和内部数据。在实践中,一切皆为对象使得语言非常灵活,甚至函数也可以被当作对象来操作。
2.3.1.3 注释
所有写在#号之后的文本会自动被Python解释器忽略。因此通常使用#在代码中添加注释。有时候你会想排除部分代码但又不想删除,一个简单的解决办法就是把代码注释掉:
results = [] for line in file_handle: # 当前保持行为空 # if len(line) == 0: # 则继续 results.append(line.replace('foo', 'bar'))
注释也可以写在一行被执行代码的后面。部分编程者更习惯把注释写在特定的一行后面,这在很多时候有用:
print("Reached this line") # 简单的状态报告
2.3.1.4 函数和对象方法的调用
调用函数时,向函数括号里传递0或多个参数,通常会把返回值赋值给一个变量:
result = f(x, y, z) g()
几乎所有的Python对象都有内部函数,称为方法,可以访问到对象内部的内容。你可以通过以下语法调用它们:
obj.some_method(x, y, z)
函数传参既可以是位置参数也可以是关键字参数:
result = f(a, b, c, d=5, e='foo')
之后会详细介绍Python的参数机制。
2.3.1.5 变量和参数传递
在Python中对一个变量(或者变量名)赋值时,你就创建了一个指向等号右边对象的引用。实际上,当有一个整数列表时:
In [8]: a = [1, 2, 3]
假设我们将a赋值给一个新的变量b:
In [9]: b = a
在某些语言中,会是数据[1, 2, 3]被拷贝的过程。在Python中,a和b实际上是指向了相同的对象,即原来的[1, 2, 3](在图2-7中示范)。你可以通过向a中添加一个元素,然后检查b来证明:
In [10]: a.append(4) In [11]: b Out[11]: [1, 2, 3, 4]
图2-7:两个引用指向同一个对象
理解Python引用语义中复制数据的时机、方法和原因的机制,对于利用Python处理大数据集尤其重要。
赋值也被称为绑定,这是因为我们将一个变量名绑定到了一个对象上。已被赋值的变量名有时也会被称为被绑定变量。
当你将对象作为参数传给一个函数时,指向原始对象的新的本地变量就会被创建而无须复制。如果你将一个新的对象绑定到一个函数内部的变量上,这种变更不会在上级范围中产生影响。因此,更换可变参数的内部值是可以做到的。假设我们有如下函数:
def append_element(some_list, element): some_list.append(element)
之后我们可以得到以下结果:
In [27]: data = [1, 2, 3] In [28]: append_element(data, 4) In [29]: data Out[29]: [1, 2, 3, 4]
2.3.1.6 动态引用、强类型
与Java、C++等大多数编译型语言不同,Python中的对象引用并不涉及类型。以下操作是没有问题的:
In [12]: a = 5 In [13]: type(a) Out[13]: int In [14]: a = 'foo' In [15]: type(a) Out[15]: str
变量对于对象来说只是特定命名空间中的名称;类型信息是存储在对象自身之中的。一些人可能会急于将Python总结为“非类型化语言”。这并不准确,考虑以下示例:
In [16]: '5' + 5 -------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-16-f9dbf5f0b234> in <module>() ----> 1 '5' + 5 TypeError: must be str, not int
在某些语言中,例如Visual Basic中,字符串’5’可能会隐式地转换为整数,然后得到10。而在另一些语言中,例如JavaScript,数字5可能会转换成字符串,生成一个结合字符串’55'。在这里,Python被认为是强类型语言,这意味着所有的对象都拥有一个指定的类型(或类),隐式的转换只在某些特定、明显的情况下发生:
In [17]: a = 4.5 In [18]: b = 2 # 字符串格式化,用于后面访问 In [19]: print('a is {0}, b is {1}'.format(type(a), type(b))) a is <class 'float'>, b is <class 'int'> In [20]: a / b Out[20]: 2.25
了解对象的类型是非常重要的,写出可以处理多种不同输入的函数是非常有用的。你可以使用isinstance函数来检查一个对象是否是特定类型的实例。
In [21]: a = 5 In [22]: isinstance(a, int) Out[22]: True
isinstance接受一个包含类型的元组,你可以检查对象的类型是否在元组中的类型中:
In [23]: a = 5; b = 4.5 In [24]: isinstance(a, (int, float)) Out[24]: True In [25]: isinstance(b, (int, float)) Out[25]: True
2.3.1.7 属性和方法
Python中的对象通常都会有属性(Python对象“内部”存储的其他对象)和方法(与对象内部对象有关的函数,相关的对象可以连接到对象内部数据)。属性和方法都可以通过形如obj.attribute_name的语法进行调用:
In [1]: a = 'foo' In [2]: a.<Press Tab> a.capitalize a.format a.isupper a.rindex a.strip a.center a.index a.join a.rjust a.swapcase a.count a.isalnum a.ljust a.rpartition a.title a.decode a.isalpha a.lower a.rsplit a.translate a.encode a.isdigit a.lstrip a.rstrip a.upper a.endswith a.islower a.partition a.split a.zfill a.expandtabs a.isspace a.replace a.splitlines a.find a.istitle a.rfind a.startswith
属性和方法也可以通过getattr函数获得:
In [27]: getattr(a, 'split') Out[27]: <function str.split>
在其他的语言中,通过变量名访问对象通常被称为“反射”。虽然本书中我们不会广泛使用getattr以及相关的hasattr和setattr函数,但它们可以用来高效地编写通用、可复用的代码。
2.3.1.8 鸭子类型
通常情况下你并不关心某个对象的具体类型,而是关心它是否拥有某个特殊的方法或行为。“鸭子类型”的说法源于“一个东西走起来像鸭子叫起来像鸭子,那它就是鸭子”。例如,你可以验证一个对象如果实现了迭代器协议,那它一定是可以迭代的。对于很多对象来说,它包含了一个__iter__魔术方法,但使用iter函数是一个更好的、独立的方法:
def isiterable(obj): try: iter(obj) return True except TypeError: # 不可遍历 return False
对于绝大部分Python容器类型的对象,iter函数都会返回True:
In [29]: isiterable('a string') Out[29]: True In [30]: isiterable([1, 2, 3]) Out[30]: True In [31]: isiterable(5) Out[31]: False
在编写接受多种类型输入的函数时,我总是使用这个功能。常见的案例就是写接受任意序列类型(列表、元组、n维数组),甚至是一个迭代器的函数时使用这项功能。你可以先检查对象是否是一个列表(或者一个NumPy数组),如果不是就把它转换为列表:
if not isinstance(x, list) and isiterable(x): x = list(x)
2.3.1.9 导入
在Python中,模块就是以.py为后缀名并包含Python代码的文件。假设我们有以下模块:
# some_module.py PI = 3.14159 def f(x): return x + 2 def g(a, b): return a + b
假如我们想从另一个相同路径下的文件连接到some_module.py中定义的变量和函数,我们可以这样做:
import some_module result = some_module.f(5) pi = some_module.PI
或者:
from some_module import f, g, PI result = g(5, PI)
通过使用as关键字,你可以对导入内容给予不同的变量名:
import some_module as sm from some_module import PI as pi, g as gf r1 = sm.f(pi) r2 = gf(6, pi)
2.3.1.10 二元运算符和比较运算
大部分二元数学运算操作和比较运算都和你预想的一致:
In [32]: 5-7 Out[32]: -2 In [33]: 12 + 21.5 Out[33]: 33.5 In [34]: 5 <= 2 Out[34]: False
表2-3是所有可用的二元运算符。
检查两个引用是否指向同一个对象,可以使用is关键字。is not在你想检查两个对象不是相同对象时也是有效的。
In [35]: a = [1, 2, 3] In [36]: b = a In [37]: c = list(a) In [38]: a is b Out[38]: True In [39]: a is not c Out[39]: True
因为list函数总是创建一个新的Python列表(即一份拷贝),我们可以确定c与a是不同的。is和==是不同的,因为在这种情况下我们可以得到:
In [40]: a == c Out[40]: True
is和is not的常用之处是检查一个变量是否为None,因为None只有一个实例:
In [41]: a = None In [42]: a is None Out[42]: True
表2-3:二元操作符
2.3.1.11 可变对象和不可变对象
Python中的大部分对象,例如列表、字典、NumPy数组都是可变对象,大多数用户定义的类型(类)也是可变的。可变对象中包含的对象和值是可以被修改的:
In [43]: a_list = ['foo', 2, [4, 5]] In [44]: a_list[2] = (3, 4) In [45]: a_list Out[45]: ['foo', 2, (3, 4)]
还有其他一些对象是不可变的,比如字符串、元组:
In [46]: a_tuple = (3, 5, (4, 5)) In [47]: a_tuple[1] = 'four' -------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-47-b7966a9ae0f1> in <module>() ----> 1 a_tuple[1] = 'four' TypeError: 'tuple' object does not support item assignment
请牢记,你可以修改一个对象不代表你应该那么做。修改行为通常会有副作用。例如,当编写一个函数时,任何副作用都应当显式地在函数的文档或注释中告诉使用者。如果可能的话,我建议使用不可变性,避免副作用,尽管不可变对象中也可能包含可变对象。
2.3.2 标量类型
Python的标准库中拥有一个小的内建类型集合,用来处理数值数据、字符串、布尔值(True或False)以及日期和时间。这类的“单值”类型有时被称为标量类型,我们在本书中称之为标量。表2-4是主要的标量类型。由于标准库中含有datetime模块,日期和时间的处理将单独讨论。
表2-4:标准Python标量类型
2.3.2.1 数值类型
基础的Python数字类型就是int和float。int可以存储任意大小数字:
In [48]: ival = 17239871 In [49]: ival ** 6 Out[49]: 26254519291092456596965462913230729701102721
浮点数在Python中用float表示,每一个浮点数都是双精度64位数值。它们可以用科学计数法表示:
In [50]: fval = 7.243 In [51]: fval2 = 6.78e-5
整数除法会把结果自动转型为浮点数:
In [52]: 3 / 2 Out[52]: 1.5
如果需要C风格的整数除法(去除了非整数结果的小数部分),可以使用整除操作符//:
In [53]: 3 // 2 Out[53]: 1
2.3.2.2 字符串
很多人是因为Python强大、灵活的内建字符串操作功能而使用Python的。你可以用单引号’或双引号"写一个字符串字面值:
a = 'one way of writing a string' b = "another way"
对于含有换行的多行字符串,你可以使用三个单引号’'’或三个双引号""":
c = """ This is a longer string that spans multiple lines """
你可能会惊讶字符串c实际上包含了四行文本;"""后的换行和lines后的换行都是包含在字符串中的。我们可以使用count方法来计算c的回车符:
In [55]: c.count('\n') Out[55]: 3
Python的字符串是不可变的,你无法修改一个字符串:
In [56]: a = 'this is a string' In [57]: a[10] = 'f' -------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-57-5ca625d1e504> in <module>() ----> 1 a[10] = 'f' TypeError: 'str' object does not support item assignment In [58]: b = a.replace('string', 'longer string') In [59]: b Out[59]: 'this is a longer string'
操作后,变量a就是不可修改的:
In [60]: a Out[60]: 'this is a string'
很多Python对象可以通过str函数转成字符串:
In [61]: a = 5.6 In [62]: s = str(a) In [63]: print(s) 5.6
字符串是Unicode字符的序列,因此可以被看作是除了列表和元组(下一章将会详细讨论)外的另一种序列:
In [64]: s = 'python' In [65]: list(s) Out[65]: ['p', 'y', 't', 'h', 'o', 'n'] In [66]: s[:3] Out[66]: 'pyt'
s[:3]这种语法被称为切片,在多种Python序列中都有实现。本书后续将会更为详细地介绍,这个语法将在本书中广泛使用。
反斜杠符号\是一种转义符号,它用来指明特殊符号,比如换行符号\n或Unicode字符。如果要在字符串中写反斜杠则需要将其转义:
In [67]: s = '12\\34' In [68]: print(s) 12\34
如果你有一个不含特殊符号但含有大量反斜杠的字符串时,你会觉得很烦。幸运的是,你可以在字符串前面加一个前缀符号r,表明这些字符是原生字符:
In [69]: s = r'this\has\no\special\characters' In [70]: s Out[70]: 'this\\has\\no\\special\\characters'
r是raw的简写,表示原生的。
将两个字符串结合到一起产生一个新字符串:
In [71]: a = 'this is the first half ' In [72]: b = 'and this is the second half' In [73]: a + b Out[73]: 'this is the first half and this is the second half'
字符串格式化是另一个重要主题。Python 3中进行字符串格式化的方式又拓展了。我将简明地介绍主流方式中的一种。字符串对象拥有一个format方法,可以用来代替字符串中的格式化参数,并产生一个新的字符串:
In [74]: template = '{0:.2f} {1:s} are worth US${2:d}'
在这个字符串中,
· {0:.2f}表示将第一个参数格式化为2位小数的浮点数
· {1:s}表示将第二个参数格式化为字符串
· {2:d}表示将第三个参数格式化整数
为了替代这些格式化参数,我们将含有参数的序列传给format方法:
In [75]: template.format(4.5560, 'Argentine Pesos', 1) Out[75]: '4.56 Argentine Pesos are worth US$1'
字符串格式化是一个深奥的主题;有多种方式和大量的选项可用于控制如何把值格式化到结果字符串中。如果要深入地了解这些,我建议参考Python官方文档(https://docs.python.org/3.6/library/string.html)。
我将在第8章中更为详细地介绍如何在数据分析中进行通用字符串处理。
2.3.2.3 字节与Unicode
在现代Python中(此处指Python 3.0及以上), Unicode成为字符串类型的一等类,用于更好地兼容处理ASCII和非ASCII文本。在Python的早期版本中,字符串完全是字节,而没有显式的Unicode编码。如果你知道字符的编码,你就可以将其转换为Unicode。让我们看以下示例:
In [76]: val = "español" In [77]: val Out[77]: 'español'
我们可以使用enocde方法将这个Unicode字符串转换为UTF-8字节:
In [78]: val_utf8 = val.encode('utf-8') In [79]: val_utf8 Out[79]: b'espa\xc3\xb1ol' In [80]: type(val_utf8) Out[80]: bytes
假设你知道一个字节对象的Unicode编码,你可以再使用decode方法进行解码:
In [81]: val_utf8.decode('utf-8') Out[81]: 'español'
由于偏好使用UTF-8编码的人越来越多,你会因为历史遗留问题在数据中碰到一些不同的编码:
In [82]: val.encode('latin1') Out[82]: b'espa\xf1ol' In [83]: val.encode('utf-16') Out[83]: b'\xff\xfee\x00s\x00p\x00a\x00\xf1\x00o\x00l\x00' In [84]: val.encode('utf-16le') Out[84]: b'e\x00s\x00p\x00a\x00\xf1\x00o\x00l\x00'
在文件的上下文中,最常遇到的就是bytes(字节)对象,在字节对象中我们并不想让所有的数据都隐式地解码为Unicode字符串。
你可以在字符串前加前缀b来定义字符文本,尽管可能很少需要这么做:
In [85]: bytes_val = b'this is bytes' In [86]: bytes_val Out[86]: b'this is bytes' In [87]: decoded = bytes_val.decode('utf8') In [88]: decoded # this is str (Unicode) now Out[88]: 'this is bytes'
2.3.2.4 布尔值
Python中的布尔值写作True和False。比较运算和其他条件表达式的结果为True或False。布尔值可以与and和or关键字合用:
In [89]: True and True Out[89]: True In [90]: False or True Out[90]: True
2.3.2.5 类型转换
str、bool、int和float既是数据类型,同时也是可以将其他数据转换为这些类型的函数:
In [91]: s = '3.14159' In [92]: fval = float(s) In [93]: type(fval) Out[93]: float In [94]: int(fval) Out[94]: 3 In [95]: bool(fval) Out[95]: True In [96]: bool(0) Out[96]: False
2.3.2.6 None
None是Python的null值类型。如果一个函数没有显式地返回值,则它会隐式地返回None:
In [97]: a = None In [98]: a is None Out[98]: True In [99]: b = 5 In [100]: b is not None Out[100]: True
None还可以作为一个常用的函数参数默认值:
def add_and_maybe_multiply(a, b, c=None): result = a + b if c is not None: result = result * c return result
从技术角度来说,None不仅是一个关键字,它还是NoneType类型的唯一实例:
In [101]: type(None) Out[101]: NoneType
2.3.2.7 日期和时间
Python中内建的datetime模块,提供了datetime、data和time类型。可能正如你想象的,datetime类型是包含日期和时间信息的,最常用的方法是:
In [102]: from datetime import datetime, date, time In [103]: dt = datetime(2011, 10, 29, 20, 30, 21) In [104]: dt.day Out[104]: 29 In [105]: dt.minute Out[105]: 30
对于datetime实例,你可以分别使用date和time方法获取它的date和time对象:
In [106]: dt.date() Out[106]: datetime.date(2011, 10, 29) In [107]: dt.time() Out[107]: datetime.time(20, 30, 21)
strftime方法将datetime转换为字符串
In [108]: dt.strftime('%m/%d/%Y %H:%M') Out[108]: '10/29/2011 20:30'
字符串可以通过strptime函数转换为datetime对象:
In [109]: datetime.strptime('20091031', '%Y%m%d') Out[109]: datetime.datetime(2009, 10, 31, 0, 0)
表2-5是完整的格式化详细说明。
当你在聚合或分组时间序列数据时,会常常用到替代datetime时间序列中的一些值,比如将分钟、秒替换为0:
In [110]: dt.replace(minute=0, second=0) Out[110]: datetime.datetime(2011, 10, 29, 20, 0)
由于datetime.datetime是不可变类型,以上的方法都是产生新的对象。
两个不同的datetime对象会产生一个datatime.timedelta类型的对象:
In [111]: dt2 = datetime(2011, 11, 15, 22, 30) In [112]: delta = dt2- dt In [113]: delta Out[113]: datetime.timedelta(17, 7179) In [114]: type(delta) Out[114]: datetime.timedelta
输出的timedelta(17, 7179)表示时间间隔为17天又7,179秒。
将timedelta加到一个datetime上将产生一个新的对象。
In [115]: dt Out[115]: datetime.datetime(2011, 10, 29, 20, 30, 21) In [116]: dt + delta Out[116]: datetime.datetime(2011, 11, 15, 22, 30)
表2-5:Datetime格式化详细说明(ISO C89兼容)
2.3.3 控制流
其他语言中的标准控制流概念在Python中也是通过一些条件逻辑、循环等内建关键字的方式实现的。
2.3.3.1 if、elif和else
if语句是最广为人知的控制流类型。它检查一个条件是否为True,请看以下代码示例:
if x < 0: print('It's negative')
一个if语句可以接多个elif代码块和一个else代码块,如果所有的elif条件均为False,则执行else代码块:
if x < 0: print('It's negative') elif x == 0: print('Equal to zero') elif 0 < x < 5: print('Positive but smaller than 5') else: print('Positive and larger than or equal to 5')
如果某个条件为True,则后面的elif和else代码块则不会执行。当使用and和or进行混合条件判断时,条件判断是从左到右的并且在and或or两侧的条件会有“短路”现象:
In [117]: a = 5; b = 7 In [118]: c = 8; d = 4 In [119]: if a < b or c > d: .....: print('Made it') Made it
在本例中,c>d是不会去判断的,因为第一个比较判断的值是True。
链式比较也是可以的:
In [120]: 4 > 3 > 2 > 1 Out[120]: True
2.3.3.2 for循环
for循环用于遍历一个集合(例如列表或元组)或一个迭代器。标准的for循环语法如下:
for value in collection: # 用值做些什么
使用continue关键字可以跳过conitnue后面的代码进入下一次循环。考虑以下代码,对列表中的非None值进行累加:
sequence = [1, 2, None, 4, None, 5] total = 0 for value in sequence: if value is None: continue total += value
使用break关键字可以结束一个for循环。以下代码会对列表元素累加,直到5出现:
sequence = [1, 2, 0, 4, 6, 5, 2, 1] total_until_5 = 0 for value in sequence: if value == 5: break total_until_5 += value
break关键字只结束最内层的for循环;外层的for循环会继续运行:
In [121]: for i in range(4): .....: for j in range(4): .....: if j > i: .....: break .....: print((i, j)) .....: (0, 0) (1, 0) (1, 1) (2, 0) (2, 1) (2, 2) (3, 0) (3, 1) (3, 2) (3, 3)
更详细地说,如果集合或迭代器中的元素是一个序列(比如元组或列表),它们可以在for循环语句中很方便地通过拆包成为变量:
for a, b, c in iterator: # 做些什么
2.3.3.3 while循环
while循环会在条件符合时一直执行代码块,直到条件判断为False或显式地以break结尾时才结束:
x = 256 total = 0 while x > 0: if total > 500: break total += x x = x // 2
2.3.3.4 pass
pass就是Python中的“什么都不做”的语句。它用于在代码段中表示不执行任何操作(或者是作为还没有实现的代码占位符);之所以需要它,是因为Python使用了缩进来分隔代码块:
if x < 0: print('negative! ') elif x == 0: # 在这里放点聪明的代码 pass else: print('positive! ')
2.3.3.5 range
range函数返回一个迭代器,该迭代器生成一个等差整数序列:
In [122]: range(10) Out[122]: range(0, 10) In [123]: list(range(10)) Out[123]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
起始、结尾、步进(可以是负的)可以传参给range函数:
In [124]: list(range(0, 20, 2)) Out[124]: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] In [125]: list(range(5, 0, -1)) Out[125]: [5, 4, 3, 2, 1]
如你所见,range产生的整数包含起始但不包含结尾。range常用于根据序列的索引遍历序列:
seq = [1, 2, 3, 4] for i in range(len(seq)): val = seq[i]
尽管你可以使用函数,比如list函数将range产生的整数存储在其他数据结构,但通常默认的迭代器形式就是你想要的。以下代码会将0到99,999中所有可以被3或5整除的整数相加:
sum = 0 for i in range(100000): # % 是求模操作符 if i % 3 == 0 or i % 5 == 0: sum += i
虽然range产生的序列可以是任意大小,但在任意给定时间内的内存使用量是非常小的。
2.3.3.6 三元表达式
Python中的三元表达式允许你将一个if-else代码块联合起来,在一行代码或一个语句中生成数据。语法如下:
value = true-expr if condition else false-expr
此处的true-expr和false-expr可以是任意Python表达式.它与以下更详细的代码效果一致:
if condition: value = true-expr else: value = false-expr
以下是具体的示例:
In [126]: x = 5 In [127]: 'Non-negative' if x >= 0 else 'Negative' Out[127]: 'Non-negative'
在if-else代码块中,是按顺序逐个执行的。因此,三元表达式的“if”侧和“else”侧可能会包含计算消耗,但是只有真分支会被采用。
虽然我们可以使用三元表达式来压缩代码量,但请注意如果条件以及真假表达式非常复杂,可能会牺牲可读性。