05_python提高

1_Gil

01_单线程死循环

# 主线程死循环,占满cpu
while True:
    pass  # 占位符,占在这个地方,从而使程序不会因没有代码而出错,相当于一条空语句,也会执行

02_两个线程死循环

import threading
"""如果程序是多线程,实际上是假的多任务"""
"""
线程里边包含GIL,它使得多个线程在同一时刻只有一个在执行,如果要发挥多核cpu的能力,请使用进程.
进程占用资源很多,可以考虑用线程,这句话要看是什么解释器,要是c语言解释器,由于含有GIL,还不如使用多进程,因为多进程可以真正的占用cpu多核
若用的不是c语言解释器,那么就是多线程要比多进程更加节省资源


计算密集型程序:程序中没有延时,即没有recv(),time.sleep()等需要延时等待的语句,一停不停的执行,没有办法在等待一个的时候将其他的请求发送出去
这个时候一定是用进程

io(input输入,output输出-直白讲就是读,写)密集型程序:文件的读写,优盘的读写,网络的收与发.因为读写是相当慢的,cpu一秒钟十万八千里出
去了,但是硬盘,网络相当慢,那么这个时候就完全可以使用多线程来做.

由于协程gevent说直白点就是单线程,单线程里边的一个特点,把这个线程在去等待某个资源到来的时候利用了这个时间,多线程是把一个线程在等待资源的
这个时间拿出来让另外的线程去做.即协程就相当于让单线程里边一个代码去执行的时候把等待的时间拿出来去做其他函数里边的代码,所以io密集型的时候
也可以使用协程,但计算密集型只能考虑用进程,发挥多核cpu的威力,但是线程和协程只有一个核在做,占用的资源更轻量级


两种方法可以来解决GIL的问题:
1.更换其他语言编写的解释器,避免使用c语言编写的解释器
2.用其他的语言来替代将来子线程里边要做的事情

"""


# 子线程死循环
def test():
    while True:
        pass


t1 = threading.Thread(target=test)
t1.start()


# 主线程死循环
while True:
    pass

03_两个进程死循环

import multiprocessing


# 子进程死循环
def test():
    while True:
        pass

p1 = multiprocessing.Process(target=test)
p1.start()

# 主进程死循环
while True:
    pass

04_main

from ctypes import *
from threading import Thread

# 加载动态库
lib = cdll.LoadLibrary("./libdead_loop.so")
# 创建一个子线程,让其执行c语言编写的函数,此函数是一个死循环
t = Thread(target=lib.DeadLoop)
# target指定哪个函数就去哪个函数执行,DeadLoop()是c语言里边的代码,通过gcc xxx.c -shared -o libxxxx.so将该函数所在的c语言文件编译成.so文件
# libdead_loop.so(动态库,也就是二进制)指定线程要执行的函数的时候指定动态库里的函数
t.start()

# 主线程
while True:
    pass

05_python可以调用c语言的处理过程

把 一个c语言文件编译成一个动态库的命令(linux平台下):

gcc xxx.c -shared -o libxxxx.so

2_私有化.import.封装继承多态

01_私有化

"""
私有化:
在继承中有所体现,子类从父类中继承,父类中的私有属性,私有方法,在子类中是不会被调用的
私有属性,方法的命名规则:两个下划线开头"__":__**
"__**__"这样前边两个下划线后边两个下划线的定义方法是共有性质的依旧会被继承
"_**"使用一个下划线开头会导致使用from somemodule import *导入时,无法将该属性或者方法导入.
作用:在别人导入你的模块的时候,你不想让导的部分可以加一个"_"
"**_"后边加一个下划线,目的是避免与关键字冲突,一般不要用

为什么要搞私有化?
若在子模块和main模块中含有同名的变量,因为有一些变量是在一个模块里边完成全局变量功能的,但是在其
他的模块里边是不允许被导入的
即:自己用的你自己有,不要让别人用
"""

02_import导入模块

"""
import导入模块的几种方式:
1.from xxx import yyy
2.import xxx
3.from xxx import *
4.import xxx,zzz
5.from xxx import yyy,ppp
6.import xxx as xxx  # 相当于给模块起别名,避免有重名的模块,或者与模块内部变量重名
如果导入的模块名与模块中的某个变量重名就完蛋了
import aa有两个功能:
1.把aa当做模块,去找这个模块,把aa这个模块导入python解释器里边去
2.然在当前的模块里边定义一个变量aa(AA),使这个变量指向这个模块
实现上边两个功能之后才可以使用aa.yyy.如果铁了心指定一个变量,aa = 100,那么原来指向导入模块的变量就
指向100了
"""
"""
1.模块路径
当导入一个模块的时候导入不进去,基本上就是因为路径不对
切记一个思想:去验证"sys.path"使用需要先导入sys模块,返回的是一个列表,列表内保存的是路径元素
并且,路径元素的排列顺序就是将来import一个模块的时候,搜索的路径的先后顺序,只要在某个路径中搜
到了,就直接使用了,不会再往下搜索了,当都找不到时出错

同时,既然返回的是一个列表,就可以通过append("./home/itcast/xxx")往里边插入路径(从最后插)
                 通过insert(0,"./home/itcast/xxx")插队往里边插,可以实现想先搜哪个就把哪个插到前边去
若以后在开发的过程当中,找一个模块的时候找不到,但你确确实实是安装过了,那么这个时候,
只要往这个列表里边append()或者insert()添加这个路径即可
"""
"""
2.重新导入模块
import 具有避免重复导入的功能,即导入一次后不能导入第二次
如果在程序没有结束的时候,重新修改了导入模块里边的值,要想在此次程序中使用,
需要使用reload(模块名)再次加载一遍模块,同样需要先from imp import reload
"""

