import

导入模块路径问题

  • 存在的问题:当我们把模块文件放到工程文件夹的外部的文件,发现无法正常引入模块
  • 原因: 外部的文件夹的路径,没有放到环境变量中
  • 查看环境变量
    1. 导入 sys模块
    2. sys.path 查看环境变量 返回值是列表
  • 把自己写的模块的路径加入到环境变量中
    • sys.path.append(“自己的路径”) # 加入环境变量的末位
    • sys.path.insert(0, “自己的路径”) # 加入到环境变量的开头位置

D盘创建一个app.py的文件。在项目中引入。

  1. #引入模块
  2. import module
  3. #获取模块属性
  4. print(module.name)
  5. import sys
  6. for p in sys.path:
  7. print(p)
  8. print('--'*20)
  9. # 把指定的路径加入到环境变量中
  10. # 追加到末尾
  11. # sys.path.append("D:\\")
  12. # 追加到开头
  13. #加载外部依赖
  14. sys.path.insert(0, "D:\\")
  15. for p in sys.path:
  16. print(p)
  17. import app
  18. print(app.name)

reload加载问题

  • import 导入模块后,如果模块被修改,此时再次 import 不起作用import 自动防止重复包含
  • 强制重新加载一次模块reload() 函数
    1. from imp import reload
    2. reload(要重新加载的模块)

修改D盘创建一个app.py的文件。在项目中引入。需要重新加载。
这个情况: 需要在交互模式的情况下或者项目运行期间。
image.png
image.png

from…import的私有化问题

  • 私有化: 模块中的一些变量不希望被其他模块导入,可以使用私有化解决
  • 私有化使用的前提:必须使用 “ from xxx import * “
  • 用法: 在模块中,把变量前增加一个下划线 _变量名
  • 注意:如果使用其他的方式导入模块,私有化将无效from xxx import 私有变量print(私有变量) 不会报错 ```python

    from module import *

    #

    print(name)

    print(_age)

from module import _age print(_age)

  1. <a name="C1oZ2"></a>
  2. ## import和from...import的区别
  3. - 区别
  4. - 写法:
  5. - import 模块名.变量/函数/类
  6. - from ... import * 变量名/函数/类
  7. - 底层的区别:
  8. - import 直接引用了源模块的 变量/函数/类
  9. - from ... import * 拷贝源模块的 变量/函数/类 到当前自己类
  10. <a name="x1X1L"></a>
  11. # 参数
  12. <a name="PdN8w"></a>
  13. ## 可变参数的拆包问题
  14. - 可变参数 *args **kwargs 默认会封包过程
  15. - 如果想要这种单数继续传递到下一个函数,传递的时候 func(*args,**kwargs)
  16. ```python
  17. # 定义两个函数 func02 func01
  18. # func02 调用func01
  19. # func02 有可变参数
  20. def func01(*args, **kwargs):
  21. print("--------- func01 ---------")
  22. print(args)
  23. print(kwargs)
  24. def func02(*args, **kwargs):
  25. print("args = ", args)
  26. print("kwargs = ", kwargs)
  27. # 调用func01
  28. # 此处没有进行拆包,导致参数传递过去不不和要求
  29. # func01(args,kwargs)
  30. if __name__ == '__main__':
  31. func02(10,20,30,40,50,a=10,b=20)

继承

