零、序言

为了帮助几个Python班的同学提高期末复习效率,我准备把一些我认为重要的内容整理成文分享给大家,大家可以根据自己的情况选择阅读,也欢迎把这篇文章转发给有需要的同学。限于个人水平,文章难免有缺漏或不足,欢迎大家批评指正、提出建议。

本文的主要内容:以编程的核心思想为起点,以数据类型、输入输出、算法基础、函数、面向对象、常用内置函数为基本框架,把Python基础的重点内容进行体系化梳理,不求知识点覆盖全面,而是把常用的、易混淆及核心内容有逻辑地贯通,并在部分篇幅中附上案例,便于读者快速掌握。

希望大家在期末考试中都能取得满意的成绩。Python是一门非常适合初学者的编程语言,且社区生态繁荣,用途广泛,尤其是在人工智能、数据分析、信息安全等领域更是必不可少的生产力工具。伴随新一代信息技术的飞速发展,数字化、智能化已经深入各行各业,数字经济成为时代主题,而掌握数字时代的基本工具:编程,是开启数智未来的第一步。Python课程只是起点,希望大家以后可以继续学习Python,把它作为自己的生产力工具,提高自己的学习工作效率,加强对技术的理解,便于今后走向技术与产业结合的道路。

人生苦短我用Python.png

一、重点汇总

1. 编程的核心思想

编程,顾名思义就是编写计算机程序。计算机不具备人的智能,只能通过执行人类给它的精准指令从而解决特定的问题,这种解决问题的具体过程、方法也就是算法程序设计的核心内容就是把算法设计出来,编程的主要工作就是把算法通过编程语言变成一段一段的计算机程序,然后交给计算机来执行。

你在教我做事.jpg

众所周知,计算机最擅长也是最基础的功能就是处理数据,计算机程序就是把人类或其他程序输入的数据通过某种算法处理之后按照特定的要求输出。注意,这里有三个关键词:数据输入/输出算法

请大家在编程的过程中时刻想着这三个关键词以及对应的问题:我输入的数据是什么(类型/格式)?我要用这些数据来干什么或者我希望这些数据被如何处理(算法)?、我希望程序接收的输入数据经过算法处理后输出什么(类型/格式)?本章后续内容也将从数据、输入/输出和算法为主线展开。

2. 基本数据类型

采用Python官方文档中对内置类型的分类方法,本门课程中主要涉及的基本数据类型有:数字类型、序列类型、集合类型和映射类型。不同类型的数据支持不同的运算操作,拥有该类型特定的方法。一般来说,只有同一种类型的数据之间可以进行运算操作(运算操作指像数字加减乘除、列表合并、集合交并差这一类的操作,不包含调用类型方法)。

TIPS: 以下从三种基本类型展开的内容,只涵盖重点知识,并非该类型的全部内容。

2.1 数字类型

数字类型主要有 int (整数)、 float (浮点数/小数)等,通过构造函数 int()float() 可以将其他类型的数据转换成数字类型,常用于输入中将字符串转为数字。

Python数字类型支持混合运算,即可以把 intfloat 混在一起进行运算。

数字类型除加减乘除外,还支持下列运算:

运算结果
x // yxy 的商数,整数,如:5 // 2 = 2
x % yx / y 的余数,整数
abs(x)x 的绝对值
pow(x, y)xy 次幂
x ** yxy 次幂
round(x, n)x 舍入到 n 位小数,如果省略参数 n,则默认为 0
math.floor(x)x 向下取整,如:math.floor(5.2) = 5
math.ceil(x)x 向上取整,如:math.floor(5.2) = 6

2.2 序列类型

序列类型主要有 list (列表)、 tuple (元组)、 str (字符串)和 range 。其中,tuplestrrange 为不可变序列类型,list 为可变序列类型。

不可变类型不能通过索引修改数据,例如,下面的程序运行会报错:

>>> str_a = 'HelloWorld'
>>> str_a[0] = 'A'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
2.2.1 通用序列操作

大部分序列类型都支持以下操作,包括不可变和可变序列类型。 在下表中,st 是具有相同类型的序列,n, i, jk 是整数,而 x 是任何满足 s 所规定的类型和值限制的任意对象。

运算结果
x in s如果 s 中的某项等于 x 则结果为 True,否则为 False
x not in s如果 s 中的没有等于 x 的元素则结果为 False,否则为 True
s + tst 相拼接,如: [0] + [0] = [0, 0]
s * nn * s相当于 s 与自身进行 n 次拼接,如: [0] * 5 = [0, 0, 0, 0, 0]
s[i:j]sij 的切片,左闭右开,如:[1, 2, 3, 4][0:2] = [1, 2]
min(s)s 的最小项
max(s)s 的最大项
s.index(x, i, j)xs 中首次出现项的索引号,索引号在 i 或之后且在 j 之前,左闭右开,若没找到会报错
s.count(x)xs 中出现的总次数

注意:

  1. 使用 s * n 的方式来创建多维列表时,s 中的元素会被引用而不是复制,例如:

    s = [[0]]
    s_3 = s * 3
    print(f"{s_3 = }")
    s_3[0][0] = 1
    print(f"{s_3 = }")
s_3 = [[0], [0], [0]]
s_3 = [[1], [1], [1]]

若想要避免以上情况,请使用列表推导式创建多维列表,例如:

s_3 = [[0] for i in range(3)]
print(f"{s_3 = }")
s_3[0][0] = 1
print(f"{s_3 = }")
s_3 = [[0], [0], [0]]
s_3 = [[1], [0], [0]]

这下就符合我们的期望了。

  1. 对序列类型逆序,最简单的方法是使用切片,即 s[::-1],例如:

    list_a = [1, 2, 3]
    tuple_a = (1, 2, 3)
    str_a = '123'
    print(f"{list_a[::-1] = }")
    print(f"{tuple_a[::-1] = }")
    print(f"{str_a[::-1] = }")
list_a[::-1] = [3, 2, 1]
tuple_a[::-1] = (3, 2, 1)
str_a[::-1] = '321'
2.2.2 可变序列操作

下表列出的操作仅可在可变序列类型中使用(目前学过的只有 list )。其中, s 是可变类型的序列,t 是任意可迭代对象(包括可变和不可变序列),n, i, jk 是整数,而 x 是任何满足 s 所规定的类型和值限制的任意对象。

TIPS: 为方便举例,这里先定义 a = [1, 2, 3]b = (4, 5, 6)

运算结果
s[i] = xs 的第 i 项替换为 x
del s[i]删除 s 的第 i
s.append(x)x 添加到序列的末尾
s.copy()创建 s 的浅拷贝
s.extend(t)s += tt 的内容扩展 s,如:a.extend(b)a 变为: [1, 2, 3, 4, 5, 6]
s.insert(i, x)在由 i 给出的索引位置将 x 插入 s ,如:a.insert(1, 10)a 变为: [1, 10, 2, 3]
s.pop(i)提取在 i 位置上的项,并将其从 s 中移除(i 为可选参数,不传入则默认为 -1)
s.remove(x)删除 s 中从左到右第一个等于 x 的项,若 s 中没有等于 x 的项则会报错
s.reverse()就地将列表中的元素逆序

注意:

  1. 关于 s.copy():如果将一个值为可变序列的变量直接赋值给一个新变量,则新变量只是对原有变量的引用,若对新变量中的项进行修改,则原有变量也会受到影响,例如:

    a = [1, 2, 3]
    b = a
    print(f"before modified: {a = }, {b = }")
    b[0] = 4
    print(f"after modified: {a = }, {b = }")
before modified: a = [1, 2, 3], b = [1, 2, 3]
after modified: a = [4, 2, 3], b = [4, 2, 3]

此时,若想 b 的修改不对 a 产生影响,则需要使用 s.copy() 来完成,例如:

a = [1, 2, 3]
b = a.copy()
print(f"before modified: {a = }, {b = }")
b[0] = 4
print(f"after modified: {a = }, {b = }")
before modified: a = [1, 2, 3], b = [1, 2, 3]
after modified: a = [1, 2, 3], b = [4, 2, 3]

从程序运行结果可以看到,a 的值在 b 修改前后没有受到任何影响。

  1. s.append()s.insert()s.extend()s.remove()s.reverse()没有返回值,所以不要使用 a = s.reverse() 来企图获取逆序后的列表。
2.2.3 列表

list(列表)是可变序列,通常用于存放同类项目的集合,注意:列表是有序的。

创建列表的常用方法:

  1. 列表推导式:[x for x in range(n)]
  2. 类型构造器:list('HelloWorld'),结果为 ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']
list.sort(key=None, reverse=False):列表排序
  • key(可选):带参数的函数(常用 lambda 函数),告诉列表使用项的哪一个元素进行排序,优先级如何,常用于二维列表、字典items()的排序
  • reverse(可选):是否逆序,默认为 False,从小到大升序排列

例如:

list_a = [(3, 4), (1, 5), (2, 5)]
# 要求:按每一项的第二个元素升序排列,每一项的第二个元素相同的情况下,按每一项的第一个元素降序排列
list_a.sort(key=lambda item: (item[1], -item[0])) # - 只能在 item[i] 为数字的时候使用,对数字取反,可以实现逆序效果(2>1, -2<-1)
print(list_a)
[(3, 4), (2, 5), (1, 5)]

又如:

list_a = [('a', 1), ('b', 2), ('c', 2)]
# 要求:按每一项的第一个元素降序排列(为字符串时,不能进行 - 取反操作)
list_a.sort(key=lambda item: item[0], reverse=True)
print(list_a)
[('c', 2), ('b', 2), ('a', 1)]

再如:

dict_list = [
    {
        'name': 'Zofia',
        'age': 26
    },
    {
        'name': 'Glaz',
        'age': 28
    },
    {
        'name': 'Lesion',
        'age': 26
    }
]
# 按 age 升序排列,age 相同的按 name 升序排列
dict_list.sort(key=lambda item: (item['age'], item['name']))
print(dict_list)
[{'name': 'Lesion', 'age': 26}, {'name': 'Zofia', 'age': 26}, {'name': 'Glaz', 'age': 28}]
2.2.4 元组

tuple 是不可变类型,没啥好说的。。。

256375946.jpeg

2.2.5 range对象
range(stop) or range(start, stop[, step]):

​ 参数 startstopstep 必须为整数,要么只传入 stop ,要么同时传入 startstop ,同时传入时,step 为可选参数。

  • start:起始值,默认为 0
  • stop:结束值
  • step:步长,即相邻两个数相差多少,默认为 1

TIPS: range 为左闭右开,即 range(1, 3) 里面只有 1, 2,没有 3。

