Python学习笔记(十)深浅拷贝与三大器

“Life is short, You need Python” – Bruce Eckel

Environment

  • OS: macOS Mojave
  • Python version: 3.7
  • IDE: Jupyter Notebook


0. 写在前面

Python 基础系列学习笔记最后一篇,记录一些必要的问题,包括深浅拷贝问题、迭代器与可迭代对象、生成器、装饰器。

至此,Python 基础内容就搞定了,能够应付基本的需求。

1. 拷贝

Python 中的拷贝分为浅拷贝和深拷贝,首先还要知晓一下可变类型和不可变类型。

1.1 不可变与可变

  • 不可变的数据类型包括数字、字符串、元组,在它们的生命周期,本身且其中元素的id保持不变

字符串

string = '12'
ori_id = id(string)

string += '3'
cur_id = id(string)

print(ori_id == cur_id) # False

元组

tp = (1, 2)
ori_id = id(tp)

tp += (3,)
cur_id = id(tp)

print(ori_id == cur_id) # False
  • 可变的类型包括列表、元组、集合,在它们的生命周期中,本身的id不变的情况下,其中的元素id可以发送变动。

列表

ls = [1, 2]
ori_id = id(ls)

ls.append(3)
cur_id = id(ls)

print(ori_id == cur_id) # True

字典

dc = {0: 1, 1: 2}
ori_id = id(dc)

dc[2] = 3
cur_id = id(dc)

print(ori_id == cur_id) # True



接着,以这个列表为例,了解直接赋值、浅拷贝和深拷贝的机制

ls = [0, ['python', 'java'], (6, 66, 666), {1: 'one', 2: 'two'}, 'string']

1.2 直接赋值

赋值创建一个新变量指向原变量引用的对象

在这里插入图片描述

1.3 浅拷贝

浅拷贝仅复制对象的第一层变量
在这里插入图片描述

1.4 深拷贝

深拷贝将复制对象的所有层变量,直到引用了不可变类型的变量为止
在这里插入图片描述

2. 迭代器

迭代器作为 Python 三器(迭代器、生成器、装饰器)之一,要理解这个东西,先得搞清楚迭代的概念,再分清可迭代对象迭代器的区别。

2.1 迭代的概念

迭代(iteration) 是为了接近某个目标而不断进行重复的过程,每次重复被称为一次迭代,每次迭代的结果作为下一次迭代的初始。

2.2 可迭代对象

能够被 for 结构进行迭代的对象称为可迭代对象,如字符串、列表、元组、字典、集合。这些对象含有定义了 __iter__ 魔法方法。

可以使用 collections.abc 中的 Iterable 类判断对象是否为可迭代的

from collections.abc import Iterable

ls = [1, 2, 3]
print(ls.__iter__)
# <method-wrapper '__iter__' of list object at 0x138157d48>

print(isinstance(ls, Iterable)) # True

作用于可迭代对象的常用函数

  • max(iterable, *[, default=obj, key=func])min(iterable, *[, default=obj, key=func]) 求最值