单继承中super()

  • super() 使用的时候,传递参数的时候,self 不用传递
  • super() 调用顺序,按照 mro顺序来完成Grandson.mro 是一个元组当在类中使用 super() 在 mro列表中找到当前类的下一个元素,调用该元素的方法 ```python class Parent(object): def init(self, name):
      self.name = name
      print('parent的init结束被调用')
    

class Son1(Parent): def init(self, name, age):

    self.age = age
    super().__init__(name)
    print('Son1的init结束被调用')

class Grandson(Son1): def init(self, name, age, gender): self.gender = “男” super().init(name, age) # 单继承不能提供全部参数 print(‘Grandson的init结束被调用’)

gs = Grandson(‘grandson’, 12, ‘男’) print(Grandson.mro) print(‘姓名:’, gs.name) print(‘年龄:’, gs.age) print(‘性别:’, gs.gender)

执行顺序:父->子<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1867021/1636861229638-81a58b2e-bf68-450d-bc4c-9f76ce56a418.png#clientId=ufbcb5df2-b088-4&from=paste&height=175&id=uaefe2e81&margin=%5Bobject%20Object%5D&name=image.png&originHeight=175&originWidth=816&originalType=binary&ratio=1&size=16276&status=done&style=none&taskId=u72014027-d6fe-43ec-8caa-d13c49062bf&width=816)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1867021/1636861082810-05b069a7-6a5a-4945-a0d4-47ba3aed68bd.png#clientId=ufbcb5df2-b088-4&from=paste&height=197&id=u33583ab5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=197&originWidth=1790&originalType=binary&ratio=1&size=249075&status=done&style=none&taskId=u8d2b48ec-05c5-4ff2-833a-8ce198e628d&width=1790)
<a name="KITLI"></a>
## 多继承和MRO顺序

- 多继承中 super() 执行顺序,严格执行 MRO顺序表
- MRO顺序表:
   - 类名.mro()
   - 类名.__mro__
- 注意:当在类中使用 super() 在 mro列表中找到当前类的下一个元素,调用该元素的方法 多继承中,不建议使用类名 直接调用父类的方法
```python
# 定义父类Parent
class Parent(object):
    def __init__(self, name, *args, **kwargs):
        self.name = name
        print('parent的init结束被调用')


# 定义子类  Son1 --继承--> Parent
class Son1(Parent):
    def __init__(self, name, age, *args, **kwargs):
        self.age = age
        Parent.__init__(self, name, *args, **kwargs)
        print('Son1的init结束被调用')

# 定义子类  Son2 --继承--> Parent
class Son2(Parent):
    def __init__(self, name, gender, *args, **kwargs):
        self.gender = gender
        Parent.__init__(self, name, *args, **kwargs)
        print('Son2的init结束被调用')

# 定义子类  Grandson --继承--> Son1 \ Son2
class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        Son1.__init__(self,name, age, gender)  # 单独调用父类的初始化方法
        Son2.__init__(self, name, age, gender)
        print('Grandson的init结束被调用')

# 创建对象
gs = Grandson('grandson', 12, '男')
print(Grandson.mro())

类名 直接调用父类的初始化方法会导致父类多次初始化。不会严格执行MRO表的顺序。
image.png
改进:

# 定义父类Parent
class Parent(object):
    def __init__(self, name,*args, **kwargs):

        self.name = name
        print('parent的init结束被调用')


# 定义子类  Son1 --继承--> Parent
class Son1(Parent):
    def __init__(self, name,age, *args, **kwargs):

        self.age = age
        super().__init__( name,*args, **kwargs)
        print('Son1的init结束被调用')


# 定义子类  Son2 --继承--> Parent
class Son2(Parent):
    def __init__(self, name, gender,*args, **kwargs):

        self.gender = gender
        super().__init__(name,*args, **kwargs)
        print('Son2的init结束被调用')


# 定义子类  Grandson --继承--> Son1 \ Son2
class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):

        super().__init__(name, age,gender)  # 单独调用父类的初始化方法
        # Son2.__init__(self, name, gender)
        print('Grandson的init结束被调用')

# 创建对象
gs = Grandson('grandson', 12, '男')
print(Grandson.mro())

image.png
查看执行顺序:MRO
image.png
image.png

property

property属性就是负责把一个方法当做属性进行使用,这样做可以简化代码使用。
定义property属性有两种方式

  1. 装饰器方式
  2. 类属性方式

    使用场景:

    1.修饰方法,是方法可以像属性一样访问。

    ```python class Person(object):

    def init(self):

     self.__age = 0
    

    装饰器方式的property, 把age方法当做属性使用, 表示当获取属性时会执行下面修饰的方法

    @property def age(self):

     return self.__age
    

    把age方法当做属性使用, 表示当设置属性时会执行下面修饰的方法

    @age.setter def age(self, new_age):

     if new_age >= 150:
         print("成精了")
     else:
         self.__age = new_age
    

创建person

p = Person() print(p.age) p.age = 100 print(p.age) p.age = 1000

**代码说明:**

- @property 表示把方法当做属性使用, 表示当获取属性时会执行下面修饰的方法
- @方法名.setter 表示把方法当做属性使用,表示当设置属性时会执行下面修饰的方法
- 装饰器方式的property属性修饰的方法名一定要一样。
<a name="l3tVE"></a>
#### @property实现分页类
```python
"""
类名: Page
方法:
    1.初始化方法
    2.获取开始的位置
    3.获取结束的位置

"""