range 的常用场景:

  1. 结合列表推导式创建多维列表,参见:2.2.1 通用序列操作
  2. for 循环中限定循环次数

创建倒序 range:

>>> for i in range(6, 0, -1):
...     print(i, end=' ')
... 
6 5 4 3 2 1
2.2.6 字符串

​ 不可变类型。字符串主要是方法比较多,完整的方法多达几十种,下面列出的只是用得比较多的方法,主要分为字符串的增删查改、数字/大小写判断、大小写转换:

str.split(sep=None):分隔

​ 将字符串按照 sep 进行分隔,返回列表。(常用于对输入的处理)

  • sep: 分隔字符串,默认为" "(空格)

例如:

>>> str_a = '1,2,3'
>>> str_a.split(',')
['1', '2', '3']
str.join(iterable): 拼接

​ 返回一个由 iterable 中的字符串拼接而成的字符串。 如果 iterable 中存在任何非字符串值则会报错。 调用该方法的字符串将作为元素之间的分隔。join 可看作对 split 的逆操作。

例如:

>>> list_a = ['Rainbow', 'Six', 'Siege']
>>> ' '.join(list_a)
'Rainbow Six Siege'

>>> ' '.join([1, 2, 3]) # 参数中存在非字符串值,引发 TypeError
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sequence item 0: expected str instance, int found
str.strip(chars): 删除前后字符

​ 返回移除前后字符的字符串。 chars 参数为指定要移除字符的字符串。 如果省略或为 None,则 chars 参数默认移除空白符。

例如:

>>> str_a = ' Rainbow Six Siege\n' # 前面有空格,后面有换行符 \n
>>> str_a.strip()
'Rainbow Six Siege'
str.replace(old, new): 替换

​ 将字符串中出现的所有子字符串 old 都将被替换为 new,并返回一个新的字符串。

例如:

>>> str_a = 'Rainbow Six Siege'
>>> str_b = str_a.replace('Siege', 'Lockdown')
>>> str_b
'Rainbow Six Lockdown'
str.find(sub): 查找子串位置

​ 返回子串 sub 在字符串中被找到的最小索引,若未找到返回 -1。

例如:

>>> str_a = 'Rainbow Six Siege'
>>> str_a.find('Rainbow')
0
>>> str_a.find('S')
8
str.isupper(): 判断字母是否全为大写字母

​ 如果字符串中至少有一个区分大小写的字符且此类字符均为大写则返回 True ,否则返回 False

例如:

>>> 'A'.isupper()
True
>>> 'b'.isupper()
False
>>> ' '.isupper()
False
>>> 'Rainbow'.isupper()
False
>>> 'RAINBOW'.isupper()
True
>>> 'RAINBOW SIX SIEGE'.isupper()
True
str.islower(): 判断字母是否全为小写字母

​ 如果字符串中至少有一个区分大小写的字符且此类字符均为小写则返回 True ,否则返回 False

str.isdigit(): 判断是否全为数字

​ 如果字符串中的所有字符都是数字,并且至少有一个字符,返回 True ,否则返回 False

例如:

>>> '123'.isdigit()
True
>>> 'Rainbow 6'.isdigit()
False
>>> ''.isdigit()
False
str.isalpha(): 判断是否全为字母

​ 如果字符串中的所有字符都是字母,并且至少有一个字符,返回 True ,否则返回 False

例如:

>>> 'Rainbow6'.isalpha()
False
>>> 'RainbowSix'.isalpha()
True
str.upper(): 所有小写转换为大写

例如:

>>> 'Zofia'.upper()
'ZOFIA'
>>> 'Rainbow 6 Siege'.upper()
'RAINBOW 6 SIEGE'
str.lower(): 所有大写转换为小写

例如:

>>> 'Zofia'.lower()
'zofia'
>>> 'Rainbow 6 Siege'.lower()
'rainbow 6 Siege'

2.3 集合类型 set

set 是由一系列不重复的、可哈希的(hashable)的对象所组成的无序多项集。

注意:

  1. 可变序列 list 不是 hashable 的,所以不能作为集合的元素
  2. 集合是无序的,所以,集合不支持索引、切片或其他序列类的操作
  3. 集合支持 x in setlen(set)for x in set 操作

集合运算:

2-1ZG2132104150.gif

如上图所示,有 2 个集合,分别为 set1={1, 2 ,3} 和 set2={3, 4, 5},它们既有相同的元素,也有不同的元素。以这两个集合为例,分别做不同运算的结果如下表所示。

运算操作运算符含义例子
交集&取两集合公共的元素>>> set1 & set2
{3}
并集\ 取两集合全部的元素>>> set1 \set2
{1,2,3,4,5}
差集-取一个集合中另一集合没有的元素>>> set1 - set2
{1,2}
>>> set2 - set1
{4,5}
对称差集^取集合 A 和 B 中不属于 A&B 的元素>>> set1 ^ set2
{1,2,4,5}

2.4 映射类型 dict

映射对象会将可哈希的值映射到任意对象, 映射属于可变对象, 目前仅有一种标准映射类型 dict(字典)。

字典的键可以是任何可哈希的值。不可哈希的值,包含列表、字典或其他可变类型的值不可用作键。

字典创建:

>>> a = dict(name='Zofia', sex='female')
>>> b = {'name': 'Zofia', 'sex': 'female'}
>>> c = dict(zip(['name', 'sex'], ['Zofia', 'female']))
>>> d = dict([('name', 'Zofia'), ('sex', 'female')])
>>> e = dict({'sex': 'female', 'name': 'Zofia'})
>>> f = dict({'name': 'Zofia'}, sex='female')
>>> a == b == c == d == e == f
True
list(d)

​ 返回字典 d 中使用的所有键的列表。

len(d)

​ 返回字典 d 中的项数。

d[key]

​ 返回 d 中以 key 为键的项。 如果映射中不存在 key 则会引发 KeyError

del d[key]

​ 将 d[key]d 中移除。 如果映射中不存在 key 则会引发 KeyError

key in d

​ 如果 d 中存在键 key 则返回 True,否则返回 False

key not in d

​ 如果 d 中不存在键 key 则返回 True,否则返回 False

d.get(key, default)

​ 如果 key 存在于字典 d 中则返回 key 的值,否则返回 default。 如果 default 未给出则默认为 None,因而此方法绝不会引发 KeyError

d.items()

​ 返回由字典 d 的项 ((键, 值) 对) 组成的dict_items 对象(可以用 list 转换成列表,便于排序)。

例如:

>>> d = {'name': 'Zofia', 'sex': 'female'}
>>> d.items()
dict_items([('name', 'Zofia'), ('sex', 'female')])

>>> list(d.items()) 
[('name', 'Zofia'), ('sex', 'female')]
d.keys()

​ 返回由字典键组成的dict_keys 对象。

例如:

>>> d = {'name': 'Zofia', 'sex': 'female'}
>>> d.keys()
dict_keys(['name', 'sex'])

>>> list(d.keys()) 
['name', 'sex']
d.values()

​ 返回由字典值组成的 dict_values 对象。

例如:

>>> d = {'name': 'Zofia', 'sex': 'female'}
>>> d.values()
dict_values(['Zofia', 'female'])

>>> list(d.values()) 
['Zofia', 'female']

3. 输入与输出

Input / OutPut(输入/输出),简称 I/O ,是计算机程序获取数据与输出数据的主要途径,主要分为控制台 I/O 和 文件 I/O 两种方式,以下逐一列出。

3.1 控制台 I/O

控制台 I/O 是指从程序运行的终端输入或输出。输入常用 input() ,输出常用 print()

3.1.2 input 数据处理

input() 函数用于获取输入,返回类型为字符串。我们常常需要将字符串转换为其他类型的数据,以便后续使用,以下列出几种常用的输入处理方法:

  1. 转换为数字

    # '6' -> 6
    >>> num = input()
    6
    >>> type(num) # 查看 num 的类型
    <class 'str'>
    >>> num = int(num)
    >>> type(num) 
    <class 'int'>
  2. 转换为列表

    # '[1, 2, 3]' -> [1, 2, 3]
    >>> nums = eval(input())
    [1, 2, 3] 
    >>> nums
    [1, 2, 3]
    >>> type(nums)
    <class 'list'>
    
    # '1 2 3' -> [1, 2, 3]
    >>> nums = list(map(int, input().split(" ")))
    1 2 3
    >>> nums
    [1, 2, 3]
  3. 转换为字典

    >>> dict_a = eval(input()) 
    {'name': 'Zofia', 'sex': 'female'} 
    >>> dict_a['name'] 
    'Zofia'
  4. 循环输入,到满足某条件时停止

    # 输入 End 结束
    >>> input_list = []
    >>> while True:
    ...   str_in = input()
    ...   if str_in != 'End':
    ...     input_list.append(str_in)
    ...   else:
    ...     break
    ... 
    Rainbow
    Six
    Siege
    End
    >>> input_list
    ['Rainbow', 'Six', 'Siege']
  5. 同时给多个变量赋值

    >>> name, sex = input().split(" ")
    Zofia female
    >>> name
    'Zofia'
    >>> sex
    'female'
3.1.3 print 格式化输出

格式化输出常用的方法有:% 操作符、format() 函数和 f-strings

% 操作符
>>> print("%s %d %s" % ('Rainbow', 6, 'Siege')) 
Rainbow 6 Siege
>>> print("kd = %.2f" % 0.6247) # 保留两位小数
kd = 0.62
format() 函数
>>> print("{} {} {}".format('Rainbow', 6, 'Siege'))
Rainbow 6 Siege
>>> print("kd = {:.2f}".format(0.6247)) # 保留两位小数,注意冒号
kd = 0.62
f-strings
>>> series = 'Rainbow'
>>> version = 6
>>> name = 'Siege'
>>> kd = 0.6247
>>> print(f"{series} {version} {name}")
Rainbow 6 Siege
>>> print(f"kd = {kd:.2f}") # 保留两位小数
kd = 0.62
>>> print(f"{kd = :.2f}") # 注意冒号
kd = 0.62

3.2 文件 I/O

文件 I/O 是指从文件中获取输入数据或将数据写入文件。主要有四个函数,对应文件的四个操作:open()(打开文件)、close()(关闭文件)、read()(读取文件内容,返回字符串)、write()(将字符串写入文件)。

open(file, mode='r')

​ 打开 file 并返回对应的 文件对象,如果该文件不能被打开,则引发 OSError

  • file: 文件路径
  • mode: 打开模式,w: 覆盖写入(以此模式打开文件会清除文件原有内容),r: 只读,a: 在原文件末尾追加;mode末尾添加'+',表示若文件不存在则自动创建,如:'w+', 'a+'
