Python数据类型底层、推导、生成器、迭代器、装饰器

本文详细讲解了Python中列表、字典、字符串的底层实现,比较浅拷贝与深拷贝的区别,以及迭代器和装饰器在代码中的应用。通过实例演示列表操作的特性,包括浅拷贝导致的意外行为,以及如何使用深拷贝解决可变类型问题。
摘要由CSDN通过智能技术生成

系列文章:



数据类型的底层实现

1、列表的复制

1.1、浅拷贝复制的奇怪现象

对拷贝前后的两个列表进行操作

  1. list_2 进行操作
list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "Sarah"}]
# 浅拷贝
list_2 = list_1.copy()
list_2.append(55)
print("list_1:  ", list_1) 
#list_1:   [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}]

print("list_2:  ", list_2) 
#list_2:   [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 55]
  1. list_2[1] 进行操作
list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "Sarah"}]
# 浅拷贝
list_2 = list_1.copy()
list_2[1].append(55)
print("list_1:  ", list_1) 
#list_1:   [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]

print("list_2:  ", list_2)
#llist_2:   [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]

1.2、列表的底层实现

  1. 新增元素
list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "Sarah"}]
list_2 = list(list_1) # 浅拷贝  与list_1.copy()功能相同
list_1.append(100)
list_2.append("n")
print("list_1:  ", list_1) 
#list_1:   [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 100]

print("list_2:  ", list_2) 
#list_2:   [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 'n']
  1. 修改元素
list_1[0] = 10
list_2[0] = 20
print("list_1:  ", list_1) 
#list_1:   [10, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 100]

print("list_2:  ", list_2) 
#list_2:   [20, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 'n']
  1. 对列表型元素进行操作
list_1[1].remove(44)
list_2[1] += [55,66]
print("list_1:  ", list_1) 
# list_1:   [10, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah'}, 100]

print("list_2:  ", list_2) 
#list_2:   [20, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah'}, 'n']

结果表明:对列表型元素进行操作,两个表的列表型元素都同时改变

  1. 对元组型元素进行操作
list_2[2] += (8,9)
print("list_1:  ", list_1) 
#list_1:   [10, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah'}, 100]

print("list_2:  ", list_2) 
#list_2:   [20, [22, 33, 55, 66], (5, 6, 7, 8, 9), {'name': 'Sarah'}, 'n']

元组是不可变的!
对元组类型元素进行操作,两个表是独立的,互不影响
5. 对字典型元素进行操作

list_1[-2]["age"] = 18 
print("list_1:  ", list_1) 
#list_1:   [10, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah', 'age': 18}, 100]

print("list_2:  ", list_2) 
#list_2:   [20, [22, 33, 55, 66], (5, 6, 7, 8, 9), {'name': 'Sarah', 'age': 18}, 'n']

结论同列表

1.3、引入深拷贝

浅拷贝后:
针对不可变元素(数字、字符串、元组)的操作,两个列表互不影响
针对可变元素(列表、集合、字典)的操作,列表中的元素是有影响的

引入深拷贝
深拷贝将所有层级的相关元素全部复制,完全分开,渭泾分明,避免了上述问题

import copy

list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "Sarah"}]
list_2 = copy.deepcopy(list_1)
list_1[-1]["age"] = 18
list_2[1].append(55)

print("list_1:  ", list_1) #list_1:   [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah', 'age': 18}]
print("list_2:  ", list_2) #list_2:   [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]

2、字典

2.1、快速的查找

import time
ls_1 = list(range(1000000))
# [-10]*500 是初始化值为-10的长度为500的list, 之后使用+ 拼接两个list
# 最终ls_2 长度是500 + 500
ls_2 = list(range(500))+[-10]*500

start = time.time()
count = 0
for n in  ls_2:
    if n in ls_1:
        count += 1
end = time.time()
print("查找{}个元素,在ls_1列表中的有{}个,共用时{}秒".format(len(ls_2), count, end-start))
#查找1000个元素,在ls_1列表中的有500个,共用时3.092348575592041秒
import  time
d = {i: i for i in range(1000000)}
ls_2 = list(range(500))+[-10]*500

start = time.time()
count = 0
for n in ls_2:
    try:
        d[n]
    except:
        pass
    else: count += 1
end = time.time()
print("查找{}个元素,在ls_1列表中的有{}个,共用时{}秒".format(len(ls_2), count,round(end-start)))
# 查找1000个元素,在ls_1列表中的有500个,共用时0秒

2.2、字典的底层实现

字典的创建过程

  1. 第一步:创建一个散列表(稀疏数组N >> n)
    d = {}
  2. 第二步:通过hash() 计算键的散列值
