Python数据处理
¶

02. Python基本语法
¶

主讲人:丁平尖

Python基本语法¶

函数 条件 递归 迭代 字符串 列表 字典 元组

文件 类和对象 类和函数 类和方法 继承

函数¶

  • 我们已经见过一些函数的例子:例如:type()和print()
  • 函数的调用形式:函数名(函数参数)
  • 一个函数可以输入零个或多个参数,也可以返回零个或多个值
In [5]:
import math
rt2 = math.sqrt(2) 
print(rt2)
1.4142135623730951
  • Python的‘math’模块提供了许多数学函数。我们在使用这些函数之前,必须先导入该模块。然后使用’math.sqrt()’来调用’math’模块中的‘sqrt’函数。
  • ‘math.sqrt()’接收一个参数,返回其平方根

函数¶

In [4]:
a = 2
b = 3
math.pow(a, b)
Out[4]:
8.0
  • ‘math.pow()’接受两个参数,返回第一个参数的第二个参数次幂

Python math模块的文档:https://docs.python.org/zh-cn/3/library/math.html

定义函数¶

  • 我们可以使用函数定义来创建新函数,这样我们就可以在需要时调用它。
In [7]:
# ‘def’关键字告诉Python,我们正在定义一个函数。
def return_multiple_values(text):
    '''
    函数的任何参数都在括号内给出。
    冒号(:)用于表示缩进代码块的开始。
    '''
    value1 = 10 # 空白符可以是制表符或空格,只要保持一致即可。
    value2 = text
    value3 = [1, 2, 3]
    return value1, value2, value3 # 使用‘return’关键字,定义函数的返回结果。

# 下一行不属于函数主体,通过缩进向Python传达这一事实。
result1, result2, result3 = return_multiple_values("hello") 
print(result1)
print(result2)
print(result3)
10
hello
[1, 2, 3]

注意:在R、C/C++和Java等语言中,代码块用大括号({})组织的。Python是以空白符号分隔的。

布尔表达式¶

  • 布尔表达式用于判断语句的真伪。

  • ‘bool’类型的变量可以是“True”或“False”。

  • Python中提供了一些比较运算符。

    • 涉及比较运算符的表达式会返回一个布尔值。
In [ ]:
x, y = 20, 10
x == y # x is equal to y
x != y # x is not equal to y
x > y # x is strictly greater than y
x < y # x is strictly less than y
x >= y # x is greater than or equal to y
x <= y # x is less than or equal to y

布尔表达式¶

  • 通过逻辑运算符‘and’、‘or’和‘not’将布尔表达式组合成更大的表达式。
In [10]:
x = 10
x < 20 and x > 0
Out[10]:
True
In [11]:
x > 100 and x > 0
Out[11]:
False
In [12]:
x > 100 or x > 0
Out[12]:
True
In [13]:
not x > 0
Out[13]:
False

条件执行¶

  • 有时我们希望根据某些条件执行不同的操作,最简单的就是‘if’语句了。
In [16]:
def pos_neg_or_zero(x):
    if x < 0: # 这个布尔表达式成为测试条件,或者简称为条件。
        # 如果条件评估为“True”(真),那么Python将运行‘if’语句主体中的代码。
        # 如果条件评估为‘False’(假),那么Python将跳过主体,并从‘if’语句末尾继续运行代码。
        print('That is negative')
    elif x == 0: # elif是‘else if’ 的缩写
        print('That is zero.') 
    else: # 如果所有其他条件都不成立,我们将执行‘else’部分的代码块。
        print('That is positive')

pos_neg_or_zero(1)
That is positive
In [17]:
pos_neg_or_zero(0)
pos_neg_or_zero(-100)
pos_neg_or_zero(20)
That is zero.
That is negative
That is positive

条件执行¶

注意:条件语句的主体可以包含任意数量的代码行,但必须至少有一行。如果不想执行任何操作,可以使用‘pass’关键字。

In [20]:
def thres_100(y):
    if y > 100:
        pass
    if y < 100:
        print('y is less than 100')