class Page(object):

    # 1.初始化方法
    def __init__(self,num):

        # 当前页
        self.current_page = num
        # 每页大小
        self.page_size = 10

    # 2.获取开始的位置
    @property
    def start(self):
        # limit (当前页-1)* 每页大小, 每页大小
        # 1,10
        # 11,20
        return (self.current_page-1)*10+1

    # 3.获取结束的位置
    @property
    def end(self):
        return self.current_page*self.page_size


# 创建类的对象
page = Page(2)
# 获取开始位置
# print(page.start())
print(page.start)
# 获取结束位置
print(page.end)
# print(page.end())

2.property其他使用方式

@price.deleter 删除
@property 获取
@price.setter 添加 ,可以传递一个参数

"""
类: Goods
方法:
    1.初始化方法
    2.获取价格的方法
    3.设置价格的方法
    4.删除价格的方法

"""
class Goods(object):

    # 1.初始化方法
    def __init__(self):
        # 初始化一个原价
        self.org_price = 1000
        # 初始化折扣
        self.discount = 0.7

    # 2.获取价格的方法
    @property
    def price(self):
        return self.org_price * self.discount

    # 3.设置价格的方法
    @price.setter
    def price(self,val):

        if val > 0:
            self.org_price = val

    # 4.删除价格的方法
    @price.deleter
    def price(self):
        print("执行了 deleter 方法")
        # del self.org_price


# 创建对象
goods = Goods()
# goods.price == goods.price()
print(goods.price)
# goods.price == goods.price(500)
goods.price = 500
print(goods.price)

# del goods.price == @price.deleter 装饰的方法
del goods.price

方法名都要一样,限制有点太古板。这就有了下面的类属性方法。

3.property对象的类属性

class Person(object):

    def __init__(self):
        self.__age = 0

    def get_age(self):
        """当获取age属性的时候会执行该方法"""
        return self.__age

    def set_age(self, new_age):
        """当设置age属性的时候会执行该方法"""
        if new_age >= 150:
            print("成精了")
        else:
            self.__age = new_age

    # 类属性方式的property属性
    age = property(get_age, set_age)

# 创建person
p = Person()
print(p.age)
p.age = 100
print(p.age)
p.age = 1000

代码说明:

  • property的参数说明:
    • 第一个参数是获取属性时要执行的方法
    • 第二个参数是设置属性时要执行的方法

如:

class Goods(object):

    # 1.初始化方法
    def __init__(self):
        # 初始化一个原价
        self.org_price = 1000
        # 初始化折扣
        self.discount = 0.7

    # 2.获取价格的方法
    def get_price(self):
        return self.org_price * self.discount

    # 3.设置价格的方法
    def set_price(self,val):

        if val > 0:
            self.org_price = val

    # 4.删除价格的方法
    def del_price(self):
        print("执行了 deleter 方法")
        # del self.org_price
    # property(第一个参数,第二个参数,第三个参数,第四个参数)
    # 第一个参数,当我们 foo.BAR 自动调用第一个参数的方法
    #  第二个参数,当我们 foo.BAR = 100,自动调用第二个参数的方法
    #  第三个参数,当我们 del foo.BAR ,自动调用第三个参数的方法
    #  第四个参数,当我们 Foo.BAR.__doc__,自动获取第四个参数的内容
    BAR = property(get_price,set_price,del_price,"BAR 是一个property对象")