ls = [0, 1, 2, 3, 4]
tp = (0, 1, 2, 3, 4)
dc = {0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four'}
st = {0, 1, 2, 3, 4}

print(max(ls), max(tp), max(dc), max(st)) # 4 4 4 4

还可以获得字典最大的值对应的键

dc = {0: 9, 1: 5, 2: 2, 3: 7}
print(max(dc)) # 3
print(max(dc, key=dc.get)) # 0
  • sum(iterable, start=0, /) 求和,这里传入的对象只能是以整数或浮点数为元素的组合数据类型
ls = [0, 1, 2, 3, 4]
tp = (0, 1, 2, 3, 4)
dc = {0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four'}
st = {0, 1, 2, 3, 4}

print(sum(ls), sum(tp), sum(dc), sum(st)) # 10 10 10 10
  • sorted(iterable, /, *, key=None, reverse=False) 对序列类型进行排序,返回一个新的排序后的列表对象
ls = [0, 1, 2, 3, 4]
tp = (0, 1, 2, 3, 4)
dc = {0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four'}
st = {0, 1, 2, 3, 4}

print(sorted(ls)) # [0, 1, 2, 3, 4]
print(sorted(tp)) # [0, 1, 2, 3, 4]
print(sorted(dc)) # [0, 1, 2, 3, 4]
print(sorted(st)) # [0, 1, 2, 3, 4]
  • reversed(sequence) ,只能传入有序的数据类型,返回一个以倒序排列后的迭代器
reversed('string') # <reversed at 0x12ebcd9b0>

2.3 迭代器

迭代器一定是可迭代对象,而可迭代对象不一定是迭代器。

两者的相同之处在于都了定义了 __iter__ 魔法方法;不同之处在于,

  1. 迭代器还需要定义 __next__ 魔法方法,使之能够被传入 next 函数;
  2. 迭代器是惰性计算的,无法得到具体的长度,其没有 __len__ 属性。

可以通过 iter 函数由可迭代对象得到迭代器

from collections.abc import Iterable, Iterator

ls = [1, 2, 3]
ls_iter = iter(ls)
print(isinstance(ls_iter, Iterable)) # True
print(isinstance(ls_iter, Iterator)) # True

print(ls_iter.__next__)
# <method-wrapper '__next__' of list_iterator object at 0x137e32cc0>

可以使用 next 函数得到迭代器的下一个返回值,事实上这就是 for 迭代的机制

# for 迭代的机制
ls_iter = iter(ls)
while True:
    try:
        elem = next(ls_iter)
    except StopIteration:
        break
    
    pass # 执行体

尝试获得迭代器的长度,将报错

ls = [1, 2, 3]
ls_iter = iter(ls)
len(ls_iter)
# TypeError: object of type 'list_iterator' has no len()

2.3.1 迭代器的意义

ls = [i for i in range(100000)]
print(sys.getsizeof(ls)) # 824464

ls_iter = iter(range(100000))
print(sys.getsizeof(ls_iter)) # 48

迭代器的目的是将对迭代对象更好地进行迭代。

2.3.2 文件对象是迭代器

Python基础(七)文件中提到,可以对文件对象进行迭代,这里再次证实文件对象是一个迭代器

from collections.abc import Iterator

# 打开同目录下的 zen.txt 文件
with open('zen.txt', 'r', encoding='utf-8') as f:
    print(isinstance(f, Iterator))
# True

2.3.3 range 函数返回的对象不是迭代器

range 函数用于生产等差数字序列,注意,它返回的对象是可迭代的,但并不是迭代器。

print(isinstance(range(10), Iterable)) # True
print(isinstance(range(10), Iterator)) # False

可以称 range 函数的返回对象为一个懒序列,它是一种序列,但不包括任何内存中的内容,而是通过计算来回答问题。

range 函数返回的对象具有以下几个区别于迭代器的性质

numbers = range(3)
  • 有长度
len(numbers) # 3
  • 可索引
numbers[1] # 1
  • 不可进行 next 操作
next(numbers) # 报错
# TypeError: 'range' object is not an iterator
  • 当元素取完后会从头开始
for i in numbers:
    print(i)
print()

for i in numbers: # 依然可以继续从头开始迭代到结尾
    print(i)

2.3.4 创建迭代器

相较于 iter 函数外,以下函数更常被用作得到迭代器

  • enumerate(iterable, start=0) 返回一个以二元组(原序列每个元素的索引,元素)为元素的迭代器
ls = [1, 2, 3]
ls_enumerated = enumerate(ls)
print(isinstance(ls_enumerated, Iterator)) # True

while True:
    try:
        print(next(ls_enumerated))
    except StopIteration:
        break
# (0, 1)
# (1, 2)
# (2, 3)
  • zip(self, /, *args, **kwargs) 返回一个 n 元组的迭代器,元组以传入的 n 个可迭代对象的相同索引位置对应的元素
# 当传入的可迭代对象长度不同时,以最短者为准
ls_zipped = zip([1, 2, 3, 4], [5, 6, 7], [8, 9])
print(isinstance(ls_zipped, Iterator)) # True

while True:
    try:
        print(next(ls_zipped))
    except StopIteration:
        break
# (1, 5, 8)
# (2, 6, 9)

3. 生成器

3.1 生成器的概念

生成器与迭代器的区别:

  • 迭代器是从一系列元素中依次获取;
  • 生成器强调按规则生成值的序列,是“无中生有”的意思。

生成器对象同样有 __iter____next__ 方法,说明

  1. 生成器是一种特殊的迭代器
from collections.abc import Iterator

gen = (i for i in range(10))
print(type(gen)) # <class 'generator'>
print(isinstance(gen, Iterator)) # True
  1. 可以被 for 迭代作用
gen = (i for i in range(5))
for elem in gen:
    print(elem)
# 0
# 1
# 2
# 3
# 4
  1. 可以被传入 next 获取下一个元素
gen = (i for i in range(5))
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2

3.2 创建生成器

生成器可以通过生成器解析式和生成器函数创建。生成器解析式已在Python基础(三)组合数据类型 Part 5.4中介绍。这里主要介绍生成器函数的使用。

yield 代替普通函数中的 return 即可

生成斐波那契数列

def fib():
    '''一个生成斐波那契数列的生成器'''
    a, b = 0, 1
    while True:
        a, b = b, a + b
        print("Just before 'yield'")
        yield a
        print("Just after 'yield'")
        
        
gen = fib()
print(type(gen)) # <class 'generator'>

yield 关键字的两个作用

  1. 返回生成器函数的执行结果

  2. 保存一个生成器函数的运行状态(每次运行完 yield 语句,挂起)

观察斐波那契数列生成器函数的运行流程

# 第一次调用
res = next(gen)
# Just before 'yield'
print(res) # 1

# 第二次调用
res = next(gen)
# Just after 'yield'
# Just before 'yield'
print(res) # 1

# 第三次调用
res = next(gen)
# Just after 'yield'
# Just before 'yield'
print(res) # 2

# 之后同理

在生成器函数中的 yield 关键字位置定义一个赋值操作,允许调用使用生成器的 send 方法传入参数

定义一个素数生成器

import math


def is_prime(number):
    """判断数字是否为素数"""
    if number <= 1:
        return False
    if number == 2:
        return True
    if number % 2 == 0:
        return False
    for divisor in range(3, int(math.sqrt(number) + 1), 2):
        if number % divisor == 0:
            return False
    return True


def generatePrime(start=1):
    """生成素数的生成器,若传入了某一整数,则从该整数开始"""
    number = start
    while True:
        if is_prime(number):
            print("Just before 'yield'")
            start = yield number # 定义一个这样的赋值操作
            print("Jest after 'yield'")
        if start is not None:
            number = start
            start = None
        number += 1
        
        
prime_generator = generatePrime(5)

对于定义了 = yield 的生成器函数,next 函数相当于运行 send 方法,传入 None。也就是说,send 方法除了完成了 next 函数的任务以外,还将参数传入给生成器。

# 第一次调用使用 next 函数
res = next(prime_generator)
# Just before 'yield'
print(res) # 5

# 第二次使用 send 方法,传入 16,就会从 16 开始向后生成
res = prime_generator.send(16)
# Jest after 'yield'
# Just before 'yield'
print(res) # 17

# 再次调用 send 方法,传入 9,又从 9 开始向后生成
res = prime_generator.send(9)
# Jest after 'yield'
# Just before 'yield'
print(res) # 11

注意,生成器的运行流程为先运行至 yield 处,再发生可能的参数接收。因此,第一次调用生成器函数时,其并未在 yield 处挂起,无法进行变量接收。也就是说,第一次调用时,必须使用 next 函数或对 send 方法不传入参数。

prime_generator = generatePrime()

# 第一次调用
prime_generator.send(6) # 报错
# TypeError: can't send non-None value to a just-started generator

4. 装饰器

4.1 定义装饰器

装饰器能够在不改动程序中函数的调用方式的前提下,添加某些功能。

为函数的运行计时

import time


def timer(func):
    
    def inner():
        print('inner is running...')
        since = time.time()
        func()
        print('time taken:', time.time() - since)
        print('inner done.')
        
    return inner


def foo():
    print('foo is being called.')
    time.sleep(3)
    
    
foo = timer(foo)
foo()
# inner is running...
# foo is being called.
# time taken: 3.0018310546875
# inner done.

根据闭包(参考Python基础(五)函数 Part 5)可知,foo 包含了 inner 函数和 timer 的环境,如接收的参数 func。

foo = timer(foo) 之后,调用 foo 函数除了能够实现功能还达到了计时的效果。foo = timer(foo) 这个操作就像是使用 timer 对 foo 进行了装饰。

4.2 装饰器语法糖

装饰器语法糖语法

@decorator
def func(*args):
	pass

例1 等价于上例

import time


def timer(func):
    
    def inner():
        print('inner is running...')
        since = time.time()
        func()
        print('time taken:', time.time() - since)
        print('inner done.')
        
    return inner


@timer # 相当于执行了 foo = timer(foo)
def foo():
    print('foo is being called.')
    time.sleep(3)
    
    
foo()
# inner is running...
# foo is being called.
# time taken: 3.0018310546875
# inner done.

例2 被装饰的函数接收参数

import time


def timer(func):
    
    def inner(*args, **kwargs):
        print('inner is running...')
        since = time.time()
        func(*args, **kwargs)
        print('{} function done. Time taken: {}'.format(func.__name__, time.time() - since))
        
    return inner


@timer
def foo(n):
    print('foo is being called.')
    time.sleep(n)
    
    
foo(5)
# inner is running...
# foo is being called.
# foo function done. Time taken: 5.003183126449585

例3 被装饰的函数返回值不为 None

import time


def timer(func):
    
    def inner(*args, **kwargs):
        print('inner is running...')
        since = time.time()
        ret = func(*args, **kwargs)
        print('{} function done. Time taken: {}'.format(func.__name__, time.time() - since))
        return ret
        
    return inner


@timer
def foo(n):
    print('foo is being called.')
    time.sleep(n)
    return "I'm awake."

    
ret = foo(5)
# inner is running...
# foo is being called.
# foo function done. Time taken: 5.004913091659546

print(ret) # I'm awake.

例4 装饰器接收参数传入,实现按需求记录函数运行的时间或打印出当前日期

import time
import datetime


def timer(manner):
    
    def outer(func):
    
        def inner(*args, **kwargs):
            print('inner is running...')
            since = time.time()
            ret = func(*args, **kwargs)
            
            if manner == 'timer':
                print('{} function done. Time taken: {}'.format(
                    func.__name__, time.time() - since))
            elif manner == 'datetime':
                print('{} function done.\nToday is: {}'.format(
                    func.__name__, datetime.date.today()))
                
            return ret

        return inner
    
    return outer


@timer(manner='datetime')
def foo(n):
    print('foo is being called.')
    time.sleep(n)
    return "I'm awake."


ret = foo(3)
# inner is running...
# foo is being called.
# foo function done.
# Today is: 2020-03-10

print(ret) # I'm awake.

4.3 保持本真

虽然 timer 对 foo 进行了装饰,但实际上,调用装饰器后返回的 foo 与原本的 foo 并不相同

import time


def timer(func):
    
    def inner():
        since = time.time()
        func()
        print('time taken:', time.time() - since)
        
    return inner


def foo():
    print('foo is being called.')
    time.sleep(3)
    
    
foo = timer(foo)
print(foo.__name__) # inner

使用在 functools 中的 @wraps 可以让 foo 被装饰后名字不变

import time
from functools import wraps


def timer(func):
    @wraps(func)
    def inner():
        since = time.time()
        func()
        print('time taken:', time.time() - since)
        
    return inner


def foo():
    print('foo is being called.')
    time.sleep(3)
    
    
foo = timer(foo)
print(foo.__name__) # foo

4.4 用类作装饰器

import time


class Timer:
    def __init__(self, func):
        self.func = func
        
    def __call__(self, *args, **kwargs):
        since = time.time()
        self.func(*args, **kwargs)
        print('Time taken:', time.time() - since)
        
        
@Timer # 等价于 foo = Timer(foo)
def foo():
    print('foo is being called.')
    time.sleep(3)
    

foo()
# foo is being called.
# Time taken: 3.00045108795166

4.5 内置装饰器

记录一下 Python 中常用的内置装饰器:property、staticmethod 和 classmethod。

4.5.1 property 装饰器

property 函数见Python基础(六)类和对象 Part 3.3

使用 property 对访问、设置、删除实例属性的函数进行装饰

class Human:
    def __init__(self):
        self._skin_colour = 'Yellow'
    
    @property
    def skin_colour(self):
        """Skin colour"""
        print('Getting the attribute...')
        return self._skin_colour
    
    @skin_colour.setter
    def skin_colour(self, colour):
        print('Setting the attribute...')
        self._skin_colour = colour
        
    @skin_colour.deleter
    def skin_colour(self):
        print('Removing the attribute.')
        del self._skin_colour
        
        
human = Human()
print(Human.skin_colour.__doc__)
# Skin colour

print(human.skin_colour)
# Getting the attribute...
# Yellow

human.skin_colour = 'Brown'
# Setting the attribute...

del human.skin_colour
# Removing the attribute

注意,不要让被装饰的方法和属性重名

class Human:
    def __init__(self):
        self._skin_colour = 'Yellow'
    
    @property
    def skin_colour(self):
        """Skin colour"""
        print('Getting the attribute...')
        return self.skin_colour
    
    @skin_colour.setter
    def skin_colour(self, colour):
        print('Setting the attribute...')
        self.skin_colour = colour
        
    @skin_colour.deleter
    def skin_colour(self):
        print('Removing the attribute.')
        del self.skin_colour
        
        
human = Human()
print(human.skin_colour)
# Getting the attribute...
# Getting the attribute...
# Getting the attribute...
# .
# .
# .
# 报错 RecursionError

4.5.2 staticmethod 装饰器

该装饰器使类方法变为静态,不会绑定到实例对象上,以节省内存开销。

class Human:
    def walk(self):
        print("{} is walking..".format(self))
        
    @staticmethod
    def run(): # 不传入 self
        print('running...')
        
        
human = Human()
Human.run() # running...
human.run() # running...

4.5.3 classmethod 装饰器

该装饰器将类中定义的实例方法变为类方法

class Human:
    def walk(self):
        print("{} is walking..".format(self))
        
    @classmethod
    def run(cls): # 传入 cls
        print('{} is running...'.format(cls))
        
        
human = Human()
human.run()
# <class '__main__.Human'> is running...

Human.run()
# <class '__main__.Human'> is running...
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值