common.py
"""
recv_msg()只负责收,handle_msg()只负责处理,需要知道从哪里获取数据.
将recv_msg()收到的数据存储的列表定义在大家公用(common)的模块里边去,由其他模块导入使用
"""
RECV_DATA_LIST = list()  # 用来存储数据
HANDLE_FLAG = False  # 用来标记是否处理完成




handle_msg.py

from common import RECV_DATA_LIST
# from common import HANDLE_FLAG
import common


def handle_data():
    """模拟处理recv_msg模块接收的数据"""
    print("--->handle_data")
    for i in RECV_DATA_LIST:
        print(i)

    # 既然处理完成了,那么将变量HANDLE_FLAG设置为True,意味着处理完成
    # global HANDLE_FLAG
    # HANDLE_FLAG = True
    common.HANDLE_FLAG = True


def test_handle_data():
    """测试处理是否完成,变量是否设置为True"""
    print("--->test_handle_data")
    # if HANDLE_FLAG:
    if common.HANDLE_FLAG:
        print("=====已经处理完成====")
    else:
        print("=====未处理完成====")



recv_msg.py

from common import RECV_DATA_LIST
# from common import HANDLE_FLAG
import common


def recv_msg():
    """模拟接收到数据,然后添加到common模块中的列表中"""
    print("--->recv_msg")
    for i in range(5):
        RECV_DATA_LIST.append(i)


def test_recv_data():
    """测试接收到的数据"""
    print("--->test_recv_data")
    print(RECV_DATA_LIST)


def recv_msg_next():
    """已经处理完成后,再接收另外的其他数据"""
    print("--->recv_msg_next")
    # if HANDLE_FLAG:
    if common.HANDLE_FLAG:
        print("------发现之前的数据已经处理完成,这里进行接收其他的数据(模拟过程...)----")
    else:
        print("------发现之前的数据未处理完,等待中....------")


main.py
from recv_msg import *
from handle_msg import *
"""
一个大程序基本上都是这个套路:
分成n个模块;公用的数据放在一个模块;独立的小功能放在一个单独的模块;大家由main()来开始发起调用
"""


def main():
    # 1. 接收数据
    recv_msg()
    # 2. 测试是否接收完毕
    test_recv_data()
    # 3. 判断如果处理完成,则接收其它数据
    recv_msg_next()
    # 4. 处理数据
    handle_data()
    # 5. 测试是否处理完毕
    test_handle_data()
    # 6. 判断如果处理完成,则接收其它数据
    recv_msg_next()


if __name__ == "__main__":
    main()

03_多个模块import导入注意点

多模块开发时的注意点1
多模块开发时的注意点1
多模块开发时的注意点2
在这里插入图片描述

04_再议封装继承多态

"""
封装,继承,多态是面向对象的三大特性

一.对封装的思考:

在实际开发过程中:
为了完成较为复杂的任务,往往需要多个函数配合
当一个函数中收到了数据,为了让其他函数中能够使用,很多人想到了使用全局变量的方法来实现数据传递
的功能,当对象比较多的时候全局变量将都会是列表存储不同对象数据其实这真的很方便,但是有时会出现
一些问题例如:并发程序会出现对同一个全局变量操作的问题

面向过程的一个体现,程序越复杂实现越难,那么面向它最终是想要做什么呢?你要从面向过程当中抽出的核心
问题是什么,你就知道面向对象的好处了.

在这里可以讨论一下为什么要封装:
在面向过程的程序中(坦克大战)多个全局变量列表可能每一个都需要调用一遍各个方法函数,会产生一个问题
将来处理起来十分复杂
解决方法:把所有的全局变量和函数找一个地方封装起来,作为一个创建对象的模版,想创建一个对象就可以从
模版中生成一个.即申请一块空间,里边含有两部分,一部分将原来的一些数据单独放在里边,这个时候就不需
要使用列表了,直接使用变量存储即可(攻击力:,防御力:....)(变量值都是独有的,各个对象内部变
量名可以相同,比如都叫攻击力防御力).另一部分指向模版的引用,用来调用函数,需要什么功能就调用什么样
的函数.各个对象之间是分开的独立的,互不干扰,这也是面向对象的一个体现.相当于给每个对象单独开辟了
一个空间,里边有变量和函数,但不是真正的将函数复制一份放到里边去,这个对象里边一定有一个特殊的属性
叫__class__属性(python3中每个对象里边都有这个属性),通过这个属性可以找到是谁创建的这个对象,即可
以找到模版,模版中含有方法(函数)即可以实现共用一份代码,任何改动都不会影响这个对象外的值
"""
"""
面向对象的一个直白的体现:模版创建出来的实体对象相当于一个独立的空间,在这个独立的小空间里边变量想
怎么用就怎么用,他们之间独立.通过面向对象使程序写起来更加简洁
"""
"""
函数和方法的区别:定义了一个函数放在了类里边去,调用的时候要先找到类调用类里边的函数就叫做方法,如果
是实例方法一定要有一个self(就像在对象内部给它起了个名字,方便叫它干啥,干啥,比如让它调用属性方法之类)
self其实就是一个形参变量名,(不一定非写self但是一般写成self,但是就像函数的形参一样,只要写上了,接下
来在对象中使用的时候就必须用写好的名称(self))
"""
"""
面向对象的好处:
1.在使用面向过程编程时,当需要对数据处理时,需要考虑用哪个模板中哪个函数来进行操作,但是当用面向对象编程
时,因为已经将数据存储到了这个独立的空间中,这个独立的空间(即对象)中通过一个特殊的变量(__class__)
能够获取到类(模板),而且这个类中的方法是有一定数量的,与此类无关的将不会出现在本类中,因此需要对数据
处理时,可以很快速的定位到需要的方法是谁 这样更方便
2.全局变量是只能有1份的,多很多个函数需要多个备份时,往往需要利用其它的变量来进行储存;而通过封装 会将
用来存储数据的这个变量 变为了对象中的一个“全局”变量,只要对象不一样那么这个变量就可以再有1份,所以这样更
方便
3.代码划分更清晰,拿着全局变量和函数,将它们整合成一个整体,变成属性和方法
"""
# 例如
# 面向过程
#
# 全局变量1
# 全局变量2
# 全局变量3
...