print(hash("python"))
print(hash(1024))
print(hash((1,2)))
-4771046564460599764
1024
3713081631934410656
d["age"] = 18    # 增加键值对的操作,首先会计算键的散列值hash("age")
print(hash("age")) 
  1. 第三步根据计算的散列值确定其在散列表中的位置
    极个别时候,散列值会发生冲突,则内部有相应的解决冲突的办法
  2. 在该位置上存入值

键值对的访问过程

d["age"]
  1. 第一步:计算要访问的键的散列值
  2. 第二步:根据计算的散列值,通过一定的规则,确定其在散列表中的位置
  3. 第三步:读取该位置上存储的值
    如果存在,,则返回该值
    如果不存在,则报错 KeyError

小结

  1. 字典数据类型,以空间换时间(空间利用率低),实现类快速地数据查找
  2. 因为散列值对应位置的顺序与键在字典中显示的顺序可能不同,因此表现出字典是无序的
    如果N=n 会产生很多位置冲突

3、字符串

通过紧凑数组实现字符串的存储

  • 数据在内存中是连续存放的,效率高
  • 同为序列类型,列表采用引用数组,而字符串采用紧凑数组

3.1、可变类型与不可变类型

不可变类型:数字、元组、字符串
在生命周期中保持内容不变
当改变了时,会有新的id,实际上创建了一个新的对象

x= 1
y = "Python"
print("x id:", id(x)) #x id: 1447911844144
print("y id:", id(y)) #y id: 1447917164912
x += 2
y += "3.7"
print("x id:", id(x)) #x id: 1447911844208
print("y id:", id(y)) #y id: 1447917208304

元组并不是总是不可变的

t = (1,[2])
print(id(t)) #2524431430976
t[1].append(3) 
print(id(t))#2524431430976
print(t) #(1, [2, 3])

可变类型:列表、字典、集合
id 保持不变,但里面的内容可以变

ls = [1, 2, 3]
d = {"Name": "Sarah", "Age": 18}

print("ls id:", id(ls)) #ls id: 2864434660288
print("d id:", id(d)) #d id: 2864434589056
ls += [4, 5]
d_2 = {"Sex": "female"}
d.update(d_2)    # 把d_2中的元素更新到d中
print("ls id:", id(ls)) #ls id: 2864434660288
print("d id:", id(d)) #d id: 2864434589056

4、列表操作的几个例子

4.1、删除列表中特定的元素

  1. 法1 存在运算删除法
alist = ["d", "d", "d", "2", "2", "d" ,"d", "4"]
s = "d"
while True:
    if s in alist:
        alist.remove(s)
    else:
        break
  1. 法2 一次性遍历元素执行删除
alist = ["d", "d", "d", "2", "2", "d" ,"d", "4"]
s = "d"
for i in alist:
    if i == s:
        alist.remove(s) # remove(s) 删除列表中第一次出现的该元素
print(alist) #['2', '2', 'd', 'd', '4']

解决方法:使用负向索引

alist = ["d", "d", "d", "2", "2", "d" ,"d", "4"]
s = "d"
for i in range(-len(alist),0):
    if alist[i] == s:
        alist.remove(alist[i])
print(alist) #['2', '2', '4']

4.2、多维列表的创建

print([0]*10) #[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
print([[0]*10]*3)
# [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
ls[0][0] = 1
print(ls)
# [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
#  [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
ls = [[0]*10 for i in range(3)]
print(ls)
# [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
ls[0][0]=1
print(ls)
# [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

4.4 列表推导

解析语法的基本结构—以列表解析为例(也称为列表推导)

[expression for value in iterable if conditihon]
三要素:表达式、可迭代对象、if条件(可选)
执行过程

  1. 从可迭代对象中拿出一个元素
  2. 通过if条件(如果有的话),对元素进行筛选。
    若通过筛选,则把元素传递给表达式
    若未通过,则进入步骤1,进入下一次迭代
  3. 将传递给表达式的元素,带入表达式进行处理,产生一个结构
  4. 将步骤3产生的结果作为列表的一个元素进行存储
  5. 重复步骤1-4直到迭代对象迭代结束,返回新创建的列表

等价于如下过程:

# 等价于如下代码
result = []
for value in iterale:
    if condition:
        result.append(expression)

【例】求20以内奇数的平方

squares = []
for i in range(1,21):
    if i%2 == 1 :
        squares.append(i**2)
print(squares) #[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]

squares= [i**2 for i in range(1,21) if i%2==1]
print(squares) #[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]

支持多变量

x = [1,2,3]
y = [1,2,3]

result = [i*j for i,j in zip(x,y)]
print(result)

支持嵌套循环

colors = ["black", "white"]
sizes = ["S", "M", "L"]
tshirts = ["{} {}".format(color, size) for color in colors for size in sizes]
print(tshirts)
#['black S', 'black M', 'black L', 'white S', 'white M', 'white L']

