第02章 Python语法基础,IPython和Jupyter Notebook (2)

2.3 Python语法基础

在本节中,我将概述基本的Python概念和语言机制。在下一章,我将详细介绍Python的数据结构、函数和其它内建工具。

语言的语义

Python的语言设计强调的是可读性、简洁和清晰。有些人称Python为“可执行的伪代码”。

使用缩进,而不是括号

Python使用空白字符(tab和空格)来组织代码,而不是像其它语言,比如R、C++、JAVA和Perl那样使用括号。看一个排序算法的for循环:

for x in array:
    if x < pivot:
        less.append(x)
    else:
        greater.append(x)

冒号标志着缩进代码块的开始,冒号之后的所有代码的缩进量必须相同,直到代码块结束。不管是否喜欢这种形式,使用空白符是Python程序员开发的一部分,在我看来,这可以让python的代码可读性大大优于其它语言。

笔记:我强烈建议你使用四个空格作为默认的缩进,可以使用tab代替四个空格。许多文本编辑器的设置是使用制表位替代空格。某些人使用tabs或不同数目的空格数,常见的是使用两个空格。大多数情况下,四个空格是大多数人采用的方法,因此建议你也这样做。
你应该已经看到,Python的语句不需要用分号结尾。但是,分号却可以用来给同在一行的语句切分:

a = 5; b = 6; c = 7

Python不建议将多条语句放到一行,这会降低代码的可读性。

万物皆对象

Python语言的一个重要特性就是它的对象模型的一致性。每个数字、字符串、数据结构、函数、类、模块等等,都是在Python解释器的自有“盒子”内,它被认为是Python对象。每个对象都有类型(例如,字符串或函数)和内部数据。在实际中,这可以让语言非常灵活,因为函数也可以被当做对象使用。

注释

任何前面带有井号#的文本都会被Python解释器忽略。这通常被用来添加注释。有时,你会想排除一段代码,但并不删除。简便的方法就是将其注释掉:

results = []
for line in file_handle:
    # keep the empty lines for now
    # if len(line) == 0:
    #   continue
    results.append(line.replace('foo', 'bar'))

也可以在执行过的代码后面添加注释。一些人习惯在代码之前添加注释,前者这种方法有时也是有用的:

print("Reached this line")  # Simple status report

函数和对象方法调用

你可以用圆括号调用函数,传递零个或几个参数,或者将返回值给一个变量:

result = f(x, y, z)
g()

几乎Python中的每个对象都有附加的函数,称作方法,可以用来访问对象的内容。可以用下面的语句调用:

obj.some_method(x, y, z)

函数可以使用位置和关键词参数:

result = f(a, b, c, d=5, e='foo')

后面会有更多介绍。

变量和参数传递

当在Python中创建变量(或名字),你就在等号右边创建了一个对这个变量的引用。考虑一个整数列表:

In [8]: a = [1, 2, 3]

假设将a赋值给一个新变量b:

In [9]: b = a

在有些方法中,这个赋值会将数据[1, 2, 3]也复制。在Python中,a和b实际上是同一个对象,即原有列表[1, 2, 3]。你可以在a中添加一个元素,然后检查b:

In [10]: a.append(4)

In [11]: b
Out[11]: [1, 2, 3, 4]

理解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]

动态引用,强类型

与许多编译语言(如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 [5]: '5' + 5
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-4dd8efb5fac1> in <module>()
----> 1 '5' + 5

TypeError: can only concatenate str (not "int") to str

在某些语言中,例如Visual Basic,字符串‘5’可能被默许转换(或投射)为整数,因此会产生10。但在其它语言中,例如JavaScript,整数5会被投射成字符串,结果是联结字符串‘55’。在这个方面,Python被认为是强类型化语言,意味着每个对象都有明确的类型(或类),默许转换只会发生在特定的情况下,例如:

In [17]: a = 4.5

In [18]: b = 2

# String formatting, to be visited later
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

属性和方法

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函数,使用这些函数可以高效编写原生的、可重复使用的代码。

鸭子类型

经常地,你可能不关心对象的类型,只关心对象是否有某些方法或用途。这通常被称为“鸭子类型”,来自“走起来像鸭子、叫起来像鸭子,那么它就是鸭子”的说法。例如,你可以通过验证一个对象是否遵循迭代协议,判断它是可迭代的。对于许多对象,这意味着它有一个__iter__魔术方法,其它更好的判断方法是使用iter函数:

def isiterable(obj):
    try:
        iter(obj)
        return True
    except TypeError: # not iterable
        return False

这个函数会返回字符串以及大多数Python集合类型为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

我总是用这个功能编写可以接受多种输入类型的函数。常见的例子是编写一个函数可以接受任意类型的序列(list、tuple、ndarray)或是迭代器。你可先检验对象是否是列表(或是NUmPy数组),如果不是的话,将其转变成列表:

if not isinstance(x, list) and isiterable(x):
    x = list(x)

引入

在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)