f.read()

​ 读取文件内容,返回字符串。

# data.txt 有如下内容
# Rainbow Six Siege
>>> f = open('data.txt', 'r')
>>> f.read()
'Rainbow Six Siege'
>>> f.close()
f.readlines()

​ 按行读取文件内容,返回所有行包含的字符串列表。此函数会将每一行末尾的换行符 \n 一并读入,如果不想要换行符可以通过 str.strip() 去除:

# data.txt 有如下内容
# Rainbow
# Six
# Siege
>>> f = open('data.txt', 'r')
>>> f.readlines()
['Rainbow\n', 'Six\n', 'Siege']
>>> f.close()

# 去除换行符
>>> f = open('data.txt', 'r')
>>> [row.strip() for row in f.readlines()]
['Rainbow', 'Six', 'Siege']
>>> f.close()
f.write(string)

​ 将 string 的内容写入文件,并返回写入的字符数。

>>> f = open('data.txt', 'w') # 注意写入时,open 函数的 mode 要使用 w
>>> f.write('Rainbow\nSix\nSiege')
17
>>> f.close()

>>> f = open('data.txt', 'r')
>>> print(f.read())
Rainbow
Six
Siege
>>> f.close()
f.writelines(lines)

​ 将字符串列表写入文件。注意,此函数写入时不会自动换行,若需要换行则需在每个列表项尾部自行添加换行符。

>>> f = open('data.txt', 'w')
>>> f.writelines(['Rainbow', 'Six', 'Siege'])
>>> f.close()
>>> f = open('data.txt', 'r')
>>> print(f.read())
RainbowSixSiege
>>> f.close()

# 添加换行符
>>> lines = ['Rainbow', 'Six', 'Siege']
>>> lines = [item + '\n' for item in lines]
>>> f = open('data.txt', 'w')
>>> f.writelines(lines)
>>> f.close()
>>> f = open('data.txt', 'r')
>>> print(f.read())
Rainbow
Six
Siege

>>> f.close()

4. 算法基础

算法即为解决问题的办法,也是程序设计的主要工作。我们要让程序按照我们预期的想法运行,就需要逻辑严谨地将算法设计准确,再通过编程来对我们设计的算法进行代码实现,进行代码实现时,需要特别注意数据类型的变化函数的返回值与返回值的类型逻辑结构的选择(顺序结构/选择结构/循环结构)以及避免犯常见的语法错误(根据程序运行报错来检查)。

常见的语法错误:

  1. 变量、函数名称拼写错误
  2. 缩进
  3. 函数定义 def 后面没加空格
  4. 引号没有正确配对,单引号写成双引号,双引号写成单引号
  5. 括号没有正确配对、嵌套
  6. ……

算法的基础逻辑结构主要有三种:顺序结构、选择结构、循环结构。不管是简单的片段,还是庞大的软件系统,最基础的逻辑结构就是这三种,我们要熟悉每一种逻辑结构的特点和使用场景,在程序设计时像搭积木一样把这些结构搭起来。

4.1 顺序结构

最基础的结构,程序从头到尾逐行执行。

# 顺序结构的案例
>>> a = 1
>>> b = 2
>>> print(a, b)
1 2

4.2 选择结构

又称“条件结构”,可以有多个条件分支,通过判断某些数据是否满足某些条件,从而控制程序的流向,在 Python 中通过 if/elif/else 语句来实现。

if/else 后面跟一个可以返回 True or False 的语句,用于条件判断,为 True 则执行该部分对应的代码块,为 False 则跳过。

if/elif/else 语句可以多层嵌套,用于实现复杂的选择结构。

# if/elif/else 多层嵌套实例
# 判断干员是防守方还是进攻方,属于哪一支部队
operators = ['Thatcher', 'Kapkan', 'Glaz', '666']
attacker = ['Sledge', 'Thatcher', 'Ash', 'Thermite', 'Twitch', 'Montagne', 'Glaz', 'Fuze', 'Blitz', 'IQ']
defender = ['Smoke', 'Mute', 'Castle', 'Pulse', 'Doc', 'Rook', 'Kapkan', 'Tachanka', 'Jäger', 'Bandit']
SAS = ['Sledge', 'Thatcher', 'Smoke', 'Mute']
FBI_SWAT = ['Ash', 'Thermite', 'Castle', 'Pulse']
GIGN = ['Twitch', 'Montagne', 'Doc', 'Rook']
Spetsnaz = ['Glaz', 'Fuze', 'Kapkan', 'Tachanka']
GSG_9 = ['Blitz', 'IQ', 'Jäger', 'Bandit']

for operator in operators:
    if operator in attacker:
        if operator in SAS:
            print(f"operator {operator} is attacker, belongs to SAS")
        elif operator in FBI_SWAT:
            print(f"operator {operator} is attacker, belongs to FBI SWAT")
        elif operator in GIGN:
            print(f"operator {operator} is attacker, belongs to GIGN")
        elif operator in Spetsnaz:
            print(f"operator {operator} is attacker, belongs to Spetsnaz")
        else:
            print(f"operator {operator} is attacker, belongs to GSG_9")
    elif operator in defender:
        if operator in SAS:
            print(f"operator {operator} is defender, belongs to SAS")
        elif operator in FBI_SWAT:
            print(f"operator {operator} is defender, belongs to FBI SWAT")
        elif operator in GIGN:
            print(f"operator {operator} is defender, belongs to GIGN")
        elif operator in Spetsnaz:
            print(f"operator {operator} is defender, belongs to Spetsnaz")
        else:
            print(f"operator {operator} is defender, belongs to GSG_9")
    else:
        print(f"operator {operator} is unknown")