def 函数1():
    pass


def 函数2():
    pass


def 函数3():
    pass


def 函数4():
    pass


def 函数5():
    pass

# 面向对象


class(object):
    """将功能相关的方法和属性定义在一个类中,无关的就不要放在一起"""
    # 属性1
    # 属性2

    def 方法1(self):
        pass

    def 方法2(self):
        pass


class2(object):
    # 属性3

    def 方法3(self):
        pass

    def 方法4(self):
        pass

    def 方法5(self):
        pass



"""
二.对继承的理解

若一个类1中有很多代码,接下来想要对这个类的功能进行扩充和修改,是复制一份进行修改还是
通过某一句代码直接使用类1里边的代码呢,肯定使用后边的方法,若要是使用复制的方法会造成
冗余(相同的代码,冗余量越大,代码越差)所以要是用继承的方法,提高代码的重复利用率,这
是继承的一大特点
"""


"""
三.对多态的理解
什么叫做多态?
安装的方式,调动的过程还是一个引用里边的install()方法,一个子类创建的对象作为引用传递进一个
方法内部时传进来的子类如果没有修改,则调用的还是父类的install()方法,若子类中已经将父类的
install()方法给重写了,再去调用install()的时候就是调用的子类中重写的install()方法
即子类重写了,调子类的,子类没有重写调父类的,这就是多态
"""


class MiniOS(object):
    """MiniOS 操作系统类 """
    def __init__(self, name):
        self.name = name
        self.apps = []  # 安装的应用程序名称列表

    def __str__(self):
        return "%s 安装的软件列表为 %s" % (self.name, str(self.apps))

    def install_app(self, app):
        # 判断是否已经安装了软件
        if app.name in self.apps:
            print("已经安装了 %s,无需再次安装" % app.name)
        else:
            app.install()  # 调用pycharm里边的install
            self.apps.append(app.name)


class App(object):
    def __init__(self, name, version, desc):
        self.name = name
        self.version = version
        self.desc = desc

    def __str__(self):
        return "%s 的当前版本是 %s - %s" % (self.name, self.version, self.desc)

    def install(self):
        print("将 %s [%s] 的执行程序复制到程序目录..." % (self.name, self.version))


class PyCharm(App):  # 继承自App这个类,父类中的__init__;__str__;都是公有的,可以在子类中直接使用
    pass


class Chrome(App):
    def install(self):  # 对父类中的install方法不满意,重写父类install()方法
        print("正在解压缩安装程序...")
        super().install()


linux = MiniOS("Linux")
print(linux)

pycharm = PyCharm("PyCharm", "1.0", "python 开发的 IDE 环境")
chrome = Chrome("Chrome", "2.0", "谷歌浏览器")

linux.install_app(pycharm)
# 将另外一个对象的引用当做实参传给install_app,此时install_app(self, app)里边的app指向pycharm这个对象
linux.install_app(chrome)
linux.install_app(chrome)

print(linux)

# 运行结果

# Linux 安装的软件列表为 []
# 将 PyCharm [1.0] 的执行程序复制到程序目录...
# 正在解压缩安装程序...
# 将 Chrome [2.0] 的执行程序复制到程序目录...
# 已经安装了 Chrome,无需再次安装
# Linux 安装的软件列表为 ['PyCharm', 'Chrome']


3_方法解析顺序表MRO

01_多继承中的MRO顺序

"""
调用被重写的父类方法有几种?哪三种?
1.父类名.这种方式切记要把self当做第一个实参传递
eg:Parent.__init__(self,name),将self作为第一个实参传递进去,这个方法就自动的被调用
缺点:
这个调用方法能会导致最顶层的类被调用多次,比如有三层,爷爷类,父类(假设这层有三个类),孙
子类,孙子类调用父类层的每一个父类,每个父类里边都调用了一遍爷爷类,导致爷爷类重复调用多遍
若是爷爷类的__init__方法里边含有创建套接字的代码,只要孙子类一创建对象,爷爷层就会被调用
多遍,自动创建多个套接字,浪费资源(一个进程里边默认打开文件的个数是1024个)
优点:简洁明了,一眼看出调用的是谁
总结:弊大于利
2.写上在父类名位置上写上super()里边啥也不写的
eg:super().__init__()
3.写上super(父类名,self)里边写上父类名和self的


调用super()的区别:
在python3里边当有多继承的时候,往往会出现那种调用父类的时候调用super()反而调用不了父类,为什么?
因为python3里边默认的这种有一种来处理多继承的一个调用的先后顺序叫c3算法,c3算法是一个去保证将来每
一个类只调一次的算法,这个算法最终的一个体现就是,当你写上最底下的那个类的名字.__mro__的时候,你可
以看到最终的结论,是一个元组,元组里边有很多个类的名字在里边,类的名字的先后顺序决定了将来我去调用
super()的先后顺序.拿着当前类的名字到print(Grandson.__mro__)打印的元组里边去匹配,匹配成功后super()调
用的就是这个类的下一个类,因此Son1这个类就被调,再拿着当前类的名字Son1到print(Grandson.__mro__)
打印的元组内部继续进行匹配
可以保证爷爷 类只被执行一次
"""
# 单独调用父类方法,法一
print("******多继承使用类名.__init__ 发生的状态******")


class Parent(object):
    def __init__(self, name):
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')