4.5 字典推导

解析语法构造字典(字典推导)

squares = {i: i**2 for i in range(5)}
for k, v in squares.items():
    print(k, ":", v,end=",")#0 : 0,1 : 1,2 : 4,3 : 9,4 : 16,

4.6集合推导

squares = {i**2 for i in range(5)}
print(squares)#{0, 1, 4, 9, 16}

4.7 生成器表达式

squares = (i**2 for i in range(10))
print(squares) #<generator object <genexpr> at 0x00000204790BF0B0>
colors = ["black", "white"]
sizes = ["S", "M", "L"]
tshirts = ("{} {}".format(color , size) for color in colors for size in sizes)
print(tshirts) #<generator object <genexpr> at 0x000002EF6451F0B0>
for tshirt in tshirts:
    print(tshirt)
# black S
# black M
# black L
# white S
# white M
# white L

4.8条件表达式

expr1 if condition else expr2
【例】将变量n的绝对值赋值给变量x

n = -10
if n >= 0:
    x = n
else: x = -n
print(x) #10
n = -10
x = n if n >= 0 else  -n
print(x) #10

条件表达式和解析语法简单实用、运行速度相对更快一些

三大神器

1、生成器

ls = [i**2 for i in range(1,100001)]
for i in ls:
    pass

缺点:占用大量内存

生成器

  • List item采用惰性计算方式
  • 无需一次性存储海量数据
  • 一边执行一边计算,只计算每次需要的值
  • 实际上一直在执行next()操作,直到无值可取

1.1生成器表达式

  • 海量数据,不需存储
squares = (i**2 for i in range(100000))
for i in squares:
    pass
  • 求0~100的和

无需显示存储全部数据,节省内存

sum((i for i in range(101))) #5050

1.2 生成器函数—yield

  • 生产斐波那契数列
    数列前两个元素为1,1 之后的元素为其前两个元素之和
def fib(max):
    n ,a, b = 0, 1, 1
    while n < max:
        print(a)
        a, b = b, a + b
        n += 1
fib(5)
1
1
2
3
5

构造生成器函数
在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句继续执行

def fib(max):
    n ,a, b = 0, 1, 1
    while n < max:
        yield a
        a, b = b, a + b
        n += 1
print(fib(5)) #<generator object fib at 0x000001FEF92CF0B0>
for i in  fib(5):
    print(i)
1
1
2
3
5

2.迭代器

1、可迭代对象

可迭代对象:可直接作用于for循环的对象统称为可迭代对象:Iterable
(1) 列表、元组、字符串、字典、集合、文件

from collections.abc import Iterable
# Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
print(isinstance([1, 2, 3],Iterable)) #True
print(isinstance((1, 2, 3),Iterable)) #True
print(isinstance({1, 2, 3},Iterable)) #True
print(isinstance("123",Iterable)) #True
print(isinstance({1:1, "2":2},Iterable)) #True

(2) 生成器

from collections.abc import  Iterable
squares = (i**2 for i in range(5))
print(isinstance(squares, Iterable)) #True

生成器不但可以用于for循环,还可以被next()函数调用

from collections.abc import  Iterable
squares = (i**2 for i in range(5))
print(next(squares)) 
print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))
0
1
4
9
16

直到没有数据可取,抛出StopIteration

print(next(squares))
Traceback (most recent call last):
  File "E:\code\deeplea\pytorchlearn\ForTest\test.py", line 8, in <module>
    print(next(squares))
StopIteration

2、迭代器

可以被next()函数调用并不断返回下一个值,直到没有数据可取的对象称为迭代器: Iterator
可以使用 isinstance() 判断一个对象是否是Iterator对象
注意 可迭代对象:Iterable 用 isinstance([1, 2, 3],Iterable)判断
迭代器:Iterator 用 isinstance([1, 2, 3], Iterator)

(1)生成器都是迭代器

from collections.abc import Iterator
sequares = (i**2 for i in range(5))
print(isinstance(sequares, Iterator)) #True

(2)列表、元组、字符串、字典、集合不是迭代器Iterator,但它们是可迭代对象Iterable

from collections.abc import  Iterator

print(isinstance([1, 2, 3], Iterator)) #False

可以通过 iter(Iterable) 创建迭代器

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

for item in Iterable 等价于:
先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration异常后循环结束。
(3)zip enumetate 等itertools里的函数都是迭代器

from collections.abc import Iterator

x = [1, 2]
y = ["a", "b"]
print(zip(x ,y)) #<zip object at 0x0000019095162E00>
for i in zip(x, y):
    print(i)
print(isinstance(zip(x, y),Iterator)) #True
from collections.abc import Iterator