二元运算符和比较运算符

大多数二元数学运算和比较都不难想到:下图二元运算符


二元运算符

可变与不可变对象

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

记住,可以修改一个对象并不意味就要修改它。这被称为副作用。例如,当写一个函数,任何副作用都要在文档或注释中写明。如果可能的话,我推荐避免副作用,采用不可变的方式,即使要用到可变对象。

标量类型

Python的标准库中有一些内建的类型,用于处理数值数据、字符串、布尔值,和日期时间。这些单值类型被称为标量类型,本书中称其为标量。表2-4列出了主要的标量。日期和时间处理会另外讨论,因为它们是标准库的datetime模块提供的。


主要的标量

数值类型

Python的主要数值类型是intfloat

字符串

许多人是因为Python强大而灵活的字符串处理而使用Python的。你可以用单引号或双引号来写字符串:

a = 'one way of writing a string'
b = "another way"
#对于有换行符的字符串,可以使用三引号,'''或"""都行
c = """
This is a longer string that
spans multiple lines
"""

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'  #r表示raw

In [70]: s
Out[70]: 'this\\has\\no\\special\\characters'

将两个字符串合并,会产生一个新的字符串:

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}表示格式化第一个参数为带有两位小数的浮点数。

  • {1:s}表示格式化第二个参数为字符串。

  • {2:d}表示格式化第三个参数为一个整数。

字节和Unicode

在Python 3及以上版本中,Unicode是一级的字符串类型,这样可以更一致的处理ASCII和Non-ASCII文本。在老的Python版本中,字符串都是字节,不使用Unicode编码。假如知道字符编码,可以将其转化为Unicode。看一个例子:

In [76]: val = "español"

In [77]: val
Out[77]: 'español'

布尔值

Python中的布尔值有两个,True和False。比较和其它条件表达式可以用True和False判断。布尔值可以与and和or结合使用:

In [89]: True and True
Out[89]: True

In [90]: False or True
Out[90]: True

类型转换

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

None

None是Python的空值类型。如果一个函数没有明确的返回值,就会默认返回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

日期和时间

Python内建的datetime模块提供了datetime、datetime类型。datetime类型结合了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实例,你可以用datetime提取出各自的对象:

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)

控制流

Python有若干内建的关键字进行条件逻辑、循环和其它控制流操作。

if、elif和else

if是最广为人知的控制流语句。它检查一个条件,如果为True,就执行后面的语句:

if x < 0:
    print('It's negative')

if后面可以跟一个或多个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')

for循环

for循环是在一个集合(列表或元组)中进行迭代,或者就是一个迭代器。for循环的标准语法是:

for value in collection:
    # do something with value

你可以用continue使for循环提前,跳过剩下的部分(跳过 total += value这部分)。看下面这个例子,将一个列表中的整数相加,跳过None:

sequence = [1, 2, None, 4, None, 5]
total = 0
for value in sequence:
    if value is None:
        continue
    total += value
#结果
>>>print(total)
12

可以用break跳出for循环。下面的代码将各元素相加,直到遇到none:

sequence = [1, 2, None, 4, None, 5]
total = 0
for value in sequence:
    if value is None:
        break
    total += value
#结果
>>>print(total)
3

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)

While循环

while循环指定了条件和代码,当条件为False或用break退出循环,代码才会退出:

x = 256
total = 0
while x > 0:
    if total > 500:
        break
    total += x
    x = x // 2

pass

pass是Python中的非操作语句。代码块不需要任何动作时可以使用(作为未执行代码的占位符);因为Python需要使用空白字符划定代码块,所以需要pass:

if x < 0:
    print('negative!')
elif x == 0:
    # TODO: put something smart here
    pass
else:
    print('positive!')

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产生的整数不包括终点。下面的代码对0到99999中3或5的倍数求和:

sum = 0
for i in range(100000):
    # % is the modulo operator
    if i % 3 == 0 or i % 5 == 0:  # %是求余数
        sum += i

三元表达式

Python中的三元表达式可以将if-else语句放到一行里。语法如下:

value = true-expr if condition else false-expr

true-exprfalse-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'

虽然使用三元表达式可以压缩代码,但会降低代码可读性。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容