class Son1(Parent):
    def __init__(self, name, age):
        print('Son1的init开始被调用')
        self.age = age
        Parent.__init__(self, name)
        print('Son1的init结束被调用')


class Son2(Parent):
    def __init__(self, name, gender):
        print('Son2的init开始被调用')
        self.gender = gender
        Parent.__init__(self, name)
        print('Son2的init结束被调用')


class Grandson(Son1, Son2):
    """
    由于类Parent,Son1,Son2,Grandson中都含有__init__方法,若要通过类Grandson创建一个
    实例对象,那么将来__init__方法一定会被调用,那到底是调用的类Parent,Son1,Son2,Grandson
    中谁的__init__方法--Grandson的.
    切记一个原则:不管这个类的继承方式是怎么继承,将来通过这个类去创建实例对象时,一定是调用它里边的
    __init__方法,唯一能做的事情就是在 它的__init__方法里边想方设法去调用其他的

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

gs = Grandson('grandson', 12, '男')  # 创建对象后__init__方法会自动被调用
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)

print("******多继承使用类名.__init__ 发生的状态******\n\n")

# 运行结果:
#
# ******多继承使用类名.__init__ 发生的状态******
# Grandson的init开始被调用
# Son1的init开始被调用
# parent的init开始被调用
# parent的init结束被调用
# Son1的init结束被调用
# Son2的init开始被调用
# parent的init开始被调用
# parent的init结束被调用
# Son2的init结束被调用
# Grandson的init结束被调用
# 姓名: grandson
# 年龄: 12
# 性别: 男
# ******多继承使用类名.__init__ 发生的状态******


# 2. 多继承中super调用有所父类的被重写的方法

print("******多继承使用super().__init__ 发生的状态******")


class Parent(object):
    def __init__(self, name, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        """
        如果在去定义一个函数/方法的时候,要传入的参数有可能不确定(不定长参数),可以用一个*来标记
        也可以用两个*来标记,*args是以元组的身份来保存参数,**kwargs以字典的身份来保存参数,传递过
        来的是关键字参数
        *args, **kwargs这两个形参的名字:*只是告诉python解释器,这个哥们特殊对待,特殊到什么程度
        呢?调用这个哥们的时候多传的那一部分没有名字的通通给*args,传递的多余的关键字参数通通给**kwargs
        也就是说变量名依然叫args, kwargs无非就是*,**是告诉python解释器有特殊功能而已,就这么点特点
        然而当在函数中使用*args, **kwargs的时候就不一样了,那么此时还有什么区别呢
        """
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')


class Son1(Parent):
    def __init__(self, name, age, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son1的init开始被调用')
        self.age = age
        super().__init__(name, *args, **kwargs)  # 为避免多继承报错,使用不定长参数,接受参数
        # 使用super()的时候传递的第一个实参就不用写self了
        print('Son1的init结束被调用')


class Son2(Parent):
    def __init__(self, name, gender, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son2的init开始被调用')
        self.gender = gender
        super().__init__(name, *args, **kwargs)  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son2的init结束被调用')


class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print('Grandson的init开始被调用')
        # 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
        # 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
        # super(Grandson, self).__init__(name, age, gender)
        # 这个第三种方法super()里边写上了Grandson, self,写也行不写也行,他们之间有什么区别呢?
        # 答:第三种方法super()里边写上哪个类名,他就调用print(Grandson.__mro__)
        # 打印的元组内部该类名的下一个类,不写的时候就默认拿着当前类的类名到print(Grandson.__mro__)
        # 打印的元组内部进行匹配

        super().__init__(name, age, gender)
        # super()是啥意思:掉用super()的时候,他会自动判断调用哪个父类,一次只调一个父类,
        # 故不会导致Son1和Son2都被调用的,print(Grandson.__mro__)打印出的结果是谁,将来
        # 调的谁就很确定了
        print('Grandson的init结束被调用')

print(Grandson.__mro__)
# 类里边有一个默认属性__mro__,表示有一个元组,这个元组就是用这个变量名来标记,那么最终的结论就是
# 打印的这句话是一个元组,元组里边有好几个值这些值的先后顺序决定了你将来调用父类的先后顺序,这个顺
# 序使用python3解释器决定的(c3算法的计算结果)
"""
在一个类里边调用super()的时候,到底调用哪一个父类,不是简简单单看上去那么简单,那是调用的谁呢?
怎么看:拿着当前类的名字到print(Grandson.__mro__)打印的元组里边去匹配,匹配成功后super()调
用的就是这个类的下一个类,因此Son1这个类就被调,再拿着当前类的名字Son1到print(Grandson.__mro__)
打印的元组内部继续进行匹配
"""

gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
print("******多继承使用super().__init__ 发生的状态******\n\n")

"""
运行结果:

******多继承使用super().__init__ 发生的状态******
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
# Grandson调用super()会调用Son1,Son1再调用super()的时候会调用Son2,不会调用Parent
Grandson的init开始被调用
Son1的init开始被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
******多继承使用super().__init__ 发生的状态******

注意:

        以上2个代码执行的结果不同
        如果2个子类中都继承了父类,当在子类中通过父类名调用时,parent被执行了2次
        如果2个子类中都继承了父类,当在子类中通过super调用时,parent被执行了1次


