学习资料: https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000

基础

dict(字典), List, tuple(元组), set

dict

又称字典, 一种键值对形式的存储容器. 定义如下.

d1 = {"a" : 1, "b" : 2, "c" : 3} 
d2 = {"a" : "aaa", "b" : 2, "c" : 3}
d3 = {"a" : "aaa", "b" : {"bb": "bbb"}, "c" : 3}

# 获取数据
# 返回值为 None    
d = d3.get("d")
# 自定义返回值, -1
d = d3.get("d", -1)
# 删除数据
d3.pop("c")

ps: dictkey 必须是不可变对象

List

一种有序集合的存储容器, 可看做可变的数组. 定义如下.

nameList = ["Amy", "Bob", "Daniel", "Candy"]
# 添加
nameList.append("Michael")
# 在对应的位置插入
nameList.insert(0, "Tom")
# 删除末尾的一个数据
nameList.pop()
# 删除指定位置的数据
nameList.pop(1)
# 不可以直接删除某个值 例如 nameList.pop("Tom")
# 替换数据
nameList[0] = "Adam"
# 直接取倒数位置的数据, 例如倒数第一个使用 -1, 需要注意的是如果没有数据时会报索引越界.
nameList[-1]
# 排序, 注意只有纯数字或者字符串的List才可以排序
nameList.sort()
# 另外的定义方式.
L2 = ["aaa", 111, True, ["a", "b"]]
L3 = [] #空数组长度为0
# 遍历数据
for l in L2:
    print(l)

tuple

又称元组, 可看做不可变数组. 定义如下:

nameTuple = ("Amy", "Bob", "Daniel", "Candy")
# 定义单个元素时加逗号,
nameT2 = ("Jobs",)
# 只可取值不可修改, 例如 nameT2[0] = "Adam" 会直接报错
T = nameT2[0]
# 值为对象时可更改对象内容, 但对象指向的地址不会改变.
nameT3 = (["Amy", "Bob"],)
nameT3[0][0] = "Adam"
# 与List一样也可获取倒数位置的数据, 例如 -1
nameTuple[-1]

set

与List类似, 两者的区别是 set 中的值是唯一的, 而 List 中允许出现相同的值. 定义如下:

# 重复的值会被自动过滤掉, 下面的set 最终的值为 {1, 2, 3}
s1 = set([1, 2, 3, 3])
# 增加值
s1.add(4)
# 删除值, 注意如果没有这个值会报错 例如 s1.remove(5)
s1.remove(4)
s2 = set([2, 3, 4])
# 交集操作, 结果 {2, 3}
s3 = s1 & s2
# 并集操作, 结果 {1, 2, 3, 4}
s4 = s1 | s2

函数

定义函数

# 定义一个普通的函数
def add(a, b):
if a > b:
    return a
elif a == b:
    return "same"
else :
    return b
# 调用add函数并打印, 结果为"same"
print(add(10, 10))

# 定义空函数, 当没想好怎么实现逻辑时可以先用 pass 跑起来而不报错
def add2(a , b):
    pass
# 同时pass还可以应用在其他地方. 例如
def add3(a, b):
    if a > b :
        return a
    elif a == b :
        # 当还没想好如何处理时可以先用pass占位, 但如果判断进入该逻辑时返回值为None
        pass
    else :
        return b
# 类型检查
def add4(a, b):
    if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
        # 抛出类型异常
        raise TypeError("Param type error!")
    if a > b:
        return a
    elif a == b:
        return "same"
    else :
        return b

导入函数

如果要在一个Python文件中使用另一个py文件的函数. 例如已经把add(a, b) 函数保存到Math.py文件中, 在另一个py文件中引用时可以这么做.

# 引入Math.py的函数
from Math import add
add(10, 10)

如果使用导入语句时, 文件内有一个相同命名的函数, 则按照他们的先后顺序, 顺序靠后的会覆盖掉前一个函数. 例如先使用导入语句, 再定义一个相同命名的函数(即使参数不同), 则在调用时必须按照后一个定义的函数调用.

# 引入Math.py的函数
from Math import add
def add(a, b, c):
    if a > b:
        return a
    elif a == b:
        return "same2"
    else :
        return b
# 这种情况下必须调用传入3个参数的add函数
# 除非为多出来的参数c设定一个默认值. 如下
def add(a, b, c = 10):
    if a > b:
        return a
    elif a == b:
        return "same2"
    else :
        return b