operator Thatcher is attacker, belongs to SAS
operator Kapkan is defender, belongs to Spetsnaz
operator Glaz is attacker, belongs to Spetsnaz
operator 666 is unknown

4.3 循环结构

循环结构,用于控制程序反复执行某一段代码块,直到满足某个条件时停止。常用 for ... in ...while 实现。

for ... in ... 用于遍历序列(列表、元组、字符串等)的场景,while 常用于知道循环终止条件的场景,如循环输入到输入为 'ok' 时停止,for ... in ...while 很多时候可以互相替换,哪个方便用哪个就行。

4.3.1 for ... in ... 常见用法
  1. 遍历列表、元组、集合

    for item in [1, 2, 3]: # (1, 2, 3) {1, 2, 3} 输出一样
        print(item)
1
2
3
  1. 遍历字典 keys

    zofia = {'name': 'Zofia', 'sex': 'female'}
    for key in zofia:
        print(zofia[key])
Zofia
female
  1. 遍历 range

    for i in range(3):
        print(i)
0
1
2
  1. 遍历字符串

    for char in 'Rainbow 6':
        print(char)
R
a
i
n
b
o
w
 
6
4.3.2 while 常见用法
  1. 循环获取输入,参见 3.1.2 input 数据处理

5. 函数

在我们编写程序时,经常会遇到同一段代码需要多次使用的场景,如果不使用一些手段对这些可重复使用的代码进行封装,那么我们的程序将会变得十分冗长。函数的用途就在于此,将某一段实现特定功能的代码封装成函数,程序中其他地方需要使用这个功能时,只需要调用这个函数就可以了,大大提高了代码复用率,提升我们编写程序的工作效率。

所以一般来说,函数就是两个操作:定义函数、调用函数。

定义函数:

def functionName(param1, param2, ...):
    # function code
    return someValue

函数可以没有参数和返回值:

# 定义函数
def print_header(): # 没有参数的函数
    print(f"Operators in Rainbow Six Siege")
    print('-' * 30)
    print(f"{'Name':12}{'Sex':8}{'Attacker':10}")
    print('=' * 30)

def print_row(name, sex, op_type): # 没有返回值的函数
    print(f"{name:12}{sex:8}{op_type:10}")

def is_attacker(name): # 有参数也有返回值的函数
    if name in ['Zofia', 'Glaz']:
        return 'YES'
    return 'NO'

operators = [
    {
        'name': 'Zofia',
        'sex': 'female'
    },
    {
        'name': 'Kapkan',
        'sex': 'male'
    }
]
# 调用函数
print_header()
for operator in operators:
    print_row(operator['name'], operator['sex'], is_attacker(operator['name']))
Operators in Rainbow Six Siege
------------------------------
Name        Sex     Attacker  
==============================
Zofia       female  YES       
Kapkan      male    NO 

匿名函数 lambda

形如 lambda x: x + 1 的被称为匿名函数(又称 lambda 表达式),顾名思义,这种函数是没有名称的,常用于简化简单函数的使用。

TIPS: lambda 表达式只能包含一行代码。

例如:

  1. 排序,参见 list.sort(key=None, reverse=False):列表排序**:列表排序)
  2. map 配合使用对序列进行操作:

    # 二维列表项的子项求和
    list_a = [[1, 2], [3, 4], [5, 6]]
    res = list(map(lambda item: item[0] + item[1], list_a))
    print(res)
[3, 7, 11]

简单来说,lambda 表达式是对函数定义和调用的简化,不会也没关系,用 def 也🉑。

6. 面向对象

Object-oriented programming(面向对象编程),核心是OOP的思想,这里不展开讲,只列出和类&实例对象相关的重点内容,主要有:类的定义、实例对象创建&销毁、类变量&实例变量、类方法&实例方法、类的继承。

6.1 类的定义

主要有类的命名初始化

类的命名

Python中一般采用首字母大写的驼峰命名法来命名类(当然你用其他方法命名也没问题,只要是Python认可的合法标识符都🉑)。

class Operator:
    ...
类的初始化

在构造函数 __init__() 中对类进行初始化操作,一般用于创建实例对象时初始化一些实例属性&类属性,或者执行实例方法&类方法。

class Operator:
    def __init__(self, name, sex, is_attacker): # 构造函数 __init__(), 在对类进行实例化操作时执行
        self.name = name
        self.sex = sex
        self.is_attacker = is_attacker

TIPS: 构造函数 __init__()self 参数表示通过这个类创建的某个实例,可以通过 self 来访问实例属性或调用实例方法

6.2 实例对象创建&销毁

实例对象即为通过类的实例化操作创建的对象,如果把实例对象比喻成PPT,那么类就是PPT模板。

实例对象创建

通过类的实例化操作可以创建实例对象(采用函数表示法,看起来和调用函数差不多):

zofia = Operator('Zofia', 'female', True) # 实例化 Operator 类,创建 zofia 对象
print(type(zofia))
<class '__main__.Operator'>
实例对象销毁