"""
# 3. 单继承中super,使用方法与使用父类的名字调用没有什么区别


print("******单继承使用super().__init__ 发生的状态******")
class Parent(object):
    def __init__(self, name):
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')

class Son1(Parent):
    def __init__(self, name, age):
        print('Son1的init开始被调用')
        self.age = age
        super().__init__(name)  # 单继承不能提供全部参数
        print('Son1的init结束被调用')

class Grandson(Son1):
    def __init__(self, name, age, gender):
        print('Grandson的init开始被调用')
        super().__init__(name, age)  # 单继承不能提供全部参数
        print('Grandson的init结束被调用')

gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
#print('性别:', gs.gender)
print("******单继承使用super().__init__ 发生的状态******\n\n")

# 总结
#
#     super().__init__相对于类名.__init__,在单继承上用法基本无差
#     但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次,具体看前面的输出结果
#     多继承时,使用super方法,对父类的传参数,应该是由于python中super的算法导致的原因,必须把参数全部传递,否则会报错
#     单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
#     多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍, 而使用super方法,只需写一句话便执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因



02_args-kwargs

"""
如果定义一个函数的时候,为了保证这个函数可以接收不定长个参数,可以用一个*接收多余的没有名字的参数,可以用两个*接收多余的带名字的参数

"""

def test2(a, b, *args, **kwargs):
    print("---------")
    print(a)
    print(b)
    print(args)
    print(kwargs)





def test1(a, b, *args, **kwargs):
    print(a)
    print(b)
    print(args)
    print(kwargs)


    # test2(a, b, args, kwargs)
    # 调用时将a,b的引用传递了过去,此时的args/kwargs会被看作多余的元组和字典的引用传递给*args这个元组
    # 相当于传递test2((3, 4, 5, 45), {"name" : "laowamg", "age" : 18})
    test2(a, b, *args, **kwargs)
    # 在 args/kwargs前边分别加上*/**表示将元组和字典拆开(拆包),不仅可以放在形参里边,也可以放在实参里边,相当于拆成
    # test2(3, 4, 5, 45, name = "laowang", age = 18)

test1(1, 2, 3, 4, 5, 45, name = "laowang", age = 18)

03_面试小题,加深继承理解

# 小试牛刀(以下为面试题)

#     以下的代码的输出将是什么? 说出你的答案并解释。


class Parent(object):
    x = 1


class Child1(Parent):
    pass


class Child2(Parent):
    pass

print(Parent.x, Child1.x, Child2.x)
Child1.x = 2
print(Parent.x, Child1.x, Child2.x)
Parent.x = 3
print(Parent.x, Child1.x, Child2.x)

# 答案, 以上代码的输出是:

# 1 1 1
# 1 2 1
# 3 2 3

在这里插入图片描述

4_类对象和实例对象访问属性的区别和property

00_再议静态方法/属性,类方法/属性

再议之实例属性/类属性
在这里插入图片描述

再议之实例方法,类方法,静态方法
在这里插入图片描述

01_使用父类名单独调用父类的方法

自己写详见3-01

class Parent(object):
    def __init__(self, name):
        print("Parent的__init__方法开始调用")
        self.name = name
        print("Parent的__init__方法调用结束")


class Son1(Parent):
    def __init__(self, name, age):
        print("Son1的__init__方法开始调用")
        self.age = age
        Parent.__init__(self, name)
        print("Son1的__init__方法调用结束")


class Son2(Parent):
    def __init__(self, name, gender):
        print("Son2的__init__方法开始调用")
        self.gender = gender
        Parent.__init__(self, name)
        print("Son2的__init__方法调用结束")


class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print("Grandson的__init__方法开始调用")
        Son1.__init__(self, name, age)
        Son2.__init__(self, name, gender)
        print("Grandson的__init__方法调用结束")

dog1 = Grandson("小狗", 11, "公的")
print("dog1的名字 %s " % dog1.name)
print("dog1的年龄 %d " % dog1.age)
print("dog1的性别 %s " % dog1.gender)

02_使用super调用父类方法

自己写详见3-01
"""

法二:拿着当前类的名字到print(Grandson.__mro__)打印的元组里边去匹配,匹配成功后super()调
用的就是这个类的下一个类,因此Son1这个类就被调,再拿着当前类的名字Son1到print(Grandson.__mro__)
打印的元组内部继续进行匹配

class Parent(object):
    def __init__(self, name):
        print("Parent的__init__方法开始调用")
        self.name = name
        print("Parent的__init__方法调用结束")


class Son1(Parent):
    def __init__(self, name, age, gender):
        print("Son1的__init__方法开始调用")
        self.age = age
        super().__init__(name, gender)
        print("Son1的__init__方法调用结束")


class Son2(Parent):
    def __init__(self, name, gender):
        print("Son2的__init__方法开始调用")
        self.gender = gender
        super().__init__(name)
        print("Son2的__init__方法调用结束")


class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print("Grandson的__init__方法开始调用")
        super().__init__(name, age, gender)
        print("Grandson的__init__方法调用结束")

print(Grandson.__mro__)

dog1 = Grandson("小狗", 11, "公的")
print("dog1的名字 %s " % dog1.name)
print("dog1的年龄 %d " % dog1.age)
print("dog1的性别 %s " % dog1.gender)
"""
"""

法三:写上super(父类名,self)里边写上父类名和self的
第三种方法super()里边写上哪个类名,他就调用print(Grandson.__mro__)
打印的元组内部该类名的下一个类,不写的时候就默认拿着当前类的类名到print(Grandson.__mro__)
打印的元组内部进行匹配


class Parent(object):
    def __init__(self):
        print("Parent的__init__方法开始调用")
        print("Parent的__init__方法调用结束")


class Son1(Parent):
    def __init__(self):
        print("Son1的__init__方法开始调用")
        super().__init__()
        print("Son1的__init__方法调用结束")


class Son2(Parent):
    def __init__(self):
        print("Son2的__init__方法开始调用")
        super().__init__()
        print("Son2的__init__方法调用结束")


class Grandson(Son1, Son2):
    def __init__(self):
        print("Grandson的__init__方法开始调用")
        super(Son2, self).__init__()
        print("Grandson的__init__方法调用结束")

print(Grandson.__mro__)

