13.import、property、继承、with

import

import导入模块路径问题

  • 存在的问题:当我们把模块文件放到工程文件夹的外部的文件,发现无法正常引入模块
  • 原因: 外部的文件夹的路径,没有放到环境变量中
  • 查看环境变量
    • 导入 sys模块
    • sys.path 查看环境变量 返回值是列表
  • 把自己写的模块的路径加入到环境变量中
    • sys.path.append("自己的路径") # 加入环境变量的末位
    • sys.path.insert(0, "自己的路径") # 加入到环境变量的开头位置
    • 添加路径后才能导入
import sys
sys.path
# sys.path.append("d:\\git-python\\进阶\\py")  增加路径
# sys.path.pop(-1)  删除路径
# sys.path.pop(0)
# sys.path.insert(0, "d:\\git-python\\进阶\\py")
['d:\\git-python\\进阶',
 'd:\\python\\python312.zip',
 'd:\\python\\DLLs',
 'd:\\python\\Lib',
 'd:\\python',
 '',
 'C:\\Users\\吴玉堂\\AppData\\Roaming\\Python\\Python312\\site-packages',
 'C:\\Users\\吴玉堂\\AppData\\Roaming\\Python\\Python312\\site-packages\\win32',
 'C:\\Users\\吴玉堂\\AppData\\Roaming\\Python\\Python312\\site-packages\\win32\\lib',
 'C:\\Users\\吴玉堂\\AppData\\Roaming\\Python\\Python312\\site-packages\\Pythonwin',
 'd:\\python\\Lib\\site-packages',
 'd:\\git-python\\进阶\\py']

import的reload加载问题

  • import 导入模块后,如果模块被修改,此时再次 import 不起作用
    • import 自动防止重复包含,多次导入不会起作用
  • 强制重新加载一次模块
    • reload() 函数
    • from importlib import reload
    • reload(要重新加载的模块)
# reload_test.py
def test():
    print("---111---")
    print("---222---")
name = "haha"
_age = "18"
import reload_test
reload_test.test()

# reload_test.py  增加输出 ---222---
# ---111---     输出不变
---111---
import reload_test
from importlib import reload
reload(reload_test)
reload_test.test()

# ---111---
# ---222---
---111---
---222---

from...import的私有化问题

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

# print(_age)     # NameError: name '_age' is not defined
haha
from reload_test import *
from reload_test import _age
from importlib import reload
reload(reload_test)
print(name)
print(_age)
haha
18

import和from...import的区别

  • 区别
    • 写法:
    • import 模块名.变量/函数/类
    • from 模块 import 变量名/函数/类
    • 底层的区别:
    • import 直接引用了源模块的 变量/函数/类
    • from ... import *  添加新引用 变量/函数/类 到当前自己类

可变参数的拆包问题

  • 可变参数 *args **kwargs 默认会有封包过程
  • 如果想要这种单数继续传递到下一个函数,传递的时候必须加 号 func(args,**kwargs)
# 定义两个函数  func02 func01
# func02 调用func01
# func02 有可变参数

def func01(*args, **kwargs):
    print("--------- func01 ---------")
    print("args =", args)
    print("kwargs =", kwargs)

def func02(*args, **kwargs):
    print("--------- func02 ---------")
    print("args =", args)
    print("kwargs =", kwargs)

    # 调用func01
    # 此处没有进行拆包,导致参数传递过去不符合要求
    func01(args, kwargs)
    func01(*args, **kwargs)

if __name__ == '__main__':
    func02(10, 20, 30, 40, 50, a=10, b=20)
--------- func02 ---------
args = (10, 20, 30, 40, 50)
kwargs = {'a': 10, 'b': 20}
--------- func01 ---------
args = ((10, 20, 30, 40, 50), {'a': 10, 'b': 20})
kwargs = {}
--------- func01 ---------
args = (10, 20, 30, 40, 50)
kwargs = {'a': 10, 'b': 20}

单继承中super()

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

class Son1(Parent):
    def __init__(self, name, age):
        # super(Son1, self).__init__(name)
        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)
parent的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
(, , , )
姓名: grandson
年龄: 12
性别: 男

多继承和MRO顺序

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

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

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

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

# 创建对象
gs = Grandson('grandson', 12, '男')
print(Grandson.mro())
parent的init结束被调用
Son2的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
[, , , , ]
class A(object):
    def __init__(self):
        print("A")

class B(A):
    def __init__(self):
        super().__init__()
        # A.__init__(self)
        print("B")

class C(A):
    def __init__(self):
        super().__init__()
        # A.__init__(self)
        print("C")

class D(B, C):
    def __init__(self):
        super().__init__()
        print("D")

d = D()
# A C B D
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
A
C
B
D
(, , , , )

property基本使用

