Python 开发指南:基础、复合数据类型

2022-09-1811:30:48编程语言入门到精通Comments809 views字数 7400阅读模式

本章使用的 Python 版本是 3.8。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

Python 对代码的书写格式制定了各种规范,它们被收录在了 Python Enhancement Proposals ( PEP ) 中。不过,随着学习的进行,你自然会适应并遵守这些书写格式,因此这里不再赘述。在 PyCharm 当中,你可以使用 Ctrl + Alt + L 快速规范代码书写。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

基础数据类型

数值型

这里仅需简单地将数值分为三种类型:整型 int,浮点数 float,布尔值 bool,复数 complex。其中,浮点数不区分单精度和双精度。Python 是一个动态类型语言,所有的变量都是动态确定类型的。可以使用 type() 函数确定一个变量当前的类型。如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

# <class 'float'>
x = 1.00
# python 只内置 print 进行控制台输出,默认情况下自带回车。
print(type(x))

# <class 'int'>
x = 1
print(type(x))

# bool: True, False
# <class 'bool'>
x = True
print(type(x))

# <class 'complex'>
x = 3 + 2j
print(type(x))

在这个例子中,打印了四次变量 x 的数据类型,且每一次 x 的类型都不同。可以通过 :type 的方式主动声明变量的数据类型,但事实上并不会影响脚本的执行。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

x: int = 10
x = "hello"
print(x, end="\n")

Python 会自动处理数值计算的精度转换。比如说:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

print(1/2)

程序输出的结果将是 0.5 ,而非 0。然而,Python 提供了 int()float()str()complex() 等类型转换函数,可以实现强制类型转换的效果。下面的输出将是 0文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

print(int(1/2))

字符串

Python 的字符串类型为 str。无论是使用 '' 或者 "" 包括的文本都可以被认为是字符串。如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

h = "hello"  # str
w = 'world'  # str

print(h, w)

可以使用三引号的形式表示一段文本块 ( 仍然属于 str 类型 ),它的另一个用法是做脚本内的长文注释。如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

"""
2022/6/17
    author: Me
    This is the first python script written by myself.
    you can use text block as the code description.
"""
print("hello world")

Python 的 str 有两个实用的运算符重载。其中,+ 操作符可以简单地将两个字符串拼接起来,而 * 操作符可以令字符串自身重复拼接。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

x = "hello"

print(x + "world")  # helloworld
print(x * 2)  # hellohello

注,字符串在 Python 中可被视作由单个字符组成的字符列表 list。后文在列表中介绍的所有操作同样适用于字符串。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

Python 有另外一种嵌入拼接的字符串模板写法,如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

age = 18
name = "me"
info = f"""
    studentInfo:{age}
    name: {name}
"""

字符串前面的 f 代表 format。Python 会将 agename 两个变量的值嵌入到 info 字符串内部。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

复合数据类型

列表 list 与区间 range

列表 list 是最常用的线性数据结构,使用 [] 声明。Python 不要求一个列表下的所有元素都保持同一类型。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

xs = [1, "2", 3, 4.00, 5]

# len() 是 Python 的内置函数,可以打印列表的长度。
print(len(xs))

可以通过列表嵌套的形式生成高维列表。不过,我们更倾向于使用 numpy 库去生成高维数组 ( 或称矩阵 ),后者在数值运算中的性能更高。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

xxs = [[1, 2, 3], [3, 4, 5]]
print(xxs)

在 Python 中,可以使用 0 起始的非负下标 n 表示列表中从左到右数的第 n + 1 个位置,以 -1 起始的负数下标 -m 表示列表中从右到左的第 m 个位置。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

xs = [1, "2", 3, 4.00, 5]
p1 = xs[-2]  # 4.00
p2 = xs[2]   # 3

在 Python 中,这种 x[0] 下标访问的底层指向 __getitem__() 方法,它本质上是操作符重载的一种。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

列表内的元素引用是可更改的。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

xs = [1, 2, 3]
xs[2] = 4

print(xs)  # [1, 2, 4]

列表可以像字符串那样使用 + 操作符拼接,或者是使用 * 操作符重复。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

xs = [1, 2, 3, 4] * 2
ys = [1, 2, 3, 4] + [5, 6, 7, 8]

print(xs)  # [1, 2, 3, 4, 1, 2, 3, 4]
print(ys)  # [1, 2, 3, 4, 5, 6, 7, 8]