dog1 = Grandson()
"""

03_property属性

"""
property属性的定义和调用要注意一下几点:

    定义时,在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数
        调用时,无需括号

              方法:foo_obj.func()
                    property属性:foo_obj.pro

使用property属性的好处:

可读性更高,作为一种获取数据的方式,获取数据的方式更加的直接,相当于简单的调用一个属性
"""
"""
2. 简单的实例

    对于京东商城中显示电脑主机的列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,
    而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的
    所有数据 这个分页的功能包括:

        根据用户请求的当前页和总数据条数计算出 m 和 n

        根据m 和 n 去数据库中请求数据
"""

# ############### 定义 ###############


class Pager:
    def __init__(self, current_page):
        # 用户当前请求的页码(第一页、第二页...)
        self.current_page = current_page
        # 每页默认显示10条数据
        self.per_items = 10

    @property
    # 在普通的方法上边加上@property意味着将来在去调用对象.方法名时,看上去是在获取属性的值,
    # 其实已经对应成调用该方法.相当于把那个复杂的处理结果封装到了这个方法里边去,而给最终的调用
    # 者只留了一个属性的名调
    def start(self):
        val = (self.current_page - 1) * self.per_items
        return val

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

# ############### 调用 ###############
p = Pager(1)
p.start  # 就是起始值,即:m

# 就相当于创建完对象之后一调属性立马获得一个值,比调方法简单很多
# 就想象成调用一个属性,不用考虑传值等等
# 不使用这种方式直接使用p.start()一样使用,为什么不选择带括号的方式,因为选择了带括号的形式就需要
# 考虑是否需要传参,要考虑是否需要传参就需要打开源码查看一下是否需要,浪费时间
p.end  # 就是结束值,即:n




# 3. property属性的有两种方式

#     1.装饰器 即:在方法上应用装饰器
#     2.类属性 即:在类中定义值为property对象的类属性

# 3.1 装饰器方式

# 在类的实例方法上应用@property装饰器

# Python中的类有经典类和新式类,新式类的属性比经典类的属性丰富。( 如果类继object,那么该类是新式类)
# 经典类,具有一种@property装饰器

# ############### 定义 ###############

class Goods:
    @property
    def price(self):
        return "laowang"
# ############### 调用 ###############
obj = Goods()
result = obj.price  # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
print(result)

# coding=utf-8
# ############### 定义 ###############


class Goods:
    """python3中默认继承object类
        以python2、3执行此程序的结果不同,因为只有在python3中才有@xxx.setter  @xxx.deleter
    """
    @property
    def price(self):
        print('@property')

    @price.setter
    def price(self, value):
        print('@price.setter')

    @price.deleter
    def price(self):
        print('@price.deleter')

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

1.经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法
  新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、
  @方法名.deleter修饰的方法

2.由于新式类中具有三种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为
  对同一个属性:获取、修改、删除
  新式类,具有三种@property装饰器,如果定义的属性(price)既有获取,设置又有删除,就可以写这三种
  若只需要获取写一种就可以了
"""


# Demo

class Goods(object):

    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    @property
    def price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deleter
    def price(self):
        del self.original_price

obj = Goods()
obj.price         # 获取商品价格
obj.price = 200   # 修改商品原价
del obj.price     # 删除商品原价


# 3.2 类属性方式,创建值为property对象的类属性

#     当使用类属性的方式创建property属性时,经典类和新式类无区别


class Foo:
    def get_bar(self):
        return 'laowang'

    BAR = property(get_bar)
    # 装饰器方式创建的方式是:先定义类,类里边定义各种方法,前边加上
    # @property @方法名(属性名).setter @方法(属性名).deleter
    # 然而这种方式是:先把类写了,方法也写了,然后在类里边定义一个类属性(class里边,def外边)
    # property(get_bar)返回一个对象给BAR,这个时候属性BAR对应着get_bar方法

obj = Foo()
result = obj.BAR  # 自动调用get_bar方法,并获取方法的返回值
# 此处对象obj调用BAR的时候会根据property(方法名)里边的方法进行调用
print(result)

# property方法中有个四个参数
#
#     第一个参数是方法名,调用 对象.属性 将来获取值要执行的方法的引用
#     第二个参数是方法名,调用 对象.属性 = XXX  将来设置值要执行的方法的引用
#     第三个参数是方法名,调用 del 对象.属性 将来删除值要执行的方法的引用
#     第四个参数是字符串,调用 对象.属性.__doc__ ,此参数是该属性的描述信息


class Foo(object):
    def get_bar(self):
        print("getter...")
        return 'laowang'

    def set_bar(self, value):
        """必须两个参数"""
        print("setter...")
        return 'set value' + value

    def del_bar(self):
        print("deleter...")
        return 'laowang'

    BAR = property(get_bar, set_bar, del_bar, "description...")
    # 顺序不要乱

obj = Foo()

obj.BAR  # 自动调用第一个参数中定义的方法:get_bar
obj.BAR = "alex"  # 自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入
desc = Foo.BAR.__doc__  # 自动获取第四个参数中设置的值:description...
print(desc)
del obj.BAR  # 自动调用第三个参数中定义的方法:del_bar方法

# 由于类属性方式创建property属性具有3种访问方式,我们可以根据它们几个属性的访问特点,分别将
# 三个方法定义为对同一个属性:获取、修改、删除


class Goods(object):

    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    def get_price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    def set_price(self, value):
        self.original_price = value

    def del_price(self):
        del self.original_price

    PRICE = property(get_price, set_price, del_price, '价格属性描述...')

obj = Goods()
obj.PRICE         # 获取商品价格
obj.PRICE = 200   # 修改商品原价
del obj.PRICE     # 删除商品原价