@property装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改

  • @property 的特点: 让我们通过对象.方法名的方式可以调用方法
  • 语法格式:
    @property  
    def xxx(self):
      pass
  • 注意:@property 装饰的方法,只能有一个参数self
# ############### 定义 ###############
class Foo(object):
    def __init__(self, num):
        self.num = num

    def prop(self):
        return self.num

# ############### 调用 ###############
foo_obj = Foo(10)
foo_obj.prop()
10
# ############### 定义 ###############
class Foo(object):
    def __init__(self, num):
        self.num = num

    # 定义property属性
    @property
    def prop(self):
        return self.num

# ############### 调用 ###############
foo_obj = Foo(10)
foo_obj.prop
10
# 简单分页实例
# ############### 定义 ###############
class Pager:
    def __init__(self, current_page):
        # 用户当前请求的页码(第一页、第二页...)
        self.current_page = current_page
        # 每页默认显示10条数据
        self.per_items = 10

    @property
    def start(self):
        val = (self.current_page - 1) * self.per_items + 1
        return val

    @property
    def end(self):
        val = self.current_page * self.per_items
        return val

# ############### 调用 ###############
p = Pager(2)
print(p.start)  # 就是起始值,即:11
print(p.end)  # 就是结束值,即:20
11
20

property其他使用方式

  • 经典类: @property 一种方式
  • 新式类:
    • @property
    • goods.price 获取价格的方法
    • @xxx.setter
    • goods.price = xxx
    • @xxx.deleter
    • del goods.price ---> @xxx.delete 装饰的方法
# ############### 定义 ###############
class Goods:
    """python3中默认继承object类
        以python2、3执行此程序的结果不同,因为只有在python3中才有@xxx.setter  @xxx.deleter
    """
    def __init__(self):
        # 初始化一个原价
        self.org_price = 1000
        # 初始化折扣
        self.discount = 0.7

    @property
    def price(self):
        return self.org_price * self.discount    # 打折后的价格

    @price.setter
    def price(self, value):               # 修改价格
        if value > 0:
            self.org_price = value

    @price.deleter
    def price(self):
        print("售罄")
        print('@price.deleter')

# ############### 调用 ###############
obj = Goods()
print(obj.price)           # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
obj.price = 600     # 自动执行 @price.setter 修饰的 price 方法,并将  123 赋值给方法的参数
print(obj.price)
del obj.price       # 自动执行 @price.deleter 修饰的 price 方法
700.0
420.0
售罄
@price.deleter

property作为类属性

  • 定义 property 对象的类属性
    • xxx =property(参数1,参数2,参数3,参数4)
    • 第一个参数,当我们 foo.BAR 自动调用第一个参数的方法
    • 第二个参数,当我们 foo.BAR = 100,自动调用第二个参数的方法
    • 第三个参数,当我们 del foo.BAR ,自动调用第三个参数的方法
    • 第四个参数,当我们 Foo.BAR.__doc__,自动获取第四个参数的内容
  • 使用
    • 对象.xxx 获取值
    • 对象.xxx = 100 设置值
    • del 对象.xxx 调用第三个参数方法
    • 类.xxx.__doc__ 获取第四个参数的内容
class Goods:
    def __init__(self):
        # 初始化一个原价
        self.org_price = 1000
        # 初始化折扣
        self.discount = 0.7

    def get_price(self):
        print("getter...")
        return self.org_price * self.discount    # 打折后的价格

    def set_price(self, value):               # 修改价格
        print("setter...", value)
        if value > 0:
            self.org_price = value

    def del_price(self):
        self.org_price = 0
        print("售罄")
        print('deleter...')

    BAR = property(get_price, set_price, del_price, 'description...')

obj = Goods()

obj.BAR           # 自动调用第一个参数中定义的方法:get_price
print(obj.BAR)
obj.BAR = 700    # 自动调用第二个参数中定义的方法:set_price方法,并将 700 当作参数传入
print(obj.BAR)
del obj.BAR         # 自动调用第三个参数中定义的方法:del_price方法
print(obj.BAR)
desc = Goods.BAR.__doc__  # 自动获取第四个参数中设置的值:description...
print(desc)
getter...
getter...
700.0
setter... 700
getter...
489.99999999999994
售罄
deleter...
getter...
0.0
description...