通过 del 可以销毁实例对象,可以在声明类时通过析构函数 __del__() 来执行一些销毁实例对象时的操作:

class Operator:
    def __init__(self, name, sex, is_attacker):
        self.name = name
        self.sex = sex
        self.is_attacker = is_attacker

    def __del__(self): # 析构函数 __del__(), 在实例对象被销毁时执行
        print(f"operator {self.name} is deleted")

zofia = Operator('Zofia', 'female', True)
del zofia
operator Zofia is deleted

6.3 类变量&实例变量

简单来说,类变量就是在类定义里面、构造函数外面定义的变量,实例变量就是在构造函数里面定义的变量:

class Operator:
    count = 0 # 类变量 count
    def __init__(self, name, sex, is_attacker):
        self.name = name # 实例变量 name
        self.sex = sex # 实例变量 sex
        self.is_attacker = is_attacker # 实例变量 is_attacker
        Operator.count += 1 # 在构造函数中访问类变量,通过 ClassName.varName 的方式

zofia = Operator('Zofia', 'female', True) # 类的实例化,创建实例对象 zofia
print(zofia.name, zofia.sex) # 访问实例变量
print(Operator.count) # 访问类变量

kapkan = Operator('Kapkan', 'male', False) # 类的实例化,创建实例对象 kapkan
print(kapkan.name, kapkan.sex) # 访问实例变量
print(Operator.count) # 访问类变量
Zofia female
1
Kapkan male
2

从上面的例子可以看出,通过同一个类实例化创建的实例对象,各自的实例变量是独立的,但类变量是共用的。

6.4 类方法&实例方法

类方法通过 ClassName.method() 的方式访问,且类方法中只能访问类变量;实例方法通过 instance.method() 的方式访问,实例方法中即可以访问实例变量,也可以访问类变量:

class Operator:
    ops = [] # 类变量 ops

    def __init__(self, name, sex, is_attacker):
        self.name = name # 实例变量 name
        self.sex = sex # 实例变量 sex
        self.is_attacker = is_attacker # 实例变量 is_attacker
    
    # 定义实例方法
    def add_op(self):
        op_name = self.name # 访问实例变量
        Operator.ops.append(op_name) # 访问类变量
    
    # 定义类方法
    @classmethod
    def show_ops(cls):
        print(f"We have {len(cls.ops)} operators now!") # 访问类变量
        for op in cls.ops:
            print(op)

zofia = Operator('Zofia', 'female', True) # 类的实例化,创建实例对象 zofia
zofia.add_op() # 访问实例方法
Operator.show_ops() # 访问类方法

kapkan = Operator('Kapkan', 'male', False) # 类的实例化,创建实例对象 kapkan
kapkan.add_op() # 访问实例方法
Operator.show_ops() # 访问类方法
We have 1 operators now!
Zofia
We have 2 operators now!
Zofia
Kapkan

6.5 类的继承

类可以被继承,用来作为基类定义一个新的类(基于模板的新模板)。被继承的类称为基类/父类,继承创建的新类称为子类

子类拥有父类所有的类变量、类方法,也可以根据需要为类变量赋予新的初始值、重写父类的方法(包括构造函数、类方法和实例方法)。

通过 class ChildClass(FatherClass) 的方式来继承类。

例如:

class Operator:
    ops = [] # 类变量 ops
    def __init__(self, name, sex):
        self.name = name # 实例变量 name
        self.sex = sex # 实例变量 sex
    
    # 定义实例方法
    def add_op(self):
        op_name = self.name # 访问实例变量
        Operator.ops.append(op_name) # 访问类变量
    
    # 定义类方法
    @classmethod
    def show_ops(cls, class_name):
        print('-'*20)
        print(f"{class_name}: We have {len(cls.ops)} operators now!") # 访问类变量
        for op in cls.ops:
            print(op)

# 继承 Operator 类,定义 Attacker 类
class Attacker(Operator):
    def __init__(self, name, sex):
        super(Attacker, self).__init__(name, sex) # 调用父类的构造方法
        self.is_attacker = True

    # 重写父类的实例方法
    def add_op(self):
        op_name = self.name
        Attacker.ops.append('Attacker: ' + op_name)

# 继承 Operator 类,定义 Defender 类
class Defender(Operator):
    def __init__(self, name, sex):
        super(Defender, self).__init__(name, sex) # 调用父类的构造方法
        self.is_attacker = False

    # 重写父类的实例方法
    def add_op(self):
        op_name = self.name
        Defender.ops.append('Defender: ' + op_name)

# 继承 Operator 类,定义 Others 类
class Others(Operator):
    ops = [] # 这里重新定义了类变量 ops
    def __init__(self, name, sex):
        super(Others, self).__init__(name, sex) # 调用父类的构造方法

    # 重写父类的实例方法
    def add_op(self): 
        op_name = self.name
        Others.ops.append('Others: ' + op_name)

zofia = Attacker('Zofia', 'female')
zofia.add_op() # 调用子类重写后的实例方法
Operator.show_ops('Class Operator') # 调用父类的类方法
Attacker.show_ops('Child Class Attacker') # 调用子类的类方法
print()
kapkan = Defender('Kapkan', 'male')
kapkan.add_op()
Operator.show_ops('Class Operator')
Defender.show_ops('Child Class Defender')
print()
recruit = Others('Recruit', 'unknown')
recruit.add_op()
Operator.show_ops('Class Operator')
Others.show_ops('Child Class Others')
Attacker.show_ops('Child Class Attacker')
Defender.show_ops('Child Class Defender')
--------------------
Class Operator: We have 1 operators now!
Attacker: Zofia
--------------------
Child Class Attacker: We have 1 operators now!
Attacker: Zofia