利用这个特性可以快速生成一个元素初值为 i,长度为 n 的列表。如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

i = 0
n = 10
xs = [i] * n
print(*xs)

遍历列表是最常见的程序逻辑。在 Python 中可表示为:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

for x in xs:
    print(x)

如果 xs 是一个对象列表,则在每次迭代中,Python 会以 引用拷贝 的形式将列表元素提取给临时变量 x。换句话说,如果在循环体内修改了 x 的引用,那么后续对它的状态修改将不会传递到原列表,因为引用共享关系被破坏掉了。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

# 这个对象有一个值 v
class Foo:
    def __init__(self, v_):
        self.v = v_

xs = [Foo(1)]

for x in xs:
    # 破坏引用共享
    x = Foo(2)
    x.v = 3

# 1, not 2 or 3
print(xs[0].v)

在不破坏共享引用的情况下,对 x 的内部状态的修改会传递到原列表。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

# 这个对象有一个值 v
class Foo:
    def __init__(self, v_):
        self.v = v_

xs = [Foo(1)]

for x in xs:
    x.v = 2

# 2.
print(xs[0].v)

在后文介绍的切片中也会有类似的现象。与之相对的是,数值类型 ( 包括 str ) 都是 不可变 的。此时对 x 做何修改都不会传递到原列表。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

xs = [1, 2, 3, 4, 5]

# 试图通过这种方式将 xs 内的数值 x 全部映射成 2x
for x in xs:
    x = x * 2

# 仍然打印 [1, 2, 3, 4, 5]
print(*xs)

如果要以简明的形式实现 list → list 的映射,可以参考后文的推导式来完成,而不是绞尽脑汁思考如何复现 for(i=0;i<n;i++) 这样的语法。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

如果要生成像 [0, 1, 2,..., n] 这样的等差序列,可以直接使用 range() 函数生成一个区间,支持自行设置步长。如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

# 生成的是左闭右开区间。[0, 1, ... 9]
xs = range(0, 10)
# 若起始元素为 0,则可以简写。
xs = range(10)

# [10, 7, 4, 1]
xs = range(10, 0, -3)

综上,对一个列表的逆序遍历还可以写成:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

# start: len(xs)-1 -> 由于 0 下标的存在,数组的最后一个下标是其长度 -1. 
# stop: -1         -> 遍历到 -1 下标之前,即 0 号下标。
# step: -1         -> 每次迭代下标 -1.
for x in range(len(xs) - 1, -1, -1):
    print(xs[x])

Python 内置了一个返回 逆序迭代器 的函数:recersed()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

sx = reversed("hello")  # 字符串也是列表的一种
s = "".join([x for x in sx])  # 见后文的生成式

# 切片形式的最简化版本:
sx = "hello"[::-1]

区间 range 和列表 list 是两个不同的类型,可以通过 type 函数检查出区别。range 可以被视作一种 抽象的不可变列表,因此它也可以被迭代,但是 range 类型不提供下标索引方式的访问。如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

xs = range(10)
xs[1] = -1  # don't do this

如果想利用区间生成列表,可以使用 list() 函数进行转换。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

切片

切片是基于列表 ( 或区间 ) 截取出的子序列 ( 或子区间 ),并不是一个独立的数据类型。比如,下面的代码表示从 xs[2,4) 下标位置截取出切片:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

xs = [1, 2, 3, 4, 5]
ss = xs[2:4]  # [3,4]

切片同样可以 [start:stop:step]的顺序指定步长。其中 start <= stop文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

rs = range(1,101)

# [51, 53, ... ,99]
ss = rs[50:100:2]

# *ss 表示将子区间切片 ss 的每一个元素作为独立的参数传入,否则只会打印: range(51, 101, 2)
# 见后文的可变参数部分。
print(*ss)

startstopstep 均是可以缺省的,缺省值依次为 0len(rs)1。切片还可以分为两个方向:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

  1. 如果 step > 0,则表示从左到右的顺序切片,默认值 start = 0stop = len(rs)
  2. 如果 step < 0 ,则表示从右到左的顺序切片,默认值 start = -1stop = -len(rs)-1

因此,切片有非常灵活的声明方式,以下写法均成立:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

rs = range(1, 10)  # [1, 2,..., 9]

