zoukankan      html  css  js  c++  java
  • Python_oldboy_自动化运维之路_函数,装饰器,模块,包(六)

    本节内容

    1. 上节内容回顾(函数)
    2. 装饰器
    3. 模块

    1.上节内容回顾(函数)

    函数

    1.为什么要用函数?

    使用函数之模块化程序设计,定义一个函数就相当于定义了一个工具,需要用的话直接拿过来调用。
    不使用模块化程序设计的缺点:

    1. 体系结构不清晰,可读写差
    2. 可扩展性差
    3. 程序冗长

    2.定义函数分三种

    # -*- coding: UTF-8 -*-
    #blog:http://www.cnblogs.com/linux-chenyang/
    #
    #1.无参函数
    def foo():
        print('in the foo')
    
    foo()
    
    #2.有参函数
    def bar(x,y):
        print('in the bar')
    
    bar(1,2)
    
    #3.定义空函数
    def func():
        pass
    
    def get():
        pass
    
    def put():
        pass
    展开

    3.调用函数分三种

    # -*- coding: UTF-8 -*-
    #blog:http://www.cnblogs.com/linux-chenyang/
    #
    #1.语句形式
    def foo():
        print('in the foo')
    
    foo()
    
    #2.表达式的形式
    
    def my_max(x,y):
        if x > y:
            return x
        else:
            return y
    
    res = my_max(1,2)
    res = 10*my_max(1,2)
    
    #3.作为另外一个函数的参数
    my_max(1,my_max(2,3))
    展开

    4.函数返回值的三种形式

    # -*- coding: UTF-8 -*-
    #blog:http://www.cnblogs.com/linux-chenyang/
    #
    #1.如果不定义返回值,则返回值为空。
    def foo():
        pass
    
    res = foo()
    print(res)
    
    #2.返回一个值,返回值可以是任意的数据类型
    def foo():
        return  1
    
    res = foo()
    print(res)
    
    #3.返回多个值
    def foo():
        return  1,'w',[1,2,3]
    
    res = foo()
    print(res)
    展开

    5.函数的参数

    # -*- coding: UTF-8 -*-
    #blog:http://www.cnblogs.com/linux-chenyang/
    #
    #形参和实参的对应关系:
    #实参是真是占用内存空间的func(1,2)
    #形参只有在函数调用的时候才会接收值,占用内存空间,等函数调用完就会释放。
    
    def func(x,y):
        print(x)
        print(y)
    
    func(1,2)
    func(1,'a')
    
    #形参和实参的动态性:定义形参的时候不用指定数据类型
    #坏处:不指定数据类型,若执行以下代码会报错
    def test(x,y):
        return x+y
    
    test(1,'a')
    
    #解决方法一:
    def test(x,y):
        if type(x) is int and type(y) is int:
            return x+y
    
    test(1,'a')
    #解决方法二:添加注释
    def test(x,y):
        '''
    
        :param x: int
        :param y: int
        :return:
        '''
        return x+y
    
    test(1,'a')
    展开

     6.位置参数,默认参数,*args,**kwargs

    #从实参的角度:针对同一个形参,我们要么按照位置要么按照关键字为形参传值,不能对同一个形参赋值
    
    # def foo(x,y):
    #     print(x,y)
    #
    # foo(1,2)        #按位置
    # foo(y=2,x=1)    #按照关键字的形式,必须写在位置关系的后边
    
    
    
    #从形参的角度:位置参数,默认参数,可变长参数*args,**kwargs
    
    # def test(x,y,z): #位置参数,必传值参数
    #     print(x)
    #     print(y)
    #     print(z)
    #
    # test(1,y=2,z=3)
    
    # def test2(x,y=1):  #默认参数
    #     print(x)
    #     print(y)
    #
    # test2(1,2)
    # test2(1)
    
    # def test3(x,y=1,*args):     #*args  必须放在在默认参数后面,会将多余的组成一个元组的形式
    #     print(x)
    #     print(y)
    #     print(args)
    #
    # test3(1,2,3,4,5,6,7,8,9,)
    # l = ['a','b']
    # test3(1,2,*l)               #*args的形式就等于1,2,3,4,5.l
    # test3(1,2,'a','b')          #*args的形式就等于1,2,3,4,5
    # l = ('a','b')
    # test3(1,2,*l)               #l可以是列表,可以是元组,结果都一样
    
    # def test3(x,y,z):           #*的用法
    #     print(x,y,z)
    #
    # l=[1,2,3]
    # test3(*l)                   #等同于test3(1,2,3)
    
    # def foo(x,**kwargs):          #**kwargs的用法
    #     print(x)
    #     print(kwargs)
    #
    # foo(1,y=2,z=3)
    # dic = {'a':1,'b':2}
    # foo(1,**dic)                  #等同于foo(1,a=1,b=2)
    
    #位置参数>默认参数>*args>**kwargs
    
    def func(x,*args,**kwargs):     #这样定义就表示函数可以传任意的参数。
        print(x)
        print(args)
        print(kwargs)
    展开

    总结:

      *sym 等同于展开按照位置的方式去写

      **sym 等同于把sym展开按照关键字的方式去写

    7.函数是第一类对象:意思就是函数可以被当做数据来传递

    这样执行为什么不会报错?因为函数就和变量一样,将qasd...赋值给了fun,只有在调用的时候才会检查其中的语法是否正确。

    结论:

    • 函数的定义和变量的定义是一回事,变量有什么特性,函数就有什么特性
    # -*- coding: UTF-8 -*-
    #blog:http://www.cnblogs.com/linux-chenyang/
    #
    #函数是第一类对象
    def func():
        print('in the func')
    
    # #1.可以被引用
    # f1=func
    # f1()
    
    #2.可以作为参数(高阶函数)
    def foo(x):
        x()
    
    foo(func)
    
    #3.返回值可以是函数
    def foo1():
        return  func
    res=foo1()
    res()
    
    #4.可作为容器类型的元素
    func_dic={
        'func':func
    }
    
    func_dic['func']()
    展开

     8.函数的嵌套

    # -*- coding: UTF-8 -*-
    #blog:http://www.cnblogs.com/linux-chenyang/
    #
    #嵌套调用:在一个函数的内部调用另外一个函数
    #举列:比较四个数的大小
    def my_max4(a,b,c,d):
        res1=my_max2(a,b)
        res2=my_max2(res1,c)
        res3=my_max2(res2,d)
    
        return res3
    
    def my_max2(x,y):
        if x > y:
            return x
        else:
            return y
    
    print(my_max4(1,2,100,-1))
    
    #嵌套定义
    x=1
    def f1():
        x=10
        def f2():
            x=100
            print(x)
    
        return f2
    
    func=f1()
    func()
    展开

    9.闭包函数

    # -*- coding: UTF-8 -*-
    #blog:http://www.cnblogs.com/linux-chenyang/
    
    #闭包函数:打包内部函数(f2)的时候包含对外部作用域名字的引用(x=1),f2就被称为闭包函数
    #用处:什么使用用,什么时候执行就可以了
    
    
    def f1():
        x=1
        def f2():
            print(x)
    
        return f2
    
    f=f1()
    x=100000000000
    f()
    
    #举列:获取baidu的页面
    from urllib.request import urlopen          #导入模块
    
    def page(url):
    #   url='http://www.baidu.com'
        def get():
            return urlopen(url).read().decode('utf-8')
        return get
    
    baidu=page('http://www.baidu.com')
    print(baidu())
    展开

    2.装饰器

    装饰器:在遵循下面两个原则的前提下为装饰者添加新功能
    必须遵循两个原则:

    1. 一定不能修改源代码
    2. 不能修改调用方式
    #语法如下:就相当于将index传递给func,然后wrapper的结果给了timer()函数,然后timer函数就等于index
    #举列:给原始的函数index显示出运行的时间
    
    import  time
    def timer(func):
        def wrapper():
    
            start_time = time.time()
            func()           #这个就是在调用最原始的函数
            stop_time = time.time()
            print('run time is %s' %(start_time-start_time))
    
        return wrapper
    
    @timer                   #index=time(index)
    def index():
        print('in the index')
    
    index()
    展开
    #举列:传人多个参数和返回值的用法
    import  time
    def timer(func):
    
        def wrapper(*args,**kwargs): #('test'),{'msg'='tom'}
    
            start_time = time.time()
            res=func(*args,**kwargs)    #home('test',msg='tom')---->home(user,msg)
    #        func(msg)               #这个就是在调用最原始的函数
            stop_time = time.time()
            print('run time is %s' %(start_time-start_time))
            return res
        return wrapper
    
    @timer                          #index=time(index(msg))
    def index(msg):
        print('in the index',msg)
    
    @timer
    def home(user,msg):
        print('in the home %s %s' %(user,msg))
        return 1
    
    index('hello world')
    home('test',msg='tom')
    
    res=home('test',msg='tom')
    print(res)
    展开
    #装饰器小知识:默认被装饰器的注释信息不会被调用,只会调用wrapper里的信息
    #解决方法:导入functools,然后在wrapper上面在装饰
    
    import  time,functools
    
    def timer(func):
        @functools.wraps(func)
        def wrapper(*args,**kwargs): #('test'),{'msg'='tom'}
            '''
            wrapper func
            :param args:
            :param kwargs:
            :return:
            '''
            start_time = time.time()
            res=func(*args,**kwargs)    #home('test',msg='tom')---->home(user,msg)
            stop_time = time.time()
            print('run time is %s' %(start_time-start_time))
            return res
        return wrapper
    @timer                          #index=time(index(msg))
    def index(msg):
        '''
        注释信息
        :param msg:
        :return:
        '''
        print('in the index',msg)
        return 1
    res=index('hello world')
    print(res)
    print(help(index))
    展开
    # -*- coding: UTF-8 -*-
    #blog:http://www.cnblogs.com/linux-chenyang/
    #需求:我要想执行index,必须先让我输入正确的用户名和密码才能让你执行
    
    
    # def auth(func):
    #     def wrapper(*args,**kwargs):
    #         user = 'root'
    #         passwd = 'root123'
    #         newuser = input('user:')
    #         newpasswd = input('passwd:')
    #         if user == newuser and passwd == newpasswd:
    #             return func(*args,*kwargs)
    #     return wrapper
    #
    # @auth
    # def index(msg):
    #     print("helo : %s" %(msg))
    #
    #
    # index('lijun')
    
    #有参数的装饰器
    #需求:现在我的index函数程序基于两种的验证方式,我的用户名和密码有可能是保存在本地,有可能是保存在数据库
    
    
    def auth_test(type):
        def auth(func):
            def wrapper(*args,**kwargs):
                user = 'root'
                passwd = 'root123'
                newuser = input('user:')
                newpasswd = input('passwd:')
                if type == 'mysql':
                    print('这个是mysql的验证方式')
                    return func(*args,**kwargs)
                elif type == 'file':
                    if user == newuser and passwd == newpasswd:
                        return func(*args,*kwargs)
                else:
                    pass            #可以是其他的验证方式
            return wrapper
        return auth
    
    @auth_test('file')   #等同于auth_test(mysql)-->执行的结果假如为res---->@res   index=auth(index)
    def index(msg):
        print("helo : %s" %(msg))
    
    
    index('lijun')
    展开
    #需求:假如我还有个函数,我第一次已经登陆成功过,在运行这个函数就不需要我在输入用户名和密码
    #应用场景:假如你已经登陆了京东,在打开第二个页面是否还让你输入用户名和密码
    
    accounts = {}        #先定义一个字典,登陆成功后就加进去
    current_login_user = None   #定义一个全局变量,登陆成功后改这个变量
    
    def auth_test(type):
        def auth(func):
            def wrapper(*args,**kwargs):
                if current_login_user not in accounts:   #判断是否已经认证成功
                    user = 'root'
                    passwd = 'root123'
                    newuser = input('user:')
                    newpasswd = input('passwd:')
                    if type == 'mysql':
                        print('这个是mysql的验证方式')
                        return func(*args,**kwargs)
                    elif type == 'file':
                        if user == newuser and passwd == newpasswd:
                            accounts[user] = passwd      #给字典传值{'root':'root123'}
                            global current_login_user    #先声明改这个全局变量
                            current_login_user = user
                            return func(*args,*kwargs)
                else:
                    return func(*args,**kwargs)
    
            return wrapper
        return auth
    
    @auth_test('file')   #等同于auth_test(mysql)-->执行的结果假如为res---->@res   index=auth(index)
    def index(msg):
        print("helo : %s" %(msg))
    
    @auth_test('mysql')
    def home():
        print('This is home')
    
    
    index('lijun')                          #运行第一个函数
    home()                                  #运行第二个函数
    展开

     3.模块

     什么是模块?

      一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀

    为什么要用模块?

      如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。

         随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用.

    如何使用模块?

    3.1.import

    >示例:spam.py,文件名spam.py,模块名spam

    #spam.py文件
    
    print('There is spam')
    mony = 100
    
    def read():
        print(mony)
    
    ###################################
    #在另外一个python脚本操作
    #import spam
    #import spam        #只会执行spam
    
    import spam
    print(spam.mony)
    spam.read()
    
    '''
    第一次导入模块做了三件事:
    1.创建新的作用域
    2.在该作用域中执行顶级代码
    3.得到一个模块名,绑定到该模块内的代码
    '''
    
    #输出:
    There is spam
    100
    100

    >导入的模块只对该模块的作用域有效

    #spam.py
    money = 100
    
    def change():                    #修改全局变量
        global money        
        money = 0
    
    
    #另外一个脚本操作
    import spam
    
    mony = 100000
    spam.change()
    print(mony)
    
    #输出:
    100000                        

    >为模块名起别名,相当于m1=1;m2=m1

    import spam as sm
    print(sm.money)

      为已经导入的模块起别名的方式对编写可扩展的代码很有用,假设有两个模块xmlreader.py和csvreader.py,它们都定义了函数read_data(filename):用来从文件中读取一些数据,但采用不同的输入格式。可以编写代码来选择性地挑选读取模块,例如

    if file_format == 'xml':
        import xmlreader as reader
    elif file_format == 'csv':
        import csvreader as reader
    data=reader.read_date(filename)

    >一行当中导入多个模块

    import sys,os,re

    3.2.from...import....    缺点:容易重名

    >用法,直接调用,不需要前面加模块名

    #spam.py
    money = 100
    
    def change():                    #修改全局变量
        global money        
        money = 0
    
    #另外一个脚本操作
    from spam import change
    
    mony = 100000
    change()
    print(mony)
    
    #输出:
    100000      

    >若函数和当前的脚本重名,会执行当前的函数

    #spam.py
    def change():
        print("spam里的函数")
    
    
    #另外一个脚本
    from spam import change
    
    def change():
        print("当前的脚本")
    
    change()
    
    #输出
    当前的脚本

    >也支持as,也支持导入多行

    from spam import read1 as read
    
    from spam import (read1,
                      read2,
                      money)

     >from spam import * 把spam中所有的不是以下划线(_)开头的名字都导入到当前位置

    #spam.py
    
    def change():
        print("spam里的函数")
    
    def read1():
        print("from read1")
    
    def _read2():
        print('from read2')
    
    
    #另外一个脚本执行
    from spam import *
    
    change()
    read1()
    _read2()
    
    
    
    #输出:_read2会报错
    Traceback (most recent call last):
      File "D:/pycharm/s16/day4/模块导入.py", line 32, in <module>
        _read2()
    NameError: name '_read2' is not defined
    spam里的函数
    from read1

    >__all__ 的用法,注意是两个下划线。

    #spam.py
    __all__ = ['read1','read3']    #只有用from spam import *的方法导入模块的时候,只允许调用里面的函数
    
    def change():
        print("spam里的函数")
    
    def read1():
        print("from read1")
    
    def _read2():
        print('from read2')
    
    def read3():
        print('from read3')        
    
    
    #另外一台脚本操作
    from spam import *
    
    read1()
    read3()
    #change ()                #若要导入change模块会报错

    3.3把模块当做脚本执行

    我们可以通过模块的全局变量__name__来查看模块名:
    当做脚本运行:
    __name__ 等于'__main__'

    当做模块导入:
    __name__等于'spam'

    作用:用来控制.py文件在不同的应用场景下执行不同的逻辑
    if __name__ == '__main__':

    #spam.py
    print(__name__)
    if __name__ == '__main__':
        print("文件被当做脚本执行时触发的代码")
    else:
        print("文件被当做模块模块时触发的代码")
    
    
    #当做脚本执行的结果
    __main__
    文件被当做脚本执行时触发的代码
    
    
    #当做模块执行的结果,在另外一个脚本执行
    from spam import *
    
    #输出
    spam
    文件被当做模块模块时触发的代码

    3.4 模块搜索路径

    #在D:pycharms16day4dri'的目录下有个test_path.py的脚本
    print("这个是test_path模块")
    
    
    
    #在D:pycharms16day4的目录下执行
    # -*- coding: UTF-8 -*-
    #blog:http://www.cnblogs.com/linux-chenyang/
    
    '''
    import test
    导入模块时首先要做的事情:
    第一步:先去python内置的模块去找,看是否有该模块名。
    第二步:然后去sys.path的目录按照顺序当中找是否有test的模块。
    '''
    
    #举列,假如我在当前目录下建立个dir目录,想要在dir目录下执行test_path脚本
    import sys
    #print(sys.path)
    sys.path.append(r'D:pycharms16day4dri')
    print(sys.path)
    #sys.path.insert(0,'/x/y/z') #排在前的目录,优先被搜索
    
    import test_path
    
    
    #输出
    ['D:\pycharm\s16\day4', 'D:\pycharm\s16', 'C:\Python35\python35.zip', 'C:\Python35\DLLs', 'C:\Python35\lib', 'C:\Python35', 'C:\Python35\lib\site-packages', 'D:\pycharm\s16\day4\dri']
    这个是test_path模块

    至于.egg文件是由setuptools创建的包,这是按照第三方python库和扩展时使用的一种常见格式,.egg文件实际上只是添加了额外元数据(如版本号,依赖项等)的.zip文件。

    需要强调的一点是:只能从.zip文件中导入.py,.pyc等文件。使用C编写的共享库和扩展块无法直接从.zip文件中加载(此时setuptools等打包系统有时能提供一种规避方法),且从.zip中加载文件不会创建.pyc或者.pyo文件,因此一定要事先创建他们,来避免加载模块是性能下降。

     4.包

    Packages are a way of structuring Python’s module namespace by using “dotted module names”
    包是一种通过使用‘.模块名’来组织python模块名称空间的方式。

    无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法

    包的本质就是一个包含__init__.py文件的目录。
    包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间

    先建立如下结构的包和脚本内容:

    glance/                   #Top-level package
    
    ├── __init__.py      #Initialize the glance package
    
    ├── api                  #Subpackage for api
    
    │   ├── __init__.py
    
    │   ├── policy.py
    
    │   └── versions.py
    
    ├── cmd                #Subpackage for cmd
    
    │   ├── __init__.py
    
    │   └── manage.py
    
    └── db                  #Subpackage for db
    
        ├── __init__.py
    
        └── models.py
    View Code
     1 #文件内容
     2 
     3 #policy.py
     4 def get():
     5     print('from policy.py')
     6 
     7 #versions.py
     8 def create_resource(conf):
     9     print('from version.py: ',conf)
    10 
    11 #manage.py
    12 def main():
    13     print('from manage.py')
    14 
    15 #models.py
    16 def register_models(engine):
    17     print('from models.py: ',engine)
    View Code

    4.1注意事项

    1.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。

    2.对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。

    3.对比import item 和from item import name的应用场景:
    如果我们想直接使用name那必须使用后者。

    4.2 import和from....import导入包

    # -*- coding: UTF-8 -*-
    #blog:http://www.cnblogs.com/linux-chenyang/
    #验证的脚本是在glance同级别目录下
    
    #import的方法导入,或者可以起别名
    import glance.db.models
    glance.db.models.register_models('mysql')
    
    import glance.db.models as a
    a.register_models('起别名后的')
    
    #from ... import ...的方法导入
    #需要注意的是from后import导入的模块,必须是明确的一个不能带点,否则会有语法错误,如:from a import b.c是错误语法
    from glance.db.models import register_models
    register_models('from的方法')
    View Code
    #输出
    from models.py:  mysql
    from models.py:  起别名后的
    from models.py:  from的方法
    View Code

    4.3 __init__.py文件

    不管是哪种方式,只要是第一次导入包或者是包的任何其他部分,都会依次执行包下的__init__.py文件(我们可以在每个包的文件内都打印一行内容来验证一下),这个文件可以为空,但是也可以存放一些初始化包的代码。

    #分别在glance下的__init_.py和db下的__init__.py写入内容
    #输出
    --------------glance package------------
    --------------db package------
    from models.py:  mysql
    from models.py:  起别名后的
    from models.py:  from的方法
    View Code

    4.4 from....import * 的用法

    #api/__init__.py文件
    # -*- coding: UTF-8 -*-
    #blog:http://www.cnblogs.com/linux-chenyang/
    print('--------------api package------')
    __all__=['versions','policy']
    
    
    #包的导入文件,和glance同级
    #运行from import *就是代表运行api包下的__init__.py的文件
    #若想要运行api下的其他模块,在api下的__init__.py用__all__ = []来指定
    from glance.api import *
    
    policy.get()
    versions.create_resource('versions函数')
    
    
    #输出
    --------------glance package------------
    --------------api package------
    from policy.py
    from version.py:  versions函数
    View Code

    4.5 绝对导入和相对导入

    #需求1:policy.py文件调用versions.py里的create_resource函数,在glance同级目录下执行
    
    #policy文件
    def get():
        print('from policy.py')
    
    from glance.api import versions             #要想在最外层调用模块,必须用from import的方式,在当前脚本执行会报错,除非加上环境变量
    
    versions.create_resource('这是versions的函数')
    
    
    #glance同级目录下执行
    import glance.api.policy
    glance.api.policy.get()
    
    
    #输出:
    --------------glance package------------
    --------------api package------
    from version.py:  这是versions的函数
    from policy.py
    
    
    
    #需求2:在glance/api/version.py中想要导入glance/cmd/manage.py,在glance同级目录下执行
    #version文件
    def create_resource(conf):
        print('from version.py: ', conf)
    
    #绝对导入
    from glance.cmd.manage import main
    main()
    
    #相对导入
    from ..cmd.manage import main    #一个.表示上一级目录,两个点表示上上级目录
    main()
    
    
    #glance同级目录下执行
    import glance.api.versions
    glance.api.versions.create_resource('aa')
    
    
    
    输出:
    --------------glance package------------
    --------------api package------
    from manage.py
    from manage.py
    from version.py:  aa
    View Code

    总结:from....import...的方法用来导入子包,import的方法用来导入内置的和第三方模块

    4.6 单独导入包

    #在与glance同级的test.py中
    import glance
    glance.db.models.register_models('单独导入')
    
    '''
    执行结果:
    AttributeError: module 'glance' has no attribute 'db'
    
    ''' 
    
    
    #解决方法
    #glance/__init__.py
    from . import db
     
    #glance/db/__init__.py
    from . import models
    
    
    #输出结果:
    --------------glance package------------
    --------------db package------
    from models.py:  单独导入
    View Code

    千万别问:__all__不能解决吗,__all__是用于控制from...import * ,fuck

    补充:from import的用法

    包可以用“from 目录 import 模块名”这种方式来导入,但是目录不行,只能用‘from 模块 import 函数’

     

  • 相关阅读:
    结对第一次—疫情统计可视化(原型设计)
    软工实践寒假作业(2/2)
    软工实践寒假作业(1/2)
    Luogu P3975 [TJOI2015]弦论
    【模板】后缀自动机 (SAM)
    停用FF新鲜事/FF新推荐
    模板汇总
    Luogu P4467 [SCOI2007]k短路(模板)
    【模板】 最短路
    Luogu P5960 【模板】差分约束算法
  • 原文地址:https://www.cnblogs.com/linux-chenyang/p/6406224.html
Copyright © 2011-2022 走看看