# 此时调用add(10, 10)时, 其实等同于add(10, 10, 10)

ps: 设定默认值的参数只可定义在最后面, 不可定义在前面. 除非为函数的所有参数都设置默认值.
默认参数必须指向不变对象. 如果定义默认参数为List这种类型的话会在每次调用时都重复使用上一次调用时的数据.

函数多值返回

函数支持多个返回值, 如下定义:

def echo(a, b, c):
    return a, b, c
# 返回值是一个 tuple
d = echo(10, "10", 10)
# 多值接收, 每个值是指向tuple中对应位置的元素
a, b, c = echo(10, 10, "10")

ps: 函数执行完毕也没有return语句时,自动return None

可变参数

函数接收的参数不确定数量时, 即可变参数可以如下定义.

# 定义可变参数函数
def ehco(*names):
    for name in names:
        print(name)

# 使用方式1, 传递的多个参数会被封装为一个Tuple被函数接收
ehco("Amy", "Bob", "Candy", "Daniel")
names = ["Amy", "Bob", "Candy", "Daniel"]
# 使用方式2, 看到大神的说法是取出Tuple中的元素传递到函数, 本人还有个想法是会不会取的是tuple的内存地址传递到函数中
ehco(*names)
# 传递0个参数时也不会报错
ehco()

关键字参数

与可变参数类似. 定义方式如下:

# 关键字参数
def echo2(a, b, **c):
    # 函数中接收到的是一个封装的dict字典
    print(a, b, c)
# 调用方式1
#echo2("Amy", 25, job = "dancer", gender = "F")
# 调用方式2
info = {"job" : "singer", "gender" : "M"}
echo2("Bob", 28, **info)

命名关键字参数

限制调用方必须使用相同的关键做调用可以使用命名关键字, 定义方式如下:

# 命名关键字参数
def echo3(name, age, *, job, gender):
    print(name, age, job, gender)
# 注意此处调用时传递的命名参数位置相反也是正确执行的
echo3("Candy", 26, gender = "M", job = "coder")
def echo4(name, age, *others, job, gender):
    # 判断是否存在某个参数
    if "city" in others:
        print("has city")
    print(name, age, job, gender, others)

# 如果命名参数前有可变参数时则无需使用*号, 同时可设置默认值
def echo4(name, age, *others, job, gender = "M"):
    # 判断是否存在某个参数
    if "city" in others:
        print("has city")
    print(name, age, others, job, gender)
echo4("Candy", 26, {"city" : "shenzhen"}, job = "coder")

参数组合顺序

请注意,参数定义的顺序必须按照以下顺序:

# 必选参数, 默认参数(有默认值的参数), 可变参数, 命名关键字参数, 关键字参数
def echo5(name, age = 0, *others, job = "coder", **info):
    print(name, age, others, job, info)
echo5("Daniel", 22, "abc", "def", job = "singer", gender = "M")
# 使用list或者tuple与dict也可以直接调用该函数
params = ("Edward", 22, "abc", "def")
dic = {"job" : "painter", "gender" : "M"}
echo5(*params, **dic)
# 从这里可以得出结论函数传入可变参数时确实是逐个取值传递而不是直接使用内存地址传递. 大神是对的

ps: 使用 *args**kw 是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
*args是可变参数,args接收的是一个tuple;
**kw是关键字参数,kw接收的是一个dict。

递归函数

函数体中调用自身本身的函数称为递归函数, 具体定义如下:

# 递归函数
def count(n):
    if not isinstance(n, int) or n <= 1 :
        return 1
    return n * count(n - 1)

递归调用的次数过多,会导致栈溢出。解决栈溢出的方法是通过尾递归优化.
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

# 尾递归函数
def count(n):
    if not isinstance(n, int) or n <= 1 :
        return 1
    return count_iter(n, 1)
# 这么做的好处是, 在函数递归调用前就把参数做了运算, 在有针对尾递归做优化的语言中不会造成栈溢出的问题. 
def count_iter(n, p):
    if n == 1 :
        return p
    return count_iter(n - 1, n * p)

遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使在Python中写了尾递归函数也会造成栈溢出的异常.

切片, 迭代, 列表生成式, 生成器, 迭代器

切片

切片(Slice) 就是从数据源(List/Tuple/String等)中截取数据. 定义方式如下