print(*rs[:2])  # [1, 2]
print(*rs[4:])  # [5, 6..., 9] == xs[4::]
print(*rs[::])  # [1, 2,..., 9] == xs
print(*rs[::2])  # [1, 3, 5, 7, 9] != xs[:2]
print(*rs[4::2])  # [5, 7, 9]
print(*rs[4::])  # [5, 6,..., 9] == xs[4:]

其中,可以特别记忆切片 rs[::-1] 的写法,它相当于 rs 的逆序排列,对于字符串同样适用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

Python 是通过 引用拷贝 截取对象元素的。换句话说,对切片内元素状态的更改会发生传递。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

class VV:
    def __init__(self, v_):
        self.v = v_

x = [VV(1)]
y = x[:]
y[0].v = 2

# 2 2
print(x[0].v, y[0].v)

想要避免这种耦合性,可以使用新的实例引用进行赋值,从而破坏掉引用共享。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

class VV:
    def __init__(self, v_):
        self.v = v_

x = [VV(1)]
y = x[:]
y[0] = VV(2)

# 1 2
print(x[0].v, y[0].v)

对于数值型的列表则不会有这样的问题,因为这里不涉及引用拷贝。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

a = [1]
b = a[:]
b[0] = 2

# [1] [2]
print(a, b)

元组 tuple

元组可被视作一个轻量的 引用不可变 数据容器,标准的写法是使用 () 声明。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

t = (1, 2, 3)

# 可以用下标索引的方式访问元素,但不可修改。
e = t[1]
print(e)

基于元组可以引申出相当多的特性。比如,可以利用元组进行多重赋值,或者 理解成是元组的提取式。对于丢弃不用的元素,可以使用 _ 符号简单地忽略掉。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

(x, y, _) = (1, 2, 3)
print(x, y) # x = 1, y = 2 

Python 函数也可以返回元组,或者理解成是像 Go 语言的函数一样返回了多个值。如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

def swap(x, y): return (y, x)

(x,y) = swap(1,2)
print(x, y) # x = 2, y = 1

Python 的元组可以省略 (),多个元素间仅以 , 相隔。上面的代码还可以简写成:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

def swap(x, y): return y, x
a, b = swap(1, 2)

print(a, b)

特殊地,如果要把单个元素视作元组,则在元素后加上 ,,比如如 a,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

*集合 set

关于集合和字典部分,我们事实上是在讨论更深刻的话题:Python 对象的相等性。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

集合 set 类型和列表的重要区别是:集合内的元素不会发生重复,使用 {} 声明。首先,可以直接放入集合内的元素有数值,字符串,元组。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

sets = {1, 1, 2}

# len(sets) == 2, 说明重复的 1 被筛除掉了。
print(len(sets))  

我们再来讨论保存对象的集合是什么样的。首先是一段代码示例:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

class Foo:
    def __init__(self, v_):
        self.v = v_


ref = Foo(1)
sets = {ref, ref}

#  len(sets) == 1
print(len(sets))

Python 内部以 计算哈希值 的方式判断元素是否重复。在默认情况下,Python 会使用对象的引用计算哈希值。显然,相同的引用必然会发生哈希碰撞。如果能理解这一点,下面的运行结果就很好解释了:两个 Foo(1) 是不同的引用,因此它们可以在同一个集合下共存。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

sets = {Foo(1), Foo(1)}

# len(sets) == 2
print(len(sets))

然而我们更希望能构建一个值不重复的对象集合。一个有效的方案是根据实例的所有状态 ( 或称属性 ) 计算哈希值。显然易见的是:如果两个对象的状态全都相等,那它们的哈希值也必然相等,从而进一步推导出两者重复。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

为此,在类定义中需要同时重写 __eq__()__hash__() 两个方法。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

class Foo:
    def __init__(self, v_):
        self.v = v_

    # 官方推荐的做法是将实例的属性全部混入到一个元组中,使用元组计算哈希值。
    def __hash__(self): return hash(self.v,)

    def __eq__(self, other): return self.v == other.v
    
st = {Foo(1),Foo(2),Foo(1)}
print(len(st))  # 集合的实际元素只有 2 个。

后文简称这样的类是可计算哈希的 hashable type。Python 规定仅重写 __eq__() 但未重写 __hash__() 的类是 unhashable type,它们无法作为元素放入集合,也不能作为后文字典的 key。值得一提的是,__eq__() 函数本身还是 == 操作符的重载。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