thres_100(200)
thres_100(0)
y is less than 100

递归¶

  • 一个函数被允许调用它自己,这就是所谓的递归
In [24]:
def countdown(n):
    if n <= 0:
        print('We have lift off!')
    else:
        print(n)
        countdown(n-1)
countdown(3)
3
2
1
We have lift off!

递归¶

  • 稍作修改,函数将会无限递归
In [ ]:
def countdown(n):
    if n <= 0:
        print('We have lift off!')
    else:
        print(n)
        countdown(n)
countdown(3)

迭代¶

  • 递归是我们看到的第一个用于执行重复操作的工具。
  • 但是有更好的工具来完成这项工作:while循环和for循环
In [26]:
a = 4
x = 3.5
epsilon = 10**-6
while True:
    print(x)
    y = (x + a/x)/2
    if abs(x-y) < epsilon:
        break # break关键字在被调用时终止当前循环。
    x=y # update to our new estimate
3.5
2.321428571428571
2.022252747252747
2.000122433944265
2.0000000037472883

字符串¶

  • 字符串是字符序列
In [2]:
animal = 'goat'
letter = animal[1]
letter
# Python序列从0开始计数
Out[2]:
'o'
In [8]:
'''
注意:在某些语言中,字符和长度为1的字符串是有区别的。
也就是说,字符‘g’和字符串“g”是不同的数据类型。
在Python中,不存在这样的区别。一个字符就是一个单字符字符串。
'''
animal[1]
Out[8]:
'o'
In [5]:
animal[2]
Out[5]:
'a'

字符串(迭代和遍历:for循环)¶

In [7]:
i = 0
while i < len(animal):
    print(animal[i])
    i = i + 1
g
o
a
t
In [9]:
for c in animal:
    print(c)
# For循环提供了一种简洁的方式来表达。
g
o
a
t

字符串切片¶

  • Python序列的一段成为切片。
In [10]:
s = "And now for something completely different"
# String[:m]选取从0开始到第(m-1)个字符的子序列。
s[:7]
Out[10]:
'And now'
In [11]:
# String[m :]选取从第m个字符开始到序列末尾的子序列。
s[22:]
Out[11]:
'completely different'
  • 切片也适用于负数索引。
In [12]:
s[-20: -10]
Out[12]:
'completely'
In [13]:
s[-20:]
Out[13]:
'completely different'

字符串不可变¶

  • 如何修改字符串中的单个字母?
In [14]:
mystr = "goat"
mystr[0] = 'b'
print(mystr)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[14], line 2
      1 mystr = "goat"
----> 2 mystr[0] = 'b'
      3 print(mystr)

TypeError: 'str' object does not support item assignment
  • 我们会得到一个错误,因为字符串是不可变的。不能更改已存在字符串的子序列。
In [15]:
mystr = "goat"
mystr = 'b' + mystr[1:]
mystr
Out[15]:
'boat'

字符串方法¶

  • 字符串提供了许多有用的方法
    • 更多Python中字符串的方法:https://docs.python.org/3/library/stdtypes.html#string-methods
In [16]:
mystr = "goat"
print(mystr.upper())
print('aBcDeFg'.lower())
GOAT
abcdefg
  • str.upper()使str中所有的字母变为大写。str.lower()类似。
In [17]:
'banana'.find('na')
# str.find(sub)查找str字符串中sub的第一个位置的索引。
Out[17]:
2
In [19]:
'goat'.startswith('go')
# 当str以sub开始则返回True
Out[19]:
True

列表¶

  • 字符串是字符序列。在列表中,元素可以是任何类型。
In [ ]:
fruits = ['apple', 'orange', 'banana', 'kiwi']
fibonacci = [0, 1, 1, 2, 3, 5, 8, 13, 21]
mixed = ['one', 2, 3.0]
pythagoras = [[3, 4, 5], [5, 12, 13], [8, 15, 17]]
  • 创建空列表的两种方式
x = []
x = list()

注意:与字符串不同,列表是可变的。我们可以在创建列表里后更改单个元素。