if __name__ == '__main__':

    # 创建对象
    goods = Goods()
    #    goods.BAR == goods.get_price()
    print(goods.BAR)
    # 设置价格
    #    goods.BAR = 500, ===== goods.set_price(500)
    goods.BAR = 500
    print(goods.BAR)
    # 删除价格
    #    del goods.BAR    ===== @del_price.delter
    del goods.BAR
    # 获取对象描述
    #    Goods.BAR.__doc__  === Goods.__doc__
    print(Goods.BAR.__doc__)

魔法属性

  • 魔术属性
    • doc 获取描述信息
      • 获取类的 类名.doc
      • 获取方法的描述 对象.方法名.doc
    • module 获取所属的模块(对象名.module) 直接改文件 获取的main
    • class 获取对象所属的类 对象名.class
  • 魔术方法

    • init 初始化方法 类名() 自动调用
    • del 删除对象的时候,会调用 del 对象

      1.魔术方法和属性概述

      ```python class Goods(object): “””这是一个商品的类 Goods”””

      def set_price(self): “””这是Goods类中定义的设置价格方法””” pass

      def del(self): print(“del 正在执行”)

1、类的描述信息

类名.doc

print(Goods.doc)

goods = Goods()

g2 = goods

2、对象方法的描述

对象名.方法名.doc

print(goods.setprice._doc)

3、获取当前模块

print(goods.module)

4、获取对象所属的类

print(goods.class)

5、删除对象会执行 对象的 del()

del goods

print(“xxxxx”)

<a name="RJAHD"></a>
## 2.魔法属性和方法概述2
```python
class Goods(object):
    """这是一个商品的类 Goods"""
    # 类属性
    goods_color = "白色"

    def __init__(self):
        # 实例属性
        self.org_price = 100
        self.discount = 0.7

    def set_price(self):
        """这是Goods类中定义的设置价格方法"""
        pass

    def __call__(self, *args, **kwargs):
        print("__call__ 方法被调用")

    def __str__(self):
        return "我是一个寂寞的对象"

    def __del__(self):
        print("__del__ 正在执行")

    def __getitem__(self, item):
        print("key = ", item)

    def __setitem__(self, key, value):
        print("key = %s, value = %s" % (key, value))

    def __delitem__(self, key):
        print("要删除 key = ", key)


goods = Goods()
# 对象名()  会去调用对象的 __call__() 方法
goods()
# print 打印对象的时候,默认输出<__main__.Goods object at 0x7efe5e9e0a20>
print(goods)

# 通过__dict__ 获取对象信息,对象.__dict__返回字典
print(goods.__dict__)
# 通过__dict__ 获取类的信息  类名.__dict__  返回值是一个字典
print(Goods.__dict__)

dict1 = {}
dict1['a'] = 10

# goods['a']      调用 __getitem__方法
goods['a']

# goods['a'] = 10 调用 __setitem__   key,value
goods['a'] = 10

# del goods['a']  调用 __delitem__  key
del goods['a']

with管理上下文

1. with语句的使用

基础文件中写入数据的示例代码:

# 1、以写的方式打开文件
 f = open("1.txt", "w")
 # 2、写入文件内容
 f.write("hello world")
 # 3、关闭文件
 f.close()

代码说明:

  • 文件使用完后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的

这种写法可能出现一定的安全隐患,错误代码如下:

 # 1、以读的方式打开文件
 f = open("1.txt", "r")
 # 2、读取文件内容
 f.write("hello world")
 # 3、关闭文件
 f.close()

运行结果:

Traceback (most recent call last):
  File "/home/python/Desktop/test/xxf.py", line 4, in <module>
    f.write("hello world")
io.UnsupportedOperation: not writable

代码说明:

  • 由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。
  • 为了保证无论是否出错都能正确地关闭文件,我们可以使用try … finally来解决

安全写法, 代码如下:

try:
    # 1、以读的方式打开文件
    f = open("1.txt", "r")
    # 2、读取文件内容
    f.write("xxxxx")

except IOError as e:
    print("文件操作出错", e)

finally:
    # 3、关闭文件
    f.close()

运行结果:

文件操作出错 not writable

这种方法虽然代码运行良好,但是缺点就是代码过于冗长,并且需要添加try-except-finally语句,不是很方便,也容易忘记.
在这种情况下,Python提供了 with 语句的这种写法,既简单又安全,并且 with 语句执行完成以后自动调用关闭文件操作,即使出现异常也会自动调用关闭文件操作
with 语句的示例代码:

# 1、以写的方式打开文件
with open("1.txt", "w") as f:
    # 2、读取文件内容
    f.write("hello world")

2. 上下文管理器

一个类只要实现了enter()和exit()这个两个方法,通过该类创建的对象我们就称之为上下文管理器。
上下文管理器可以使用 with 语句,with语句之所以这么强大,背后是由上下文管理器做支撑的,也就是说刚才使用 open 函数创建的文件对象就是就是一个上下文管理器对象。
自定义上下文管理器类,模拟文件操作:
定义一个File类,实现 enter() 和 exit()方法,然后使用 with 语句来完成操作文件, 示例代码:

"""
类:  MyFile()
类方法:
    1. __enter__()  上文方法
    2. __exit__()   下文方法
    3. __init__()   方法,接收参数并且初始化


with MyFile('hello.txt', 'r') as file:
    file.read()

"""
class File(object):

    # 初始化方法
    def __init__(self, file_name, file_model):
        # 定义变量保存文件名和打开模式
        self.file_name = file_name
        self.file_model = file_model

    # 上文方法
    def __enter__(self):
        print("进入上文方法")
        # 返回文件资源
        self.file = open(self.file_name,self.file_model)
        return self.file

    # 下文方法
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("进入下文方法")
        self.file.close()


if __name__ == '__main__':

    # 使用with管理文件
    with File("1.txt", "r") as file:
        file_data = file.read()
        print(file_data)

运行结果:

进入上文方法
hello world
进入下文方法

代码说明:

  • enter表示上文方法,需要返回一个操作文件对象
  • exit表示下文方法,with语句执行完成会自动执行,即使出现异常也会执行该方法。

    3.装饰器实现上下文管理

    通过装饰器 @ contextmanager 实现上下文管理

  • 装饰器

  • 待装饰的函数myopen() 分拆成上文和下文,使用yield 分拆 ```python “”” 思路: def myopen(file_name,file_model)

          上文(打开资源)
          yield
          下文(关闭资源)
    

装饰器装饰函数的步骤:

  1. 导入模块 from contextlib import contextmanager
  2. 开始装饰 @contextmanager

“”” from contextlib import contextmanager

@contextmanager def myopen(file_name,file_model):

print("进入上文")
# 1.打开文件
file = open(file_name,file_model)
# 2.返回资源
yield file
print("进入下文")
# 下文
# 3. 关闭资源
file.close()

with myopen(“hello.txt”, “r”) as file: file_data = file.read() print(file_data)

<a name="t7jDc"></a>
## 4.小结

- Python 提供了 with 语句用于简化资源释放的操作,使用 with 语句操作建立在上下文管理器(实现__enter__和__exit__)的基础上
<a name="OyK74"></a>
# 生成器的创建方式
<a name="oQFU6"></a>
## 1. 生成器的介绍
根据程序员制定的规则循环生成数据,当条件不成立时则生成数据结束。数据不是一次性全部生成出来,而是使用一个,再生成一个,可以**节约大量的内存**。
<a name="o1nDo"></a>
## 2. 创建生成器的方式

1. 生成器推导式
1. yield 关键字
<a name="gKPne"></a>
### 生成器推导式:

- 与列表推导式类似,只不过生成器推导式使用小括号
```python
# 创建生成器
my_generator = (i * 2 for i in range(5))
print(my_generator)

# next获取生成器下一个值
# value = next(my_generator)
# print(value)

# 遍历生成器
for value in my_generator:
    print(value)