Python 内置了大量 __XX__ 命名的内置方法或函数,它们又被称之为 "魔法" 函数。Python 依赖这些函数生成语法糖或者执行内部机制。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

Python 提供各种操作符进行集合基本的交并补计算,如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

A = {1, 2, 3, 4, 5}
B = {3, 4, 5, 6, 7}

print(A - B)  # {1, 2}
print(B - A)  # {6, 7}
print(A ^ B)  # {1, 2, 6, 7}, 对称差
print(A | B)  # {1, 2, 3, 4, 5, 6, 7} 并集
print(A & B)  # {3, 4, 5} 交集

*字典 dictionary

字典是一个特殊的集合,它的内部存放以 key : value 表示的键值对,同样使用 {} 声明,记作 dict 类型。同一个字典内部,key 不会发生重复。可以充当 key 的有 数值,字符串,元组,以及 hashable type,理由同集合。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

dictionary = {"a": "abandon", "b": "banana", "c": "clap"}

Python 提供两种方式来从 dict 字典中获取 value 值:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

  1. 以下标索引的方式访问。不过,字典中在查询不到指定 key 时会抛出异常。
  2. 调用 dictget 方法。当搜索 key 失败时以第二个参数提供的默认值进行代替,是更加安全的访问方式。
dictionary = {"a": "abandon", "b": "banana", "c": "clap"}

maybeKey = dictionary["d"]  # error
"""
	这里有一个特殊的细节。不要这么做:
	dictionary.get("d", default="Nil")
	这里的 get 方法是 C 语言层面实现的 (为了更高的性能),
	而它不兼容 default=xxx 这样的传参方式。
"""
getNilIfNull = dictionary.get("d","Nil")

可以使用 in 关键字查询字典内是否有某个 key 值,这个关键字后文还会给出进一步说明。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

dictionary = {"a": "abandon", "b": "banana", "c": "clap"}

boolean = "d" in dictionary
print(boolean)  # False

可以使用 del 关键字删除字典内的指定键值对。如果这个 key 并不存在,则会抛出异常。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

dictionary = {"a": "abandon", "b": "banana", "c": "clap"}

del_key = "c"
if del_key in dictionary:
    del dictionary["c"]

print(dictionary)

可以用另一个 dict 更新当前的字典。原字典中已存在的键值对会被覆盖,不存在的键值对将会添加。如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

dictionary = {"a": "abandon", "b": "banana", "c": "clap"}

dictionary.update({"a": "abuse", "d": "desk"})

# {'a': 'abuse', 'b': 'banana', 'c': 'clap', 'd': 'desk'}
print(dictionary)

字典可以使用 for 循环遍历。在下面的例子中,字典中每个键 key 被提取到了临时变量 k。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

dictionary = {"a": "abandon", "b": "banana", "c": "clap"}
for k in dictionary:
    print(dictionary[k], end=",")

*推导式

推导式是 Python 独特的,用于对复合数据类型进行映射的有效方式之一。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

xs = [1, 2, 3, 4, 5]
"""
    xs.map{x => 2*x}
"""
x2s = [2*x for x in xs] 
print(x2s)

上述的代码相当于对 xs 做了一步映射 ( map ) 操作:首先通过 for 表达式提取出 xs 的每一个值,经变换后收集到一个新的列表中去。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

再进一步,如果在推导式内安插 if 守卫,这个推导式还将可以实现对 xs 的过滤 ( fitler ) 操作。如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

xs = [1, 2, 3, 4, 5]
"""
	xs.filter{_ % 2 == 0}.map{_ * 2}
"""
x2s = [2*i for i in xs if i % 2 == 0]
print(x2s)

推导式适用于列表,区间,元组,集合,字典。比如,生成一连串的键值对:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

words = ["hello", "world", "python"]

# {'h': 'hello', 'w': 'world', 'p': 'python'}
k = {w[0]: w for w in words}
print(k)

列表推导式可以嵌套表达。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# seq2d -> row -> num
def flatten(seq2d): return [num for row in seq2d for num in row]

# 1 2 3 ... 8 9
xs = flatten(matrix)
print(*xs)

作者:花花子文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

来源:稀土掘金文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27836.html

  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/ymba/27836.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定