"""
写代码:首先看逻辑(10-20k),进一步看优化(>20k)
      故代码要往高端的方式写,能简尽量简,如此节的方式,大型优化好的框架代码都这么写
"""
"""
# 4. Django框架中应用了property属性(了解)
#
# WEB框架 Django 的视图中 request.POST 就是使用的类属性的方式创建的属性

class WSGIRequest(http.HttpRequest):
    def __init__(self, environ):
        script_name = get_script_name(environ)
        path_info = get_path_info(environ)
        if not path_info:
            # Sometimes PATH_INFO exists, but is empty (e.g. accessing
            # the SCRIPT_NAME URL without a trailing slash). We really need to
            # operate as if they'd requested '/'. Not amazingly nice to force
            # the path like this, but should be harmless.
            path_info = '/'
        self.environ = environ
        self.path_info = path_info
        self.path = '%s/%s' % (script_name.rstrip('/'), path_info.lstrip('/'))
        self.META = environ
        self.META['PATH_INFO'] = path_info
        self.META['SCRIPT_NAME'] = script_name
        self.method = environ['REQUEST_METHOD'].upper()
        _, content_params = cgi.parse_header(environ.get('CONTENT_TYPE', ''))
        if 'charset' in content_params:
            try:
                codecs.lookup(content_params['charset'])
            except LookupError:
                pass
            else:
                self.encoding = content_params['charset']
        self._post_parse_error = False
        try:
            content_length = int(environ.get('CONTENT_LENGTH'))
        except (ValueError, TypeError):
            content_length = 0
        self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
        self._read_started = False
        self.resolver_match = None

    def _get_scheme(self):
        return self.environ.get('wsgi.url_scheme')

    def _get_request(self):
        warnings.warn('`request.REQUEST` is deprecated, use `request.GET` or '
                      '`request.POST` instead.', RemovedInDjango19Warning, 2)
        if not hasattr(self, '_request'):
            self._request = datastructures.MergeDict(self.POST, self.GET)
        return self._request

    @cached_property
    def GET(self):
        # The WSGI spec says 'QUERY_STRING' may be absent.
        raw_query_string = get_bytes_from_wsgi(self.environ, 'QUERY_STRING', '')
        return http.QueryDict(raw_query_string, encoding=self._encoding)

    # ############### 看这里看这里  ###############
    def _get_post(self):
        if not hasattr(self, '_post'):
            self._load_post_and_files()
        return self._post

    # ############### 看这里看这里  ###############
    def _set_post(self, post):
        self._post = post

    @cached_property
    def COOKIES(self):
        raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '')
        return http.parse_cookie(raw_cookie)

    def _get_files(self):
        if not hasattr(self, '_files'):
            self._load_post_and_files()
        return self._files

    # ############### 看这里看这里  ###############
    POST = property(_get_post, _set_post)

    FILES = property(_get_files)
    REQUEST = property(_get_request)

 综上所述:

     定义property属性共有两种方式,分别是【装饰器】和【类属性】,而【装饰器】方式针对经典类和新式类又有所不同。
     通过使用property属性,能够简化调用者在获取数据的流程

"""

04_property属性的应用

# property属性-应用


# 1. 私有属性添加getter和setter方法,其他语言都这样做,python不推荐这样做


class Money(object):
    def __init__(self):
        self.__money = 0  # 设置私有属性,不想让别人误取出来
        # 既然只是不想让别人误取出来(即不想让对象的引用直接获取或者修改它的值),
        # 那么自己想取的时候应该怎么取呢?
        # 添加以下两个方法,一个来获取,一个来设置(设置的时候进行过滤,看是否是需要的类型的值)
        # setter方法和getter方法在其他语言里有严格的区分,即定义一个私有属性,配合两个公有方法

    def getMoney(self):  # 简称为getter方法
        # 好处:可以增加权限.如是不是会员
        return self.__money

    def setMoney(self, value):  # 简称为setter方法
        # 好处:可以增加类型审查.如是不是数字类型
        if isinstance(value, int):
            self.__money = value
        else:
            print("error:不是整型数字")

# 2. 使用property升级getter和setter方法


class Money(object):
    def __init__(self):
        self.__money = 0

    def getMoney(self):
        return self.__money

    def setMoney(self, value):
        if isinstance(value, int):
            self.__money = value
        else:
            print("error:不是整型数字")

    # 定义一个属性,当对这个money设置值时调用setMoney,当获取值时调用getMoney
    money = property(getMoney, setMoney)
    # 就相当于在中间加了一层过滤网,实现增加权限和类型审查

a = Money()
a.money = 100  # 调用setMoney方法
print(a.money)  # 调用getMoney方法
# 上边的两个调用看上去就跟设置一个属性的值和查看一个属性的值没有什么区别


# 3. 使用property取代getter和setter方法

#     重新实现一个属性的设置和读取方法,可做边界判定


class Money(object):
    def __init__(self):
        self.__money = 0

    # 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,
    # 调用装饰的方法
    @property
    def money(self):
        return self.__money

    # 使用装饰器对money进行装饰,当对money设置值时,调用装饰的方法
    @money.setter
    def money(self, value):
        if isinstance(value, int):
            self.__money = value
        else:
            print("error:不是整型数字")

a = Money()
a.money = 100
print(a.money)
"""
property学习它的一个目的:把你调用的属性这种方式对应成调用一个方法,这个方法里边可以通过各种
逻辑数据处理最终得到一个值,这个值就是想要得到的一个数据
"""

5_私有属性和名字重整,魔法属性和方法,上下文管理器

01_修改查看私有属性,名字重整

class Test(object):
    def __init__(self, name):
        self.__name = name

a = Test("laowang")

a.__name
# 因为是私有的属性,调用结果是无法产生任何值
# 这是因为python解释器对私有属性进行了名字重整从而实现无法调用的目的,格式为_test__name

a._test__name
# 会输出laowang