numbers = [1, 2]
print(enumerate(numbers)) #<enumerate object at 0x000001AF415C2E00>
for i in enumerate(numbers):
    print(i)
    # (0, 1)
    # (1, 2)
print(isinstance(enumerate(numbers),Iterator)) #True

(4)文件是迭代器

with open("测试文件.txt", "r", encoding = "utf-8") as f:
    print(isinstance(f, Iterator))#True

(5)迭代器是可耗尽的

squares = (i**2 for i in range(3))
for square in squares:
    print(square)
# 0
# 1
# 4

for square in squares:
    print(square) #无输出

(6)range() 不是迭代器

from collections.abc import Iterator
numbers  = range(10)
print(isinstance(numbers,Iterator)) #False
print(len(numbers)) ## 有长度
print(numbers[0]) # 可索引
print(9 in numbers) # 可存在计算
next(numbers) # 不可被next()调用 TypeError: 'range' object is not an iterator

不会被耗尽

for number in numbers:
    print(number) #有输出
    
for number in numbers:
    print(number) #有输出

可以称range() 为懒序列,它是一种序列,但是并不包含任何内存中的内容,而是通过计算来回答问题

3.装饰器

1、需求的提出
(1)需要对已开发上线的程序添加某些功能
(2)不能对程序中函数的源代码进行修改
(3)不能改变程序中函数的调用方式

2、函数对象
函数是Python中的第一类对象
(1)可以把函数赋值给变量
(2)对该变量进行调用,可实现原函数的功能

def square(x):
    return x**2
print(type(square)) #<class 'function'>
pow_2 = square #可以理解给这个函数起了个别名
print(pow_2(5)) #25
print(square(5)) #25

可以将函数作为参数进行传递

3、高阶函数
(1)接受函数作为参数
(2)或者返回一个函数
满足上述条件之一的函数称为高阶函数

def square(x):
    return x**2

def pow_2(fun):
    return  fun

f = pow_2(square)
print(f(8)) #64
print(f == square) #True

4、嵌套函数
在函数内部定义一个函数

def outer():
    print("outer is running")

    def inner():
        print("inner is running")

    inner()

outer()
# outer is running
# inner is running

5、闭包

def outer():
    x = 1
    z = 10
    
    def inner():
        y = x+100
        return y, z
        
    return inner


f = outer()                # 实际上f包含了inner函数本身+outer函数的环境
print(f)
<function outer.<locals>.inner at 0x000001BE11B1D730>
print(f.__closure__)         # __closure__属性中包含了来自外部函数的信息
for i in f.__closure__:
    print(i.cell_contents)
(<cell at 0x000001BE0FDE06D8: int object at 0x00007FF910D59340>, <cell at 0x000001BE0FDE0A98: int object at 0x00007FF910D59460>)
1
10
res = f()
print(res)
(101, 10)

闭包:延伸了作用域的函数
如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包,闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数+引用环境)

  • 一旦在内层函数重新定义了相同名字的变量,则变量称为局部变量
def outer():
    x = 1

    def inner():
        # nonlocal  x
        x = x+100  #显示代码有误:不能解析x
        return x

    return inner

f = outer
f()
def outer():
    x = 1

    def inner():
        nonlocal  x
        x = x+100
        return x

    return inner

f = outer
f()

6、一个简单的装饰器
嵌套函数实现

import time
def timer(func):
    def inner():
        print("inner run")
        start = time.time()
        func()
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))

    return inner

def f1():
    print("f1 run")
    time.sleep(1)

# f1 包含inner() 和timer的环境,如传递过来的参数
f1 = timer(f1) 
f1()
# inner run
# f1 run
# f1 函数运行用时1.01秒

语法糖

import time
def timer(func):
    def inner():
        print("inner run")
        start = time.time()
        func()
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))

    return inner

@timer #相当于实现类f1 = timer(f1)
def f1():
    print("f1 run")
    time.sleep(1)

f1()
# inner run
# f1 run
# f1 函数运行用时1.01秒

7、装饰有参函数

import time

def timer(func):
    def inner(*args, **kwargs):
        print("inner run")
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end - start)))

    return inner

@timer
def f1(n):
    print("f1 run")
    time.sleep(n)

f1(2)
# inner run
# f1 run
# f1 函数运行用时2.00秒

装饰函数有返回的情况

import time

def timer(func):
    
    def inner(*args, **kwargs):
        print("inner run")
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end - start)))
        return  res

    return inner

@timer
def f1(n):
    print("f1 run")
    time.sleep(n)
    return "wake up"

res = f1(2)
print(res)
# inner run
# f1 run
# f1 函数运行用时2.00秒
# wake up

8、带参数的装饰器
装饰器本身要传递一些额外参数

  • 需求:有时需要统计绝对时间,有时需要统计绝对时间的2倍
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值