--------------------
Class Operator: We have 2 operators now!
Attacker: Zofia
Defender: Kapkan
--------------------
Child Class Defender: We have 2 operators now!
Attacker: Zofia
Defender: Kapkan

--------------------
Class Operator: We have 2 operators now!
Attacker: Zofia
Defender: Kapkan
--------------------
Child Class Others: We have 1 operators now!
Others: Recruit
--------------------
Child Class Attacker: We have 2 operators now!
Attacker: Zofia
Defender: Kapkan
--------------------
Child Class Defender: We have 2 operators now!
Attacker: Zofia
Defender: Kapkan

从以上例子中,注意:

  1. 继承创建的子类之间共享父类的类变量(见 Attacker.show_ops()Defender.show_ops() 的输出差异),除非重新定义类变量(见Others.show_ops()
  2. 在子类的构造方法中调用父类的构造方法:super(ChildClass, self).__init__(param1, param2, ...)
  3. 重写父类方法(实例方法or类方法),只需要在子类中定义与父类的实例方法or类方法同名的方法即可
  4. 重新定义父类的类变量,只需要在子类中定义与父类中同名的类变量即可;重新定义类变量后,将不会与其他继承同一父类的子类共享类变量

7. 常用内置函数

Python 为我们提供了丰富的内置函数,熟练使用这些函数可以极大地提高我们的工作效率。常用的内置函数如下

enumerate(iterables)

  • iterable: 可迭代对象,如列表、元组、字符串、字典等

当我们想同时获取列表的索引和值的时候使用,例如:

list_a = [2, 3, 4]
enumerate_a = enumerate(list_a)
print(enumerate_a, type(enumerate_a))
print(list(enumerate_a)) # enumerate() 返回的是迭代器而不是列表,如果需要列表要手动转换

print("\nuse enumerate on list:")
for index, value in enumerate([1, 2, 3]):
    print(index, value)

print("\nuse enumerate on tuple:")
for index, value in enumerate((1, 2, 3)):
    print(index, value)

print("\nuse enumerate on set:")
for index, value in enumerate({1, 2, 3}):
    print(index, value)

print("\nuse enumerate on dict:")
for index, value in enumerate({'name': 'Zofia', 'sex': 'female'}):
    print(index, value)
<enumerate object at 0x7fc7d82b81c0> <class 'enumerate'>
[(0, 2), (1, 3), (2, 4)]

use enumerate on list:
0 1
1 2
2 3

use enumerate on tuple:
0 1
1 2
2 3

use enumerate on set:
0 1
1 2
2 3

use enumerate on dict:
0 name
1 sex

map(func_name, iterable)

  • func_name: 函数名称或 lambda 表达式
  • iterable: 可迭代对象,如列表、元组、字符串、字典等

当我们想对列表的每一项进行同一个操作时使用,这在处理输入时非常有用,参见 3.1.2 input 数据处理

另一个例子:

def add1(x):
    return x + 1

list_a = [1, 2, 3]
map_a = map(add1, list_a)
print(map_a, type(map_a))
print(list(map_a))
<map object at 0x0000017427640AC0> <class 'map'>
[2, 3, 4]

从以上的例子中可以看出,map() 返回的是一个 map对象 而不是列表,如果想要列表就要通过 list() 构造函数转换一下。

filter(func_name, iterable)

  • func_name: 函数名称或 lambda 表达式
  • iterable: 可迭代对象,如列表、元组、字符串、字典等

当我们想对列表、元组等进行筛选时使用,比如筛选出列表中大于5的元素:

list_a = [3, 4, 5, 6, 7, 8]
filter_a = filter(lambda item: item > 5, list_a)
print(filter_a, type(filter_a))
print(list(filter_a))
<filter object at 0x0000017426DA6CD0> <class 'filter'>
[6, 7, 8]

map() 一样,filter() 返回的也不是列表。

zip(iterables)

  • iterables: 可迭代对象,如列表、元组、字符串、字典等

官方文档关于 zip() 的定义:创建一个聚合了来自每个可迭代对象中的元素的迭代器。

返回一个元组的迭代器,其中的第 i 个元组包含来自每个参数序列或可迭代对象的第 i 个元素。 当所输入可迭代对象中最短的一个被耗尽时,迭代器将停止迭代。 当只有一个可迭代对象参数时,它将返回一个单元组的迭代器。 不带参数时,它将返回一个空迭代器。

看个例子就懂了:

operator_list = ['Zofia', 'Kapkan', 'Ela']
sex_list = ['female', 'male', 'female']
operators = zip(operator_list, sex_list)
print(operators, type(operators))
print(list(operators))
<zip object at 0x0000017427A75D40> <class 'zip'>
[('Zofia', 'female'), ('Kapkan', 'male'), ('Ela', 'female')]

map()filter() 一样,zip() 返回类型不是列表。

8. 参考来源

Last modification:June 25th, 2021 at 02:26 am
If you think my article is useful to you, please feel free to appreciate