列表方法¶

  • 列表提供了一组特定的方法:
In [20]:
# * list.append(x):将x添加到列表的末尾
animals = ['cat', 'dog', 'goat', 'bird']
animals.append('fish')
animals
Out[20]:
['cat', 'dog', 'goat', 'bird', 'fish']
In [21]:
# * list.append(x):将x添加到列表的末尾
fibonacci = [0, 1, 1, 2, 3, 5, 8]
fibonacci.append([13, 21])
fibonacci
Out[21]:
[0, 1, 1, 2, 3, 5, 8, [13, 21]]
In [22]:
# * list.sort():对列表中的元素进行排序
fibonacci = [0, 1, 1, 2, 3, 5, 8]
fibonacci.extend([13, 21])
fibonacci
Out[22]:
[0, 1, 1, 2, 3, 5, 8, 13, 21]
  • list.remove(x):从列表中移除第一个等于x的元素
  • list.pop():删除列表的最后一个元素并返回该元素

map, filter和reduce¶

  • map:将函数(此处为capitalize_all)应用到序列的每个元素上。
In [23]:
def capitalize_all(t):
    res = []
    for s in t:
        res.append(s.capitalize())
    return res
capitalize_all(["sport", "great", "I"])
Out[23]:
['Sport', 'Great', 'I']
  • filter:从列表中筛选部分元素,如函数only_upper函数。
In [24]:
def only_upper(t):
    res = []
    for s in t:
        if s.isupper():
            res.append(s)
    return res
only_upper(["sport", "great", "I"])
Out[24]:
['I']

map, filter和reduce¶

  • reduce:将一系列元素合为一个值的操作,如函数sum。
In [ ]:
t = [1, 2, 3]
sum(t)
  • 大部分的列表操作都可以由map、filter以及reduce组合构成。

列表推导式¶

  • 列表推导式是一种独特的数据处理方式,可以从一个数据序列构建另一个新的数据序列的结构体。
In [25]:
# [表达式 for 变量 in 列表]
[s.capitalize() for s in ['sport', 'great', "I"]]
Out[25]:
['Sport', 'Great', 'I']
In [26]:
# [表达式 for 变量 in 列表 if 条件]
[s for s in ['sport', 'great', 'I', 'Do'] if s.isupper()]
Out[26]:
['I']

列表和字符串¶

  • 字符串是一系列的字符,列表是一系列的值,但是字符列表和字符串并不可等同视之。
In [27]:
s = 'spam'
print(s)
t = list(s)
print(t)
spam
['s', 'p', 'a', 'm']

列表和字符串¶

  • 变量a和b都指向同一个字符串对象,然后变量al和bl指向不同的列表对象
In [28]:
a = 'banana'
b = 'banana'
print(a is b)
print(a == b)
True
True
In [30]:
al = [1, 2, 3, 4]
bl = [1, 2, 3, 4]
print(al is bl)
print(al == bl)
False
True

列表和字符串¶

  • 有多个别称的对象。如果一个有别称的对象是可变的,那么一个别称所做的修改(不是重新赋值)会影响另一个。
In [32]:
al = [1, 2, 3, 4]
print(al)
bl = al
bl[0] = [1, 2]
print(al)
[1, 2, 3, 4]
[[1, 2], 2, 3, 4]

字典和元组¶

  • 字典
    • 如同列表,但更加通用。列表中,索引必须是整数,而在字典中,索引可以使任何类型。
    • 文档:https://docs.python.org/3/tutorial/datastructures.html#dictionaries
  • 元组
    • 元组和列表很像,但两者最大的区别在于元组不可变。
    • 文档:https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences

字典¶

  • 字典包括:

    • 一个索引集合,叫做键(keys)
    • 一个值的集合
  • 键和值的这种对应关系,叫做键值对

  • 创建空字典的两种方式

In [4]:
a = {}
b = dict()
type(a), type(b)
Out[4]:
(dict, dict)

字典¶

  • 创建和填充字典