代码说明:

  • next 函数获取生成器中的下一个值
  • for 循环遍历生成器中的每一个值

    yield 关键字:

  • 只要在def函数里面看到有 yield 关键字那么就是生成器 ```python def mygenerater(n): for i in range(n):

      print('开始生成...')
      yield i
      print('完成一次...')
    

if name == ‘main‘:

g = mygenerater(2)
# 获取生成器中下一个值
# result = next(g)
# print(result)

# while True:
#     try:
#         result = next(g)
#         print(result)
#     except StopIteration as e:
#         break

# # for遍历生成器, for 循环内部自动处理了停止迭代异常,使用起来更加方便
for i in g:
    print(i)
**代码说明:**

- 代码执行到 yield 会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行
- 生成器如果把数据生成完成,再次获取生成器中的下一个数据会抛出一个StopIteration 异常,表示停止迭代异常
- while 循环内部没有处理异常操作,需要手动添加处理异常操作
- for 循环内部自动处理了停止迭代异常,使用起来更加方便,推荐大家使用。

**运行结果:**
```python
开始生成...
0
完成一次...
开始生成...
1
完成一次...

3. 生成器的使用场景

数学中有个著名的斐波拉契数列(Fibonacci),数列中第一个数为0,第二个数为1,其后的每一个数都可由前两个数相加得到:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …
现在我们使用生成器来实现这个斐波那契数列,每次取值都通过算法来生成下一个数据, 生成器每次调用只生成一个数据,可以节省大量的内存。

def fibonacci(num):
    a = 0
    b = 1
    # 记录生成fibonacci数字的下标
    current_index = 0
    while current_index < num:
        result = a
        a, b = b, a + b
        current_index += 1
        # 代码执行到yield会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行
        yield result


fib = fibonacci(5)
# 遍历生成的数据
for value in fib:
    print(value)

运行结果:

0
1
1
2
3

小结

  • 生成器是根据算法生成数据的一种机制,每次调用生成器只生成一个值,可以节省大量内存。
  • 生成器的创建有两种方式:
    1. 生成器推导式
    1. yield 关键字
    

    深拷贝和浅拷贝

    1. 浅拷贝

    copy函数是浅拷贝,只对可变类型的第一层对象进行拷贝,对拷贝的对象开辟新的内存空间进行存储,不会拷贝对象内部的子对象。
    不可变类型的浅拷贝示例代码: ```python import copy # 使用浅拷贝需要导入copy模块

不可变类型有: 数字、字符串、元组

a1 = 123123 b1 = copy.copy(a1) # 使用copy模块里的copy()函数就是浅拷贝了

查看内存地址

print(id(a1)) print(id(b1))

print(“-“ * 10) a2 = “abc” b2 = copy.copy(a2)

查看内存地址

print(id(a2)) print(id(b2))

print(“-“ * 10) a3 = (1, 2, [“hello”, “world”]) b3 = copy.copy(a3)

查看内存地址

print(id(a3)) print(id(b3))

**运行结果:**
```python
140459558944048
140459558944048
----------
140459558648776
140459558648776
----------
140459558073328
140459558073328

不可变类型的浅拷贝说明:

  • 通过上面的执行结果可以得知,不可变类型进行浅拷贝不会给拷贝的对象开辟新的内存空间,而只是拷贝了这个对象的引用。

可变类型的浅拷贝示例代码:

import copy # 使用浅拷贝需要导入copy模块

# 可变类型有: 列表、字典、集合

a1 = [1, 2]
b1 = copy.copy(a1) # 使用copy模块里的copy()函数就是浅拷贝了
# 查看内存地址
print(id(a1))
print(id(b1))
print("-" * 10)
a2 = {"name": "张三", "age": 20}
b2 = copy.copy(a2)
# 查看内存地址
print(id(a2))
print(id(b2))
print("-" * 10)
a3 = {1, 2, "王五"}
b3 = copy.copy(a3)
# 查看内存地址
print(id(a3))
print(id(b3))

print("-" * 10)
a4 = [1, 2, [4, 5]]
# 注意:浅拷贝只会拷贝父对象,不会对子对象进行拷贝
b4 = copy.copy(a4) # 使用copy模块里的copy()函数就是浅拷贝了
# 查看内存地址
print(id(a4))
print(id(b4))
print("-" * 10)
# 查看内存地址
print(id(a4[2]))
print(id(b4[2]))

# 修改数据
a4[2][0] = 6

# 子对象的数据会受影响
print(a4)
print(b4)

运行结果:

139882899585608
139882899585800
----------
139882919626432
139882919626504
----------
139882919321672
139882899616264
----------
139882899587016
139882899586952
----------
139882899693640
139882899693640
[1, 2, [6, 5]]
[1, 2, [6, 5]]