names = ["Amy", "Bob", "Candy", "Daniel", "Edward", "Frank"]
# 取 0 到 3 之间的数据, 注意不包含索引 3
names2 = names[0 : 3]
# 如果从第 0 个数据开始截取还可以省略
names3 = names[ : 3]
# 结果为 ['Amy', 'Bob', 'Candy']
# 与List一样也可以获取倒数位置的数据. 例如 -3 : -1
name4 = names[-3 : -1]
# 如果是截取到最后一个数据, 也可以省略
name5 = names[-3 : ]
# 特殊用法1, 在前5个元素中, 每2个取一个 
names6 = names[ : 5 : 2]
# 特殊用法2, 每3个取一个
names7 = names[: : 3]
# 特殊用法3, 完全复制副本
names8 = names[:]

需要注意的是从 List 中截取的切片结果是 List, 从 tuple 中截取的是 tuple.

t = (1, 2, 3, 4, 5)[ : 3]
# 此时 t 的结果是 (1, 2, 3)

# 从字符串中截取切片
sentence = "I'm a coder!"
s1 = sentence[ : 6]
# 结果是 I'm a 

迭代

# 迭代遍历
# 注意dict 不是有序的, 所以迭代结果可能不是按照内容顺序排序的.
kvDict = {"a" : 1, "b" : 2, "c" : 3, "d" : 4, "e" : 5, "f" : 6}
for key in kvDict:
    print(key)
for value in kvDict.values():
    print(value)
for k, v in kvDict.items():
    print(k, v)
# 字符串也是可迭代对象
sentence = "I'm a coder!"
for s in sentence:
    print(s)
# 判断一个对象是否是可迭代对象
from collections import Iterable
if isinstance(sentence, Iterable):
    pass

# 带索引的遍历, enumerate为内置函数
for i, v in enumerate(names):
    # i = 索引, v = 对应索引的值
    print(i, v)

列表生成式

快速生成列表的内置生成式

# 快速生成1到10的列表
l = list(range(1, 11))
# 快速生成 1*1, 2*2...10*10 的列表
l2 = [n * n for n in range(1, 11)]
# 在l2基础上加入判断只取奇数
l3 = [n * n for n in range(1, 11) if n % 2 == 1]
# 加入二次循环
l4 = [n + m for n in "abcd" for m in "xyz"]
# l4 结果是 ['ax', 'ay', 'az', 'bx', 'by', 'bz', 'cx', 'cy', 'cz', 'dx', 'dy', 'dz']

# 实用场景1 通过生成式列出当前目录下的所有文件和目录名
import os
l5 = [f for f in os.listdir(".")]
# 实用场景2 快速把dict转化为list
d = {"a" : 1, "b" : 2, "c" : 3}
l6 = [k + "=" + str(v) for k, v in d.items()]

生成器

generator, 一次性把列表生成会占用空间, 使用生成器相当于储存了列表生成式的算法. 当需要时才去算出值. 定义如下.

# 与列表生成式的区别是把中括号换为小括号
g = (x for x in range(1, 11))
# 每次调用next()函数才会去算出值.
g1 = next(g)
# for循环相当于重复调用next()函数
for gv in g:
    print(gv)

如果一个函数定义中包含 yield 关键字,那么这个函数就不再是一个普通函数,而是一个 generator

L = {"Amy" : 20, "Bob" : 22, "Candy" : 25}
# 在函数中加入yield关键字变成一个generator
def echo(L):
    for k, v in L.items():
        yield "name = " + k + " age = " + str(v)

g2 = echo(L)
for g in g2 :
    print(g)
# 使用for循环遍历generator时, 拿不到generator的return语句的返回值
# 如果有特殊需求要获取返回值, 必须捕获StopIteration错误,返回值包含在StopIteration的value中
while True :
    try :
        p = next(g2)
        print(p)
    except StopIteration as e:
            print("value", e.value)
            break

迭代器

可以被 next() 函数调用并不断返回下一个值的对象称为 迭代器 ( Iterator )

判断一个对象是否是 Iterator 对象可以使用 isinstance()

# 判断一个对象是否是 `Iterator` 对象
from collections import Iterator
isinstance((x for x in range(10)), Iterator) # 值为 True
isinstance([1,2,3], Iterator) # 值为 False

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator, 可以使用iter()函数转换成Iterator :

print(isinstance(iter([1,2,3]), Iterator)) # 值为 True