In [5]:
umid2name = dict()
umid2name['aeinstein'] = 'Albert Einstein'
umid2name['kyfan'] = 'Ky Fan'
umid2name['aeinstein']
Out[5]:
'Albert Einstein'
In [7]:
umid2name['enoether'] = 'Amalie Emmy Noether'
umid2name['enoether']
Out[7]:
'Amalie Emmy Noether'

字典¶

In [8]:
# 用三个项创建一个新字典
eng2sp = {'one': 'uno', 'two': 'dos', 'three': 'trees'}
eng2sp
Out[8]:
{'one': 'uno', 'two': 'dos', 'three': 'trees'}
In [9]:
# 向已有字典中添加元素
eng2sp['four'] = 'you'
eng2sp
Out[9]:
{'one': 'uno', 'two': 'dos', 'three': 'trees', 'four': 'you'}
In [11]:
# 运算符in对字典也同样有效;它可以判断某个key是否存在于字典的键中
'one' in eng2sp, 'uno' in eng2sp
Out[11]:
(True, False)

命名空间¶

  • 命名空间是执行代码的上下文

    • “最外层”命名空间成为main
    • 在命令行或Jupyter上运行代码,都是在__main__命名空间
    • 在__main__空间中定义的变量都是全局变量
  • 函数定义创建自己的本地名称空间

    • 局部变量不能从它们的命名空间外部访问
    • 在for循环/while循环中也有类似的规则

命名空间¶

  • 例子:我们有一个模拟灯泡的程序灯泡状态由全局布尔变量lightbulb_on表示
In [13]:
lightbulb_on = False
def lights_on():
    lightbulb_on = True
lights_on()
print(lightbulb_on)
False
In [14]:
lightbulb_on = False
def flip_switch():
    lightbulb_on = not lightbulb_on
flip_switch()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
Cell In[14], line 4
      2 def flip_switch():
      3     lightbulb_on = not lightbulb_on
----> 4 flip_switch()

Cell In[14], line 3, in flip_switch()
      2 def flip_switch():
----> 3     lightbulb_on = not lightbulb_on

UnboundLocalError: cannot access local variable 'lightbulb_on' where it is not associated with a value

这段代码导致错误的事实是。默认情况下,Python将函数定义中的变量lightbulb_on视为与主命名空间中定义的lightbulb_on不同的变量。一般来说,这是一个很好的设计。它可以防止意外地更改全局状态信息。

In [16]:
lightbulb_on = False
def flip_switch():
    global lightbulb_on
    lightbulb_on = not lightbulb_on
flip_switch()
lightbulb_on
Out[16]:
True

命名空间¶

In [17]:
known = {0:0, 1:1}
def fibo(n):
    if n in known:
        return known[n]
    res = fibo(n-1) + fibo(n-2)
    known[n] = res
    return res
fibo(30)
Out[17]:
832040
  • 全局与局部的区别只对变量赋值重要。在fibo中我们没有执行任何变量赋值,所以不需要全局声明。与light_on相比,我们重新赋值lightbulb_on。默认情况下,变量赋值是局部的。

元组¶

  • 元组和列表很像,最大的区别在于元组不可变。
In [19]:
a = ('A', 'B', 'C')
a[0] = 'D'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[19], line 2
      1 a = ('A', 'B', 'C')
----> 2 a[0] = 'D'

TypeError: 'tuple' object does not support item assignment
  • 创建元组(tuple)
In [18]:
a = ()
b = tuple()
c = 'A', 'B'
type(a), type(b), type(c)
Out[18]:
(tuple, tuple, tuple)

元组赋值¶

  • 交换变量的值经常会用到. 比较传统的方法, 是使用中间变量. 比如下面例子, 互换 a 和 b 的值:
In [20]:
temp = a
a = b
b = temp

这种方法比较麻烦; 用元组赋值会优雅很多:

In [ ]:
a, b = b, a

右侧可以用各种类型的序列 (字符串, 列表或者元组). 例如, 将一个邮箱地址切分为用户名和域名, 可以这么写:

In [25]:
addr = 'monty@python.org'
uname, domain = addr.split('@')
addr.split('@'), uname, domain
Out[25]:
(['monty', 'python.org'], 'monty', 'python.org')

元组作为返回值¶

  • 严格来讲, 函数只能返回一个值, 但是如果这个值是一个元组, 效果便等同于返回了多个值.比如, 你要将两个整数相除, 计算商和余数, 先计算 x//y , 再计算 x%y, 略显低效. 更好的方法是同时计算两个值.
In [26]:
t = divmod(7, 3)
t
Out[26]:
(2, 1)
  • 自定义返回一个元组(多个值)的函数
In [ ]:
def min_max(t):
    return min(t), max(t)

变长参数即元组¶

  • 函数可以接收任意数量参数。以开头的参数会聚合*所有参数作为一个元组。比如,printall会接收任意数量的参数,并打印输出:
In [27]:
def printall(*args):
    print(args)
printall(1, 2.0, '3')
(1, 2.0, '3')
  • 聚合的对立面是扩展,如果你有一个值的序列,想将其作为多个参数,传入函数,那便可以使用*运算符。比如:divmod仅接收两个参数;如果传入一个元组,则运行异常:
In [28]:
t = (7, 3)
divmod(t)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[28], line 2
      1 t = (7, 3)
----> 2 divmod(t)

TypeError: divmod expected 2 arguments, got 1

变长参数即元组¶

In [29]:
divmod(*t)
Out[29]:
(2, 1)
  • 很多内置函数都会用到变长参数元组。比如,max和min都可以接收任意数量的参数:
In [30]:
max(1, 2, 3)
Out[30]:
3
  • 但sum函数不行。做个练习,写个函数sum_all,使其可以接收任意数量参数,并返回综合。
In [31]:
def sum_all(*args):
    return sum(args)
sum_all(1, 2, 3)
Out[31]:
6

列表和元组¶

  • zip是个内置函数,共接收两个或者更多序列作为参数,并交错获取其中元素。
In [4]:
s = 'abc'
t = [0, 1, 2]
x = zip(s, t)
next(x)
Out[4]:
('a', 0)
In [33]:
for pair in zip(s, t):
    print(pair)
('a', 0)
('b', 1)
('c', 2)
  • zip对象是一种迭代器。迭代器和列表在某些方面比较相似,但与列表不同的是,你不能从迭代器中通过索引获取元素
  • 如果你想使用列表的运算符和方法,可以通过zip对象构建列表。
In [34]:
list(zip(s, t))
Out[34]:
[('a', 0), ('b', 1), ('c', 2)]

列表和元组¶

In [35]:
# 如果序列长度不同,则结果长度取决于较短的那个序列
list(zip('Anne', 'Elk'))
Out[35]:
[('A', 'E'), ('n', 'l'), ('n', 'k')]
  • 如果结合zip, for和元组赋值,便可以同时遍历两个(或多个)序列
In [ ]:
def has_match(t1, t2):
    for x, y in zip(t1, t2):
        if x == y:
            return True
    return False
  • 如果需要遍历序列元素以及索引,可以使用内置函数enumerate
In [36]:
for index, element in enumerate('abc'):
    print(index, element)
0 a
1 b
2 c

字典和元组¶

  • 字典有个方法叫做items,其返回一个元组序列,每个元组都是一个键值对
In [37]:
d = {'a': 0, 'b' : 1, 'c': 2}
d.items()
Out[37]:
dict_items([('a', 0), ('b', 1), ('c', 2)])
  • dict_items对象是一个迭代键值对的迭代器
In [38]:
for key, value in d.items():
    print(key, value)
a 0
b 1
c 2
  • 将dict和zip结合使用,产生了一种新建字典的简洁方法
In [1]:
d = dict(zip([1, 2, 3], ['a', 'b', 'c']))
print(d)
d = dict(zip('abc', range(3)))
print(d)
{1: 'a', 2: 'b', 3: 'c'}
{'a': 0, 'b': 1, 'c': 2}