# 而当使用a.__name = ****的时候会再添加一个属性,私有属性只有在定义类的时候,使用两个下划线开头

a.__dict__
# 实例对象通过调用__dict__方法可以查看实例对象内部所有的属性
Test.__dict__
# 查看类对象里边的属性

02_魔法属性

"""
魔法属性/方法
特殊情况下python解释器会自动调用特殊的东西这个就叫魔法的体现.不仅使用for循环的时候可以遍历一个对象
还可以切片还可以当做一个字典去用

无论人或事物往往都有不按套路出牌的情况,Python的类属性也是如此,存在着一些具有特殊含义的属性,详情如下:
"""


"""
# 1. __doc__
#     表示类的描述信息
"""


class Foo:
    """ 取出描述类信息,这是用于看片的神器 """
    def func(self):
        pass

print(Foo.__doc__)
# 输出:类的描述信息


"""
2. __module__ 和 __class__
    __module__ 表示当前操作的对象在那个模块
    __class__ 表示当前操作的对象的类是什么,即显示当前的实例对象是谁创建的
"""
# test.py(test模块)


class Person(object):
    def __init__(self):
        self.name = 'laowang'

# main.py(main模块)

from test import Person

obj = Person()
print(obj.__module__)  # 输出 test 即:输出模块
print(obj.__class__)  # 输出 test.Person 即:输出类


"""
3. __init__
     初始化方法,通过类创建对象时,自动触发执行
      不要说成构造方法,因为构造方法包含的是新建和初始化在python里边
      (__new__ + __init__)完成的是构造方法的功能
"""


class Person:
    def __init__(self, name):
        self.name = name
        self.age = 18


obj = Person('laowang')  # 自动执行类中的 __init__ 方法


"""
 4. __del__
     当对象在内存中被释放时,自动触发执行。
    注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,
    因为此工作都是交给Python解释器来执行,所以,__del__的调用是由解释器在进行垃圾回收时自动
    触发执行的。
"""


class Foo:
    def __del__(self):
        pass


"""
 5. __call__
     对象后面加括号,触发执行。
注:__init__方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行
是由对象后加括号触发的,即:对象() 或者 类()()
__call__什么时候会调用呢?
首先要保证类里边有一个call方法,然后利用该类创建该类的一个实例对象,实例对象直接括起来obj()
到底能干什么,等学完装饰器就有了
"""


class Foo:
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):
        print('__call__')


obj = Foo()  # 执行 __init__
obj()  # 执行 __call__


"""
6. __dict__
     用来查看类或对象中的所有属性
     类的实例属性属于对象;类中的类属性和方法等属于类,即:
"""


class Province(object):
    country = 'China'

    def __init__(self, name, count):
        self.name = name
        self.count = count

    def func(self, *args, **kwargs):
        print('func')

# 获取类的属性,即:类属性、方法、
print(Province.__dict__)
# 输出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}

obj1 = Province('山东', 10000)
print(obj1.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 10000, 'name': '山东'}

obj2 = Province('山西', 20000)
print(obj2.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 20000, 'name': '山西'}


"""
7. __str__
        如果想要获取对象的描述就会调用这个方法
     如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。
"""


class Foo:
    def __str__(self):
        return 'laowang'


obj = Foo()
print(obj)  # 用print打印一个对象的描述
# 输出:laowang
"当前对象的描述是%s" % obj  # 加入此时对象obj没有返回一个值,也会自动调用它的__str__方法
# 除了print()之外,"当前对象的描述是%s" % obj也可以调用obj指向的对象的__str__方法


"""
8、__getitem__、__setitem__、__delitem__

     用于索引操作,如字典。以上分别表示获取、设置、删除数据
"""


class Foo(object):

    def __getitem__(self, key):
        print('__getitem__', key)

    def __setitem__(self, key, value):
        print('__setitem__', key, value)

    def __delitem__(self, key):
        print('__delitem__', key)


obj = Foo()

result = obj['k1']      # 自动触发执行 __getitem__,实现这个方法就可以当做字典用
# 创建了对象加上了中括号,把obj当做字典对象,将k1当做key值,到对象oj内部去key值对应的value值
# 那么此时会自动调用__getitem__方法,会将k1当做实参传递过去
obj['k2'] = 'laowang'   # 自动触发执行 __setitem__
# 根据k2设置一个新的值,k2会传递给key,laowang会传递给value
del obj['k1']           # 自动触发执行 __delitem__


"""
9、__getslice__、__setslice__、__delslice__

     该三个方法用于分片操作,如:列表
"""


class Foo(object):

    def __getslice__(self, i, j):
        print('__getslice__', i, j)

    def __setslice__(self, i, j, sequence):
        print('__setslice__', i, j)

    def __delslice__(self, i, j):
        print('__delslice__', i, j)

obj = Foo()

obj[-1:1]                   # 自动触发执行 __getslice__
# [-1:1]表示:列表的切片.-1表示是最后一个,1:表示从最后一个找一个位置,
# 后边没有说明:默认的步长是向右走
# 对象依然可以使用切片,当去调用切片的时候,会自动调用__getslice__方法
obj[0:1] = [11,22,33,44]    # 自动触发执行 __setslice__
# 切片操作
del obj[0:2]                # 自动触发执行 __delslice__


在这里插入图片描述

03_面向对象设计

在这里插入图片描述在这里插入图片描述

04_with与上下文管理器

在这里插入图片描述

6_浅拷贝/深拷贝

01_浅拷贝

在这里插入图片描述

02_浅拷贝分析

在这里插入图片描述

03_深拷贝的分析

在这里插入图片描述

04_列表切片的拷贝特点

在这里插入图片描述

05_字典拷贝探析

在这里插入图片描述

06_函数调用中的拷贝探析

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值