可变类型的浅拷贝说明:

  • 通过上面的执行结果可以得知,可变类型进行浅拷贝只对可变类型的第一层对象进行拷贝,对拷贝的对象会开辟新的内存空间进行存储,子对象不进行拷贝。

    2. 深拷贝

    deepcopy函数是深拷贝, 只要发现对象有可变类型就会对该对象到最后一个可变类型的每一层对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储。
    不可变类型的深拷贝示例代码: ```python import copy # 使用深拷贝需要导入copy模块

不可变类型有: 数字、字符串、元组

a1 = 1 b1 = copy.deepcopy(a1) # 使用copy模块里的deepcopy()函数就是深拷贝了

查看内存地址

print(id(a1)) print(id(b1)) print(“-“ * 10) a2 = “张三” b2 = copy.deepcopy(a2)

查看内存地址

print(id(a2)) print(id(b2)) print(“-“ * 10) a3 = (1, 2) b3 = copy.deepcopy(a3)

查看内存地址

print(id(a3)) print(id(b3)) print(“-“ * 10)

注意: 元组里面要是有可变类型对象,发现对象有可变类型就会该对象到最后一个可变类型的每一层对象进行拷贝

a4 = (1, [“李四”]) b4 = copy.deepcopy(a4)

查看内存地址

print(id(a4)) print(id(b4))

元组里面的可变类型子对象也会进行拷贝

print(id(a4[1])) print(id(b4[1]))

**运行结果:**
```python
9289120
9289120
----------
140115621848320
140115621848320
----------
140115621859592
140115621859592
----------
140115602480584
140115621834568
140115602328136
140115602436168

不可变类型的深拷贝说明:

  • 通过上面的执行结果可以得知:
    • 不可变类型进行深拷贝如果子对象没有可变类型则不会进行拷贝,而只是拷贝了这个对象的引用,否则会对该对象到最后一个可变类型的每一层对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储。

可变类型的深拷贝示例代码:

import copy  # 使用深拷贝需要导入copy模块

# 可变类型有: 列表、字典、集合

a1 = [1, 2]
b1 = copy.deepcopy(a1)  # 使用copy模块里的deepcopy()函数就是深拷贝了
# 查看内存地址
print(id(a1))
print(id(b1))
print("-" * 10)
a2 = {"name": "张三"}
b2 = copy.deepcopy(a2)
# 查看内存地址
print(id(a2))
print(id(b2))
print("-" * 10)
a3 = {1, 2}
b3 = copy.deepcopy(a3)
# 查看内存地址
print(id(a3))
print(id(b3))
print("-" * 10)

a4 = [1, 2, ["李四", "王五"]]
b4 = copy.deepcopy(a4)  # 使用copy模块里的deepcopy()函数就是深拷贝了
# 查看内存地址
print(id(a4))
print(id(b4))

# 查看内存地址
print(id(a4[2]))
print(id(b4[2]))
a4[2][0] = "王五"
# 因为列表的内存地址不同,所以数据不会收到影响
print(a4)
print(b4)

运行结果:

140348291721736
140348291721928
----------
140348311762624
140348311221592
----------
140348311457864
140348291752456
----------
140348291723080
140348291723144
140348291723208
140348291723016
[1, 2, ['王五', '王五']]
[1, 2, ['李四', '王五']]

可变类型的深拷贝说明:

  • 通过上面的执行结果可以得知, 可变类型进行深拷贝会对该对象到最后一个可变类型的每一层对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储。


3. 浅拷贝和深拷贝的区别

  • 浅拷贝最多拷贝对象的一层
  • 深拷贝可能拷贝对象的多层

    总结

  • 浅拷贝使用copy.copy函数

  • 深拷贝使用copy.deepcopy函数
  • 不管是给对象进行深拷贝还是浅拷贝,只要拷贝成功就会开辟新的内存空间存储拷贝的对象。
  • 浅拷贝和深拷贝的区别是:
    • 浅拷贝最多拷贝对象的一层,深拷贝可能拷贝对象的多层。