魔法属性和方法

  • 魔术属性
    • __doc__ 获取描述信息
    • 获取类的 类名.__doc__
    • 获取方法的描述 对象.方法名.__doc__
    • __module__ 获取所属的模块 对象名.__module__ 直接运行py文件 获取的main
    • __class__ 获取对象所属的类 对象名.__class__
  • 魔术方法
    • __init__ 初始化方法 类名() 自动调用
    • __del__ 删除对象的时候,会调用 del 对象
  • 魔术属性__dict__ 获取对象或者类的信息
    • 获取对象信息 对象名.__dict__对象的实例属性信息
    • 获取类的信息 类名.__dict__ 模块、类描述、对象方法...
  • 魔术方法
    • __call__() 当使用 对象名() 会调用该方法
    • __str__() 打印对象的会调用 print(obj) str方法一定要return,而且return 一定字符串内容
    • 用字典的书写格式操作对象的方法
    • __getitem__ 对象['xx']
    • __setitem__ 对象['xx'] = xxx
    • __delitem__ del 对象['xx']
class Goods(object):
    """这是一个商品类 Goods"""

    def __init__(self):
        print("执行__init__方法")

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

    def __del__(self):
        print("执行__del__方法")

# __doc__ 描述信息获取
print(Goods.__doc__)
print(Goods.set_price.__doc__)

# 获取当前模块
goods = Goods()     # 执行__init__方法
print(goods.__module__)    # __main__
print(goods.set_price.__module__)  # __main__

# 获取对象所属的类
print(goods.__class__)    # <class '__main__.Goods'>

# 删除对象
del goods     # 执行__del__方法
这是一个商品类 Goods
这是Goods类中定义的设置价格的方法
执行__init__方法__main__
__main__

执行__del__方法
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>
# __str__() 可以重写此输出
print(goods)

# 通过__dict__ 获取对象信息,对象.__dict__返回字典
# {'org_price': 100, 'discount': 0.7}
print(goods.__dict__)

# 通过__dict__ 获取类的信息  类名.__dict__  返回值是一个字典
# {'__module__': '__main__', '__doc__': '这是一个商品的类 Goods', 'goods_color': '白色', '__init__': <function Goods.__init__ at 0x0000022FF2C43730>, 'set_price': <function Goods.set_price at 0x0000022FF2C436A8>, '__call__': <function Goods.__call__ at 0x0000022FF2C430D0>, '__str__': <function Goods.__str__ at 0x0000022FF2C437B8>, '__del__': <function Goods.__del__ at 0x0000022FF2C43840>, '__getitem__': <function Goods.__getitem__ at 0x0000022FF2C438C8>, '__setitem__': <function Goods.__setitem__ at 0x0000022FF2C43950>, '__delitem__': <function Goods.__delitem__ at 0x0000022FF2C439D8>, '__dict__': <attribute '__dict__' of 'Goods' objects>, '__weakref__': <attribute '__weakref__' of 'Goods' objects>}
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']
__call__ 方法被调用
我是一个寂寞的对象
{'org_price': 100, 'discount': 0.7}
{'__module__': '__main__', '__doc__': '这是一个商品的类 Goods', 'goods_color': '白色', '__init__': , 'set_price': , '__call__': , '__str__': , '__getitem__': , '__setitem__': , '__delitem__': , '__dict__': , '__weakref__': }
key =  a
key = a, value = 10
要删除 key =  a

with管理上下文方式

  • 上下文:以 with open 来说,打开文件在上文 关闭文件在下文
  • 上下文管理器:
    • __enter__ 上文方法
    • __exit__ 下文方法
  • 自定义一个满足满足上下文管理器的 类
  • 通过装饰器 @ contextmanager 实现上下文管理
    • 装饰器
    • 待装饰的函数
    • myopen() 分拆成上文和下文,使用yield 分拆
  • 装饰的过程:

    • 导入模块 from contextlib import contextmanager
    • 第二步:
    @contextmanager  
    def myopen(file_name,file_model):
    ...
"""
类:  MyFile()
类方法:
    1. __enter__()  上文方法
    2. __exit__()   下文方法
    3. __init__()   方法,接收参数并且初始化

实现效果:
with MyFile("log.txt", "r", "UTF-8") as file:
    file.read()
"""

class MyFile(object):

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

    # 2. __exit__()   下文方法
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("进入下文....")
        # 关闭文件资源
        self.file.close()

    # 3. __init__()   方法,接收参数并且初始化
    def __init__(self, file_name, file_model, file_encode):
        # 保存文件名和文件打开模式,到实例属性中
        self.file_name = file_name
        self.file_model = file_model
        self.file_encode = file_encode

if __name__ == '__main__':

    with MyFile("log.txt", "r", "UTF-8") as file:
        # 开始读取文件
        file_data = file.read()
        print(file_data)

"""
进入上文....
....
....
进入下文....
"""
"""
思路:
    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, file_encode):
    print("进入上文")
    # 1.上文 打开文件
    file = open(file_name, file_model, encoding=file_encode)

    # 2.返回资源
    yield file

    print("进入下文")
    # 3. 下文 关闭资源
    file.close()

with myopen("log.txt", "r", "UTF-8") as file:
    file_data = file.read()
    print(file_data)