diff --git "a/2024/03/13/Python\346\250\241\345\235\227\345\260\201\350\243\205\345\257\274\345\205\245\345\222\214\345\214\205\347\232\204\347\233\270\345\205\263\347\237\245\350\257\206/index.html" "b/2024/03/13/Python\346\250\241\345\235\227\345\260\201\350\243\205\345\257\274\345\205\245\345\222\214\345\214\205\347\232\204\347\233\270\345\205\263\347\237\245\350\257\206/index.html" index 8d01cd7..acac859 100644 --- "a/2024/03/13/Python\346\250\241\345\235\227\345\260\201\350\243\205\345\257\274\345\205\245\345\222\214\345\214\205\347\232\204\347\233\270\345\205\263\347\237\245\350\257\206/index.html" +++ "b/2024/03/13/Python\346\250\241\345\235\227\345\260\201\350\243\205\345\257\274\345\205\245\345\222\214\345\214\205\347\232\204\347\233\270\345\205\263\347\237\245\350\257\206/index.html" @@ -28,7 +28,7 @@ - + @@ -421,7 +421,7 @@

1
2
3
>>> from fibo import *
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
  • 模块名使用as,直接把as后的名称与导入模块绑定

    -
    1
    2
    >>> import fibo as fibo
    >> fib.fib(500)
    +
    1
    2
    >>> import fibo as fibo
    >>> fib.fib(500)
  • 结合from一起使用

    1
    2
    >>> from fibo import fib as fibonacci
    >>> fibonacci(500)
  • diff --git a/atom.xml b/atom.xml index 020634b..8c112fb 100644 --- a/atom.xml +++ b/atom.xml @@ -6,7 +6,7 @@ - 2024-03-14T06:22:24.711Z + 2024-03-14T06:29:05.733Z https://zade23.github.io/ @@ -21,9 +21,9 @@ https://zade23.github.io/2024/03/13/Python%E6%A8%A1%E5%9D%97%E5%B0%81%E8%A3%85%E5%AF%BC%E5%85%A5%E5%92%8C%E5%8C%85%E7%9A%84%E7%9B%B8%E5%85%B3%E7%9F%A5%E8%AF%86/ 2024-03-13T06:12:24.000Z - 2024-03-14T06:22:24.711Z + 2024-03-14T06:29:05.733Z -
  • 背景介绍
  • TL;DR
  • 模块

  • 背景介绍

    作为非科班的程序员,在代码规范和程序思维上是有欠缺的(仅代表我个人)。

    这些问题会在合作开发项目中暴露出来(阅读其他成员代码以及在其他成员代码上续写功能这类型的任务中暴露的更加明显),比如:对程序模块的封装、底层架构的了解(底层架构对于阅读代码和理解代码很重要)、Python语言的标准库以及装饰器使用等等……

    2024年的主题就是:“还债”。目标是尽快补齐在程序架构和工程领域的能力。

    TL;DR

    在Python工程中,模块是一个包含Python定义和语句的文件,一般以.py作为后缀。模块中的定义可以导入到其他模块或者主程序(main)中,这样做的的目的是方便程序的维护和复用。

    模块功能所做的一切就是为了:代码的复用和方便维护。

    总览:

    modules&package

    模块

    快速理解

    现在有个程序模块名称为fibo.py,通过它的名字大致猜测应该是斐波那契数列的功能实现。

    打开这个.py文件,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # Fibonacci numbers module

    def fib(n): # write Fibonacci series up to n
    a, b = 0, 1
    while a < n:
    print(a, end=' ')
    a, b = b, a+b
    print()

    def fib2(n): # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while a < n:
    result.append(a)
    a, b = b, a+b
    return result

    可以看到该模块中有两个方法,分别是:fibfib2

    如果我想在该模块中使用这两个函数的功能,可以直接调用函数名称并传入参数即可:

    1
    2
    3
    4
    >>> fib(1000)
    0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
    >>> fib2(100)
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

    这时自然而然的诞生一个新的问题,如果在这个函数之外,我依然想使用这两个函数的功能在怎办呢?

    先展示结果,最后再讲解细节。

    现在,新建一个脚本文件(保证该脚本文件和fibo.py在同一目录下),内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    >>> import fibo

    >>> fibo.fib(1000)
    0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

    >>> fibo.fib2(100)
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

    >>> fibo.__name__
    'fibo'

    >>> fib = fibo.fib # 如果想要经常使用某个函数功能,可以把它赋值给局部变量
    >>> fib(500)
    0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

    可以看到,新建的脚本文件中通过import fibo调用了开头写的斐波那契数列功能的模块。

    当想要使用模块中的函数方法,仅需要用导入的模块名称加上”.功能函数名称”,就可以实现功能的调用甚至重新命名变量等操作。

    导入方式

    使用import导入包的方式现列出4种,例如:

    1. 导入模块中的方法名称

      1
      2
      >>> from fibo import fib, fib2
      >>> fib(500)
    2. 导入模块内定义的所有名称(不包括含_开头的名称,并且不建议使用这种方法导入)

      1
      2
      3
      >>> from fibo import *
      >>> fib(500)
      0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
    3. 模块名使用as,直接把as后的名称与导入模块绑定

      1
      2
      >>> import fibo as fibo
      >> fib.fib(500)
    4. 结合from一起使用

      1
      2
      >>> from fibo import fib as fibonacci
      >>> fibonacci(500)

    以脚本方式运行模块

    通常在命令行执行脚本文件的语句:

    1
    python fibo.py <arguments>

    直接运行.py脚本会在我们看不见的地方默认的执行一个事情,即:**把__name__赋值为"__main__"**。

    也就是把下列代码添加到了模块的末尾

    1
    2
    3
    if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

    这样做的含义是,在模块作为”main”文件(脚本)进行执行的时候才会运行。

    举个例子:

    1. 当模块作为脚本文件执行时(会执行):

      1
      2
      $ python fibo.py 50
      0 1 1 2 3 5 8 13 21 34
    2. 当模块被导入到其它模块或主程序时(不会执行):

      1
      2
      >>> import fibo
      >>>

    Python文件的编译

    这部分在官网文档讲解的非常清晰,参考6.1.3. “已编译的” Python 文件

    这里附上“Python模块快速加载”的决策细节流程图:

    pyc_flow_chart

    使用内置函数dir()查看模型定义的名称

    dir()用于查找模块定义的名称,返回值为排序之后的字符串列表。

    上实例:

    1. dir()含参数时:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      >>> import fibo, sys
      >>> dir(fibo)
      ['__name__', 'fib', 'fib2']
      >>> dir(sys)
      ['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
      '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
      '__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
      '_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
      '_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
      'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
      'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing',
      'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info',
      'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info',
      'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
      'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
      'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
      'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
      'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
      'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
      'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
      'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix',
      'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags',
      'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
      'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info',
      'warnoptions']
    2. dir()不含参数时:

      1
      2
      3
      4
      5
      >>> a = [1, 2, 3, 4, 5]
      >>> import fibo
      >>> fib = fibo.fib
      dir()
      ['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']

    包是通过使用“带点号模块名”来构造 Python 模块命名空间的一种方式。

    例如,模块名 A.B 表示名为 A 的包中名为 B 的子模块。

    使用modules.func的这种调用方式还有一个好处,就是避免在不同模块中的功能函数命名冲突。

    例如:在 NumPy 或 Pillow 等多模块包中很多功能函数命名相同,这样使用np.funcPillow.func就不必担心彼此的func模块名冲突了。

    假设要为统一处理声音文件与声音数据设计一个模块集(“包”)。声音文件的格式很多(通常以扩展名来识别,例如:.wav.aiff.au),因此,为了不同文件格式之间的转换,需要创建和维护一个不断增长的模块集合。

    为了实现对声音数据的不同处理(例如,混声、添加回声、均衡器功能、创造人工立体声效果),还要编写无穷无尽的模块流。

    下面这个分级文件树展示了这个包的架构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    sound/                          Top-level package
    __init__.py Initialize the sound package
    formats/ Subpackage for file format conversions
    __init__.py
    wavread.py
    wavwrite.py
    aiffread.py
    aiffwrite.py
    auread.py
    auwrite.py
    ...
    effects/ Subpackage for sound effects
    __init__.py
    echo.py
    surround.py
    reverse.py
    ...
    filters/ Subpackage for filters
    __init__.py
    equalizer.py
    vocoder.py
    karaoke.py
    ...

    导入包时,Python搜索sys.path里的目录,查找包的子目录。

    需要有__init__.py文件才能让Python将包含改文件的目录当做“包”来处理。这样可以防止重名目录如string在无意中屏蔽后续出现在模块搜索路径中的有效模块。

    最简单的情况就是,__init__.py可以是一个空文件,但是它也可以执行包的初始化代码或设置__all__变量,这将在稍后详细描述。

    一些例子说明:

    1
    2
    3
    4
    import sound.effects.echo

    # 加载子模块 sound.effects.echo 必须通过全名来引用
    sound.effects.echo.echofilter(input, output, delay)

    另一种导入子模块的方法:

    1
    2
    3
    4
    from sound.effects import echo

    # 加载子模块 echo,并且不必加包前缀
    echo.echofilter(input, output, delay = 0.7, atten = 4)

    还有一种,直接导入所需的函数或变量:

    1
    2
    3
    4
    from sound.effect.echo import echofilter

    # 加载子模块 echo,使其函数 echofilter() 直接可用:
    echofilter(input, output, delay = 0.7, atten = 4)

    从包中导入*

    同理于模块的导入,同样不建议这样做。

    一些需要提及的知识点:如果直接使用*进行导入,一般执行的操作为通过包中的__init__.py代码部分的以下__all__中的模块名列表。

    1
    __all__ = ["echo", "surround", "reverse"]

    子模块的命名有可能会受到本地定义名称的影响!

    模块中的模块如果和环境中已存在的模块重名,则会被本地定义过的函数名称遮挡。以reverse函数为例:

    1
    2
    3
    4
    5
    6
    7
    8
    __all__ = [
    "echo", # refers to the 'echo.py' file
    "surround", # refers to the 'surround.py' file
    "reverse", # !!! refers to the 'reverse' function now !!!
    ]

    def reverse(msg: str): # <-- this name shadows the 'reverse.py' submodule
    return msg[::-1] # in the case of a 'from sound.effects import *'

    官方文档中,推荐的做法是:frome package import submodule.

    相对导入

    当包由多个子包构成(如示例中的 sound 包)时,可以使用绝对导入来引用同级包的子模块。

    例如,如果 sound.filters.vocoder 模块需要使用 sound.effects 包中的 echo 模块,它可以使用 from sound.effects import echo

    你还可以编写相对导入代码,即使用 from module import name 形式的 import 语句。

    这些导入使用前导点号来表示相对导入所涉及的当前包和上级包。

    例如对于 surround 模块,可以使用:

    1
    2
    3
    from . import echo
    from .. import formats
    from ..filters import equalizer

    注意,相对导入基于当前模块名。

    因为主模块名永远是 "__main__" ,所以如果计划将一个模块用作 Python 应用程序的主模块,那么该模块内的导入语句必须始终使用绝对导入。

    多目录中的包

    通过 __path__ 可以传入字符串列表,找到所有 __init__.py坐在目录的位置。该功能不常用,知道就好。


    相关参考:

    ]]> +
  • 背景介绍
  • TL;DR
  • 模块

  • 背景介绍

    作为非科班的程序员,在代码规范和程序思维上是有欠缺的(仅代表我个人)。

    这些问题会在合作开发项目中暴露出来(阅读其他成员代码以及在其他成员代码上续写功能这类型的任务中暴露的更加明显),比如:对程序模块的封装、底层架构的了解(底层架构对于阅读代码和理解代码很重要)、Python语言的标准库以及装饰器使用等等……

    2024年的主题就是:“还债”。目标是尽快补齐在程序架构和工程领域的能力。

    TL;DR

    在Python工程中,模块是一个包含Python定义和语句的文件,一般以.py作为后缀。模块中的定义可以导入到其他模块或者主程序(main)中,这样做的的目的是方便程序的维护和复用。

    模块功能所做的一切就是为了:代码的复用和方便维护。

    总览:

    modules&package

    模块

    快速理解

    现在有个程序模块名称为fibo.py,通过它的名字大致猜测应该是斐波那契数列的功能实现。

    打开这个.py文件,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # Fibonacci numbers module

    def fib(n): # write Fibonacci series up to n
    a, b = 0, 1
    while a < n:
    print(a, end=' ')
    a, b = b, a+b
    print()

    def fib2(n): # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while a < n:
    result.append(a)
    a, b = b, a+b
    return result

    可以看到该模块中有两个方法,分别是:fibfib2

    如果我想在该模块中使用这两个函数的功能,可以直接调用函数名称并传入参数即可:

    1
    2
    3
    4
    >>> fib(1000)
    0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
    >>> fib2(100)
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

    这时自然而然的诞生一个新的问题,如果在这个函数之外,我依然想使用这两个函数的功能在怎办呢?

    先展示结果,最后再讲解细节。

    现在,新建一个脚本文件(保证该脚本文件和fibo.py在同一目录下),内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    >>> import fibo

    >>> fibo.fib(1000)
    0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

    >>> fibo.fib2(100)
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

    >>> fibo.__name__
    'fibo'

    >>> fib = fibo.fib # 如果想要经常使用某个函数功能,可以把它赋值给局部变量
    >>> fib(500)
    0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

    可以看到,新建的脚本文件中通过import fibo调用了开头写的斐波那契数列功能的模块。

    当想要使用模块中的函数方法,仅需要用导入的模块名称加上”.功能函数名称”,就可以实现功能的调用甚至重新命名变量等操作。

    导入方式

    使用import导入包的方式现列出4种,例如:

    1. 导入模块中的方法名称

      1
      2
      >>> from fibo import fib, fib2
      >>> fib(500)
    2. 导入模块内定义的所有名称(不包括含_开头的名称,并且不建议使用这种方法导入)

      1
      2
      3
      >>> from fibo import *
      >>> fib(500)
      0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
    3. 模块名使用as,直接把as后的名称与导入模块绑定

      1
      2
      >>> import fibo as fibo
      >>> fib.fib(500)
    4. 结合from一起使用

      1
      2
      >>> from fibo import fib as fibonacci
      >>> fibonacci(500)

    以脚本方式运行模块

    通常在命令行执行脚本文件的语句:

    1
    python fibo.py <arguments>

    直接运行.py脚本会在我们看不见的地方默认的执行一个事情,即:**把__name__赋值为"__main__"**。

    也就是把下列代码添加到了模块的末尾

    1
    2
    3
    if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

    这样做的含义是,在模块作为”main”文件(脚本)进行执行的时候才会运行。

    举个例子:

    1. 当模块作为脚本文件执行时(会执行):

      1
      2
      $ python fibo.py 50
      0 1 1 2 3 5 8 13 21 34
    2. 当模块被导入到其它模块或主程序时(不会执行):

      1
      2
      >>> import fibo
      >>>

    Python文件的编译

    这部分在官网文档讲解的非常清晰,参考6.1.3. “已编译的” Python 文件

    这里附上“Python模块快速加载”的决策细节流程图:

    pyc_flow_chart

    使用内置函数dir()查看模型定义的名称

    dir()用于查找模块定义的名称,返回值为排序之后的字符串列表。

    上实例:

    1. dir()含参数时:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      >>> import fibo, sys
      >>> dir(fibo)
      ['__name__', 'fib', 'fib2']
      >>> dir(sys)
      ['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
      '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
      '__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
      '_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
      '_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
      'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
      'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing',
      'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info',
      'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info',
      'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
      'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
      'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
      'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
      'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
      'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
      'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
      'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix',
      'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags',
      'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
      'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info',
      'warnoptions']
    2. dir()不含参数时:

      1
      2
      3
      4
      5
      >>> a = [1, 2, 3, 4, 5]
      >>> import fibo
      >>> fib = fibo.fib
      dir()
      ['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']

    包是通过使用“带点号模块名”来构造 Python 模块命名空间的一种方式。

    例如,模块名 A.B 表示名为 A 的包中名为 B 的子模块。

    使用modules.func的这种调用方式还有一个好处,就是避免在不同模块中的功能函数命名冲突。

    例如:在 NumPy 或 Pillow 等多模块包中很多功能函数命名相同,这样使用np.funcPillow.func就不必担心彼此的func模块名冲突了。

    假设要为统一处理声音文件与声音数据设计一个模块集(“包”)。声音文件的格式很多(通常以扩展名来识别,例如:.wav.aiff.au),因此,为了不同文件格式之间的转换,需要创建和维护一个不断增长的模块集合。

    为了实现对声音数据的不同处理(例如,混声、添加回声、均衡器功能、创造人工立体声效果),还要编写无穷无尽的模块流。

    下面这个分级文件树展示了这个包的架构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    sound/                          Top-level package
    __init__.py Initialize the sound package
    formats/ Subpackage for file format conversions
    __init__.py
    wavread.py
    wavwrite.py
    aiffread.py
    aiffwrite.py
    auread.py
    auwrite.py
    ...
    effects/ Subpackage for sound effects
    __init__.py
    echo.py
    surround.py
    reverse.py
    ...
    filters/ Subpackage for filters
    __init__.py
    equalizer.py
    vocoder.py
    karaoke.py
    ...

    导入包时,Python搜索sys.path里的目录,查找包的子目录。

    需要有__init__.py文件才能让Python将包含改文件的目录当做“包”来处理。这样可以防止重名目录如string在无意中屏蔽后续出现在模块搜索路径中的有效模块。

    最简单的情况就是,__init__.py可以是一个空文件,但是它也可以执行包的初始化代码或设置__all__变量,这将在稍后详细描述。

    一些例子说明:

    1
    2
    3
    4
    import sound.effects.echo

    # 加载子模块 sound.effects.echo 必须通过全名来引用
    sound.effects.echo.echofilter(input, output, delay)

    另一种导入子模块的方法:

    1
    2
    3
    4
    from sound.effects import echo

    # 加载子模块 echo,并且不必加包前缀
    echo.echofilter(input, output, delay = 0.7, atten = 4)

    还有一种,直接导入所需的函数或变量:

    1
    2
    3
    4
    from sound.effect.echo import echofilter

    # 加载子模块 echo,使其函数 echofilter() 直接可用:
    echofilter(input, output, delay = 0.7, atten = 4)

    从包中导入*

    同理于模块的导入,同样不建议这样做。

    一些需要提及的知识点:如果直接使用*进行导入,一般执行的操作为通过包中的__init__.py代码部分的以下__all__中的模块名列表。

    1
    __all__ = ["echo", "surround", "reverse"]

    子模块的命名有可能会受到本地定义名称的影响!

    模块中的模块如果和环境中已存在的模块重名,则会被本地定义过的函数名称遮挡。以reverse函数为例:

    1
    2
    3
    4
    5
    6
    7
    8
    __all__ = [
    "echo", # refers to the 'echo.py' file
    "surround", # refers to the 'surround.py' file
    "reverse", # !!! refers to the 'reverse' function now !!!
    ]

    def reverse(msg: str): # <-- this name shadows the 'reverse.py' submodule
    return msg[::-1] # in the case of a 'from sound.effects import *'

    官方文档中,推荐的做法是:frome package import submodule.

    相对导入

    当包由多个子包构成(如示例中的 sound 包)时,可以使用绝对导入来引用同级包的子模块。

    例如,如果 sound.filters.vocoder 模块需要使用 sound.effects 包中的 echo 模块,它可以使用 from sound.effects import echo

    你还可以编写相对导入代码,即使用 from module import name 形式的 import 语句。

    这些导入使用前导点号来表示相对导入所涉及的当前包和上级包。

    例如对于 surround 模块,可以使用:

    1
    2
    3
    from . import echo
    from .. import formats
    from ..filters import equalizer

    注意,相对导入基于当前模块名。

    因为主模块名永远是 "__main__" ,所以如果计划将一个模块用作 Python 应用程序的主模块,那么该模块内的导入语句必须始终使用绝对导入。

    多目录中的包

    通过 __path__ 可以传入字符串列表,找到所有 __init__.py坐在目录的位置。该功能不常用,知道就好。


    相关参考:

    ]]>
    diff --git a/content.json b/content.json index 71c1f43..a17a0de 100644 --- a/content.json +++ b/content.json @@ -1 +1 @@ -{"meta":{"title":"ANdRoid's BLOG","subtitle":"MaTRix","description":"","author":"Android","url":"https://zade23.github.io","root":"/"},"pages":[{"title":"关于","date":"2024-03-14T03:05:45.153Z","updated":"2024-03-14T03:05:45.153Z","comments":false,"path":"about/index.html","permalink":"https://zade23.github.io/about/index.html","excerpt":"","text":"Android,游戏行业算法学徒,现居广东 见贤思齐焉,见不贤则亦自省"},{"title":"标签","date":"2023-05-18T08:07:04.950Z","updated":"2023-05-18T08:07:04.950Z","comments":false,"path":"tags/index.html","permalink":"https://zade23.github.io/tags/index.html","excerpt":"","text":""},{"title":"Repositories","date":"2023-05-18T03:53:31.018Z","updated":"2023-05-18T03:53:31.018Z","comments":false,"path":"repository/index.html","permalink":"https://zade23.github.io/repository/index.html","excerpt":"","text":""},{"title":"分类","date":"2023-05-18T08:06:17.850Z","updated":"2023-05-18T08:06:17.850Z","comments":false,"path":"categories/index.html","permalink":"https://zade23.github.io/categories/index.html","excerpt":"","text":""}],"posts":[{"title":"Python模块封装导入和包的相关知识","slug":"Python模块封装导入和包的相关知识","date":"2024-03-13T06:12:24.000Z","updated":"2024-03-14T06:22:24.711Z","comments":true,"path":"2024/03/13/Python模块封装导入和包的相关知识/","link":"","permalink":"https://zade23.github.io/2024/03/13/Python%E6%A8%A1%E5%9D%97%E5%B0%81%E8%A3%85%E5%AF%BC%E5%85%A5%E5%92%8C%E5%8C%85%E7%9A%84%E7%9B%B8%E5%85%B3%E7%9F%A5%E8%AF%86/","excerpt":"","text":"背景介绍 TL;DR 模块 快速理解 导入方式 以脚本方式运行模块 Python文件的编译 使用内置函数dir()查看模型定义的名称 包 从包中导入* 相对导入 多目录中的包 背景介绍作为非科班的程序员,在代码规范和程序思维上是有欠缺的(仅代表我个人)。 这些问题会在合作开发项目中暴露出来(阅读其他成员代码以及在其他成员代码上续写功能这类型的任务中暴露的更加明显),比如:对程序模块的封装、底层架构的了解(底层架构对于阅读代码和理解代码很重要)、Python语言的标准库以及装饰器使用等等…… 2024年的主题就是:“还债”。目标是尽快补齐在程序架构和工程领域的能力。 TL;DR在Python工程中,模块是一个包含Python定义和语句的文件,一般以.py作为后缀。模块中的定义可以导入到其他模块或者主程序(main)中,这样做的的目的是方便程序的维护和复用。 模块的代入:通过import导入模块。模块不会直接把模块自身的函数名称添加到当前命名空间中,而是将模块名称添加到命名空间中。再通过模块名称访问其中的函数,例如:import torch \\ torch.nn.functional() 模块的作用:可执行的语句以及函数定义,用于初始化模块。每个模块都有自己的私有命名空间,它会被用作模块中定义的所有函数的全局命名空间。模块可以导入其他模块,被导入的模块名称会被添加到该模块的全局命名空间。(每个模块都有自己的命名空间,防止与用户的全局变量发生冲突)。 模块功能所做的一切就是为了:代码的复用和方便维护。 总览: 模块快速理解现在有个程序模块名称为fibo.py,通过它的名字大致猜测应该是斐波那契数列的功能实现。 打开这个.py文件,内容如下: 12345678910111213141516# Fibonacci numbers moduledef fib(n): # write Fibonacci series up to n a, b = 0, 1 while a < n: print(a, end=' ') a, b = b, a+b print()def fib2(n): # return Fibonacci series up to n result = [] a, b = 0, 1 while a < n: result.append(a) a, b = b, a+b return result 可以看到该模块中有两个方法,分别是:fib和fib2。 如果我想在该模块中使用这两个函数的功能,可以直接调用函数名称并传入参数即可: 1234>>> fib(1000)0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987>>> fib2(100)[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] 这时自然而然的诞生一个新的问题,如果在这个函数之外,我依然想使用这两个函数的功能在怎办呢? 先展示结果,最后再讲解细节。 现在,新建一个脚本文件(保证该脚本文件和fibo.py在同一目录下),内容如下: 1234567891011121314>>> import fibo>>> fibo.fib(1000)0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987>>> fibo.fib2(100)[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]>>> fibo.__name__'fibo'>>> fib = fibo.fib # 如果想要经常使用某个函数功能,可以把它赋值给局部变量>>> fib(500)0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 可以看到,新建的脚本文件中通过import fibo调用了开头写的斐波那契数列功能的模块。 当想要使用模块中的函数方法,仅需要用导入的模块名称加上”.功能函数名称”,就可以实现功能的调用甚至重新命名变量等操作。 导入方式使用import导入包的方式现列出4种,例如: 导入模块中的方法名称 12>>> from fibo import fib, fib2>>> fib(500) 导入模块内定义的所有名称(不包括含_开头的名称,并且不建议使用这种方法导入) 123>>> from fibo import *>>> fib(500)0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 模块名使用as,直接把as后的名称与导入模块绑定 12>>> import fibo as fibo>> fib.fib(500) 结合from一起使用 12>>> from fibo import fib as fibonacci>>> fibonacci(500) 以脚本方式运行模块通常在命令行执行脚本文件的语句: 1python fibo.py <arguments> 直接运行.py脚本会在我们看不见的地方默认的执行一个事情,即:**把__name__赋值为"__main__"**。 也就是把下列代码添加到了模块的末尾 123if __name__ == "__main__": import sys fib(int(sys.argv[1])) 这样做的含义是,在模块作为”main”文件(脚本)进行执行的时候才会运行。 举个例子: 当模块作为脚本文件执行时(会执行): 12$ python fibo.py 500 1 1 2 3 5 8 13 21 34 当模块被导入到其它模块或主程序时(不会执行): 12>>> import fibo>>> Python文件的编译这部分在官网文档讲解的非常清晰,参考6.1.3. “已编译的” Python 文件 这里附上“Python模块快速加载”的决策细节流程图: 使用内置函数dir()查看模型定义的名称dir()用于查找模块定义的名称,返回值为排序之后的字符串列表。 上实例: dir()含参数时: 12345678910111213141516171819202122232425>>> import fibo, sys>>> dir(fibo)['__name__', 'fib', 'fib2']>>> dir(sys)['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdout__', '__unraisablehook__', '_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework', '_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook', 'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix', 'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth', 'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags', 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info', 'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix', 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info', 'warnoptions'] dir()不含参数时: 12345>>> a = [1, 2, 3, 4, 5]>>> import fibo>>> fib = fibo.fibdir()['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys'] 包包是通过使用“带点号模块名”来构造 Python 模块命名空间的一种方式。 例如,模块名 A.B 表示名为 A 的包中名为 B 的子模块。 使用modules.func的这种调用方式还有一个好处,就是避免在不同模块中的功能函数命名冲突。 例如:在 NumPy 或 Pillow 等多模块包中很多功能函数命名相同,这样使用np.func或Pillow.func就不必担心彼此的func模块名冲突了。 假设要为统一处理声音文件与声音数据设计一个模块集(“包”)。声音文件的格式很多(通常以扩展名来识别,例如:.wav,.aiff,.au),因此,为了不同文件格式之间的转换,需要创建和维护一个不断增长的模块集合。 为了实现对声音数据的不同处理(例如,混声、添加回声、均衡器功能、创造人工立体声效果),还要编写无穷无尽的模块流。 下面这个分级文件树展示了这个包的架构: 1234567891011121314151617181920212223sound/ Top-level package __init__.py Initialize the sound package formats/ Subpackage for file format conversions __init__.py wavread.py wavwrite.py aiffread.py aiffwrite.py auread.py auwrite.py ... effects/ Subpackage for sound effects __init__.py echo.py surround.py reverse.py ... filters/ Subpackage for filters __init__.py equalizer.py vocoder.py karaoke.py ... 导入包时,Python搜索sys.path里的目录,查找包的子目录。 需要有__init__.py文件才能让Python将包含改文件的目录当做“包”来处理。这样可以防止重名目录如string在无意中屏蔽后续出现在模块搜索路径中的有效模块。 最简单的情况就是,__init__.py可以是一个空文件,但是它也可以执行包的初始化代码或设置__all__变量,这将在稍后详细描述。 一些例子说明: 1234import sound.effects.echo# 加载子模块 sound.effects.echo 必须通过全名来引用sound.effects.echo.echofilter(input, output, delay) 另一种导入子模块的方法: 1234from sound.effects import echo# 加载子模块 echo,并且不必加包前缀echo.echofilter(input, output, delay = 0.7, atten = 4) 还有一种,直接导入所需的函数或变量: 1234from sound.effect.echo import echofilter# 加载子模块 echo,使其函数 echofilter() 直接可用:echofilter(input, output, delay = 0.7, atten = 4) 从包中导入*同理于模块的导入,同样不建议这样做。 一些需要提及的知识点:如果直接使用*进行导入,一般执行的操作为通过包中的__init__.py代码部分的以下__all__中的模块名列表。 1__all__ = ["echo", "surround", "reverse"] 子模块的命名有可能会受到本地定义名称的影响! 模块中的模块如果和环境中已存在的模块重名,则会被本地先定义过的函数名称遮挡。以reverse函数为例: 12345678__all__ = [ "echo", # refers to the 'echo.py' file "surround", # refers to the 'surround.py' file "reverse", # !!! refers to the 'reverse' function now !!!]def reverse(msg: str): # <-- this name shadows the 'reverse.py' submodule return msg[::-1] # in the case of a 'from sound.effects import *' 官方文档中,推荐的做法是:frome package import submodule. 相对导入当包由多个子包构成(如示例中的 sound 包)时,可以使用绝对导入来引用同级包的子模块。 例如,如果 sound.filters.vocoder 模块需要使用 sound.effects 包中的 echo 模块,它可以使用 from sound.effects import echo。 你还可以编写相对导入代码,即使用 from module import name 形式的 import 语句。 这些导入使用前导点号来表示相对导入所涉及的当前包和上级包。 例如对于 surround 模块,可以使用: 123from . import echofrom .. import formatsfrom ..filters import equalizer 注意,相对导入基于当前模块名。 因为主模块名永远是 "__main__" ,所以如果计划将一个模块用作 Python 应用程序的主模块,那么该模块内的导入语句必须始终使用绝对导入。 多目录中的包通过 __path__ 可以传入字符串列表,找到所有 __init__.py坐在目录的位置。该功能不常用,知道就好。 相关参考: 6. 模块 — Python 3.12.2 文档","categories":[],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"}]},{"title":"合作开发中Git工作流程的细节","slug":"合作开发中Git工作流程的细节","date":"2024-03-11T08:39:05.000Z","updated":"2024-03-11T09:05:38.769Z","comments":true,"path":"2024/03/11/合作开发中Git工作流程的细节/","link":"","permalink":"https://zade23.github.io/2024/03/11/%E5%90%88%E4%BD%9C%E5%BC%80%E5%8F%91%E4%B8%ADGit%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B%E7%9A%84%E7%BB%86%E8%8A%82/","excerpt":"","text":"仓库中各分支的职责 清晰的Commit 记录开发过程中,提交代码仓库的一些经验总结(持续更新……) 仓库中各分支的职责 master分支:master分支是存放发布上线的代码的默认位置。当某个功能经过验证和测试后,将develop分支合并到master分支,并为该版本打上相应的标签(例如x.0.0,表示某个大版本的编号)。master分支应该是稳定且经过充分测试的代码。 develop分支:develop分支是整体开发功能的主要分支。在开发阶段,所有功能的开发工作都应提交到develop分支。在功能开发完成后,通过测试和验证后,将develop分支合并到master分支,使功能正式上线。 扩展: 除了master和develop分支外,还可以存在其他类型的分支,如feature分支、bugfix分支等。这些分支可以用于并行开发不同的功能或修复bug,并在开发完成后合并到develop分支。 开发团队通常采用分支策略,例如Git Flow或GitHub Flow,来管理不同分支之间的合并和发布流程,以确保代码质量和版本控制的有效管理。 主分支(如master或main)通常用于存放稳定的、可发布的代码,而开发分支(如develop)则用于整体功能的开发和集成。 通过使用不同的分支,可以实现并行开发、合理管理代码版本、隔离功能开发和修复等,从而提高团队的协作效率和代码质量。 重要的是,分支之间的合并应该经过适当的测试和验证,以确保代码的稳定性和功能的正确性。 清晰的Commit 使用明确的动词:在提交信息的开头使用明确的动词来描述你的更改。例如,使用 “添加”、”修复”、”更新”、”重构” 等词语,以便其他人可以快速了解你的更改类型。 保持简洁:提交信息应该简洁明了,尽量避免冗长的描述。使用简洁的语句来概括你的更改,并在需要时提供详细信息。 提供相关上下文:除了简洁的概述外,确保提交信息提供足够的上下文信息,以便其他人能够理解你的更改原因和意图。如果有相关的问题、需求或讨论,可以引用相关的编号或链接。 遵循团队或项目规范:根据你所在的团队或项目的规范,使用统一的提交格式和命名约定。这样可以帮助整个团队保持一致的提交信息风格,便于阅读和管理。 避免无意义的提交信息:提交信息应该有实际的意义,避免使用模糊或不相关的描述。确保你的提交信息传达了有用的信息,而不仅仅是表明你进行了一次提交。 使用标准的提交类型:参考常见的提交类型(如前面所示),选择最适合你更改类型的提交类型。这有助于其他人快速了解你的更改类型,并且在版本控制工具中进行过滤和分类。 审查和校对:在提交之前,花一些时间审查和校对你的提交信息。确保拼写正确、语法清晰,并且信息准确传达你的更改。 常见的提交格式实例: 当然,以下是一些常见的提交格式示例: feat: 添加新功能或特性 1feat: 添加用户注册功能 fix: 修复 bug 1fix: 修复登录页面样式错位的 bug docs: 更新文档 1docs: 更新用户手册中的安装说明 style: 代码样式、格式调整 1style: 格式化整个项目的代码风格 refactor: 重构代码,既不修复 bug 也不添加新功能 1refactor: 重构用户管理模块的代码结构 test: 添加或修改测试代码 1test: 添加用户注册页面的单元测试 chore: 构建过程或辅助工具的变动 1chore: 更新构建脚本以支持新的依赖库 这一切的目的是为了让团队其他成员明确该提交所做的工作,同时也让自己回顾代码的时候明确自己的工作内容。","categories":[{"name":"Git Workflow","slug":"Git-Workflow","permalink":"https://zade23.github.io/categories/Git-Workflow/"}],"tags":[{"name":"Git","slug":"Git","permalink":"https://zade23.github.io/tags/Git/"}]},{"title":"更新GitLab仓库SSH","slug":"更新GitLab仓库SSH","date":"2024-03-07T02:09:37.000Z","updated":"2024-03-11T08:41:35.516Z","comments":true,"path":"2024/03/07/更新GitLab仓库SSH/","link":"","permalink":"https://zade23.github.io/2024/03/07/%E6%9B%B4%E6%96%B0GitLab%E4%BB%93%E5%BA%93SSH/","excerpt":"","text":"介绍 问题分析 问题解决 介绍早上开工,在拉取代码仓库的时候,发现SSH过期,于是更新一下SSH为永久,并做一个记录。SSH过期命令行显示如下: 123456789101112$ git pullremote:remote: ========================================================================remote:remote: Your SSH key has expired.remote:remote: ========================================================================remote:fatal: Could not read from remote repository.Please make sure you have the correct access rightsand the repository exists. 问题分析直接将问题送给poe,让AI分析解答,根据分析结果一步步处理即可解决。结果如下: 问题解决 生成新的SSH密码对。ssh-keygen -t rsa -b 4096 -C "your_email@example.com",该命令将生成一个 4096 位的 RSA 密钥对,替换邮箱地址为你GitLab注册邮箱。 之后会有两次询问你保存位置和输入密码的选项,点击 Enter 键跳过选项即可。显示结果如下: 复制生成的SSH,window使用: 1clip < ~/.ssh/id_rsa.pub 找到GitLab中的SSH Keys一栏,粘贴,保存: 可以拉取仓库了","categories":[{"name":"Git Workflow","slug":"Git-Workflow","permalink":"https://zade23.github.io/categories/Git-Workflow/"}],"tags":[{"name":"Git","slug":"Git","permalink":"https://zade23.github.io/tags/Git/"}]},{"title":"conda环境报错解决:invalid choice: 'activate' ","slug":"conda环境报错解决:invalid-choice-activate","date":"2023-11-22T08:04:41.000Z","updated":"2024-03-14T03:02:38.054Z","comments":true,"path":"2023/11/22/conda环境报错解决:invalid-choice-activate/","link":"","permalink":"https://zade23.github.io/2023/11/22/conda%E7%8E%AF%E5%A2%83%E6%8A%A5%E9%94%99%E8%A7%A3%E5%86%B3%EF%BC%9Ainvalid-choice-activate/","excerpt":"","text":"介绍新部署的服务器上进行LLM模型的微调工作,在配置Anaconde环境后输入环境启动命令 conda activate ,出现从来没有见过的报错:anaconda conda: error: argument command: invalid choice when trying to update packages 问题分析环境问题,直接Google。最终在 GitHub 里官方仓库和 Stack Overflow 上找到一致的答复:安装Anaconde后没有进行初始化。 问题解决 终端运行 conda init zsh 之后重启 shell/Terminal (不重启依然报错) 终端运行 conda activate env_name 即可","categories":[{"name":"Deeplearning","slug":"Deeplearning","permalink":"https://zade23.github.io/categories/Deeplearning/"}],"tags":[{"name":"conda","slug":"conda","permalink":"https://zade23.github.io/tags/conda/"},{"name":"environment","slug":"environment","permalink":"https://zade23.github.io/tags/environment/"}]},{"title":"玩转Docker学习笔记","slug":"玩转Docker学习笔记","date":"2023-11-07T07:30:56.000Z","updated":"2024-03-11T10:00:59.954Z","comments":true,"path":"2023/11/07/玩转Docker学习笔记/","link":"","permalink":"https://zade23.github.io/2023/11/07/%E7%8E%A9%E8%BD%ACDocker%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/","excerpt":"","text":"镜像相关指令 容器相关指令 网络相关指令 数据卷相关指令 Docker Compose相关指令(使用docker-compose) 通过 DockerFile 构建 Docker Image 镜像相关指令 docker pull <image>:从远程仓库拉取一个镜像或仓库到本地。 docker build -t <tag> .:使用当前目录的Dockerfile构建一个新的镜像。 docker images:列出本地存储的镜像。 docker rmi <image>:删除一个或多个本地的镜像。 docker tag <image> <new_tag>:为镜像添加一个新的标签。 容器相关指令 docker run <image>:创建一个新的容器并运行一个命令。 docker ps:列出当前运行的容器。 docker ps -a:列出所有容器,包括未运行的。 docker stop <container>:停止一个或多个正在运行的容器。 docker start <container>:启动一个或多个已停止运行的容器。 docker restart <container>:重启容器。 docker rm <container>:删除一个或多个容器。 docker logs <container>:获取容器的日志。 docker exec -it <container> <command>:在运行的容器中执行命令,通常用于进入容器。 网络相关指令 docker network ls:列出所有网络。 docker network create <options> <network_name>:创建一个新的网络。 docker network rm <network>:删除一个或多个网络。 docker network connect <network> <container>:连接一个容器到一个网络。 docker network disconnect <network> <container>:断开容器与网络的连接。 数据卷相关指令 docker volume create <name>:创建一个新的卷。 docker volume ls:列出所有卷。 docker volume rm <volume>:删除一个或多个卷。 docker volume inspect <volume>:显示详细的卷信息。 Docker Compose相关指令(使用docker-compose) docker-compose up:在后台启动并运行整个应用。 docker-compose down:停止并移除容器,网络,图像和挂载卷。 docker-compose build:构建或重建服务关联的镜像。 docker-compose logs:查看服务的日志输出。 这些命令代表了Docker操作的基础,但是实际使用中可能还会遇到更复杂的场景和高级特性。建议通过官方文档或其他学习资源深入理解每个命令的用法和选项。 通过 DockerFile 构建 Docker Image一个 Image 是通过一个 DockerFile 定义的,然后使用 docker build 命令构建它。 DockerFile 中的每一条命令的执行结果都会成为 Image 中的一个 Layer。 这里,我们通过 Build 一个镜像,来观察 Image 的分层机制: 123456789101112131415161718192021222324252627# 使用官方的Python运行时环境作为基础镜像FROM python:2.7-slim# 设置工作目录为容器内的/app。如果目录不存在,Docker会自动为你创建这个目录。WORKDIR /app# 将当前目录(Dockerfile所在目录)的内容复制到容器内的/app目录中。# 这意味着你的应用代码和依赖文件(比如requirements.txt)都会被复制进去。COPY . /app# 利用RUN命令执行pip安装命令来安装requirements.txt中列出的所有依赖。# 这里使用了--trusted-host选项来指定可信的PyPI主机,避免SSL证书验证问题。RUN pip install --trusted-host pypi.python.org -r requirements.txt# 使用EXPOSE指令来告诉Docker容器内的应用将会在80端口上监听连接。# 需要注意的是,EXPOSE指令本身不会使容器的端口对外界开放,# 它更多的是一种文档化的作用,真正的端口映射需要在运行容器时通过docker run的-p选项来指定。EXPOSE 80# 使用ENV指令设置一个环境变量。这里定义了一个名为NAME的环境变量,值为World。# 环境变量可以在容器运行时被应用程序读取,用于配置应用行为。ENV NAME World# 使用CMD指令指定容器启动时运行的命令。# 这里的命令是“python app.py”,即使用Python解释器来运行app.py脚本。# CMD的主要作用是指定容器的默认执行命令。如果在docker run命令后面指定了其他命令,CMD指定的命令将被覆盖。CMD ["python", "app.py"] 这份Dockerfile基本上覆盖了构建一个简单Python应用的Docker镜像所需的所有步骤。从选择基础镜像开始,设置工作目录,复制应用代码,安装依赖,到最后指定运行时的命令和暴露的端口,每一步都为镜像的构建提供了必要的指令和配置。 最终的构建结果如下: 1234567891011121314151617181920212223root@rds-k8s-18-svr0:~/xuran/exampleimage# docker build -t hello ./Sending build context to Docker daemon 5.12 kBStep 1/7 : FROM python:2.7-slim ---> 804b0a01ea83Step 2/7 : WORKDIR /app ---> Using cache ---> 6d93c5b91703Step 3/7 : COPY . /app ---> Using cache ---> feddc82d321bStep 4/7 : RUN pip install --trusted-host pypi.python.org -r requirements.txt ---> Using cache ---> 94695df5e14dStep 5/7 : EXPOSE 81 ---> Using cache ---> 43c392d51dffStep 6/7 : ENV NAME World ---> Using cache ---> 78c9a60237c8Step 7/7 : CMD python app.py ---> Using cache ---> a5ccd4e1b15dSuccessfully built a5ccd4e1b15d 下面是对这些信息每一步的详细解释: Sending build context to Docker daemon 5.12 kB这一行表示Docker客户端正在将构建上下文发送给Docker守护进程。构建上下文是指Dockerfile所在目录的内容,Docker会将这些内容打包发送给守护进程。这里的大小是5.12kB,表示你的应用代码和依赖文件等总共大小。 Step 1/7 : FROM python:2.7-slim这是Dockerfile中的第一步,它基于python:2.7-slim这个镜像来构建新的镜像。---> 804b0a01ea83是基础镜像的ID。 Step 2/7 : WORKDIR /app设置工作目录为/app。如果不存在,Docker会自动创建这个目录。---> Using cache ---> 6d93c5b91703表示这一步使用了缓存,6d93c5b91703是这一层的ID。 Step 3/7 : COPY . /app将构建上下文(Dockerfile所在的目录)的内容复制到容器内的/app目录。---> Using cache ---> feddc82d321b表明这一步也使用了缓存。 Step 4/7 : RUN pip install –trusted-host pypi.python.org -r requirements.txt在容器内执行pip install命令,安装requirements.txt文件中列出的Python依赖包。---> Using cache ---> 94695df5e14d说明这一步同样使用了缓存。 Step 5/7 : EXPOSE 81通知Docker容器在运行时将会监听81端口。注意这里与前面提到的Dockerfile中的EXPOSE 80不同,可能是因为Dockerfile被修改了但构建输出没有更新。---> Using cache ---> 43c392d51dff表示使用了缓存。 Step 6/7 : ENV NAME World设置环境变量NAME的值为World。这个环境变量可以在容器运行时被应用程序使用。---> Using cache ---> 78c9a60237c8也显示这一步使用了缓存。 Step 7/7 : CMD [“python”, “app.py”]指定容器启动时默认执行的命令。这里是运行app.py脚本。---> Using cache ---> a5ccd4e1b15d意味着这一步也利用了之前的构建缓存。 Successfully built a5ccd4e1b15d最后,显示了成功构建的镜像ID为a5ccd4e1b15d。 在这个过程中,Using cache表明Docker发现之前的构建步骤与当前的完全一致,因此它复用了之前的结果来加快构建过程。如果你想要强制Docker重新执行每个步骤而不使用缓存,可以在构建时添加--no-cache选项。 参考链接: Docker 101 Tutorial Docker原理(图解+秒懂+史上最全)","categories":[],"tags":[{"name":"Docker","slug":"Docker","permalink":"https://zade23.github.io/tags/Docker/"}]},{"title":"pytorch-tutorial-official","slug":"pytorch-tutorial-official","date":"2023-10-31T08:49:36.000Z","updated":"2024-03-14T03:02:15.673Z","comments":true,"path":"2023/10/31/pytorch-tutorial-official/","link":"","permalink":"https://zade23.github.io/2023/10/31/pytorch-tutorial-official/","excerpt":"","text":"Pytorch 基础 快速入门 处理数据 构建模型 在 PyTorch 中构建神经网络 模型层 Model Layers nn.Flatten nn.Linear nn.ReLU nn.Sequential nn.Softmax 模型的参数 张量 Tensor 初始化张量 直接来自数据 来自另一个 Tensor 使用随机值或常量 张量的属性 张量的操作 类似 NumPy 的索引和切片操作 Tensor 之间的连接 张量的算术运算 单一元素张量 原地操作(不通过返回的方式进行原地修改) Tensor 和 Numpy 二者转换 Tensor2NumPy_array NumPy_array2Tensor 数据集的相关操作 数据加载 数据集迭代和数据可视化 创建自定义的数据集 _init_ _len_ _getitem_ 准备数据并使用 DataLoader 进行训练 遍历 DataLoader 转换 ToTensor() Lambda 转换 (核心内容)自动微分(TORCH.AUTOGRAD) 张量、函数、计算图 梯度计算 禁用梯度追踪 计算图部分扩展阅读 可选阅读:张量梯度 和 雅阁比积(Jacobian_Products) (重要)优化模型参数 先决条件代码 超参数 优化循环 全过程数据跟踪 保存、加载和使用模型 保存/加载模型权重 通过模型的形状参数进行保存/加载 在 PyTorch 中保存和加载常规 Checkpoint 介绍 设置 步骤 1.导入加载数据所需要的库 2.定义和初始化神经网络 3.初始化优化器 4.保存常规检查点 5.加载常规检查点 从 Checkpoint 中加载 nn.Module 的技巧 活用 torch.load(mmap = True) 活用 torch.device("meta") 活用 load_state_dict(assign = True) 结论 Pytorch 基础 https://pytorch.org/tutorials/beginner/basics/intro.html 大多数机器学习工作流: 处理数据 创建模型 优化模型参数 保存训练后模型 通过 Pytorch 基础部分的内容,读者可以完整的走完一整个MachineLearning的工作流,若读者对其中某个环节不理解或感兴趣,针对这些工作流中的每一个环节都有相关的扩展阅读链接。 我们将使用 FashionMNIST 数据集训练一个神经网络,该神经网络预测输入图像是否属于一下类别之一:T恤/上衣、裤子、套头衫、连衣裙、外套、凉鞋、成山、运动鞋、包包、靴子。(是个多分类任务) 快速入门 https://pytorch.org/tutorials/beginner/basics/quickstart_tutorial.html 本节会快速走完一个机器学习多分类的Demo,以此快速了解流程中必要的基本ML相关API。 处理数据Pytorch 中有两个用于处理数据的子库 torch.utils.data.DataLoader 和 torch.utils.data.Dataset.Dataset。顾名思义,Dataset 存储样本及其相应的标签,并将 DataLoader 可迭代对象包装在 Dataset 中。 12345import torchfrom torch import nnfrom torch.utils.data import DataLoaderfrom torchvision import datasetsfrom torchvison.transforms import ToTensor 通过上面的引用(import),我们可以发现:Pytorch 中有非常多的子库,这些子库专注于某一特定的领域,例如: TorchText, TorchVision, 和 TorchAudio, 这些所有子库中都包含相应的数据集。 本次教程中我们使用 TorchVision 数据集。 该 torchvision,.datasets 模块包含 Dataset 来自现实世界中的视觉图像数据,最经典的有:CIFAT,COCO(full list here) 本次教程中我们使用 FashionMNIST 数据集。每个 TorchVison 下的 Dataset 都包含两个参数:transform 和 target_transform 分别用来修改样本与打标签。 123456789101112131415# 从公开数据集中读取训练数据training_data = datasets.FashionMNIST( root = "data", train = True, download = True, transform = ToTensor(),)# 从公开数据集中读取测试数据test_data = datasets.FashionMNIST( root = "data", train = False, download = True, transform = Totensor()) 下图是在 colab 中运行上面程序块的输出结果: 至此为止,通过上面的工作,我们将 Dataset 作为参数传递给了 DataLoader 。同时封装了相关数据集作为一个可迭代的对象,支持自动批处理、采样、洗牌(shuffling)、和多进程的数据加载。 下一步中,我们定义 batch_size = 64 ,即 dataloader 可迭代中的每个元素都将返回一批含有64个特征的标签。 12345678910batch_size = 64# 创建数据读取工具train_dataloader = DataLoader(tarain_data, batch_size = batch_size)test_dataloader = DataLoader(tast_data, batch_size = batch_size)for X, y in dataloader: print(f"Shape of X [N, C, H, W]: {X.shape}") print(f"Shape of y []: {y.shape} {y.dtpye}") break 输出: 12Shape of X [N, C, H, W]: torch.Size([64, 1, 28, 28])Shape of y: torch.Size([64]) torch.int64 关于 loading data in PyTorch 的详细说明 构建模型为了在 Pytorch 中定义神经网络,我们创建一个继承自 nn.Module 的类。 我们通过 __init__ 函数定义神经网络的层,并指明数据如何通过 forward 函数进入神经网络层。 在设备允许的情况下,推荐使用GPU来加速神经网络的运算操作。 代码实现: 123456789101112131415161718192021222324252627282930# 获取用于训练的设备(cpu/gpu/mps)device = ( "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu")print(f"Using {device} device")# 自定义神经网络的模型结构class NeuralNetwork(nn.Module): def __init__(self): super().__init__() self.flatten = nn.Flatten() self.linear_relu_stack = nn.Sequential( nn.Linear(28*28, 512), nn.ReLU(), nn.Linear(512, 512), nn.ReLU(), nn.Linear(512, 10) ) def forward(self, x): x = self.flatten(x) logits = self.linear_relu_stack(x) return logitsmodel = NeuralNetwork().to(device)print(model) 打印结果: 1234567891011Using cpu deviceNeuralNetwork( (flatten): Flatten(start_dim=1, end_dim=-1) (linear_relu_stack): Sequential( (0): Linear(in_features=784, out_features=512, bias=True) (1): ReLU() (2): Linear(in_features=512, out_features=512, bias=True) (3): ReLU() (4): Linear(in_features=512, out_features=10, bias=True) )) 在 PyTorch 中构建神经网络 这一部分是对‘构建模型’部分的一点补充说明,也是 PyTorch 官网教程中的扩展阅读部分 神经网络是由多个对数据进行操作的层/模型组合而成的。torch.nn 命名空间几乎已经提供了构建一个神经网络所需要用到的所有模块。 所有模块都在 PyTorch 下的子块 nn.Module 中提供。 基于这样的结构化嵌套模块,整个神经网络可以自由的进行构建和管理复杂的架构。 在上面的代码块中,我们通过 NeuralNetwork 函数定义了一个神经网络模型 model。 为了使用该模型,我们将输入数据传递给它。这个操作将执行 forward 操作和一些后台操作。 请记住:不要直接使用 model.forward() ! 通过输入操作调用模型,最后将返回一个二维张量,其中 dim = 0 对应于每个类别的 10 个原始预测输出,dim = 1 对应与每个输出的单个值。 我们可以通过 nn.Softmax 模块实例传递对结果预测的概率来进行最终预测概率的判断。 12345X = torch.rand(1, 28, 28, device = device)logits = model(X)pred_probab = nn.Softmax(dim = 1)(logits)y_pred = pred_probab.argmax(1)print(f"Predicted class: {y_pred}") 打印结果: 1Predicted class: tensor([7], device = 'cuda:0') 模型层 Model Layers分解 FashionMNIST 模型中的各层。为了说明这一点,我们通过获取一个包含 3 张大小为 28*28 的小批量图像样本,看看当数据传递到网络时会发生什么。 12input_image = torch.rand(3, 28, 28)print(input_image.size()) 打印输出: 1torch.Size([3, 28, 28]) nn.Flatten初始化 nn.Flatten 层,将每个2D 28*28 图像转换成包含 784 个像素值的连续数组(保持小批量尺寸(dim = 0)) 123flatten = nn.Flatten()flat_image = flatten(input_image)print(flat_image.size()) 打印输出: 1torch.Size([3, 784]) nn.Linear线性层模块通过输入的权重w和偏差值b进行线性变换。 123layer1 = nn.Linear(in_features = 28*28, out_features = 20)hidden1 = layer1(flat_image)print(hidden1.size()) 打印输出: 1torch.Size([3, 20]) nn.ReLU非线性激活函数可以在模型的输入输出之间创建复杂的映射关系。激活函数通过引入非线性的变换帮助神经网络学习各种现象。 在实例模型中,我们在线性层之间使用ReLU激活函数。但还有其他激活函数可以在模型的线性层中间作为激活函数使用,详情参考:激活函数-wiki 123print(f"Before ReLU: {hidden1}\\n\\n")hidden1 = nn.ReLU()(hidden1)print(f"Aftr RelU: {hidden1}") 打印输出: 1234567891011121314151617181920Before ReLU: tensor([[ 0.4158, -0.0130, -0.1144, 0.3960, 0.1476, -0.0690, -0.0269, 0.2690, 0.1353, 0.1975, 0.4484, 0.0753, 0.4455, 0.5321, -0.1692, 0.4504, 0.2476, -0.1787, -0.2754, 0.2462], [ 0.2326, 0.0623, -0.2984, 0.2878, 0.2767, -0.5434, -0.5051, 0.4339, 0.0302, 0.1634, 0.5649, -0.0055, 0.2025, 0.4473, -0.2333, 0.6611, 0.1883, -0.1250, 0.0820, 0.2778], [ 0.3325, 0.2654, 0.1091, 0.0651, 0.3425, -0.3880, -0.0152, 0.2298, 0.3872, 0.0342, 0.8503, 0.0937, 0.1796, 0.5007, -0.1897, 0.4030, 0.1189, -0.3237, 0.2048, 0.4343]], grad_fn=<AddmmBackward0>)After ReLU: tensor([[0.4158, 0.0000, 0.0000, 0.3960, 0.1476, 0.0000, 0.0000, 0.2690, 0.1353, 0.1975, 0.4484, 0.0753, 0.4455, 0.5321, 0.0000, 0.4504, 0.2476, 0.0000, 0.0000, 0.2462], [0.2326, 0.0623, 0.0000, 0.2878, 0.2767, 0.0000, 0.0000, 0.4339, 0.0302, 0.1634, 0.5649, 0.0000, 0.2025, 0.4473, 0.0000, 0.6611, 0.1883, 0.0000, 0.0820, 0.2778], [0.3325, 0.2654, 0.1091, 0.0651, 0.3425, 0.0000, 0.0000, 0.2298, 0.3872, 0.0342, 0.8503, 0.0937, 0.1796, 0.5007, 0.0000, 0.4030, 0.1189, 0.0000, 0.2048, 0.4343]], grad_fn=<ReluBackward0>) nn.Sequentialnn.Sequential是一个有序的模块容器。数据按照定义好的方式顺序的通过当前模块。您可以使用顺序容器来组合一个“快捷网络” ,例如:seq_modules . 12345678seq_modules = nn.Sequential( flatten, layer1, nn.ReLU(), nn.Linear(20, 10))input_image = torch.rand(3, 28, 28)logits = seq_modules(input_image) nn.SoftmaxSoftmax激活函数通常用在最后一个线性层,用来返回对数区间介于 [-infty, infty] 中的原始值,这些值最终被传递给 nn.Softmax 模块。 Softmax 激活函数将对应输出区间范围缩放在 [0, 1] 之间,表示模型对每个类别的预测概率。其中,dim 中所有参数指示值求和应该为 1 。 12softmax = nn.Softmax(dim = 1)pred_probab = softmax(logits) 模型的参数神经网络往往非常的复杂,在整个网络的构建过程中,如果可以便捷的将每个部分表示出来,对于训练过程中的优化和修改相对的权重与偏差等都会有非常大的帮助。 子类化 nn.Module 模块可以帮助我们解决这个问题,该模块会自动跟踪模型对象中定义的所有字段,并使用模型的 parameters() 函数或 named_parameters() 函数方法访问所有参数。 在本次示例中,我们遍历每个参数,并预览它们的所有数值参数。 1234print(f"Model structure: {model}\\n\\n")for name, param in model.named_parameters(): print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \\n") 打印输出结果: 1234567891011121314151617181920212223242526272829Model structure: NeuralNetwork( (flatten): Flatten(start_dim=1, end_dim=-1) (linear_relu_stack): Sequential( (0): Linear(in_features=784, out_features=512, bias=True) (1): ReLU() (2): Linear(in_features=512, out_features=512, bias=True) (3): ReLU() (4): Linear(in_features=512, out_features=10, bias=True) ))Layer: linear_relu_stack.0.weight | Size: torch.Size([512, 784]) | Values : tensor([[ 0.0273, 0.0296, -0.0084, ..., -0.0142, 0.0093, 0.0135], [-0.0188, -0.0354, 0.0187, ..., -0.0106, -0.0001, 0.0115]], device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values : tensor([-0.0155, -0.0327], device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.2.weight | Size: torch.Size([512, 512]) | Values : tensor([[ 0.0116, 0.0293, -0.0280, ..., 0.0334, -0.0078, 0.0298], [ 0.0095, 0.0038, 0.0009, ..., -0.0365, -0.0011, -0.0221]], device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.2.bias | Size: torch.Size([512]) | Values : tensor([ 0.0148, -0.0256], device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.4.weight | Size: torch.Size([10, 512]) | Values : tensor([[-0.0147, -0.0229, 0.0180, ..., -0.0013, 0.0177, 0.0070], [-0.0202, -0.0417, -0.0279, ..., -0.0441, 0.0185, -0.0268]], device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values : tensor([ 0.0070, -0.0411], device='cuda:0', grad_fn=<SliceBackward0>) 张量 Tensor通过一张图初步了解常见的多维空间数据的命名方式(来源:PyTorch in 100 Seconds) Tensor 又称 张量,是一种专门的数据结构,与数组和矩阵非常相似。在 PyTorch 中,我们使用张量对比模型的输入和输出以及模型的参数进行编码。 Tensors 类似于 NumPy 中的ndarrays,不同之处在于张量可以在 GPU 或其他硬件加速器上运行。事实上,张量和 NumPy 数组通常可以共享相同的底层内存,从而消除了复制数据的需要。 张量也针对自动微分进行了优化。 12import torchimport numpy as np 初始化张量一般可以通过如下方式初始化 Tensor: 直接通过数据创建 通过NumPy创建 通过继承另一个Tensor的形状和数据类型 使用随机值或常量 下面分别进行介绍: 直接来自数据张量可以直接从已有的数据中创建,数据类型是自动推断的。 12data = [[1, 2], [3, 4]]x_data = torch.tensor(data) 来自另一个 Tensor新建的张量保留参考张量的部分参数(形状,数据类型),除非用显式的方式直接覆盖。 12345x_ones = torch.ones_like(x_data) # retains the properties of x_dataprint(f"Ones Tensor: \\n {x_ones} \\n")x_rand = torch.rand_like(x_data, dtype = torch.float) # overrides the datatype of x_dataprint(f"Random Tensor: \\n {x_rand} \\n") 打印输出: 1234567Ones Tensor: tensor([[1, 1], [1, 1]])Random Tensor: tensor([[0.8823, 0.9150], [0.3829, 0.9593]]) 使用随机值或常量shape 是张量维度的元组表达式。在下面的函数中,它决定了输出张量的维度: 123456789# 定义 Tensor 的维度shape = (2, 3, )rand_tensor = torch.rand(shape)ones_tensor = torch.ones(shape)zeros_tensor = torch.zeros(shape)print(f"Random Tensor: \\n {rand_tensor} \\n")print(f"Ones_Tensor: \\n {ones_tensor} \\n")print(f"Zeros_Tensor: \\n {zeros_tensor}") 打印输出结果: 1234567891011Random Tensor: tensor([[0.3904, 0.6009, 0.2566], [0.7936, 0.9408, 0.1332]])Ones Tensor: tensor([[1., 1., 1.], [1., 1., 1.]])Zeros Tensor: tensor([[0., 0., 0.], [0., 0., 0.]]) 张量的属性Tensor 的属性描述了它们的形状、数据类型 和 存储它们的设备。 tensor.shape tensor.dtype tensor.device 12345tensor = torch.rand(3, 4)print(f"Shape of tensor: {tensor.shape}")print(f"Dtype of tensor: {tensor.dtype}")print(f"Device of tensor: {tensor.device}") 打印输出: 123Shape of tensor: torch.Size([3, 4])Datatype of tensor: torch.float32Device tensor is stored on: cpu 张量的操作科学计算是深度学习领域的根本!PyTorch提供了 100+ 张量运算操作,包括算术运算、线性代数运算、矩阵运算(转职、索引、切片)、采样等。 PyTorch 中的所有逻辑运算都可以通过GPU进行加速运算 123# 将运行设备选择为 GPU (如果你有的话)if torch.cuda.is_available(): tensor = tensor.to("cuda") 类似 NumPy 的索引和切片操作123456Tensor= torch.ones(4, 4)print(f"First row: {tensor[0]}")print(f"First column: {tensor[:, 0]}")print(f"last column: {tensor[..., -1]}")tensor[:, 1] = 0print(tensor) 打印输出: 1234567First row: tensor([1., 1., 1., 1.])First column: tensor([1., 1., 1., 1])Last column: tensor([1., 1., 1., 1])tensor([[1., 0., 1., 1.], [1., 0., 1., 1.], [1., 0., 1., 1.], [1., 0., 1., 1.]]) Tensor 之间的连接torch.cat 可以用于连接指定维度的张量,拥有同样功能的另一个算子是 torch.stack_ (参考:torch.stack_)。 12t1 = torch.cat([tensor, tensor, tensor], dim = 1)print(t1) 张量的算术运算1234567891011121314# 计算两个张量之间的矩阵乘法。其中,y1, y2, y3 拥有相同的参数值# `tensor.T` 返回张量的转置y1 = tensor @ tensor.Ty2 = tensor.matmul(tensor.T)y3 = torch.rand_like(y1)torch.matmul(tensor, tensor.T, out = y3)# 将张量中的元素逐个相乘。z1, z2, z3 具有相同的值z1 = tensor * tensorz2 = tensor.mul(tensor)z3 = torch.rand_like(tensor)torch.mul(tensor, tensor, out = z3) 打印输出: 1234tensor([[1., 0., 1., 1], [1., 0., 1., 1], [1., 0., 1., 1], [1., 0., 1., 1]]) 单一元素张量如果你有一个单一元素的张量,例如希望通过将张量的所有值聚合为一个值,那么可以使用 item() 将其转换为 python 数值: 123agg = tensor.sum()agg_item = agg.item()print(agg_item, type(agg_item)) 打印输出结果: 112.0 <class 'float'> 原地操作(不通过返回的方式进行原地修改)将结果存储在操作数中的操作成为原地操作。它们由 _ 后缀表示。例如:x.copy() 、 x.t_() ,都将直接修改 x。 123print(f"{tensor} \\n")tensor.add_(5)print(tensor) 打印输出: 123456789tensor([[1., 0., 1., 1.], [1., 0., 1., 1.], [1., 0., 1., 1.], [1., 0., 1., 1.]])tensor([[6., 5., 6., 6.], [6., 5., 6., 6.], [6., 5., 6., 6.], [6., 5., 6., 6.]]) Tensor 和 Numpy 二者转换位于 CPU 位置上的 Numpy 数组与 Tensor 可以共享同一个底层的内容空间,更改一个 tensor 会同时修改另一个 tensor 。 Tensor2NumPy_array1234t = torch.ones(5)print(f"t: {t}")n = t.numpy()print(f"n: {n}") 打印对比结果(t 代表 tensor;n 代表 numpy): 12t: tensor([1., 1., 1., 1., 1.])n: [1. 1. 1. 1. 1.] 下一步,改变 tensor 中的值,同时观察 NumPy 中值的变化: 123t.add_(1)print(f"t: {t}")print(f"n: (n)") 打印对比结果(t 代表 tensor;n 代表 numpy): 12t: tensor([2., 2., 2., 2., 2.])n: [2. 2. 2. 2. 2.] NumPy_array2Tensor12n = np.ones(5)t = torch.from_numpy(n) NumPy 数组中的更改回反映在张量(tensor)中 123np.add(n, 1, out = n)print(f"t: {t}")print(f"n: {n}") 12t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64)n: [2. 2. 2. 2. 2.] 数据集的相关操作从代码的架构设计上考虑,无论是出于可读性考虑还是出于代码逻辑的模块化管理考虑,我们都希望数据集代码与模型训练代码分离。 在数据预加载上,PyTorch 提供了两个功能函数 torch.utils.data.DataLoader 和 torch.utils.data.Dataset 分别读取预加载的数据和自己的数据。 Dataset 存储样本和对应的标签,并在 DataLoader 范围内包装成一个可迭代对象 Dataset 以便轻松访问样本。 在 PyTorch 函数库中预先提供好了很多可供预加载的数据集(例如:FashionMNIST),这些数据集借助 torch.utils.data.Dataset 子类化,并实现位于特定数据的函数。它们可用于对模型进行原型设计和基准测试。可以通过如下链接访问:Image Datasets,Text Datasets,Audio Datasets 数据加载下面例子是从 TorchVision 加载 Fashion-MNIST 数据集的示例。Fashion-MNIST 由 60000 个训练样本和 10000 个测试样本组成。每个示例都包含一个 28*28 灰度图像和一个来自 10 个类之一的关联标签。 我们通过如下几个参数,对 FashionMNIST Dataset 数据集进行加载: root 是存储训练/测试数据的根目录 train 指定训练或测试数据集 download = TRUE 允许从互联网上搜索并下载数据集,前提是 root 路径下的数据集文件不存在 transform 和 target_transform 分别执行 标定特征 和 标注转换 12345678910111213141516171819import torchfrom torch.utils.data import Datasetfrom torchvision import datasetsfrom torchvision.transforms import ToTensorimport matplotlib.pyplot as plttraining_data = datasets.FashionMNIST( root = "data", train = True, download = True, transform = ToTensor())test_data = datasets.FashionMNIST( root = "data", train = False, download = True, transform = ToTensor()) 数据集迭代和数据可视化使用 Datasets ,我们可以实现像 Python 中列表那样的手动索引 training_data[index]。 使用 matplotlib 可视化训练数据集中的样本进行展示。 1234567891011121314151617181920212223labels_map = { 0: "T-Shirt", 1: "Trouser", 2: "Pullover", 3: "Dress", 4: "Coat", 5: "Sandal", 6: "Shirt", 7: "Sneaker", 8: "Bag", 9: "Ankle Boot",}figure = plt.figure(figsize = (8, 8))cols, rows = 4, 4for i in range(1, cols * rows + 1): sample_idx = torch.randint(len(training_data), size(1,)).item() image, lable = training_data[sample_idx] figure.add_subplot(rows, cols, i) plt.title(lables_map[label]) plt.axis("off") plt.imshow(img.squeeze(), cmap = "gray")plt.show() # 打印出来的输出为数据集图像示例展示 打印输出: 创建自定义的数据集自定义数据集必须含有三个函数: __init__ __len__ __gititem__ 通过下面这个示例实现了解相关函数的使用方法。 FashionMNIST 图像存储在目录 img_dir,其自身的标签单独存储在CSV文件 annotations_file 中 先看代码块部分: 1234567891011121314151617181920212223import osimport pandas as pdimport torchvision.io as read_imageclass CustomImageDataset(Dataset): def __init__(self, annotations_file, img_dir, transform = None, target_transform = None): self.img_labels = pd.read_csv(annotations_file) self.img_dir = img_dir self.transform = transform self.target_transform = target_transform def __len__(self): return len(self.img_labels) def __getitem__(self): img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0]) image = read_image(img_path) label = self.img_labels.iloc[idx, 1] if self.transform: image = self.transform(image) if self.target_transform: label = self.target_transform(label) return image, label _init_init 作为初始化函数,在实例化 Dataset 对象时运行一次。 我们初始化包含图像、注释文件和两个转换的目录 labels.csv 文件的展示效果如下所示: 1234tshirt1.jpg, 0tshirt2.jpg, 0......ankleboot999.jpg, 9 12345def __init__(self, annotations_file, img_dir, transform = None, target_transform = None): self.img_labels = pd.read_csv(annotations_file) self.img_dir = img_dir self.transform = transform self.target_transform = target_transform _len_len 函数返回数据集中的样本数。 12def __len__(self): return len(self.img_labels) _getitem_getitem 函数从给定索引目录的 idx 处返回数据集中的样本。 根据索引,它识别图像在磁盘上的位置,使用 read_image,从 csv 数据中检索相应的标签self.img_labels,调用它们的转换函数(前提是支持转换),并在元组中返回张量图像和相应的标签。 123456789def __getitem__(self): img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0]) image = read_image(img_path) label = self.img_labels.iloc[idx, 1] if self.transform: image = self.transform(image) if self.target_transform: label = self.target_transform(label) return image, label 准备数据并使用 DataLoader 进行训练检索 Dataset 数据集的特征,并一次标记一个样本。 在训练模型的过程中,我们通常希望通过“小批量”的方式传递样本,在新一轮 epoch 下数据 reshuffle(洗牌) 减少模型过拟合,并使用 Python 中的 multiprocessing 函数来加快数据检索的速度。 DataLoader 是一个可迭代的对象,它通过一个简单的 API 为我们抽象了这种复杂性。 1234from torch.utils.data import DataLoadertrain_dataloader = DataLoader(training_data, batch_size = 64, shuffle = True)test_dataloader = DataLoader(test_data, batch_size = 64, shuffle = True) 遍历 DataLoader通过上面的步骤,我们已经将数据集加载到了 DataLoader 并可以根据我们的需要来遍历该数据集。 后续程序中每一次迭代都会返回一批 train_features 和 train_labels,每批结果中都包含 batch_size = 64 特征和标签。 在上面代码块中,我们指定了 shuffle = True ,在我们遍历了所有批次后,数据会被洗牌(目的是为了更细粒度地控制数据加载顺序,可参考Samplers) 123456789# Display image and label.train_features, train_labels = next(iter(train_dataloader))print(f"Feature batch shape: {train_features.size()}")print(f"Labels batch shape: {train_labels.size()}")img = train_features[0].squeeze()label = train_labels[0]plt.imshow(img, cmap = "gray")plt.show() # 输出数据集中的图片print(f"Label: {label}") 转换数据的格式不总是按照训练机器学习算法所需要的格式出现的,因此我们需要通过转换来对数据进行一系列操作使得其适合于机器学习任务。 所有 TorchVision 数据集都具有两个参数: transform 用于修改标签 target_transform 接受包含转换逻辑的可调用对象 在 torchvision.transforms 中提供了几个开箱即用的转换格式。 FashionMNIST 特征采用 PIL Image 格式,标签为整数。 在训练任务开始前,我们需要将特征处理为归一化之后的张量。 为了进行这些转换,需要使用 ToTensor 和 Lambda 函数方法。 1234567891011import torchfrom torchvision import datasetsfrom torchvision.transforms import ToTensor, Lambdads = datasets.FashionMNIST( root = "data", train = True, download = True, transform = ToTensor(), target_transform = Lambda(lambda y: torch.zero(10, dtype = torch.float).scatter_(0, torch.tensor(y), value = 1))) 打印输出: 123456789101112131415161718192021222324252627282930313233343536373839404142434445Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gzDownloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to data/FashionMNIST/raw/train-images-idx3-ubyte.gz 0%| | 0/26421880 [00:00<?, ?it/s] 0%| | 65536/26421880 [00:00<01:12, 362364.70it/s] 1%| | 229376/26421880 [00:00<00:38, 680532.51it/s] 3%|2 | 786432/26421880 [00:00<00:11, 2194389.90it/s] 7%|7 | 1933312/26421880 [00:00<00:05, 4185622.75it/s] 17%|#6 | 4423680/26421880 [00:00<00:02, 9599067.02it/s] 25%|##5 | 6717440/26421880 [00:00<00:01, 11175748.57it/s] 34%|###4 | 9109504/26421880 [00:01<00:01, 14174360.51it/s] 44%|####3 | 11567104/26421880 [00:01<00:01, 14358310.56it/s] 53%|#####2 | 13959168/26421880 [00:01<00:00, 16463421.66it/s] 62%|######2 | 16449536/26421880 [00:01<00:00, 15864345.49it/s] 71%|#######1 | 18776064/26421880 [00:01<00:00, 17449238.29it/s] 81%|######## | 21397504/26421880 [00:01<00:00, 16758523.84it/s] 90%|########9 | 23691264/26421880 [00:01<00:00, 18055860.39it/s]100%|##########| 26421880/26421880 [00:01<00:00, 13728491.83it/s]Extracting data/FashionMNIST/raw/train-images-idx3-ubyte.gz to data/FashionMNIST/rawDownloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gzDownloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to data/FashionMNIST/raw/train-labels-idx1-ubyte.gz 0%| | 0/29515 [00:00<?, ?it/s]100%|##########| 29515/29515 [00:00<00:00, 327895.25it/s]Extracting data/FashionMNIST/raw/train-labels-idx1-ubyte.gz to data/FashionMNIST/rawDownloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gzDownloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz to data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz 0%| | 0/4422102 [00:00<?, ?it/s] 1%|1 | 65536/4422102 [00:00<00:11, 363267.41it/s] 5%|5 | 229376/4422102 [00:00<00:06, 683985.17it/s] 19%|#8 | 819200/4422102 [00:00<00:01, 2304448.10it/s] 33%|###3 | 1474560/4422102 [00:00<00:00, 2999709.36it/s] 83%|########2 | 3670016/4422102 [00:00<00:00, 7976134.77it/s]100%|##########| 4422102/4422102 [00:00<00:00, 5985529.02it/s]Extracting data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz to data/FashionMNIST/rawDownloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gzDownloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz 0%| | 0/5148 [00:00<?, ?it/s]100%|##########| 5148/5148 [00:00<00:00, 39473998.16it/s]Extracting data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw ToTensor()ToTensor 将 PIL 图像 或 NumPy ndarray 转换为 FloatTensor. 并在[0., 1.] 范围内缩放图像的像素空间。 Lambda 转换Lambda 函数允许任意用户定义 lambda 函数。在这里,我们定义了一个函数,将整数转换为 one-hot 编码的张量。 Lambda首先创建一个大小为10(我们数据集中的标签数量)的零向量,并调用 scatter_ 函数在索引 y 上分配标签 value = 1。 12target_transform = Lambda(lambda y:torch.zeros( 10, dtype = torch.float).scatter_(dim = 0, index = torch.tensor(y), value = 1)) 阅读延伸:torchvision.transforms API (核心内容)自动微分(TORCH.AUTOGRAD)在训练神经网络时,常用的算法是反向传播。在该算法中,参数(模型权重)根据损失函数相对于给定参数的梯度进行调整。 为了计算这些梯度,PyTorch 有一个内置的用于自动计算微分方程的引擎,称为 torch.autograd . 它支持任何计算图的梯度自动运算。 下面距离一个最简单的单层神经网络,其中包含输入 x、参数 w 和 b,以及一些损失函数。可以在 PyTorch 中按以下方式定义它: 12345678import torchx = torch.ones(5) # input tensory = torch.zeros(3) # expected outputw = torch.randn(5, 3, requires_grad = True)b = torch.randn(3, requires_grad = True)z = torch.matmul(x, w) + bloss = torch.nn.functional.binary_cross_entropy_with_logits(z, y) 张量、函数、计算图上一小节的代码框中实现的损失函数计算流程如下图所示: 在上面网络中,w 和 b 是我们需要优化的参数。因此,我们需要能够计算这些变量损失函数的梯度。 为了做到这些点,我们设置了这些张量的 requires_grad 函数来定义其属性。 设置张量的值有两种方式:一种是在生成张量的时候使用 requires_grad 进行初始化设置;另一种是在后续使用 x.requires_grad_(True) 函数。 用来构造计算图的函数实际上是类 Function 的对象。该对象指导如何计算正向函数,以及如何在反向传播步骤中计算其导数。对向后传播函数的引起存储在张量的属性中的 grad_fn。您可以在PyTorch的官方文档中找到更多信息 Function。 12print(f"Gradient function for z = {z.grad_fn}")print(f"Gradient function for loss = {loss.grad_fn}") 打印输出: 12Gradient function for z = <AddBackward0 object at 0x7d800ac85840>Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward0 object at 0x7d800ac85ea0> 梯度计算为了优化神经网络中的参数权重,我们需要计算损失函数相对于参数的导数,即我们需要在固定 x 和 y 值的情况下,求出 $\\frac{\\partial loss}{\\partial w}$ 和 $\\frac{\\partial loss}{\\partial b}$。 为了求出上面的导数,我们需要调用函数 loss.backward(),然后从 w.grad 和 b.grad 中检索权重和偏置的数值。 123loss.backward()print(w.grad)print(b.grad) 打印输出: 123456tensor([[0.3287, 0.0101, 0.0988], [0.3287, 0.0101, 0.0988], [0.3287, 0.0101, 0.0988], [0.3287, 0.0101, 0.0988], [0.3287, 0.0101, 0.0988]])tensor([0.3287, 0.0101, 0.0988]) 可以通过将参数 requires_grad 的 grad 属性设置为 True 来或测计算图的叶节点属性。对于计算图中的所有其他节点,梯度将不可用。 PyTorch考虑性能上的原因,在给定图形上只能调用 backward 调用一次梯度计算。如果我们需要再同一个图上执行多个 backward 调用,我们需要参数传递给 retain_graph = True,再调用 backward 。 禁用梯度追踪默认情况下,所有张量都在 require_grad = True 跟踪其计算历史并支持梯度计算。但是,在某些情况下,只想将模型应用于默写输入数据时,即我们只想通过网络进行前向计算。我们可以通过将计算代码加上 torch.no_grad() 还书,用来停止跟踪计算。 123456z = torch.matmul(x, w) +bprint(z.requires_grad) # Truewith torch.no_grad(): z = torch.matmul(x, w) + bprint(z.requires_grad) # False 另一种方法是在张量上使用 detach() 方法: 123z = torch.matmul(x, w) + bz_det = z.detach()print(z_det.requires_grad) # False 考虑要禁用梯度跟踪的情况: 将神经网络中的某些参数标记为冻结参数 为了优化计算速度,在只进行前向传播时禁用梯度跟踪的选项 计算图部分扩展阅读(留白,前面的区域以后再探索吧~) 可选阅读:张量梯度 和 雅阁比积(Jacobian_Products)(留白,前面的区域以后再探索吧~) (重要)优化模型参数在拥有了模型和数据之后,就可以通过优化数据参数来训练、验证和测试我们的模型了。训练模型是一个带带过程,每次迭代中,模型都会对输出进行*猜测*,计算其猜测中的误差,计算其猜测中的误差(损失),收集误差对参数的导数(上一节中进行的工作),并使用梯度下降这些参数。 更详细的视频讲解,可以参考 backpropagation from 3Blue1Brown 先决条件代码超参数优化循环全过程数据跟踪保存、加载和使用模型保存/加载模型权重通过模型的形状参数进行保存/加载在 PyTorch 中保存和加载常规 Checkpoint介绍设置步骤1.导入加载数据所需要的库2.定义和初始化神经网络3.初始化优化器4.保存常规检查点5.加载常规检查点从 Checkpoint 中加载 nn.Module 的技巧活用 torch.load(mmap = True)活用 torch.device("meta")活用 load_state_dict(assign = True)结论","categories":[{"name":"Deeplearning","slug":"Deeplearning","permalink":"https://zade23.github.io/categories/Deeplearning/"}],"tags":[{"name":"PyTorch","slug":"PyTorch","permalink":"https://zade23.github.io/tags/PyTorch/"}]},{"title":"工具网站","slug":"工具网站","date":"2023-10-18T09:04:52.000Z","updated":"2023-10-18T09:17:18.908Z","comments":true,"path":"2023/10/18/工具网站/","link":"","permalink":"https://zade23.github.io/2023/10/18/%E5%B7%A5%E5%85%B7%E7%BD%91%E7%AB%99/","excerpt":"","text":"内容分类 学习资源网站 aigc llm tts rl diffusion huggingface awesome 数学基础 机器学习 深度学习/模型训练 抽象概念可视化 传统算法 汇编语言 电子书 工具网站 在线大语言模型 在线api 数据集 音频处理 视频处理 图像处理 设计 下载工具 格式压缩 内容分类学习资源网站aigc 【关于AIGC的各种精选教程和资源,既适合初学者也适合进阶AI爱好者】github.com/luban-agi/Awesome-AIGC-Tutorials/blob/main/README_zh.md llm 【收集各种垂直领域的大语言模型】Awesome Domain LLM 【关于大型语言模型(LLM)的一切,包括了LLMs的入门、微调方法、多模态模型、稳定扩散、注意力机制优化和数据效率等方面的信息,从nanoGPT到LoRA、QLoRA、RLHF到CLIP等多模态模型】Everything-about-LLMs 【提示工程实例:用实际项目学习提示工程技术,从大型语言模型获得更好的结果,内容涵盖零样本和少样本提示,分隔符,步骤编号,角色提示,思维链(CoT)提示等】 《Prompt Engineering: A Practical Example – Real Python》 tts 【如何在浏览器中处理音视频】Web 音视频系列 【基于华为诺亚方舟实验室Grad-TTS的Grad-SVC】https://github.com/PlayVoice/Grad-SVC 【台大课程《深度学习音乐分析与生成》资料】 github.com/affige/DeepMIR 【大型音频模型相关文献资源列表】 github.com/EmulationAI/awesome-large-audio-models rl 【Generative Agents with Llama2】https://github.com/rlancemartin/generative_agents diffusion 【扩散模型相关论文资源列表,涵盖了文本到视频生成、文本引导视频编辑、个性化视频生成、视频预测等方面】 github.com/ChenHsing/Awesome-Video-Diffusion-Models 【3D Diffusion相关文献列表】 github.com/cwchenwang/awesome-3d-diffusion huggingface 【Hugging Face - Learn】https://huggingface.co/learn awesome 【收集各种生成式 AI 的教程】Awesome AIGC Tutorials 数学基础 【给程序员的线性代数指南】Linear Algebra for programmers 机器学习 【ML Papers Explained:机器学习论文解析】 github.com/dair-ai/ML-Papers-Explained 深度学习/模型训练 【Batched LoRAs:通过同一批次的多个 LoRA 路由推理,最大化 GPU 利用率】Batched LoRAs - batched 抽象概念可视化 【机器学习MachineLearning】www.r2d3.us 【波Waveforms】Let’s Learn About Waveforms 传统算法 【开源算法库Algorithms】The Algorithms 汇编语言 Python 【Python官方文档】Python语法官方文档 【从Python内置函数理解Python】Understanding all of Python, through its builtins 【GraphLearn-for-PyTorch(GLT):PyTorch图学习库,使分布式 GNN 训练和推理变得简单高效】’GraphLearn-for-PyTorch(GLT) A graph learning library for PyTorch that makes distributed GNN training and inference easy and efficient C++ 【C++在线学习】Learn C++ – Skill up with our free tutorials 电子书 (图书馆)【zlibrary】Z-Library – the world’s largest e-book library. Your gateway to knowledge and culture. 【免费书《人工智能:计算Agent基础,(第三版)》】《Artificial Intelligence: Foundations of Computational Agents, 3rd Edition》David L. Poole and Alan K. Mackworth (2023) 【配有动画的数据结构与算法电子书】Hello 算法 工具网站在线大语言模型 百度(文心一言) 抖音(云雀大模型) 智谱AI(GLM大模型) 中科院(紫东太初大模型) 百川智能(百川大模型) ChatGPT Poe 讯飞星火 谷歌bard 阿里通义千问 在线api 【收集各种 AI 工具和资源】AIHub 【免费AI API列表】free-ai-apis 【作文批改:使用GPT4对雅思托福作文判分和批改】https://www.essay.art/ 数据集 【OpenDataLab 为国产大模型提供高质量的开放数据集】OpenDataLab 音频处理 【Whisper:英语音频转成文本的在线工具】Whisper Web 视频处理 【Spikes Studio】https://spikes.studio/ 图像处理 【在线体验-图像分割算法Meta-SegementAnything】Segment Anything 【SDXL在线体验】StableDiffusion XL 体验站 【nutsh:旨在通过人工反馈进行视觉学习的平台,具有用户友好的界面和 API,支持一系列视觉模式、多样化的人工输入方法以及基于人工反馈的学习机制】Nutsh 设计 【中文的图标搜索引擎,作者利用 ChatGPT 翻译了 Iconify 的 18 万个图标名】yesicon 【JupyterCAD - 用于3D几何建模的JupyterLab扩展】https://github.com/QuantStack/jupytercad 【图标搜索引擎收入了10万+的图标】Iconbuddy — 180K+ open source icons 【AI漫画|可选择漫画风格和页面布局】AI Comic Factory - a Hugging Face Space by jbilcke-hf 【Figma在线版】Figma 下载工具 【YouTube视频解析下载 】snapsave.io 【bilibili视频下载】bili.iiilab 格式压缩 【图片压缩软件】Topspeed Image Compressor 在线压缩","categories":[],"tags":[{"name":"工具网站","slug":"工具网站","permalink":"https://zade23.github.io/tags/%E5%B7%A5%E5%85%B7%E7%BD%91%E7%AB%99/"}]},{"title":"Python元组集合字典","slug":"Python元组集合字典","date":"2023-10-18T08:20:18.000Z","updated":"2024-03-14T02:59:36.156Z","comments":true,"path":"2023/10/18/Python元组集合字典/","link":"","permalink":"https://zade23.github.io/2023/10/18/Python%E5%85%83%E7%BB%84%E9%9B%86%E5%90%88%E5%AD%97%E5%85%B8/","excerpt":"","text":"1. 元组 1.1 元组的初始化 1.2 元组的解包 1.3 元组的其他操作 2. 集合 2.1 集合的初始化 2.2 集合的常用操作 2.3 使用for循环遍历集合 3. 字典 3.1 字典的初始化 3.2 字典的常用操作 3.3 使用for循环遍历字典 4. 作业扩展 1. 元组元组跟 列表 类似,只是不支持动态添加、删除元素,以及不能修改元素。 1.1 元组的初始化元组需要用小括号括起来,中间的元素用逗号隔开。 注意:如果初始化只包含一个元素的元组,需要在该元素后添加逗号。 1234567a = () # 初始化一个空元组b = (1, 2) # 含有2个整数的元组c = 6, "Python", 3.14 # 小括号可以省略,等价于(6, "Python", 3.14)d = (5,) # 注意不能写成(5),(5)表示整数5e = 5, # 等价于(5,)print(a, b, c, d, e) 1.2 元组的解包123t = 12345, 54321, "Hello!" # 初始化一个元组x, y, z = t # 将元组解包,将元组内的三个值按顺序赋值给x、y、zprint(x, y, z) 所以,判断语句中的交换操作,本质上是元组的解包: 12a, b = 3, 4 # 将元组(3, 4)解包,分别赋值给a、ba, b = b, a # 将元组(b, a)解包,分别赋值给a、b 同样地,函数中函数返回多个值,本质上也是返回了一个元组: 1234567def calc(x, y): return x + y, x * y # 等价于 return (x + y, x * y)x, y = 3, 4s, p = calc(x, y) # 将(x + y, x * y)解包,分别赋值给s、pprint(s, p) 1.3 元组的其他操作元组的下标访问元素、循环遍历、切片、加法和乘法运算等操作,都与列表相同。 2. 集合集合是Python中最常用的数据结构之一,用来存储不同元素。注意,集合中的元素是无序的。 2.1 集合的初始化创建集合用花括号或set()函数。注意:创建空集合只能用set(),不能用{},因为{}创建的是空字典,会在下一小节里介绍字典。 集合常见的初始化方式: 123456789101112131415basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'} # 会自动去除重复元素print(basket) # 重复的元素已经去除了s = set() # 初始化一个空列表print(s)a = [1, 2, 1, 3, 1]b = set(a) # 将列表转化成集合,一般是为了去重。c = list(b) # 将集合转化回列表print(b, c)x = "abracadabra"a = set(x) # 将字符串中的每个字符存到集合中b = str(a) # 注意,这里并不能将集合转化回原字符串,而是用格式化表示集合中的内容print(a, b) 2.2 集合的常用操作假设a表示一个集合。 12345len(a) 返回集合中包含的元素数量。a.add(x) 在集合中添加一个元素。a.remove(x) 删除集合中的x,如果x不存在,则报异常。a.discard(x) 删除集合中的x,如果x不存在,则不进行任何操作。x in a 判断x是否在a中。 例如: 1234567891011121314a = {1, 2, 3}print(len(a)) # 输出3a.add(4)print(a) # 输出 {1, 2, 3, 4},注意集合中的元素是无序的。a.remove(2)print(a) # 输出 {1, 3, 4}a.remove(5) # 因为5不存在,所以会报异常a.discard(5) # 因为5不存在,所以不进行任何操作print(a) # {1, 3, 4} 2.3 使用for循环遍历集合类似于列表,集合也可以用for ... in ...的形式遍历。例如: 1234a = {1, 2, 3}for x in a: # 循环遍历整个集合 print(x, end=' ') 3. 字典字典是Python中最常用的数据结构之一,用来存储映射关系。注意,字典中的元素是无序的。 不同于列表,字典是以key进行索引的,可以将每个key映射到某个value。key可以是任何不可变类型,常用可以作为key的类型有数字和字符串。列表因为是可变的,所以不能作为key。value可以是任意类型。 3.1 字典的初始化创建字典用花括号或dict()函数。 12345678910tel = {'jack': 4098, 'sape': 4139} # 创建一个字典print(tel) # 输出 {'jack': 4098, 'sape': 4139}a = dict() # 创建一个空字典a[123] = "abc" # 在字典中插入一个key-value对a[456] = "def" # 在字典中插入一个key-value对print(a) # 输出 {123: 'abc', 456: 'def'}b = list(a) # 将字典的关键字转化成列表print(b) # 输出[123, 456] 3.2 字典的常用操作假设a表示一个字典。 len(a):返回字典中的元素对数。 a[x]:获取关键字x对应的值,如果x不存在,会报异常。 a.get(x):获取关键字x对应的值,如果x不存在,会返回None,不会报异常。 a.get(x, y):获取关键字x对应的值,如果x不存在,会返回默认值y,不会报异常。 a[x] = y:在字典中插入一对元素,如果关键字x已存在,则将它之前映射的值覆盖掉。 del a[x]:删除关键字x对应的元素对,如果x不存在,会报异常。 x in a:检查字典中是否存在关键字x。 x not in a:检查字典中是否不存在关键字x。 a.keys():返回字典的所有key。 a.values():返回字典的所有value。 a.items():返回字典的所有由key和value组成的元组。 例如: 1234567891011121314151617181920a = {'abc': 1, 'def': 2, 'python': 3} # 初始化一个字典print(len(a)) # 输出3print(a['def']) # 输出2print(a.get('def')) # 输出2print(a.get('xyz', 5)) # 因为'xyz'不存在,所以输出默认值5a['hello'] = 4 # 插入一对元素 'hello' -> 4print(a) # 输出{'abc': 1, 'def': 2, 'python': 3, 'hello': 4}a['def'] = 5 # 更新'def'映射的值print(a['def']) # 输出5del a['python'] # 删除关键字'python'print(a) # 输出{'abc': 1, 'def': 5, 'hello': 4}print('hello' in a) # 输出Trueprint(a.keys()) # 输出dict_keys(['abc', 'def', 'hello'])print(a.values()) # 输出dict_values([1, 5, 4])print(a.items()) # 输出dict_items([('abc', 1), ('def', 5), ('hello', 4)]) 3.3 使用for循环遍历字典类似于列表,字典也可以用for ... in ...的形式遍历。例如: 1234567891011121314151617a = {'abc': 1, 'def': 2, 'python': 3} # 初始化一个字典for k in a: # 遍历key print(k, end=' ')print() # 输出回车for k in a.keys(): # 遍历key print(k, end=' ')print() # 输出回车for v in a.values(): # 遍历value print(v, end=' ')print() # 输出回车for k, v in a.items(): # 遍历key-value对 print("(%s, %d) " % (k, v), end=' ')print() # 输出回车 4. 作业扩展map()也可以用for ... in ...的形式遍历。例如:for x in map(int, input().split())可以遍历一行内用空格隔开的每个整数。","categories":[],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"}]},{"title":"Python字符串","slug":"Python字符串","date":"2023-10-18T08:18:06.000Z","updated":"2024-03-14T02:59:45.263Z","comments":true,"path":"2023/10/18/Python字符串/","link":"","permalink":"https://zade23.github.io/2023/10/18/Python%E5%AD%97%E7%AC%A6%E4%B8%B2/","excerpt":"","text":"1. 字符与整数的联系——ASCII码 2. 字符串常量的写法 3. 表示特殊字符——转义 4. 访问字符串中的每个字符 5. 使用循环语句遍历字符串 6. 字符串的切片操作 7. 字符串的复制 8. 字符串的运算 9. 字符串的常用操作 10. 更复杂的格式化输出 11. 作业题扩展内容 1. 字符与整数的联系——ASCII码每个常用字符都对应一个-128 ~ 127的数字,二者之间可以相互转化。注意:目前负数没有与之对应的字符。 ord()函数可以求一个字符的ASCII码。注意输入是一个字符,而不是字符串。 chr()函数可以将一个ASCII码转化成对应的字符。 12345c = 'a'print(ord(c))a = 66print(chr(a)) 常用ASCII值:'A'- 'Z'是65 ~ 90,'a' - 'z'是97 - 122,0 - 9是48 - 57。 注意:虽然字符可以跟整数相互转化,但在Python中,字符不能参与数值运算,这一点跟C++、Java等语言是不同的。 2. 字符串常量的写法在Python中,字符串既可以用单引号来表示,也可以用双引号来表示,二者完全相同。这一点跟C++、Java等编程语言是不同的,在这些编程语言中,用单引号来表示字符,用双引号来表示字符串。 12345a = "Hello World" # 双引号写法print(a)b = 'Hello World' # 单引号写法print(b) 两个或多个字符串常量并排写,会被自动合并,例如: 12a = "My " "name " "is yxc."print(a) # 输出:My name is yxc. 一个字符串如果包含多行,可以采用"""..."""或者'''...'''的初始化方式,字符串中将自动包含回车字符,例如: 1234a = """Usage: thingy [OPTIONS] -h Display this usage message -H hostname Hostname to connect to"""print(a) 会得到如下输出: 123Usage: thingy [OPTIONS] -h Display this usage message -H hostname Hostname to connect to 3. 表示特殊字符——转义当想在字符串中表示特殊字符时,一般可以在字符前加反斜杠\\。 常见需要转义的字符有: 转义字符 含义 ASCII码(十进制) \\n 回车 10 `` 代表一个反斜杠\\ 92 `“` 表示一个双引号 34 `‘` 表示一个单引号 39 例如: 1print("My name is:\\n\\"yxc!\\"") 会得到如下输出: 12My name is:"yxc!" 另外,如果想输出单引号,也可以用双引号来表示,反之亦然。例如: 12print("My name is 'yxc!'") # 输出:My name is 'yxc!'print('My name is "yxc!"') # 输出:My name is "yxc!" 4. 访问字符串中的每个字符可以通过下标读取字符串中的每个字符,下标从0开始,也可以是负数,负数下标表示的是除以字符串长度的余数对应的位置。 负数下标相当于将字符串首位相接,然后从0往前数。 如果字符串长度是 nn,那么下标只能取 −n∼n−1−n∼n−1 之间的整数,超出范围会报错。 注意:字符串中的每个字符不能修改。 例如: 1234a = "Hello World"print(a[0], ord(a[5])) # 输出H 32a[2] = 'x' # 会报错,字符串不能修改 5. 使用循环语句遍历字符串可以通过下标访问,例如: 1234s = "acwing"for i in range(6): print(s[i], end=' ')print() # 输出回车 可以通过for ... in ...直接遍历,例如: 123for c in "python": print(c, end=' ') # 注意c本身也是字符串类型print() # 输出回车 6. 字符串的切片操作字符串的切片操作会返回一个新字符串。用法: a[begin:end] 会返回包含a[begin], a[begin + 1], ..., a[end - 1]的字符串。 省略begin时,begin的默认值是0。 省略end时,end的默认值是字符串长度。 如果begin或end是负数,表示的是除以字符串长度后的余数。 例如: 1234567a = "ABCDE"print(a[1:4]) # 输出BCDprint(a[1:]) # 输出BCDEprint(a[:4]) # 输出ABCDprint(a[:]) # 输出ABCDEprint(a[-4:-1]) # 等价于print(a[1:4]) 注意:字符串的切片不支持写操作。 例如: 12a = "ABCDE"a[1:4] = "XY" # 会报错,字符串不能修改 7. 字符串的复制跟列表不同,字符串的每次复制操作,都会得到一个全新的字符串。 8. 字符串的运算 字符串的加法可以将两个字符串拼接起来,得到一个新字符串。 字符串乘以一个整数,可以将若干个自身拼接起来,得到一个新字符串。 字符串支持比较运算符,按字典序比较大小。即如果两个字符串相同,则表示相等;否则找到两个字符串从左到右数第一个不一样的字符,哪个字符串的字符的ASCII码小,哪个字符串的字典序就小;另外空字符比任何字符都小。 例如: 12345678910111213a = "Hello "b = "World"c = a + bprint(c) # 输出Hello Worldd = a * 3print(d) # 输出Hello Hello Helloe = a * 3 + "World"print(e) # 输出Hello Hello Hello Worldprint(a <= b) # 按字典序比较大小,输出Trueprint("123" > "22") # 按字典序比较大小,输出False 9. 字符串的常用操作假设s是一个字符串,则: len(s)返回字符串长度。 s.split(sep)返回一个字符串列表。如果给出了sep就按sep分隔;如果没给出,则会按空格分隔,但连续的空格会被视为单个分隔符,而且会忽略首尾的空白字符。 s.strip()将首尾的空白字符删除。 s.replace(old, new)将s中所有的old子串都改成new。 s.find("abc")查询某个子串在s中第一次出现的下标;如果不存在,则返回-1。 s.startswith(prefix)判断prefix是否为s的前缀。 s.endswith(suffix)判断suffix是否为s的后缀。 s.lower()将所有大写字母变成小写。 s.upper()将所有小写字母变成大写。 s.join(a),a是一个字符串列表,这个函数返回将a中的字符用s作为分隔符拼接起来的结果。 注意:返回的所有字符串都是新字符串,原字符串不变。 例如: 123456789101112131415161718s1 = "abc def xyz"print(len(s1)) # 输出11print(s1.split()) # 输出['abc', 'def', 'xyz']s2 = " abc abc "print(s2.strip()) # 输出abc abcprint(s2.replace("abc", "*")) # 输出 * *print(s2.find("abc"), s2.find("xyz")) # 输出2 -1s3 = "Abc deF"print(s3.startswith("Ab")) # 输出Trueprint(s3.endswith("deF")) # 输出Trueprint(s3.lower()) # 输出abc defprint(s3.upper()) # 输出ABC DEFs4 = ", "a = ["aa", "bb", "cc"]print(s4.join(a)) # 输出aa, bb, cc 10. 更复杂的格式化输出当需要用到更复杂的格式化输出时,现查即可。可以参考: 更复杂的输出格式 printf 风格的字符串格式化 11. 作业题扩展内容 作业的评测器会自动忽略每一行的行末空格,所以行末输出多余空格也视为正确。 s.isdigit():当字符串s不是空字符串,且包含的所有字符都是数字时返回True,否则返回False。 a, b = ["abc", "def"]这种写法可以将"abc"赋值给第一个变量a,将"def"赋值给第二个变量b。 s.rfind("abc")查询某个子串在s中最后一次出现的下标;如果不存在,则返回-1。 当不知道读入的具体行数时,可以采用如下方法一次性读取所有行: 1234from sys import stdinfor line in stdin.readlines(): print(line.strip()) # strip()是为了去掉行末的回车","categories":[],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"}]},{"title":"经济学——流动性陷阱","slug":"经济学——流动性陷阱","date":"2023-10-16T02:37:26.000Z","updated":"2024-03-07T02:16:13.691Z","comments":true,"path":"2023/10/16/经济学——流动性陷阱/","link":"","permalink":"https://zade23.github.io/2023/10/16/%E7%BB%8F%E6%B5%8E%E5%AD%A6%E2%80%94%E2%80%94%E6%B5%81%E5%8A%A8%E6%80%A7%E9%99%B7%E9%98%B1/","excerpt":"","text":"流动性陷阱——名词解释 白话解释 维基百科解释 换个视角看问题 国家层面 人民层面 流动性陷阱——名词解释白话解释流动性陷阱是一个经济学概念,出现在名义利率已经接近零或非常低,而央行进一步减少利率仍然不能刺激投资和消费的情况。在这种情境下,货币政策的传统工具,如购买国债以增加货币供应量,可能变得无效,因为商业银行和公众可能选择持有现金而不是投资或消费。这是因为他们预期未来的回报率很低,或对未来经济有所担忧。 维基百科解释流动性陷阱(英语:liquidity trap),是英国经济学家凯恩斯提出的概念,又称凯恩斯陷阱。 在凯恩斯主义经济学中,流动性陷阱是指一个当货币政策完全没法刺激经济的情况,无论降低利率抑或增加货币供应量也没有作用。典型的流动性陷阱在当预期经济不景气出现(例如通货紧缩、总需求不足、发生战争),使持有流动资产的人不愿意投资。 换个视角看问题 GPT4的回答 国家层面站在国家的角度: 财政政策:当货币政策变得无效时,国家可以使用财政政策,如增加公共支出、减税或直接向家庭提供资金,以刺激经济。 结构性改革:国家可以采取长期的结构性改革,如改善教育、健康、基础设施,以增加生产力和潜在产出。 消除不确定性:政府可以采取措施减少经济中的不确定性,这可能包括提供更清晰的经济前景、稳定的政策环境等。 合作:与其他国家合作,通过国际贸易和投资来促进增长。 人民层面站在人民的角度: 储蓄与投资:在经济不确定性和低回报期望的背景下,人们可能会倾向于增加储蓄。然而,他们也应考虑多样化投资,可能在国外或不同的资产类别,以获得更好的回报。 消费:尽管流动性陷阱可能导致对未来的担忧,但是长时间的压抑消费可能进一步伤害经济。如果有能力,人民可以考虑适度的消费。 教育与技能提升:在经济低迷的时期,提高自己的技能和教育背景可以帮助人们为未来更好的机会做好准备。 避免过度债务:在低利率环境下,尽管贷款更便宜,但人们应避免过度债务,以确保在经济形势变化时能够应对。 总之,流动性陷阱是一个复杂的经济问题,需要多方面的策略来应对。","categories":[],"tags":[{"name":"经济学小知识","slug":"经济学小知识","permalink":"https://zade23.github.io/tags/%E7%BB%8F%E6%B5%8E%E5%AD%A6%E5%B0%8F%E7%9F%A5%E8%AF%86/"}]},{"title":"Python函数","slug":"Python函数","date":"2023-10-12T10:22:54.000Z","updated":"2024-03-14T02:59:41.168Z","comments":true,"path":"2023/10/12/Python函数/","link":"","permalink":"https://zade23.github.io/2023/10/12/Python%E5%87%BD%E6%95%B0/","excerpt":"","text":"1. 函数基础 1.1 编写函数 1.2 调用函数 1.3 形参和实参 1.3.1 形参的初始化方式 1.3.2 带默认值的形参 1.3.3 其它参数写法 1.4 变量的作用域 2. 参数传递 2.1 值传递 2.2 引用传递 3.return语句 4.lambda表达式 5. 函数递归 Python中函数的用法非常多,80%的用法不常用,20%的用法常用。大家不要把精力浪费在背完所有用法上,而要把主要精力放到最常用的20%的用法和代码逻辑上,至于另外80%不常用的用法,边用边查就行。 1. 函数基础Python中一个典型的函数定义包括以下部分:关键字def、函数名称、由0个或多个形参组成的列表以及函数体。 1.1 编写函数我们来编写一个求阶乘的函数。例如: 12345def fact(n): res = 1 for i in range(1, n + 1): res *= i return res 函数名称是fact,给它传入一个n,会返回n的阶乘。return语句负责结束函数并返回res的值。 1.2 调用函数123print("我们要计算5的阶乘,答案是:")print(fact(5)) # 输出 120print("计算结束啦!") 函数的调用完成两项工作: 用实参初始化函数对应的形参 将控制权转移给被调用的函数 此时,代码原本的执行顺序被暂时中断,被调函数开始执行。等被调用函数执行完后,再继续执行之前的代码。 1.3 形参和实参实参指调用函数时传入的变量或常量,形参指定义函数时参数列表里的变量。 形参列表可以为空,例如: 1234def f(): print("Hello World")f() # 输出 Hello World 1.3.1 形参的初始化方式调用函数时会用实参去初始化形参,初始化的顺序有两种: 第一种是用位置实参来初始化形参。顾名思义,实参会按位置关系来初始化形参,第一个实参初始化第一个形参,第二个实参初始化第二个形参,依此类推。形参和实参的个数必须匹配。例如: 123456789def f(a, b, c, d): print("a =", a, end=", ") print("b =", b, end=", ") print("c =", c, end=", ") print("d =", d)f(1, True, "Python", 4.2) # 输出 a = 1, b = True, c = Python, d = 4.2f(1, True, "Python", 4.2, 3) # 会报错,因为实参个数多于形参f(1, True, "Python") # 会报错,因为实参个数少于形参 第二种是用关键字实参来初始化形参。此时实参不再按位置关系来初始化形参,而是按变量名初始化。例如: 12# f()的定义如上所述f(b=1, c=True, a="Python", d=4.2) # 输出 a = Python, b = 1, c = True, d = 4.2 两种方式也可以混合使用,但是位置实参一定要放到关键字实参之前。例如: 123# f()的定义如上所述f(1, 2, d="Python", c=4.2) # 输出 a = 1, b = 2, c = 4.2, d = Pythonf(1, b=3, "Python", d=4.2) # 会报错,因为位置实参位于关键字实参后面了。 1.3.2 带默认值的形参形参也可以设置默认值,但所有带默认值的形参必须是最后几个。当某些形参没有被初始化时,这些形参会使用默认值。例如: 123456789def f(a, b, c=3, d="Python"): print("a =", a, end=", ") print("b =", b, end=", ") print("c =", c, end=", ") print("d =", d)f(1, 2) # c和d没有被初始化,采用默认值。输出 a = 1, b = 2, c = 3, d = Pythonf(1, b=2, d="AcWing") # c没有被初始化,采用默认值。输出 a = 1, b = 2, c = 3, d = AcWing 1.3.3 其它参数写法其它参数写法用得不多,想了解的同学可以参考函数定义详解。 1.4 变量的作用域函数内定义的变量为局部变量,只能在函数内部使用。 当需要修改用全局变量时,需要用global关键字在函数内声明全局变量。例如: 12345678910111213x = 1def f(): global x # 在函数内声明全局变量 x = 666 y = 777 print(x, y)f() # 输出 666 777print(x) # 会发现全局变量x也被修改了print(y) # 会报错,因为y是局部变量,函数外无法使用 1.5 嵌套定义函数函数内部也可以定义函数。例如: 12345678def f(): def g(x): # 定义函数g() x += 1 print(x) g(5) # 调用函数g()f() # 输出6 1.6 pass语句当函数定义完但还不想实现时,可以用pass占位符,来避免出现语法错误。例如: 12def f(): pass 2. 参数传递2.1 值传递int、float、bool、字符串等采用值传递。 将实参的初始值拷贝给形参。此时,对形参的改动不会影响实参的初始值。例如: 12345678def f(y): y = 5 print(y)x = 10f(x)print(x) # 会发现x的值没变 2.2 引用传递列表采用引用传递。 将实参的引用传给形参,此时对形参的修改会影响实参的初始值。例如: 12345678def f(b): for i in range(len(b)): b[i] += 1a = [0, 1, 2, 3, 4]f(a)print(a) # 会发现列表a中的每个数加了1 3.return语句return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方,并返回结果。例如: 1234567891011121314def f(x): if x == 1: return # 不写返回值时,会返回None if x == 2: return 3 # 返回一个变量 if x == 3: return 4, 5 # 返回多个变量a = f(1)b = f(2)c, d = f(3)e = f(4) # 没写return时,也会返回Noneprint(a, b, c, d, e) # 输出 None 3 4 5 None 4.lambda表达式lambda关键字可以创建匿名函数,目的是为了简化代码。可以对比下面两种写法,会发现lambda表达式的写法更短一些。 常与sort()函数配合使用,例如: 1234pairs = [[1, "one"], [2, "two"], [3, "three"], [4, "four"]]pairs.sort(key=lambda pair: pair[1]) # 每个元素使用第二个变量比较大小print(pairs) # 输出:[[4, 'four'], [1, 'one'], [3, 'three'], [2, 'two']] 等价于下面的写法: 123456789pairs = [[1, "one"], [2, "two"], [3, "three"], [4, "four"]]def compare(pair): return pair[1]pairs.sort(key=compare) # 每个元素使用第二个变量比较大小print(pairs) # 输出:[[4, 'four'], [1, 'one'], [3, 'three'], [2, 'two']] 5. 函数递归在一个函数内部,也可以调用函数自身。这种写法被称为递归。 写递归函数可以从集合的角度来思考。理解递归函数的执行顺序可以用树的形式来思考。 例如,求解斐波那契数列第 nn 项可以采用如下写法: 1234567def fib(n): if n <= 2: return 1 return fib(n - 1) + fib(n - 2)print(fib(6)) # 输出 8","categories":[],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"}]},{"title":"github_workflow自动合并主线失败问题","slug":"githun-workflow自动合并主线失败问题","date":"2023-10-08T02:16:53.000Z","updated":"2023-10-08T02:37:06.373Z","comments":true,"path":"2023/10/08/githun-workflow自动合并主线失败问题/","link":"","permalink":"https://zade23.github.io/2023/10/08/githun-workflow%E8%87%AA%E5%8A%A8%E5%90%88%E5%B9%B6%E4%B8%BB%E7%BA%BF%E5%A4%B1%E8%B4%A5%E9%97%AE%E9%A2%98/","excerpt":"","text":"github_workflow自动提交在合并主线时出现的问题介绍看到 GitHub 上的一个好玩的项目,大意就是保持个人主页的提交栏保持尝绿,看起来很有意思,于是学着构建了一个 GitHub_actions_workflow 。仓库是用来每日清晨推送 bing 的当日壁纸的,仓库为:https://github.com/zade23/auto-green 但是部署之后发现 Actions 的 合并提交 动作一直失败,报错如下: 123456789remote: Permission to zade23/auto-green.git denied to github-actions[bot].fatal: unable to access 'https://github.com/zade23/auto-green.git/': The requested URL returned error: 403Error: Invalid exit code: 128 at ChildProcess.<anonymous> (/home/runner/work/_actions/ad-m/github-push-action/master/start.js:30:21) at ChildProcess.emit (node:events:513:28) at maybeClose (node:internal/child_process:1100:16) at Process.ChildProcess._handle.onexit (node:internal/child_process:304:5) { code: 128} 问题分析分析主要问题,应试这段 The requested URL returned error: 403 。 最终在 Stack Overflow 上找到了解决办法: Permission denied to github-actions[bot]. The requested URL returned error: 403 问题解决GitHub Actions 中自动提交功能必须在对应仓库下面手动设置允许 bot 合并提交的权限,否则合并动作就会被拒绝。 启动 actions 的 repository - Settings -> Action -> General -> Workflow permissions","categories":[],"tags":[]},{"title":"Python数据结构和算法","slug":"Python数据结构和算法","date":"2023-07-18T07:12:00.000Z","updated":"2024-03-14T02:58:36.019Z","comments":true,"path":"2023/07/18/Python数据结构和算法/","link":"","permalink":"https://zade23.github.io/2023/07/18/Python%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/","excerpt":"","text":"算法分析相关 复杂度计算 大 O 表示法 类 都是对数据的构成(状态)以及数据能做什么(行为)的描述。由于类的使用者只能看到数据项的状态和行为,因此类与抽象数据类型是相似的、 在面向对象编程范式中,数据项被称作 对象 一个对象就是类的一个实例。 1.内建原子数据结构 python是通过两种内建数据类型实现整数类型和浮点数类型的,相应的python类就是 int 和 float 。 列表列表 是零个或多个指向 Python 数据对象的引用的有序集合,通过在方括号内以逗号分隔的一系列值来表达。空列表就是 []。列表是异构的,这意味着其指向的数据对象不需要都是同一个类,并且这一集合可以被赋值给一个变量。下面的代码段展示了列表含有多个不同的 Python 数据对象。 元组 通常写成由括号包含并且以逗号分隔的一系列值。与序列一样,元组允许之前描述的任一操作。 列表提供的方法 方法名 用法 解释 append alist.append(item) 在列表末尾添加一个新元素 insert alist.insert(i,item) 在列表的第! i 个位置插入一个元素 pop alist.pop() 删除并返回列表中最后一个元素 pop alist.pop(i) 删除并返回列表中第 i 个位置的元素 sort alist.sort() 将列表元素排序 reverse alist.reverse() 将列表元素倒序排列 del del alist[i] 删除列表中第 i 个位置的元素 index alist.index(item) 返回 item 第一次出现时的下标 count alist.count(item) 返回 item 在列表中出现的次数 remove alist.remove(item) 从列表中移除第一次出现的 item 对应输出: 123456789101112131415161718192021222324252627282930313233343536>>> myList[1024, 3, True, 6.5]>>> myList.append(False)>>> myList[1024, 3, True, 6.5, False]>>> myList.insert(2,4.5)>>> myList[1024, 3, 4.5, True, 6.5, False]>>> myList.pop()False>>> myList[1024, 3, 4.5, True, 6.5]>>> myList.pop(1)3>>> myList[1024, 4.5, True, 6.5]>>> myList.pop(2)True>>> myList[1024, 4.5, 6.5]>>> myList.sort()>>> myList[4.5, 6.5, 1024]>>> myList.reverse()>>> myList[1024, 6.5, 4.5]>>> myList.count(6.5)1>>> myList.index(4.5)2>>> myList.remove(6.5)>>> myList[1024, 4.5]>>> del myList[0]>>> myList[4.5] 字符串字符串是零个或多个字母、数字和其他符号的有序集合。这些字母、数字和其他符号被称为 字符。常量字符串值通过引号(单引号或者双引号均可)与标识符进行区分。 123456789>>> "David"'David'>>> myName = "David">>> myName[3]'i'>>> myName*2'DavidDavid'>>> len(myName)5 字符串提供的方法 方法名 用法 解释 center astring.center(w) 返回一个字符串,原字符串居中,使用空格填充新字符串,使其长度为 w count astring.count(item) 返回 item 出现的次数 ljust astring.ljust(w) 返回一个字符串,将原字符串靠左放置并填充空格至长度 w rjust astring.rjust(w) 返回一个字符串,将原字符串靠右放置并填充空格至长度 w lower astring.lower() 返回均为小写字母的字符串 upper astring.upper() 返回均为大写字母的字符串 find astring.find(item) 返回 item 第一次出现时的下标 split astring.split(schar) 在 schar 位置将字符串分割成子串,不填则默认分割空格和换行符 列表和字符串的区别: 列表有 可修改性 ,字符串没有 集合集(set)是由零个或多个不可修改的 Python 数据对象组成的无序集合。集不允许重复元素,并且写成由花括号包含、以逗号分隔的一系列值。空集由 set() 来表示。集是异构的,并且可以通过下面的方法赋给变量。 Python 集支持的运算 运算名 运算符 解释 成员 in 询问集中是否有某元素 长度 len 获取集的元素个数 | aset | otherset 返回一个包含 aset 与 otherset 所有元素的新集 & aset & otherset 返回一个包含 aset 与 otherset 共有元素的新集 - aset - otherset 返回一个集,其中包含只出现在 aset 中的元素 <= aset <= otherset 询问 aset 中的所有元素是否都在 otherset 中 Python 集提供的方法 方法名 用法 解释 union aset.union(otherset) 返回一个包含 aset 和 otherset 所有元素的集 intersection aset.intersection(otherset) 返回一个仅包含两个集共有元素的集 difference aset.difference(otherset) 返回一个集,其中仅包含只出现在 aset 中的元素 issubset aset.issubset(otherset) 询问 aset 是否为 otherset 的子集 add aset.add(item) 向 aset 添加一个元素 remove aset.remove(item) 将 item 从 aset 中移除 pop aset.pop() 随机移除 aset 中的一个元素 clear aset.clear() 清除 aset 中的所有元素 12345678910111213141516171819202122232425262728293031>>> mySet{False, 4.5, 3, 6, 'cat'}>>> yourSet = {99,3,100}>>> mySet.union(yourSet){False, 4.5, 3, 100, 6, 'cat', 99}>>> mySet | yourSet{False, 4.5, 3, 100, 6, 'cat', 99}>>> mySet.intersection(yourSet){3}>>> mySet & yourSet{3}>>> mySet.difference(yourSet){False, 4.5, 6, 'cat'}>>> mySet - yourSet{False, 4.5, 6, 'cat'}>>> {3,100}.issubset(yourSet)True>>> {3,100}<=yourSetTrue>>> mySet.add("house")>>> mySet{False, 4.5, 3, 6, 'house', 'cat'}>>> mySet.remove(4.5)>>> mySet{False, 3, 6, 'house', 'cat'}>>> mySet.pop()False>>> mySet{3, 6, 'house', 'cat'}>>> mySet.clear()>>> mySet 字典字典 是无序结构,由相关的元素对构成,其中每对元素都由一个键和一个值组成。这种键–值对通常写成键:值的形式。字典由花括号包含的一系列以逗号分隔的键–值对表达,如下所示。 12>>> capitals = {'Iowa':'DesMoines','Wisconsin':'Madison'}>>> capitals{'Wisconsin':'Madison', 'Iowa':'DesMoines'} 可以通过键访问其对应的值,也可以向字典添加新的键–值对。访问字典的语法与访问序列的语法十分相似,只不过是使用键来访问,而不是下标。添加新值也类似。 1234567>>> capitals['Iowa'] = 'DesMoines'>>> capitals['Utah'] = 'SaltLakeCity'>>> capitals{'Utah':'SaltLakeCity', 'Wisconsin':'Madison', 'Iowa':'DesMoines'}>>> capitals['California']='Sacramento'>>> capitals{'Utah':'SaltLakeCity', 'Wisconsin':'Madison', 'Iowa':'DesMoines', 'California':'Sacramento'}>>> len(capitals)>>> 4 需要谨记,字典并不是根据键来进行有序维护的。第一个添加的键–值对('Utah':'SaltLakeCity')被放在了字典的第一位,第二个添加的键–值对('California':'Sacramento')则被放在了最后。 字典的运算keys、values 和 items 方法均会返回包含相应值的对象。可以使用 list 函数将字典转换成列表。 运算名 运算符 解释 [] myDict[k] 返回与 k 相关联的值,如果没有则报错 in key in adict 如果 key 在字典中,返回 True,否则返回 False del del adict[key] 从字典中删除 key 的键–值对 字典的方法get 方法有两种版本。如果键没有出现在字典中,get 会返回 None。然而,第二个可选参数可以返回特定值。 方法名 用法 解释 keys adict.keys() 返回包含字典中所有键的 dict_keys 对象 values adict.values() 返回包含字典中所有值的 dict_values 对象 items adict.items() 返回包含字典中所有键–值对的 dict_items 对象 get adict.get(k) 返回 k 对应的值,如果没有则返回 None get adict.get(k, alt) 返回 k 对应的值,如果没有则返回 alt 字典实例12345678910111213141516171819>>> phoneext={'david':1410, 'brad':1137}>>> phoneext{'brad':1137, 'david':1410}>>> phoneext.keys()dict_keys(['brad', 'david'])>>> list(phoneext.keys())['brad', 'david']>>> phoneext.values()dict_values([1137, 1410])>>> list(phoneext.values())[1137, 1410]>>> phoneext.items()dict_items([('brad', 1137), ('david', 1410)])>>> list(phoneext.items())[('brad', 1137), ('david', 1410)]>>> phoneext.get("kent")>>> phoneext.get("kent", "NO ENTRY")'NO ENTRY'>>> 输入和输出程序经常要和用户进行交互。 目前的大多数程序使用对话框作为要求用户提供某种输入的方式。尽管 Python 确实有方法来创建这样的对话框,但是可以利用更简单的函数。Python 提供了一个函数,它使得我们可以要求用户输入数据并且返回一个字符串的引用。这个函数就是 input。 提示字符串input 函数接受一个字符串作为参数。由于该字符串包含有用的文本来提示用户输入,因此它经常被称为 提示字符串。举例来说,可以像下面这样调用 input。 不论用户在提示字符串后面输入什么内容,都会被存储在 aName 变量中。使用 input 函数,可以非常简便地写出程序,让用户输入数据,然后再对这些数据进行进一步处理。例如,在下面的两条语句中,第一条要求用户输入姓名,第二条则打印出对输入字符串进行一些简单处理后的结果。 12345aName = input("Please enter your name ")print("Your name in all capitals is ", aName.upper(), "and has length", len(aName)) 需要注意的是,input 函数返回的值是一个字符串,它包含用户在提示字符串后面输入的所有字符。如果需要将这个字符串转换成其他类型,必须明确地提供类型转换。在下面的语句中,用户输入的字符串被转换成了浮点数,以便于后续的算术处理。 123sradius = input("Please enter the radius of the circle ")radius = float(sradius)diameter = 2 * radius 格式化字符串print 函数为输出 Python 程序的值提供了一种非常简便的方法。它接受零个或者多个参数,并且将单个空格作为默认分隔符来显示结果。通过设置 sep 这一实际参数可以改变分隔符。此外,每一次打印都默认以换行符结尾。这一行为可以通过设置实际参数 end 来更改。下面是一些例子。 12345678>>> print("Hello")Hello>>> print("Hello","World")Hello World>>> print("Hello","World", sep="***")Hello***World>>> print("Hello","World", end="***")Hello World*** Python 提供了另一种叫作 格式化字符串 的方式。格式化字符串是一个模板,其中包含保持不变的单词或空格,以及之后插入的变量的占位符。例如,下面的语句包含 is 和 years old.,但是名字和年龄会根据运行时变量的值而发生改变。 1print(aName, "is", age, "years old.") 使用格式化字符串,可以将上面的语句重写成下面的语句。 1print("%s is %d years old." % (aName, age)) 格式化字符串可用的类型声明% 是字符串运算符,被称作 格式化运算符。 表达式的左边部分是模板(也叫格式化字符串),右边部分则是一系列用于格式化字符串的值。 格式化字符串可以包含一个或者多个转换声明。转换字符告诉格式化运算符,什么类型的值会被插入到字符串中的相应位置。 在上面的例子中,%s 声明了一个字符串,%d 则声明了一个整数。其他可能的类型声明还包括 i、u、f、e、g、c 和 %。 字符 输出格式 d、i 整数 u 无符号整数 f m.dddd 格式的浮点数 e m.dddde+/-xx 格式的浮点数 E m.ddddE+/-xx 格式的浮点数 g 对指数小于-4 或者大于 5 的使用 %e,否则使用 %f c 单个字符 s 字符串,或者任意可以通过 str 函数转换成字符串的 Python 数据对象 % 插入一个常量 % 符号 格式化修改符 修改符 例子 解释 数字 %20d 将值放在 20 个字符宽的区域中 - %-20d 将值放在 20 个字符宽的区域中,并且左对齐 + %+20d 将值放在 20 个字符宽的区域中,并且右对齐 0 %020d 将值放在 20 个字符宽的区域中,并在前面补上 0 . %20.2f 将值放在 20 个字符宽的区域中,并且保留小数点后 2 位 (name) %(name)d 从字典中获取 name 键对应的值 1234567891011>>> price = 24>>> item = "banana">>> print("The %s costs %d cents" % (item,price))The banana costs 24 cents>>> print("The %+10s costs %5.2f cents" % (item,price))The banana costs 24.00 cents>>> print("The %+10s costs %10.2f cents" % (item,price))The banana costs 24.00 cents>>> itemdict = {"item":"banana","cost":24}>>> print("The %(item)s costs %(cost)7.1f cents" % itemdict)The banana costs 24.0 cents 控制结构Python提供的标准的标准控制语句有 while 语句以及 for 语句。 1234567891011>>> counter = 1>>> while counter <= 5:... print("Hello, world")... counter = counter + 1...Hello, worldHello, worldHello, worldHello, worldHello, world 看下面这个例子。 1while counter <= 10 and not done: 迭代语句只有在上面两个条件都满足的情况下才会被执行。变量 counter 的值需要小于或等于 10,并且变量 done 的值需要为 False(not False 就是 True),因此 True and True 的最后结果才是 True。 for在遍历每一个成员上非常的方便。例如: 12345678>>> for item in [1,3,6,2,5]:... print(item)...13625 列表解析式列表可以通过使用迭代结构和分支结构来创建。这种方式被称为 列表解析式。通过列表解析式,可以根据一些处理和分支标准轻松创建列表。 举例来说,如果想创建一个包含前 10 个完全平方数的列表,可以使用以下的 for 语句。 12345>>> sqlist = []>>> for x in range(1,11): sqlist.append(x*x)>>> sqlist[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] 进阶:使用列表解析式,只需一行代码即可创建完成。 123>>> sqlist = [x*x for x in range(1,11)]>>> sqlist[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] 进阶:变量 x 会依次取由 for 语句指定的 1 到 10 为值。之后,计算 x*x 的值并将结果添加到正在构建的列表中。列表解析式也允许添加一个分支语句来控制添加到列表中的元素。 123>>> sqlist = [x*x for x in range(1,11) if x%2 != 0]>>> sqlist[1, 9, 25, 49, 81] 例子:(力扣的原题:输出列表中非元音的字母并以大写形式返回) 12>>>[ch.upper() for ch in 'comprehension' if ch not in 'aeiou']['C', 'M', 'P', 'R', 'H', 'N', 'S', 'N'] 函数下面定义的简单函数会返回传入值的平方。 1234567>>> def square(n):... return n**2...>>> square(3)9>>> square(square(3))81 同样的,我们可以自己定义一个平方根函数 squareroot() 通过牛顿迭代法求解平方根 1234561. def squareroot(n):2. root = n/2 #initial guess will be 1/2 of n3. for k in range(20):4. root = (1/2)*(root + (n / root))5.6. return root 接下来,模拟调用这个函数 1234>>> squareroot(9)3.0>>> squareroot(4563)67.549981495186216 面向对象:定义 类算法分析相关复杂度计算大 O 表示法 如果可以通过观察循环结构和算法的方式快速判断出复杂度就算出师 异序词排序问题 1234567891011121314151617181920212223def anagramSolution1(s1, s2): alist = list(s2) pos1 = 0 StillOK = True while pos1 < len(s1) and stillOK: pos2 = 0 found = False while pos2 < len(alist) and not found: if s1[pos1] == alist[pos2]: found = True else: pos2 += 1 if found: alist[pos2] = None else: stillOK = False pos1 += 1 return stillOK 123456789101112131415161718def anagramSolution2(s1, s2): alist1 = list(s1) alist2 = list(s2) # 这里需要记住,常用的几类排序时间复杂度在 O(n^2) 或 O(nlogn) alist1.sort() alist2.sort() pos = 0 matches = True while pos < len(s1) and matches: if alist1[pos] == alist2[pos]: pos += 1 else: matches = False return matches 123456789101112131415161718192021def anagramSolution3(s1, s2): c1 = [0] * n c2 = [0] * n for i in range(len(s1)): pos = ord(s1[i]) - ord('a') c1[pos] += 1 for i in range(len(s2)): pos = ord(s2[i] - ord('a')) c2[pos] += 1 j = 0 stillOk = True while j < 26 and stillOK: if c1[j] == c2[j]: j += 1 else: stillOK = False return stillOk 第四个例子是最快的。因为没有循环嵌套,只有O(n)的复杂度。倘若需要考虑关于空间上的需求,那么第四个例子新开了额外的空间用于存储计数器,这种方式就是常说的:空间换时间的方法。","categories":[{"name":"Algorithm","slug":"Algorithm","permalink":"https://zade23.github.io/categories/Algorithm/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"}]},{"title":"完备区间判断下的二分查找万能模板","slug":"完备区间判断下的二分查找万能模板","date":"2023-07-07T06:57:15.000Z","updated":"2024-03-14T03:01:45.072Z","comments":true,"path":"2023/07/07/完备区间判断下的二分查找万能模板/","link":"","permalink":"https://zade23.github.io/2023/07/07/%E5%AE%8C%E5%A4%87%E5%8C%BA%E9%97%B4%E5%88%A4%E6%96%AD%E4%B8%8B%E7%9A%84%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E4%B8%87%E8%83%BD%E6%A8%A1%E6%9D%BF/","excerpt":"","text":"场景单调数列/数组 一定可以用二分;但如果非单调的情况,也有可能存在能够使用二分的情况 二分模板整数二分C++123456789101112131415161718192021222324252627#include <iostream>// 整数二分的模板bool check(int x) {/*......*/} // 检查x是否满足某种性质/* 目前发现整数二分的所有情况都可以被下面的两个板子涵盖*/// 2.1.1 区间被划分成 [l, mid] 和 [mid + 1, r]int bsearch_1(int l, int r) { while (l < r) { int mid = l + r >> 1; if (check(mid)) r = mid; else l = mid + 1; } return l}// 2.1.2 区间被划分成 [l, mid - 1] 和 [mid, r]int bsearch_2(int l, int r) { while (l < r) { int mid = l + r + 1 >> 1; if (chedk(mid)) l = mid; else r = mid - 1; }} Python1234567891011121314151617181920212223242526# 二分查找def check(x): # 判断x是否满足某种性质 if x > 0: return True else: return False# 2.1.1 区间被划分成 [l, mid] 和 [mid + 1, r]def bsearch_2(l, r): while l < r: mid = l + r >> 1 if (check(mid)): r = mid else: l = mid + 1 return l# 2.1.2 区间被划分成 [l, mid - 1] 和 [mid, r]def bsearch_1(l, r): while l < r: mid = l + r + 1 >> 1 if (check(mid)): l = mid else: r = mid - 1 return l 浮点数二分只有C++要考虑,高贵的Python不需要考虑。 1234567891011#include <iostream>double bsearch_3(double l, double r) { const double eps = 1e-6; while (r - l > eps) { double mid = (l + r) / 2; if (check(mid)) r = mid; else l = mid; } return l;}","categories":[{"name":"Algorithm","slug":"Algorithm","permalink":"https://zade23.github.io/categories/Algorithm/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"},{"name":"C++","slug":"C","permalink":"https://zade23.github.io/tags/C/"}]},{"title":"Python条件判断的优先级","slug":"Python条件判断的优先级","date":"2023-07-06T12:53:13.000Z","updated":"2024-03-14T02:59:07.047Z","comments":true,"path":"2023/07/06/Python条件判断的优先级/","link":"","permalink":"https://zade23.github.io/2023/07/06/Python%E6%9D%A1%E4%BB%B6%E5%88%A4%E6%96%AD%E7%9A%84%E4%BC%98%E5%85%88%E7%BA%A7/","excerpt":"","text":"背景做LeetCode的时候经常能够发现很多基本功不扎实的问题。这一次是在做一道简单题时候忽视的条件判断优先级的问题 题目连接:https://leetcode.cn/problems/count-the-number-of-vowel-strings-in-range 代码部分 问题代码12345678910class Solution: def vowelStrings(self, words: List[str], left: int, right: int) -> int: words = words[left : right + 1] n = len(words) print(n) res = 0 for i in range(n): if words[i][0] == 'a' or words[i][0] == 'e' or words[i][0] == 'i' or words[i][0] == 'o' or words[i][0] == 'u' and words[i][-1] == 'a' or words[i][-1] == 'e' or words[i][-1] == 'i' or words[i][-1] == 'o' or words[i][-1] == 'u': res += 1 return res 正确代码1234567891011class Solution: def vowelStrings(self, words: List[str], left: int, right: int) -> int: words = words[left : right + 1] n = len(words) print(n) res = 0 for i in range(n): if words[i][0] == 'a' or words[i][0] == 'e' or words[i][0] == 'i' or words[i][0] == 'o' or words[i][0] == 'u': if words[i][-1] == 'a' or words[i][-1] == 'e' or words[i][-1] == 'i' or words[i][-1] == 'o' or words[i][-1] == 'u': res += 1 return res 二者的差别就在如何衔接 or 和 and 之间的关系上。 具体分析分析错误代码的bug: 在第二段错误代码中,条件判断部分存在问题。以下是有问题的代码片段: 12if words[i][0] == 'a' or words[i][0] == 'e' or words[i][0] == 'i' or words[i][0] == 'o' or words[i][0] == 'u' and words[i][-1] == 'a' or words[i][-1] == 'e' or words[i][-1] == 'i' or words[i][-1] == 'o' or words[i][-1] == 'u': res += 1 问题出在逻辑运算符的优先级上。在Python中,and 运算符的优先级高于 or 运算符,因此该条件判断实际上被解释为: 12if words[i][0] == 'a' or words[i][0] == 'e' or words[i][0] == 'i' or words[i][0] == 'o' or (words[i][0] == 'u' and words[i][-1] == 'a') or words[i][-1] == 'e' or words[i][-1] == 'i' or words[i][-1] == 'o' or words[i][-1] == 'u': res += 1 由于 or 运算符的短路特性,只要 words[i][0] 的首字母为元音字母之一,整个条件判断就会被认为是True,导致res增加。而第二个部分 words[i][-1] == 'a'、words[i][-1] == 'e'等都是单独的条件,不会影响整个条件判断的结果。 修正该bug的方法是使用括号明确指定条件的分组,确保逻辑关系正确。以下是修正后的代码: 12if (words[i][0] == 'a' or words[i][0] == 'e' or words[i][0] == 'i' or words[i][0] == 'o' or words[i][0] == 'u') and (words[i][-1] == 'a' or words[i][-1] == 'e' or words[i][-1] == 'i' or words[i][-1] == 'o' or words[i][-1] == 'u'): res += 1 这样修改后,条件判断会首先检查首字母是否为元音字母,然后再检查末尾字母是否为元音字母,两个条件都满足时才会增加res的值。","categories":[{"name":"Algorithm","slug":"Algorithm","permalink":"https://zade23.github.io/categories/Algorithm/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"}]},{"title":"Hello World","slug":"hello-world","date":"2023-05-16T09:45:58.000Z","updated":"2024-03-11T08:34:34.956Z","comments":false,"path":"2023/05/16/hello-world/","link":"","permalink":"https://zade23.github.io/2023/05/16/hello-world/","excerpt":"","text":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1hexo new "My New Post" More info: Writing Run server1hexo server More info: Server Generate static files1hexo generate More info: Generating Deploy to remote sites1hexo deploy More info: Deployment","categories":[{"name":"whatever","slug":"whatever","permalink":"https://zade23.github.io/categories/whatever/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://zade23.github.io/tags/Hexo/"}]}],"categories":[{"name":"Git Workflow","slug":"Git-Workflow","permalink":"https://zade23.github.io/categories/Git-Workflow/"},{"name":"Deeplearning","slug":"Deeplearning","permalink":"https://zade23.github.io/categories/Deeplearning/"},{"name":"Algorithm","slug":"Algorithm","permalink":"https://zade23.github.io/categories/Algorithm/"},{"name":"whatever","slug":"whatever","permalink":"https://zade23.github.io/categories/whatever/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"},{"name":"Git","slug":"Git","permalink":"https://zade23.github.io/tags/Git/"},{"name":"conda","slug":"conda","permalink":"https://zade23.github.io/tags/conda/"},{"name":"environment","slug":"environment","permalink":"https://zade23.github.io/tags/environment/"},{"name":"Docker","slug":"Docker","permalink":"https://zade23.github.io/tags/Docker/"},{"name":"PyTorch","slug":"PyTorch","permalink":"https://zade23.github.io/tags/PyTorch/"},{"name":"工具网站","slug":"工具网站","permalink":"https://zade23.github.io/tags/%E5%B7%A5%E5%85%B7%E7%BD%91%E7%AB%99/"},{"name":"经济学小知识","slug":"经济学小知识","permalink":"https://zade23.github.io/tags/%E7%BB%8F%E6%B5%8E%E5%AD%A6%E5%B0%8F%E7%9F%A5%E8%AF%86/"},{"name":"C++","slug":"C","permalink":"https://zade23.github.io/tags/C/"},{"name":"Hexo","slug":"Hexo","permalink":"https://zade23.github.io/tags/Hexo/"}]} \ No newline at end of file +{"meta":{"title":"ANdRoid's BLOG","subtitle":"MaTRix","description":"","author":"Android","url":"https://zade23.github.io","root":"/"},"pages":[{"title":"关于","date":"2024-03-14T03:05:45.153Z","updated":"2024-03-14T03:05:45.153Z","comments":false,"path":"about/index.html","permalink":"https://zade23.github.io/about/index.html","excerpt":"","text":"Android,游戏行业算法学徒,现居广东 见贤思齐焉,见不贤则亦自省"},{"title":"标签","date":"2023-05-18T08:07:04.950Z","updated":"2023-05-18T08:07:04.950Z","comments":false,"path":"tags/index.html","permalink":"https://zade23.github.io/tags/index.html","excerpt":"","text":""},{"title":"Repositories","date":"2023-05-18T03:53:31.018Z","updated":"2023-05-18T03:53:31.018Z","comments":false,"path":"repository/index.html","permalink":"https://zade23.github.io/repository/index.html","excerpt":"","text":""},{"title":"分类","date":"2023-05-18T08:06:17.850Z","updated":"2023-05-18T08:06:17.850Z","comments":false,"path":"categories/index.html","permalink":"https://zade23.github.io/categories/index.html","excerpt":"","text":""}],"posts":[{"title":"Python模块封装导入和包的相关知识","slug":"Python模块封装导入和包的相关知识","date":"2024-03-13T06:12:24.000Z","updated":"2024-03-14T06:29:05.733Z","comments":true,"path":"2024/03/13/Python模块封装导入和包的相关知识/","link":"","permalink":"https://zade23.github.io/2024/03/13/Python%E6%A8%A1%E5%9D%97%E5%B0%81%E8%A3%85%E5%AF%BC%E5%85%A5%E5%92%8C%E5%8C%85%E7%9A%84%E7%9B%B8%E5%85%B3%E7%9F%A5%E8%AF%86/","excerpt":"","text":"背景介绍 TL;DR 模块 快速理解 导入方式 以脚本方式运行模块 Python文件的编译 使用内置函数dir()查看模型定义的名称 包 从包中导入* 相对导入 多目录中的包 背景介绍作为非科班的程序员,在代码规范和程序思维上是有欠缺的(仅代表我个人)。 这些问题会在合作开发项目中暴露出来(阅读其他成员代码以及在其他成员代码上续写功能这类型的任务中暴露的更加明显),比如:对程序模块的封装、底层架构的了解(底层架构对于阅读代码和理解代码很重要)、Python语言的标准库以及装饰器使用等等…… 2024年的主题就是:“还债”。目标是尽快补齐在程序架构和工程领域的能力。 TL;DR在Python工程中,模块是一个包含Python定义和语句的文件,一般以.py作为后缀。模块中的定义可以导入到其他模块或者主程序(main)中,这样做的的目的是方便程序的维护和复用。 模块的代入:通过import导入模块。模块不会直接把模块自身的函数名称添加到当前命名空间中,而是将模块名称添加到命名空间中。再通过模块名称访问其中的函数,例如:import torch \\ torch.nn.functional() 模块的作用:可执行的语句以及函数定义,用于初始化模块。每个模块都有自己的私有命名空间,它会被用作模块中定义的所有函数的全局命名空间。模块可以导入其他模块,被导入的模块名称会被添加到该模块的全局命名空间。(每个模块都有自己的命名空间,防止与用户的全局变量发生冲突)。 模块功能所做的一切就是为了:代码的复用和方便维护。 总览: 模块快速理解现在有个程序模块名称为fibo.py,通过它的名字大致猜测应该是斐波那契数列的功能实现。 打开这个.py文件,内容如下: 12345678910111213141516# Fibonacci numbers moduledef fib(n): # write Fibonacci series up to n a, b = 0, 1 while a < n: print(a, end=' ') a, b = b, a+b print()def fib2(n): # return Fibonacci series up to n result = [] a, b = 0, 1 while a < n: result.append(a) a, b = b, a+b return result 可以看到该模块中有两个方法,分别是:fib和fib2。 如果我想在该模块中使用这两个函数的功能,可以直接调用函数名称并传入参数即可: 1234>>> fib(1000)0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987>>> fib2(100)[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] 这时自然而然的诞生一个新的问题,如果在这个函数之外,我依然想使用这两个函数的功能在怎办呢? 先展示结果,最后再讲解细节。 现在,新建一个脚本文件(保证该脚本文件和fibo.py在同一目录下),内容如下: 1234567891011121314>>> import fibo>>> fibo.fib(1000)0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987>>> fibo.fib2(100)[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]>>> fibo.__name__'fibo'>>> fib = fibo.fib # 如果想要经常使用某个函数功能,可以把它赋值给局部变量>>> fib(500)0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 可以看到,新建的脚本文件中通过import fibo调用了开头写的斐波那契数列功能的模块。 当想要使用模块中的函数方法,仅需要用导入的模块名称加上”.功能函数名称”,就可以实现功能的调用甚至重新命名变量等操作。 导入方式使用import导入包的方式现列出4种,例如: 导入模块中的方法名称 12>>> from fibo import fib, fib2>>> fib(500) 导入模块内定义的所有名称(不包括含_开头的名称,并且不建议使用这种方法导入) 123>>> from fibo import *>>> fib(500)0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 模块名使用as,直接把as后的名称与导入模块绑定 12>>> import fibo as fibo>>> fib.fib(500) 结合from一起使用 12>>> from fibo import fib as fibonacci>>> fibonacci(500) 以脚本方式运行模块通常在命令行执行脚本文件的语句: 1python fibo.py <arguments> 直接运行.py脚本会在我们看不见的地方默认的执行一个事情,即:**把__name__赋值为"__main__"**。 也就是把下列代码添加到了模块的末尾 123if __name__ == "__main__": import sys fib(int(sys.argv[1])) 这样做的含义是,在模块作为”main”文件(脚本)进行执行的时候才会运行。 举个例子: 当模块作为脚本文件执行时(会执行): 12$ python fibo.py 500 1 1 2 3 5 8 13 21 34 当模块被导入到其它模块或主程序时(不会执行): 12>>> import fibo>>> Python文件的编译这部分在官网文档讲解的非常清晰,参考6.1.3. “已编译的” Python 文件 这里附上“Python模块快速加载”的决策细节流程图: 使用内置函数dir()查看模型定义的名称dir()用于查找模块定义的名称,返回值为排序之后的字符串列表。 上实例: dir()含参数时: 12345678910111213141516171819202122232425>>> import fibo, sys>>> dir(fibo)['__name__', 'fib', 'fib2']>>> dir(sys)['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdout__', '__unraisablehook__', '_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework', '_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook', 'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix', 'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth', 'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags', 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info', 'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix', 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info', 'warnoptions'] dir()不含参数时: 12345>>> a = [1, 2, 3, 4, 5]>>> import fibo>>> fib = fibo.fibdir()['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys'] 包包是通过使用“带点号模块名”来构造 Python 模块命名空间的一种方式。 例如,模块名 A.B 表示名为 A 的包中名为 B 的子模块。 使用modules.func的这种调用方式还有一个好处,就是避免在不同模块中的功能函数命名冲突。 例如:在 NumPy 或 Pillow 等多模块包中很多功能函数命名相同,这样使用np.func或Pillow.func就不必担心彼此的func模块名冲突了。 假设要为统一处理声音文件与声音数据设计一个模块集(“包”)。声音文件的格式很多(通常以扩展名来识别,例如:.wav,.aiff,.au),因此,为了不同文件格式之间的转换,需要创建和维护一个不断增长的模块集合。 为了实现对声音数据的不同处理(例如,混声、添加回声、均衡器功能、创造人工立体声效果),还要编写无穷无尽的模块流。 下面这个分级文件树展示了这个包的架构: 1234567891011121314151617181920212223sound/ Top-level package __init__.py Initialize the sound package formats/ Subpackage for file format conversions __init__.py wavread.py wavwrite.py aiffread.py aiffwrite.py auread.py auwrite.py ... effects/ Subpackage for sound effects __init__.py echo.py surround.py reverse.py ... filters/ Subpackage for filters __init__.py equalizer.py vocoder.py karaoke.py ... 导入包时,Python搜索sys.path里的目录,查找包的子目录。 需要有__init__.py文件才能让Python将包含改文件的目录当做“包”来处理。这样可以防止重名目录如string在无意中屏蔽后续出现在模块搜索路径中的有效模块。 最简单的情况就是,__init__.py可以是一个空文件,但是它也可以执行包的初始化代码或设置__all__变量,这将在稍后详细描述。 一些例子说明: 1234import sound.effects.echo# 加载子模块 sound.effects.echo 必须通过全名来引用sound.effects.echo.echofilter(input, output, delay) 另一种导入子模块的方法: 1234from sound.effects import echo# 加载子模块 echo,并且不必加包前缀echo.echofilter(input, output, delay = 0.7, atten = 4) 还有一种,直接导入所需的函数或变量: 1234from sound.effect.echo import echofilter# 加载子模块 echo,使其函数 echofilter() 直接可用:echofilter(input, output, delay = 0.7, atten = 4) 从包中导入*同理于模块的导入,同样不建议这样做。 一些需要提及的知识点:如果直接使用*进行导入,一般执行的操作为通过包中的__init__.py代码部分的以下__all__中的模块名列表。 1__all__ = ["echo", "surround", "reverse"] 子模块的命名有可能会受到本地定义名称的影响! 模块中的模块如果和环境中已存在的模块重名,则会被本地先定义过的函数名称遮挡。以reverse函数为例: 12345678__all__ = [ "echo", # refers to the 'echo.py' file "surround", # refers to the 'surround.py' file "reverse", # !!! refers to the 'reverse' function now !!!]def reverse(msg: str): # <-- this name shadows the 'reverse.py' submodule return msg[::-1] # in the case of a 'from sound.effects import *' 官方文档中,推荐的做法是:frome package import submodule. 相对导入当包由多个子包构成(如示例中的 sound 包)时,可以使用绝对导入来引用同级包的子模块。 例如,如果 sound.filters.vocoder 模块需要使用 sound.effects 包中的 echo 模块,它可以使用 from sound.effects import echo。 你还可以编写相对导入代码,即使用 from module import name 形式的 import 语句。 这些导入使用前导点号来表示相对导入所涉及的当前包和上级包。 例如对于 surround 模块,可以使用: 123from . import echofrom .. import formatsfrom ..filters import equalizer 注意,相对导入基于当前模块名。 因为主模块名永远是 "__main__" ,所以如果计划将一个模块用作 Python 应用程序的主模块,那么该模块内的导入语句必须始终使用绝对导入。 多目录中的包通过 __path__ 可以传入字符串列表,找到所有 __init__.py坐在目录的位置。该功能不常用,知道就好。 相关参考: 6. 模块 — Python 3.12.2 文档","categories":[],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"}]},{"title":"合作开发中Git工作流程的细节","slug":"合作开发中Git工作流程的细节","date":"2024-03-11T08:39:05.000Z","updated":"2024-03-11T09:05:38.769Z","comments":true,"path":"2024/03/11/合作开发中Git工作流程的细节/","link":"","permalink":"https://zade23.github.io/2024/03/11/%E5%90%88%E4%BD%9C%E5%BC%80%E5%8F%91%E4%B8%ADGit%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B%E7%9A%84%E7%BB%86%E8%8A%82/","excerpt":"","text":"仓库中各分支的职责 清晰的Commit 记录开发过程中,提交代码仓库的一些经验总结(持续更新……) 仓库中各分支的职责 master分支:master分支是存放发布上线的代码的默认位置。当某个功能经过验证和测试后,将develop分支合并到master分支,并为该版本打上相应的标签(例如x.0.0,表示某个大版本的编号)。master分支应该是稳定且经过充分测试的代码。 develop分支:develop分支是整体开发功能的主要分支。在开发阶段,所有功能的开发工作都应提交到develop分支。在功能开发完成后,通过测试和验证后,将develop分支合并到master分支,使功能正式上线。 扩展: 除了master和develop分支外,还可以存在其他类型的分支,如feature分支、bugfix分支等。这些分支可以用于并行开发不同的功能或修复bug,并在开发完成后合并到develop分支。 开发团队通常采用分支策略,例如Git Flow或GitHub Flow,来管理不同分支之间的合并和发布流程,以确保代码质量和版本控制的有效管理。 主分支(如master或main)通常用于存放稳定的、可发布的代码,而开发分支(如develop)则用于整体功能的开发和集成。 通过使用不同的分支,可以实现并行开发、合理管理代码版本、隔离功能开发和修复等,从而提高团队的协作效率和代码质量。 重要的是,分支之间的合并应该经过适当的测试和验证,以确保代码的稳定性和功能的正确性。 清晰的Commit 使用明确的动词:在提交信息的开头使用明确的动词来描述你的更改。例如,使用 “添加”、”修复”、”更新”、”重构” 等词语,以便其他人可以快速了解你的更改类型。 保持简洁:提交信息应该简洁明了,尽量避免冗长的描述。使用简洁的语句来概括你的更改,并在需要时提供详细信息。 提供相关上下文:除了简洁的概述外,确保提交信息提供足够的上下文信息,以便其他人能够理解你的更改原因和意图。如果有相关的问题、需求或讨论,可以引用相关的编号或链接。 遵循团队或项目规范:根据你所在的团队或项目的规范,使用统一的提交格式和命名约定。这样可以帮助整个团队保持一致的提交信息风格,便于阅读和管理。 避免无意义的提交信息:提交信息应该有实际的意义,避免使用模糊或不相关的描述。确保你的提交信息传达了有用的信息,而不仅仅是表明你进行了一次提交。 使用标准的提交类型:参考常见的提交类型(如前面所示),选择最适合你更改类型的提交类型。这有助于其他人快速了解你的更改类型,并且在版本控制工具中进行过滤和分类。 审查和校对:在提交之前,花一些时间审查和校对你的提交信息。确保拼写正确、语法清晰,并且信息准确传达你的更改。 常见的提交格式实例: 当然,以下是一些常见的提交格式示例: feat: 添加新功能或特性 1feat: 添加用户注册功能 fix: 修复 bug 1fix: 修复登录页面样式错位的 bug docs: 更新文档 1docs: 更新用户手册中的安装说明 style: 代码样式、格式调整 1style: 格式化整个项目的代码风格 refactor: 重构代码,既不修复 bug 也不添加新功能 1refactor: 重构用户管理模块的代码结构 test: 添加或修改测试代码 1test: 添加用户注册页面的单元测试 chore: 构建过程或辅助工具的变动 1chore: 更新构建脚本以支持新的依赖库 这一切的目的是为了让团队其他成员明确该提交所做的工作,同时也让自己回顾代码的时候明确自己的工作内容。","categories":[{"name":"Git Workflow","slug":"Git-Workflow","permalink":"https://zade23.github.io/categories/Git-Workflow/"}],"tags":[{"name":"Git","slug":"Git","permalink":"https://zade23.github.io/tags/Git/"}]},{"title":"更新GitLab仓库SSH","slug":"更新GitLab仓库SSH","date":"2024-03-07T02:09:37.000Z","updated":"2024-03-11T08:41:35.516Z","comments":true,"path":"2024/03/07/更新GitLab仓库SSH/","link":"","permalink":"https://zade23.github.io/2024/03/07/%E6%9B%B4%E6%96%B0GitLab%E4%BB%93%E5%BA%93SSH/","excerpt":"","text":"介绍 问题分析 问题解决 介绍早上开工,在拉取代码仓库的时候,发现SSH过期,于是更新一下SSH为永久,并做一个记录。SSH过期命令行显示如下: 123456789101112$ git pullremote:remote: ========================================================================remote:remote: Your SSH key has expired.remote:remote: ========================================================================remote:fatal: Could not read from remote repository.Please make sure you have the correct access rightsand the repository exists. 问题分析直接将问题送给poe,让AI分析解答,根据分析结果一步步处理即可解决。结果如下: 问题解决 生成新的SSH密码对。ssh-keygen -t rsa -b 4096 -C "your_email@example.com",该命令将生成一个 4096 位的 RSA 密钥对,替换邮箱地址为你GitLab注册邮箱。 之后会有两次询问你保存位置和输入密码的选项,点击 Enter 键跳过选项即可。显示结果如下: 复制生成的SSH,window使用: 1clip < ~/.ssh/id_rsa.pub 找到GitLab中的SSH Keys一栏,粘贴,保存: 可以拉取仓库了","categories":[{"name":"Git Workflow","slug":"Git-Workflow","permalink":"https://zade23.github.io/categories/Git-Workflow/"}],"tags":[{"name":"Git","slug":"Git","permalink":"https://zade23.github.io/tags/Git/"}]},{"title":"conda环境报错解决:invalid choice: 'activate' ","slug":"conda环境报错解决:invalid-choice-activate","date":"2023-11-22T08:04:41.000Z","updated":"2024-03-14T03:02:38.054Z","comments":true,"path":"2023/11/22/conda环境报错解决:invalid-choice-activate/","link":"","permalink":"https://zade23.github.io/2023/11/22/conda%E7%8E%AF%E5%A2%83%E6%8A%A5%E9%94%99%E8%A7%A3%E5%86%B3%EF%BC%9Ainvalid-choice-activate/","excerpt":"","text":"介绍新部署的服务器上进行LLM模型的微调工作,在配置Anaconde环境后输入环境启动命令 conda activate ,出现从来没有见过的报错:anaconda conda: error: argument command: invalid choice when trying to update packages 问题分析环境问题,直接Google。最终在 GitHub 里官方仓库和 Stack Overflow 上找到一致的答复:安装Anaconde后没有进行初始化。 问题解决 终端运行 conda init zsh 之后重启 shell/Terminal (不重启依然报错) 终端运行 conda activate env_name 即可","categories":[{"name":"Deeplearning","slug":"Deeplearning","permalink":"https://zade23.github.io/categories/Deeplearning/"}],"tags":[{"name":"conda","slug":"conda","permalink":"https://zade23.github.io/tags/conda/"},{"name":"environment","slug":"environment","permalink":"https://zade23.github.io/tags/environment/"}]},{"title":"玩转Docker学习笔记","slug":"玩转Docker学习笔记","date":"2023-11-07T07:30:56.000Z","updated":"2024-03-11T10:00:59.954Z","comments":true,"path":"2023/11/07/玩转Docker学习笔记/","link":"","permalink":"https://zade23.github.io/2023/11/07/%E7%8E%A9%E8%BD%ACDocker%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/","excerpt":"","text":"镜像相关指令 容器相关指令 网络相关指令 数据卷相关指令 Docker Compose相关指令(使用docker-compose) 通过 DockerFile 构建 Docker Image 镜像相关指令 docker pull <image>:从远程仓库拉取一个镜像或仓库到本地。 docker build -t <tag> .:使用当前目录的Dockerfile构建一个新的镜像。 docker images:列出本地存储的镜像。 docker rmi <image>:删除一个或多个本地的镜像。 docker tag <image> <new_tag>:为镜像添加一个新的标签。 容器相关指令 docker run <image>:创建一个新的容器并运行一个命令。 docker ps:列出当前运行的容器。 docker ps -a:列出所有容器,包括未运行的。 docker stop <container>:停止一个或多个正在运行的容器。 docker start <container>:启动一个或多个已停止运行的容器。 docker restart <container>:重启容器。 docker rm <container>:删除一个或多个容器。 docker logs <container>:获取容器的日志。 docker exec -it <container> <command>:在运行的容器中执行命令,通常用于进入容器。 网络相关指令 docker network ls:列出所有网络。 docker network create <options> <network_name>:创建一个新的网络。 docker network rm <network>:删除一个或多个网络。 docker network connect <network> <container>:连接一个容器到一个网络。 docker network disconnect <network> <container>:断开容器与网络的连接。 数据卷相关指令 docker volume create <name>:创建一个新的卷。 docker volume ls:列出所有卷。 docker volume rm <volume>:删除一个或多个卷。 docker volume inspect <volume>:显示详细的卷信息。 Docker Compose相关指令(使用docker-compose) docker-compose up:在后台启动并运行整个应用。 docker-compose down:停止并移除容器,网络,图像和挂载卷。 docker-compose build:构建或重建服务关联的镜像。 docker-compose logs:查看服务的日志输出。 这些命令代表了Docker操作的基础,但是实际使用中可能还会遇到更复杂的场景和高级特性。建议通过官方文档或其他学习资源深入理解每个命令的用法和选项。 通过 DockerFile 构建 Docker Image一个 Image 是通过一个 DockerFile 定义的,然后使用 docker build 命令构建它。 DockerFile 中的每一条命令的执行结果都会成为 Image 中的一个 Layer。 这里,我们通过 Build 一个镜像,来观察 Image 的分层机制: 123456789101112131415161718192021222324252627# 使用官方的Python运行时环境作为基础镜像FROM python:2.7-slim# 设置工作目录为容器内的/app。如果目录不存在,Docker会自动为你创建这个目录。WORKDIR /app# 将当前目录(Dockerfile所在目录)的内容复制到容器内的/app目录中。# 这意味着你的应用代码和依赖文件(比如requirements.txt)都会被复制进去。COPY . /app# 利用RUN命令执行pip安装命令来安装requirements.txt中列出的所有依赖。# 这里使用了--trusted-host选项来指定可信的PyPI主机,避免SSL证书验证问题。RUN pip install --trusted-host pypi.python.org -r requirements.txt# 使用EXPOSE指令来告诉Docker容器内的应用将会在80端口上监听连接。# 需要注意的是,EXPOSE指令本身不会使容器的端口对外界开放,# 它更多的是一种文档化的作用,真正的端口映射需要在运行容器时通过docker run的-p选项来指定。EXPOSE 80# 使用ENV指令设置一个环境变量。这里定义了一个名为NAME的环境变量,值为World。# 环境变量可以在容器运行时被应用程序读取,用于配置应用行为。ENV NAME World# 使用CMD指令指定容器启动时运行的命令。# 这里的命令是“python app.py”,即使用Python解释器来运行app.py脚本。# CMD的主要作用是指定容器的默认执行命令。如果在docker run命令后面指定了其他命令,CMD指定的命令将被覆盖。CMD ["python", "app.py"] 这份Dockerfile基本上覆盖了构建一个简单Python应用的Docker镜像所需的所有步骤。从选择基础镜像开始,设置工作目录,复制应用代码,安装依赖,到最后指定运行时的命令和暴露的端口,每一步都为镜像的构建提供了必要的指令和配置。 最终的构建结果如下: 1234567891011121314151617181920212223root@rds-k8s-18-svr0:~/xuran/exampleimage# docker build -t hello ./Sending build context to Docker daemon 5.12 kBStep 1/7 : FROM python:2.7-slim ---> 804b0a01ea83Step 2/7 : WORKDIR /app ---> Using cache ---> 6d93c5b91703Step 3/7 : COPY . /app ---> Using cache ---> feddc82d321bStep 4/7 : RUN pip install --trusted-host pypi.python.org -r requirements.txt ---> Using cache ---> 94695df5e14dStep 5/7 : EXPOSE 81 ---> Using cache ---> 43c392d51dffStep 6/7 : ENV NAME World ---> Using cache ---> 78c9a60237c8Step 7/7 : CMD python app.py ---> Using cache ---> a5ccd4e1b15dSuccessfully built a5ccd4e1b15d 下面是对这些信息每一步的详细解释: Sending build context to Docker daemon 5.12 kB这一行表示Docker客户端正在将构建上下文发送给Docker守护进程。构建上下文是指Dockerfile所在目录的内容,Docker会将这些内容打包发送给守护进程。这里的大小是5.12kB,表示你的应用代码和依赖文件等总共大小。 Step 1/7 : FROM python:2.7-slim这是Dockerfile中的第一步,它基于python:2.7-slim这个镜像来构建新的镜像。---> 804b0a01ea83是基础镜像的ID。 Step 2/7 : WORKDIR /app设置工作目录为/app。如果不存在,Docker会自动创建这个目录。---> Using cache ---> 6d93c5b91703表示这一步使用了缓存,6d93c5b91703是这一层的ID。 Step 3/7 : COPY . /app将构建上下文(Dockerfile所在的目录)的内容复制到容器内的/app目录。---> Using cache ---> feddc82d321b表明这一步也使用了缓存。 Step 4/7 : RUN pip install –trusted-host pypi.python.org -r requirements.txt在容器内执行pip install命令,安装requirements.txt文件中列出的Python依赖包。---> Using cache ---> 94695df5e14d说明这一步同样使用了缓存。 Step 5/7 : EXPOSE 81通知Docker容器在运行时将会监听81端口。注意这里与前面提到的Dockerfile中的EXPOSE 80不同,可能是因为Dockerfile被修改了但构建输出没有更新。---> Using cache ---> 43c392d51dff表示使用了缓存。 Step 6/7 : ENV NAME World设置环境变量NAME的值为World。这个环境变量可以在容器运行时被应用程序使用。---> Using cache ---> 78c9a60237c8也显示这一步使用了缓存。 Step 7/7 : CMD [“python”, “app.py”]指定容器启动时默认执行的命令。这里是运行app.py脚本。---> Using cache ---> a5ccd4e1b15d意味着这一步也利用了之前的构建缓存。 Successfully built a5ccd4e1b15d最后,显示了成功构建的镜像ID为a5ccd4e1b15d。 在这个过程中,Using cache表明Docker发现之前的构建步骤与当前的完全一致,因此它复用了之前的结果来加快构建过程。如果你想要强制Docker重新执行每个步骤而不使用缓存,可以在构建时添加--no-cache选项。 参考链接: Docker 101 Tutorial Docker原理(图解+秒懂+史上最全)","categories":[],"tags":[{"name":"Docker","slug":"Docker","permalink":"https://zade23.github.io/tags/Docker/"}]},{"title":"pytorch-tutorial-official","slug":"pytorch-tutorial-official","date":"2023-10-31T08:49:36.000Z","updated":"2024-03-14T03:02:15.673Z","comments":true,"path":"2023/10/31/pytorch-tutorial-official/","link":"","permalink":"https://zade23.github.io/2023/10/31/pytorch-tutorial-official/","excerpt":"","text":"Pytorch 基础 快速入门 处理数据 构建模型 在 PyTorch 中构建神经网络 模型层 Model Layers nn.Flatten nn.Linear nn.ReLU nn.Sequential nn.Softmax 模型的参数 张量 Tensor 初始化张量 直接来自数据 来自另一个 Tensor 使用随机值或常量 张量的属性 张量的操作 类似 NumPy 的索引和切片操作 Tensor 之间的连接 张量的算术运算 单一元素张量 原地操作(不通过返回的方式进行原地修改) Tensor 和 Numpy 二者转换 Tensor2NumPy_array NumPy_array2Tensor 数据集的相关操作 数据加载 数据集迭代和数据可视化 创建自定义的数据集 _init_ _len_ _getitem_ 准备数据并使用 DataLoader 进行训练 遍历 DataLoader 转换 ToTensor() Lambda 转换 (核心内容)自动微分(TORCH.AUTOGRAD) 张量、函数、计算图 梯度计算 禁用梯度追踪 计算图部分扩展阅读 可选阅读:张量梯度 和 雅阁比积(Jacobian_Products) (重要)优化模型参数 先决条件代码 超参数 优化循环 全过程数据跟踪 保存、加载和使用模型 保存/加载模型权重 通过模型的形状参数进行保存/加载 在 PyTorch 中保存和加载常规 Checkpoint 介绍 设置 步骤 1.导入加载数据所需要的库 2.定义和初始化神经网络 3.初始化优化器 4.保存常规检查点 5.加载常规检查点 从 Checkpoint 中加载 nn.Module 的技巧 活用 torch.load(mmap = True) 活用 torch.device("meta") 活用 load_state_dict(assign = True) 结论 Pytorch 基础 https://pytorch.org/tutorials/beginner/basics/intro.html 大多数机器学习工作流: 处理数据 创建模型 优化模型参数 保存训练后模型 通过 Pytorch 基础部分的内容,读者可以完整的走完一整个MachineLearning的工作流,若读者对其中某个环节不理解或感兴趣,针对这些工作流中的每一个环节都有相关的扩展阅读链接。 我们将使用 FashionMNIST 数据集训练一个神经网络,该神经网络预测输入图像是否属于一下类别之一:T恤/上衣、裤子、套头衫、连衣裙、外套、凉鞋、成山、运动鞋、包包、靴子。(是个多分类任务) 快速入门 https://pytorch.org/tutorials/beginner/basics/quickstart_tutorial.html 本节会快速走完一个机器学习多分类的Demo,以此快速了解流程中必要的基本ML相关API。 处理数据Pytorch 中有两个用于处理数据的子库 torch.utils.data.DataLoader 和 torch.utils.data.Dataset.Dataset。顾名思义,Dataset 存储样本及其相应的标签,并将 DataLoader 可迭代对象包装在 Dataset 中。 12345import torchfrom torch import nnfrom torch.utils.data import DataLoaderfrom torchvision import datasetsfrom torchvison.transforms import ToTensor 通过上面的引用(import),我们可以发现:Pytorch 中有非常多的子库,这些子库专注于某一特定的领域,例如: TorchText, TorchVision, 和 TorchAudio, 这些所有子库中都包含相应的数据集。 本次教程中我们使用 TorchVision 数据集。 该 torchvision,.datasets 模块包含 Dataset 来自现实世界中的视觉图像数据,最经典的有:CIFAT,COCO(full list here) 本次教程中我们使用 FashionMNIST 数据集。每个 TorchVison 下的 Dataset 都包含两个参数:transform 和 target_transform 分别用来修改样本与打标签。 123456789101112131415# 从公开数据集中读取训练数据training_data = datasets.FashionMNIST( root = "data", train = True, download = True, transform = ToTensor(),)# 从公开数据集中读取测试数据test_data = datasets.FashionMNIST( root = "data", train = False, download = True, transform = Totensor()) 下图是在 colab 中运行上面程序块的输出结果: 至此为止,通过上面的工作,我们将 Dataset 作为参数传递给了 DataLoader 。同时封装了相关数据集作为一个可迭代的对象,支持自动批处理、采样、洗牌(shuffling)、和多进程的数据加载。 下一步中,我们定义 batch_size = 64 ,即 dataloader 可迭代中的每个元素都将返回一批含有64个特征的标签。 12345678910batch_size = 64# 创建数据读取工具train_dataloader = DataLoader(tarain_data, batch_size = batch_size)test_dataloader = DataLoader(tast_data, batch_size = batch_size)for X, y in dataloader: print(f"Shape of X [N, C, H, W]: {X.shape}") print(f"Shape of y []: {y.shape} {y.dtpye}") break 输出: 12Shape of X [N, C, H, W]: torch.Size([64, 1, 28, 28])Shape of y: torch.Size([64]) torch.int64 关于 loading data in PyTorch 的详细说明 构建模型为了在 Pytorch 中定义神经网络,我们创建一个继承自 nn.Module 的类。 我们通过 __init__ 函数定义神经网络的层,并指明数据如何通过 forward 函数进入神经网络层。 在设备允许的情况下,推荐使用GPU来加速神经网络的运算操作。 代码实现: 123456789101112131415161718192021222324252627282930# 获取用于训练的设备(cpu/gpu/mps)device = ( "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu")print(f"Using {device} device")# 自定义神经网络的模型结构class NeuralNetwork(nn.Module): def __init__(self): super().__init__() self.flatten = nn.Flatten() self.linear_relu_stack = nn.Sequential( nn.Linear(28*28, 512), nn.ReLU(), nn.Linear(512, 512), nn.ReLU(), nn.Linear(512, 10) ) def forward(self, x): x = self.flatten(x) logits = self.linear_relu_stack(x) return logitsmodel = NeuralNetwork().to(device)print(model) 打印结果: 1234567891011Using cpu deviceNeuralNetwork( (flatten): Flatten(start_dim=1, end_dim=-1) (linear_relu_stack): Sequential( (0): Linear(in_features=784, out_features=512, bias=True) (1): ReLU() (2): Linear(in_features=512, out_features=512, bias=True) (3): ReLU() (4): Linear(in_features=512, out_features=10, bias=True) )) 在 PyTorch 中构建神经网络 这一部分是对‘构建模型’部分的一点补充说明,也是 PyTorch 官网教程中的扩展阅读部分 神经网络是由多个对数据进行操作的层/模型组合而成的。torch.nn 命名空间几乎已经提供了构建一个神经网络所需要用到的所有模块。 所有模块都在 PyTorch 下的子块 nn.Module 中提供。 基于这样的结构化嵌套模块,整个神经网络可以自由的进行构建和管理复杂的架构。 在上面的代码块中,我们通过 NeuralNetwork 函数定义了一个神经网络模型 model。 为了使用该模型,我们将输入数据传递给它。这个操作将执行 forward 操作和一些后台操作。 请记住:不要直接使用 model.forward() ! 通过输入操作调用模型,最后将返回一个二维张量,其中 dim = 0 对应于每个类别的 10 个原始预测输出,dim = 1 对应与每个输出的单个值。 我们可以通过 nn.Softmax 模块实例传递对结果预测的概率来进行最终预测概率的判断。 12345X = torch.rand(1, 28, 28, device = device)logits = model(X)pred_probab = nn.Softmax(dim = 1)(logits)y_pred = pred_probab.argmax(1)print(f"Predicted class: {y_pred}") 打印结果: 1Predicted class: tensor([7], device = 'cuda:0') 模型层 Model Layers分解 FashionMNIST 模型中的各层。为了说明这一点,我们通过获取一个包含 3 张大小为 28*28 的小批量图像样本,看看当数据传递到网络时会发生什么。 12input_image = torch.rand(3, 28, 28)print(input_image.size()) 打印输出: 1torch.Size([3, 28, 28]) nn.Flatten初始化 nn.Flatten 层,将每个2D 28*28 图像转换成包含 784 个像素值的连续数组(保持小批量尺寸(dim = 0)) 123flatten = nn.Flatten()flat_image = flatten(input_image)print(flat_image.size()) 打印输出: 1torch.Size([3, 784]) nn.Linear线性层模块通过输入的权重w和偏差值b进行线性变换。 123layer1 = nn.Linear(in_features = 28*28, out_features = 20)hidden1 = layer1(flat_image)print(hidden1.size()) 打印输出: 1torch.Size([3, 20]) nn.ReLU非线性激活函数可以在模型的输入输出之间创建复杂的映射关系。激活函数通过引入非线性的变换帮助神经网络学习各种现象。 在实例模型中,我们在线性层之间使用ReLU激活函数。但还有其他激活函数可以在模型的线性层中间作为激活函数使用,详情参考:激活函数-wiki 123print(f"Before ReLU: {hidden1}\\n\\n")hidden1 = nn.ReLU()(hidden1)print(f"Aftr RelU: {hidden1}") 打印输出: 1234567891011121314151617181920Before ReLU: tensor([[ 0.4158, -0.0130, -0.1144, 0.3960, 0.1476, -0.0690, -0.0269, 0.2690, 0.1353, 0.1975, 0.4484, 0.0753, 0.4455, 0.5321, -0.1692, 0.4504, 0.2476, -0.1787, -0.2754, 0.2462], [ 0.2326, 0.0623, -0.2984, 0.2878, 0.2767, -0.5434, -0.5051, 0.4339, 0.0302, 0.1634, 0.5649, -0.0055, 0.2025, 0.4473, -0.2333, 0.6611, 0.1883, -0.1250, 0.0820, 0.2778], [ 0.3325, 0.2654, 0.1091, 0.0651, 0.3425, -0.3880, -0.0152, 0.2298, 0.3872, 0.0342, 0.8503, 0.0937, 0.1796, 0.5007, -0.1897, 0.4030, 0.1189, -0.3237, 0.2048, 0.4343]], grad_fn=<AddmmBackward0>)After ReLU: tensor([[0.4158, 0.0000, 0.0000, 0.3960, 0.1476, 0.0000, 0.0000, 0.2690, 0.1353, 0.1975, 0.4484, 0.0753, 0.4455, 0.5321, 0.0000, 0.4504, 0.2476, 0.0000, 0.0000, 0.2462], [0.2326, 0.0623, 0.0000, 0.2878, 0.2767, 0.0000, 0.0000, 0.4339, 0.0302, 0.1634, 0.5649, 0.0000, 0.2025, 0.4473, 0.0000, 0.6611, 0.1883, 0.0000, 0.0820, 0.2778], [0.3325, 0.2654, 0.1091, 0.0651, 0.3425, 0.0000, 0.0000, 0.2298, 0.3872, 0.0342, 0.8503, 0.0937, 0.1796, 0.5007, 0.0000, 0.4030, 0.1189, 0.0000, 0.2048, 0.4343]], grad_fn=<ReluBackward0>) nn.Sequentialnn.Sequential是一个有序的模块容器。数据按照定义好的方式顺序的通过当前模块。您可以使用顺序容器来组合一个“快捷网络” ,例如:seq_modules . 12345678seq_modules = nn.Sequential( flatten, layer1, nn.ReLU(), nn.Linear(20, 10))input_image = torch.rand(3, 28, 28)logits = seq_modules(input_image) nn.SoftmaxSoftmax激活函数通常用在最后一个线性层,用来返回对数区间介于 [-infty, infty] 中的原始值,这些值最终被传递给 nn.Softmax 模块。 Softmax 激活函数将对应输出区间范围缩放在 [0, 1] 之间,表示模型对每个类别的预测概率。其中,dim 中所有参数指示值求和应该为 1 。 12softmax = nn.Softmax(dim = 1)pred_probab = softmax(logits) 模型的参数神经网络往往非常的复杂,在整个网络的构建过程中,如果可以便捷的将每个部分表示出来,对于训练过程中的优化和修改相对的权重与偏差等都会有非常大的帮助。 子类化 nn.Module 模块可以帮助我们解决这个问题,该模块会自动跟踪模型对象中定义的所有字段,并使用模型的 parameters() 函数或 named_parameters() 函数方法访问所有参数。 在本次示例中,我们遍历每个参数,并预览它们的所有数值参数。 1234print(f"Model structure: {model}\\n\\n")for name, param in model.named_parameters(): print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \\n") 打印输出结果: 1234567891011121314151617181920212223242526272829Model structure: NeuralNetwork( (flatten): Flatten(start_dim=1, end_dim=-1) (linear_relu_stack): Sequential( (0): Linear(in_features=784, out_features=512, bias=True) (1): ReLU() (2): Linear(in_features=512, out_features=512, bias=True) (3): ReLU() (4): Linear(in_features=512, out_features=10, bias=True) ))Layer: linear_relu_stack.0.weight | Size: torch.Size([512, 784]) | Values : tensor([[ 0.0273, 0.0296, -0.0084, ..., -0.0142, 0.0093, 0.0135], [-0.0188, -0.0354, 0.0187, ..., -0.0106, -0.0001, 0.0115]], device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values : tensor([-0.0155, -0.0327], device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.2.weight | Size: torch.Size([512, 512]) | Values : tensor([[ 0.0116, 0.0293, -0.0280, ..., 0.0334, -0.0078, 0.0298], [ 0.0095, 0.0038, 0.0009, ..., -0.0365, -0.0011, -0.0221]], device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.2.bias | Size: torch.Size([512]) | Values : tensor([ 0.0148, -0.0256], device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.4.weight | Size: torch.Size([10, 512]) | Values : tensor([[-0.0147, -0.0229, 0.0180, ..., -0.0013, 0.0177, 0.0070], [-0.0202, -0.0417, -0.0279, ..., -0.0441, 0.0185, -0.0268]], device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values : tensor([ 0.0070, -0.0411], device='cuda:0', grad_fn=<SliceBackward0>) 张量 Tensor通过一张图初步了解常见的多维空间数据的命名方式(来源:PyTorch in 100 Seconds) Tensor 又称 张量,是一种专门的数据结构,与数组和矩阵非常相似。在 PyTorch 中,我们使用张量对比模型的输入和输出以及模型的参数进行编码。 Tensors 类似于 NumPy 中的ndarrays,不同之处在于张量可以在 GPU 或其他硬件加速器上运行。事实上,张量和 NumPy 数组通常可以共享相同的底层内存,从而消除了复制数据的需要。 张量也针对自动微分进行了优化。 12import torchimport numpy as np 初始化张量一般可以通过如下方式初始化 Tensor: 直接通过数据创建 通过NumPy创建 通过继承另一个Tensor的形状和数据类型 使用随机值或常量 下面分别进行介绍: 直接来自数据张量可以直接从已有的数据中创建,数据类型是自动推断的。 12data = [[1, 2], [3, 4]]x_data = torch.tensor(data) 来自另一个 Tensor新建的张量保留参考张量的部分参数(形状,数据类型),除非用显式的方式直接覆盖。 12345x_ones = torch.ones_like(x_data) # retains the properties of x_dataprint(f"Ones Tensor: \\n {x_ones} \\n")x_rand = torch.rand_like(x_data, dtype = torch.float) # overrides the datatype of x_dataprint(f"Random Tensor: \\n {x_rand} \\n") 打印输出: 1234567Ones Tensor: tensor([[1, 1], [1, 1]])Random Tensor: tensor([[0.8823, 0.9150], [0.3829, 0.9593]]) 使用随机值或常量shape 是张量维度的元组表达式。在下面的函数中,它决定了输出张量的维度: 123456789# 定义 Tensor 的维度shape = (2, 3, )rand_tensor = torch.rand(shape)ones_tensor = torch.ones(shape)zeros_tensor = torch.zeros(shape)print(f"Random Tensor: \\n {rand_tensor} \\n")print(f"Ones_Tensor: \\n {ones_tensor} \\n")print(f"Zeros_Tensor: \\n {zeros_tensor}") 打印输出结果: 1234567891011Random Tensor: tensor([[0.3904, 0.6009, 0.2566], [0.7936, 0.9408, 0.1332]])Ones Tensor: tensor([[1., 1., 1.], [1., 1., 1.]])Zeros Tensor: tensor([[0., 0., 0.], [0., 0., 0.]]) 张量的属性Tensor 的属性描述了它们的形状、数据类型 和 存储它们的设备。 tensor.shape tensor.dtype tensor.device 12345tensor = torch.rand(3, 4)print(f"Shape of tensor: {tensor.shape}")print(f"Dtype of tensor: {tensor.dtype}")print(f"Device of tensor: {tensor.device}") 打印输出: 123Shape of tensor: torch.Size([3, 4])Datatype of tensor: torch.float32Device tensor is stored on: cpu 张量的操作科学计算是深度学习领域的根本!PyTorch提供了 100+ 张量运算操作,包括算术运算、线性代数运算、矩阵运算(转职、索引、切片)、采样等。 PyTorch 中的所有逻辑运算都可以通过GPU进行加速运算 123# 将运行设备选择为 GPU (如果你有的话)if torch.cuda.is_available(): tensor = tensor.to("cuda") 类似 NumPy 的索引和切片操作123456Tensor= torch.ones(4, 4)print(f"First row: {tensor[0]}")print(f"First column: {tensor[:, 0]}")print(f"last column: {tensor[..., -1]}")tensor[:, 1] = 0print(tensor) 打印输出: 1234567First row: tensor([1., 1., 1., 1.])First column: tensor([1., 1., 1., 1])Last column: tensor([1., 1., 1., 1])tensor([[1., 0., 1., 1.], [1., 0., 1., 1.], [1., 0., 1., 1.], [1., 0., 1., 1.]]) Tensor 之间的连接torch.cat 可以用于连接指定维度的张量,拥有同样功能的另一个算子是 torch.stack_ (参考:torch.stack_)。 12t1 = torch.cat([tensor, tensor, tensor], dim = 1)print(t1) 张量的算术运算1234567891011121314# 计算两个张量之间的矩阵乘法。其中,y1, y2, y3 拥有相同的参数值# `tensor.T` 返回张量的转置y1 = tensor @ tensor.Ty2 = tensor.matmul(tensor.T)y3 = torch.rand_like(y1)torch.matmul(tensor, tensor.T, out = y3)# 将张量中的元素逐个相乘。z1, z2, z3 具有相同的值z1 = tensor * tensorz2 = tensor.mul(tensor)z3 = torch.rand_like(tensor)torch.mul(tensor, tensor, out = z3) 打印输出: 1234tensor([[1., 0., 1., 1], [1., 0., 1., 1], [1., 0., 1., 1], [1., 0., 1., 1]]) 单一元素张量如果你有一个单一元素的张量,例如希望通过将张量的所有值聚合为一个值,那么可以使用 item() 将其转换为 python 数值: 123agg = tensor.sum()agg_item = agg.item()print(agg_item, type(agg_item)) 打印输出结果: 112.0 <class 'float'> 原地操作(不通过返回的方式进行原地修改)将结果存储在操作数中的操作成为原地操作。它们由 _ 后缀表示。例如:x.copy() 、 x.t_() ,都将直接修改 x。 123print(f"{tensor} \\n")tensor.add_(5)print(tensor) 打印输出: 123456789tensor([[1., 0., 1., 1.], [1., 0., 1., 1.], [1., 0., 1., 1.], [1., 0., 1., 1.]])tensor([[6., 5., 6., 6.], [6., 5., 6., 6.], [6., 5., 6., 6.], [6., 5., 6., 6.]]) Tensor 和 Numpy 二者转换位于 CPU 位置上的 Numpy 数组与 Tensor 可以共享同一个底层的内容空间,更改一个 tensor 会同时修改另一个 tensor 。 Tensor2NumPy_array1234t = torch.ones(5)print(f"t: {t}")n = t.numpy()print(f"n: {n}") 打印对比结果(t 代表 tensor;n 代表 numpy): 12t: tensor([1., 1., 1., 1., 1.])n: [1. 1. 1. 1. 1.] 下一步,改变 tensor 中的值,同时观察 NumPy 中值的变化: 123t.add_(1)print(f"t: {t}")print(f"n: (n)") 打印对比结果(t 代表 tensor;n 代表 numpy): 12t: tensor([2., 2., 2., 2., 2.])n: [2. 2. 2. 2. 2.] NumPy_array2Tensor12n = np.ones(5)t = torch.from_numpy(n) NumPy 数组中的更改回反映在张量(tensor)中 123np.add(n, 1, out = n)print(f"t: {t}")print(f"n: {n}") 12t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64)n: [2. 2. 2. 2. 2.] 数据集的相关操作从代码的架构设计上考虑,无论是出于可读性考虑还是出于代码逻辑的模块化管理考虑,我们都希望数据集代码与模型训练代码分离。 在数据预加载上,PyTorch 提供了两个功能函数 torch.utils.data.DataLoader 和 torch.utils.data.Dataset 分别读取预加载的数据和自己的数据。 Dataset 存储样本和对应的标签,并在 DataLoader 范围内包装成一个可迭代对象 Dataset 以便轻松访问样本。 在 PyTorch 函数库中预先提供好了很多可供预加载的数据集(例如:FashionMNIST),这些数据集借助 torch.utils.data.Dataset 子类化,并实现位于特定数据的函数。它们可用于对模型进行原型设计和基准测试。可以通过如下链接访问:Image Datasets,Text Datasets,Audio Datasets 数据加载下面例子是从 TorchVision 加载 Fashion-MNIST 数据集的示例。Fashion-MNIST 由 60000 个训练样本和 10000 个测试样本组成。每个示例都包含一个 28*28 灰度图像和一个来自 10 个类之一的关联标签。 我们通过如下几个参数,对 FashionMNIST Dataset 数据集进行加载: root 是存储训练/测试数据的根目录 train 指定训练或测试数据集 download = TRUE 允许从互联网上搜索并下载数据集,前提是 root 路径下的数据集文件不存在 transform 和 target_transform 分别执行 标定特征 和 标注转换 12345678910111213141516171819import torchfrom torch.utils.data import Datasetfrom torchvision import datasetsfrom torchvision.transforms import ToTensorimport matplotlib.pyplot as plttraining_data = datasets.FashionMNIST( root = "data", train = True, download = True, transform = ToTensor())test_data = datasets.FashionMNIST( root = "data", train = False, download = True, transform = ToTensor()) 数据集迭代和数据可视化使用 Datasets ,我们可以实现像 Python 中列表那样的手动索引 training_data[index]。 使用 matplotlib 可视化训练数据集中的样本进行展示。 1234567891011121314151617181920212223labels_map = { 0: "T-Shirt", 1: "Trouser", 2: "Pullover", 3: "Dress", 4: "Coat", 5: "Sandal", 6: "Shirt", 7: "Sneaker", 8: "Bag", 9: "Ankle Boot",}figure = plt.figure(figsize = (8, 8))cols, rows = 4, 4for i in range(1, cols * rows + 1): sample_idx = torch.randint(len(training_data), size(1,)).item() image, lable = training_data[sample_idx] figure.add_subplot(rows, cols, i) plt.title(lables_map[label]) plt.axis("off") plt.imshow(img.squeeze(), cmap = "gray")plt.show() # 打印出来的输出为数据集图像示例展示 打印输出: 创建自定义的数据集自定义数据集必须含有三个函数: __init__ __len__ __gititem__ 通过下面这个示例实现了解相关函数的使用方法。 FashionMNIST 图像存储在目录 img_dir,其自身的标签单独存储在CSV文件 annotations_file 中 先看代码块部分: 1234567891011121314151617181920212223import osimport pandas as pdimport torchvision.io as read_imageclass CustomImageDataset(Dataset): def __init__(self, annotations_file, img_dir, transform = None, target_transform = None): self.img_labels = pd.read_csv(annotations_file) self.img_dir = img_dir self.transform = transform self.target_transform = target_transform def __len__(self): return len(self.img_labels) def __getitem__(self): img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0]) image = read_image(img_path) label = self.img_labels.iloc[idx, 1] if self.transform: image = self.transform(image) if self.target_transform: label = self.target_transform(label) return image, label _init_init 作为初始化函数,在实例化 Dataset 对象时运行一次。 我们初始化包含图像、注释文件和两个转换的目录 labels.csv 文件的展示效果如下所示: 1234tshirt1.jpg, 0tshirt2.jpg, 0......ankleboot999.jpg, 9 12345def __init__(self, annotations_file, img_dir, transform = None, target_transform = None): self.img_labels = pd.read_csv(annotations_file) self.img_dir = img_dir self.transform = transform self.target_transform = target_transform _len_len 函数返回数据集中的样本数。 12def __len__(self): return len(self.img_labels) _getitem_getitem 函数从给定索引目录的 idx 处返回数据集中的样本。 根据索引,它识别图像在磁盘上的位置,使用 read_image,从 csv 数据中检索相应的标签self.img_labels,调用它们的转换函数(前提是支持转换),并在元组中返回张量图像和相应的标签。 123456789def __getitem__(self): img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0]) image = read_image(img_path) label = self.img_labels.iloc[idx, 1] if self.transform: image = self.transform(image) if self.target_transform: label = self.target_transform(label) return image, label 准备数据并使用 DataLoader 进行训练检索 Dataset 数据集的特征,并一次标记一个样本。 在训练模型的过程中,我们通常希望通过“小批量”的方式传递样本,在新一轮 epoch 下数据 reshuffle(洗牌) 减少模型过拟合,并使用 Python 中的 multiprocessing 函数来加快数据检索的速度。 DataLoader 是一个可迭代的对象,它通过一个简单的 API 为我们抽象了这种复杂性。 1234from torch.utils.data import DataLoadertrain_dataloader = DataLoader(training_data, batch_size = 64, shuffle = True)test_dataloader = DataLoader(test_data, batch_size = 64, shuffle = True) 遍历 DataLoader通过上面的步骤,我们已经将数据集加载到了 DataLoader 并可以根据我们的需要来遍历该数据集。 后续程序中每一次迭代都会返回一批 train_features 和 train_labels,每批结果中都包含 batch_size = 64 特征和标签。 在上面代码块中,我们指定了 shuffle = True ,在我们遍历了所有批次后,数据会被洗牌(目的是为了更细粒度地控制数据加载顺序,可参考Samplers) 123456789# Display image and label.train_features, train_labels = next(iter(train_dataloader))print(f"Feature batch shape: {train_features.size()}")print(f"Labels batch shape: {train_labels.size()}")img = train_features[0].squeeze()label = train_labels[0]plt.imshow(img, cmap = "gray")plt.show() # 输出数据集中的图片print(f"Label: {label}") 转换数据的格式不总是按照训练机器学习算法所需要的格式出现的,因此我们需要通过转换来对数据进行一系列操作使得其适合于机器学习任务。 所有 TorchVision 数据集都具有两个参数: transform 用于修改标签 target_transform 接受包含转换逻辑的可调用对象 在 torchvision.transforms 中提供了几个开箱即用的转换格式。 FashionMNIST 特征采用 PIL Image 格式,标签为整数。 在训练任务开始前,我们需要将特征处理为归一化之后的张量。 为了进行这些转换,需要使用 ToTensor 和 Lambda 函数方法。 1234567891011import torchfrom torchvision import datasetsfrom torchvision.transforms import ToTensor, Lambdads = datasets.FashionMNIST( root = "data", train = True, download = True, transform = ToTensor(), target_transform = Lambda(lambda y: torch.zero(10, dtype = torch.float).scatter_(0, torch.tensor(y), value = 1))) 打印输出: 123456789101112131415161718192021222324252627282930313233343536373839404142434445Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gzDownloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to data/FashionMNIST/raw/train-images-idx3-ubyte.gz 0%| | 0/26421880 [00:00<?, ?it/s] 0%| | 65536/26421880 [00:00<01:12, 362364.70it/s] 1%| | 229376/26421880 [00:00<00:38, 680532.51it/s] 3%|2 | 786432/26421880 [00:00<00:11, 2194389.90it/s] 7%|7 | 1933312/26421880 [00:00<00:05, 4185622.75it/s] 17%|#6 | 4423680/26421880 [00:00<00:02, 9599067.02it/s] 25%|##5 | 6717440/26421880 [00:00<00:01, 11175748.57it/s] 34%|###4 | 9109504/26421880 [00:01<00:01, 14174360.51it/s] 44%|####3 | 11567104/26421880 [00:01<00:01, 14358310.56it/s] 53%|#####2 | 13959168/26421880 [00:01<00:00, 16463421.66it/s] 62%|######2 | 16449536/26421880 [00:01<00:00, 15864345.49it/s] 71%|#######1 | 18776064/26421880 [00:01<00:00, 17449238.29it/s] 81%|######## | 21397504/26421880 [00:01<00:00, 16758523.84it/s] 90%|########9 | 23691264/26421880 [00:01<00:00, 18055860.39it/s]100%|##########| 26421880/26421880 [00:01<00:00, 13728491.83it/s]Extracting data/FashionMNIST/raw/train-images-idx3-ubyte.gz to data/FashionMNIST/rawDownloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gzDownloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to data/FashionMNIST/raw/train-labels-idx1-ubyte.gz 0%| | 0/29515 [00:00<?, ?it/s]100%|##########| 29515/29515 [00:00<00:00, 327895.25it/s]Extracting data/FashionMNIST/raw/train-labels-idx1-ubyte.gz to data/FashionMNIST/rawDownloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gzDownloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz to data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz 0%| | 0/4422102 [00:00<?, ?it/s] 1%|1 | 65536/4422102 [00:00<00:11, 363267.41it/s] 5%|5 | 229376/4422102 [00:00<00:06, 683985.17it/s] 19%|#8 | 819200/4422102 [00:00<00:01, 2304448.10it/s] 33%|###3 | 1474560/4422102 [00:00<00:00, 2999709.36it/s] 83%|########2 | 3670016/4422102 [00:00<00:00, 7976134.77it/s]100%|##########| 4422102/4422102 [00:00<00:00, 5985529.02it/s]Extracting data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz to data/FashionMNIST/rawDownloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gzDownloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz 0%| | 0/5148 [00:00<?, ?it/s]100%|##########| 5148/5148 [00:00<00:00, 39473998.16it/s]Extracting data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw ToTensor()ToTensor 将 PIL 图像 或 NumPy ndarray 转换为 FloatTensor. 并在[0., 1.] 范围内缩放图像的像素空间。 Lambda 转换Lambda 函数允许任意用户定义 lambda 函数。在这里,我们定义了一个函数,将整数转换为 one-hot 编码的张量。 Lambda首先创建一个大小为10(我们数据集中的标签数量)的零向量,并调用 scatter_ 函数在索引 y 上分配标签 value = 1。 12target_transform = Lambda(lambda y:torch.zeros( 10, dtype = torch.float).scatter_(dim = 0, index = torch.tensor(y), value = 1)) 阅读延伸:torchvision.transforms API (核心内容)自动微分(TORCH.AUTOGRAD)在训练神经网络时,常用的算法是反向传播。在该算法中,参数(模型权重)根据损失函数相对于给定参数的梯度进行调整。 为了计算这些梯度,PyTorch 有一个内置的用于自动计算微分方程的引擎,称为 torch.autograd . 它支持任何计算图的梯度自动运算。 下面距离一个最简单的单层神经网络,其中包含输入 x、参数 w 和 b,以及一些损失函数。可以在 PyTorch 中按以下方式定义它: 12345678import torchx = torch.ones(5) # input tensory = torch.zeros(3) # expected outputw = torch.randn(5, 3, requires_grad = True)b = torch.randn(3, requires_grad = True)z = torch.matmul(x, w) + bloss = torch.nn.functional.binary_cross_entropy_with_logits(z, y) 张量、函数、计算图上一小节的代码框中实现的损失函数计算流程如下图所示: 在上面网络中,w 和 b 是我们需要优化的参数。因此,我们需要能够计算这些变量损失函数的梯度。 为了做到这些点,我们设置了这些张量的 requires_grad 函数来定义其属性。 设置张量的值有两种方式:一种是在生成张量的时候使用 requires_grad 进行初始化设置;另一种是在后续使用 x.requires_grad_(True) 函数。 用来构造计算图的函数实际上是类 Function 的对象。该对象指导如何计算正向函数,以及如何在反向传播步骤中计算其导数。对向后传播函数的引起存储在张量的属性中的 grad_fn。您可以在PyTorch的官方文档中找到更多信息 Function。 12print(f"Gradient function for z = {z.grad_fn}")print(f"Gradient function for loss = {loss.grad_fn}") 打印输出: 12Gradient function for z = <AddBackward0 object at 0x7d800ac85840>Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward0 object at 0x7d800ac85ea0> 梯度计算为了优化神经网络中的参数权重,我们需要计算损失函数相对于参数的导数,即我们需要在固定 x 和 y 值的情况下,求出 $\\frac{\\partial loss}{\\partial w}$ 和 $\\frac{\\partial loss}{\\partial b}$。 为了求出上面的导数,我们需要调用函数 loss.backward(),然后从 w.grad 和 b.grad 中检索权重和偏置的数值。 123loss.backward()print(w.grad)print(b.grad) 打印输出: 123456tensor([[0.3287, 0.0101, 0.0988], [0.3287, 0.0101, 0.0988], [0.3287, 0.0101, 0.0988], [0.3287, 0.0101, 0.0988], [0.3287, 0.0101, 0.0988]])tensor([0.3287, 0.0101, 0.0988]) 可以通过将参数 requires_grad 的 grad 属性设置为 True 来或测计算图的叶节点属性。对于计算图中的所有其他节点,梯度将不可用。 PyTorch考虑性能上的原因,在给定图形上只能调用 backward 调用一次梯度计算。如果我们需要再同一个图上执行多个 backward 调用,我们需要参数传递给 retain_graph = True,再调用 backward 。 禁用梯度追踪默认情况下,所有张量都在 require_grad = True 跟踪其计算历史并支持梯度计算。但是,在某些情况下,只想将模型应用于默写输入数据时,即我们只想通过网络进行前向计算。我们可以通过将计算代码加上 torch.no_grad() 还书,用来停止跟踪计算。 123456z = torch.matmul(x, w) +bprint(z.requires_grad) # Truewith torch.no_grad(): z = torch.matmul(x, w) + bprint(z.requires_grad) # False 另一种方法是在张量上使用 detach() 方法: 123z = torch.matmul(x, w) + bz_det = z.detach()print(z_det.requires_grad) # False 考虑要禁用梯度跟踪的情况: 将神经网络中的某些参数标记为冻结参数 为了优化计算速度,在只进行前向传播时禁用梯度跟踪的选项 计算图部分扩展阅读(留白,前面的区域以后再探索吧~) 可选阅读:张量梯度 和 雅阁比积(Jacobian_Products)(留白,前面的区域以后再探索吧~) (重要)优化模型参数在拥有了模型和数据之后,就可以通过优化数据参数来训练、验证和测试我们的模型了。训练模型是一个带带过程,每次迭代中,模型都会对输出进行*猜测*,计算其猜测中的误差,计算其猜测中的误差(损失),收集误差对参数的导数(上一节中进行的工作),并使用梯度下降这些参数。 更详细的视频讲解,可以参考 backpropagation from 3Blue1Brown 先决条件代码超参数优化循环全过程数据跟踪保存、加载和使用模型保存/加载模型权重通过模型的形状参数进行保存/加载在 PyTorch 中保存和加载常规 Checkpoint介绍设置步骤1.导入加载数据所需要的库2.定义和初始化神经网络3.初始化优化器4.保存常规检查点5.加载常规检查点从 Checkpoint 中加载 nn.Module 的技巧活用 torch.load(mmap = True)活用 torch.device("meta")活用 load_state_dict(assign = True)结论","categories":[{"name":"Deeplearning","slug":"Deeplearning","permalink":"https://zade23.github.io/categories/Deeplearning/"}],"tags":[{"name":"PyTorch","slug":"PyTorch","permalink":"https://zade23.github.io/tags/PyTorch/"}]},{"title":"工具网站","slug":"工具网站","date":"2023-10-18T09:04:52.000Z","updated":"2023-10-18T09:17:18.908Z","comments":true,"path":"2023/10/18/工具网站/","link":"","permalink":"https://zade23.github.io/2023/10/18/%E5%B7%A5%E5%85%B7%E7%BD%91%E7%AB%99/","excerpt":"","text":"内容分类 学习资源网站 aigc llm tts rl diffusion huggingface awesome 数学基础 机器学习 深度学习/模型训练 抽象概念可视化 传统算法 汇编语言 电子书 工具网站 在线大语言模型 在线api 数据集 音频处理 视频处理 图像处理 设计 下载工具 格式压缩 内容分类学习资源网站aigc 【关于AIGC的各种精选教程和资源,既适合初学者也适合进阶AI爱好者】github.com/luban-agi/Awesome-AIGC-Tutorials/blob/main/README_zh.md llm 【收集各种垂直领域的大语言模型】Awesome Domain LLM 【关于大型语言模型(LLM)的一切,包括了LLMs的入门、微调方法、多模态模型、稳定扩散、注意力机制优化和数据效率等方面的信息,从nanoGPT到LoRA、QLoRA、RLHF到CLIP等多模态模型】Everything-about-LLMs 【提示工程实例:用实际项目学习提示工程技术,从大型语言模型获得更好的结果,内容涵盖零样本和少样本提示,分隔符,步骤编号,角色提示,思维链(CoT)提示等】 《Prompt Engineering: A Practical Example – Real Python》 tts 【如何在浏览器中处理音视频】Web 音视频系列 【基于华为诺亚方舟实验室Grad-TTS的Grad-SVC】https://github.com/PlayVoice/Grad-SVC 【台大课程《深度学习音乐分析与生成》资料】 github.com/affige/DeepMIR 【大型音频模型相关文献资源列表】 github.com/EmulationAI/awesome-large-audio-models rl 【Generative Agents with Llama2】https://github.com/rlancemartin/generative_agents diffusion 【扩散模型相关论文资源列表,涵盖了文本到视频生成、文本引导视频编辑、个性化视频生成、视频预测等方面】 github.com/ChenHsing/Awesome-Video-Diffusion-Models 【3D Diffusion相关文献列表】 github.com/cwchenwang/awesome-3d-diffusion huggingface 【Hugging Face - Learn】https://huggingface.co/learn awesome 【收集各种生成式 AI 的教程】Awesome AIGC Tutorials 数学基础 【给程序员的线性代数指南】Linear Algebra for programmers 机器学习 【ML Papers Explained:机器学习论文解析】 github.com/dair-ai/ML-Papers-Explained 深度学习/模型训练 【Batched LoRAs:通过同一批次的多个 LoRA 路由推理,最大化 GPU 利用率】Batched LoRAs - batched 抽象概念可视化 【机器学习MachineLearning】www.r2d3.us 【波Waveforms】Let’s Learn About Waveforms 传统算法 【开源算法库Algorithms】The Algorithms 汇编语言 Python 【Python官方文档】Python语法官方文档 【从Python内置函数理解Python】Understanding all of Python, through its builtins 【GraphLearn-for-PyTorch(GLT):PyTorch图学习库,使分布式 GNN 训练和推理变得简单高效】’GraphLearn-for-PyTorch(GLT) A graph learning library for PyTorch that makes distributed GNN training and inference easy and efficient C++ 【C++在线学习】Learn C++ – Skill up with our free tutorials 电子书 (图书馆)【zlibrary】Z-Library – the world’s largest e-book library. Your gateway to knowledge and culture. 【免费书《人工智能:计算Agent基础,(第三版)》】《Artificial Intelligence: Foundations of Computational Agents, 3rd Edition》David L. Poole and Alan K. Mackworth (2023) 【配有动画的数据结构与算法电子书】Hello 算法 工具网站在线大语言模型 百度(文心一言) 抖音(云雀大模型) 智谱AI(GLM大模型) 中科院(紫东太初大模型) 百川智能(百川大模型) ChatGPT Poe 讯飞星火 谷歌bard 阿里通义千问 在线api 【收集各种 AI 工具和资源】AIHub 【免费AI API列表】free-ai-apis 【作文批改:使用GPT4对雅思托福作文判分和批改】https://www.essay.art/ 数据集 【OpenDataLab 为国产大模型提供高质量的开放数据集】OpenDataLab 音频处理 【Whisper:英语音频转成文本的在线工具】Whisper Web 视频处理 【Spikes Studio】https://spikes.studio/ 图像处理 【在线体验-图像分割算法Meta-SegementAnything】Segment Anything 【SDXL在线体验】StableDiffusion XL 体验站 【nutsh:旨在通过人工反馈进行视觉学习的平台,具有用户友好的界面和 API,支持一系列视觉模式、多样化的人工输入方法以及基于人工反馈的学习机制】Nutsh 设计 【中文的图标搜索引擎,作者利用 ChatGPT 翻译了 Iconify 的 18 万个图标名】yesicon 【JupyterCAD - 用于3D几何建模的JupyterLab扩展】https://github.com/QuantStack/jupytercad 【图标搜索引擎收入了10万+的图标】Iconbuddy — 180K+ open source icons 【AI漫画|可选择漫画风格和页面布局】AI Comic Factory - a Hugging Face Space by jbilcke-hf 【Figma在线版】Figma 下载工具 【YouTube视频解析下载 】snapsave.io 【bilibili视频下载】bili.iiilab 格式压缩 【图片压缩软件】Topspeed Image Compressor 在线压缩","categories":[],"tags":[{"name":"工具网站","slug":"工具网站","permalink":"https://zade23.github.io/tags/%E5%B7%A5%E5%85%B7%E7%BD%91%E7%AB%99/"}]},{"title":"Python元组集合字典","slug":"Python元组集合字典","date":"2023-10-18T08:20:18.000Z","updated":"2024-03-14T02:59:36.156Z","comments":true,"path":"2023/10/18/Python元组集合字典/","link":"","permalink":"https://zade23.github.io/2023/10/18/Python%E5%85%83%E7%BB%84%E9%9B%86%E5%90%88%E5%AD%97%E5%85%B8/","excerpt":"","text":"1. 元组 1.1 元组的初始化 1.2 元组的解包 1.3 元组的其他操作 2. 集合 2.1 集合的初始化 2.2 集合的常用操作 2.3 使用for循环遍历集合 3. 字典 3.1 字典的初始化 3.2 字典的常用操作 3.3 使用for循环遍历字典 4. 作业扩展 1. 元组元组跟 列表 类似,只是不支持动态添加、删除元素,以及不能修改元素。 1.1 元组的初始化元组需要用小括号括起来,中间的元素用逗号隔开。 注意:如果初始化只包含一个元素的元组,需要在该元素后添加逗号。 1234567a = () # 初始化一个空元组b = (1, 2) # 含有2个整数的元组c = 6, "Python", 3.14 # 小括号可以省略,等价于(6, "Python", 3.14)d = (5,) # 注意不能写成(5),(5)表示整数5e = 5, # 等价于(5,)print(a, b, c, d, e) 1.2 元组的解包123t = 12345, 54321, "Hello!" # 初始化一个元组x, y, z = t # 将元组解包,将元组内的三个值按顺序赋值给x、y、zprint(x, y, z) 所以,判断语句中的交换操作,本质上是元组的解包: 12a, b = 3, 4 # 将元组(3, 4)解包,分别赋值给a、ba, b = b, a # 将元组(b, a)解包,分别赋值给a、b 同样地,函数中函数返回多个值,本质上也是返回了一个元组: 1234567def calc(x, y): return x + y, x * y # 等价于 return (x + y, x * y)x, y = 3, 4s, p = calc(x, y) # 将(x + y, x * y)解包,分别赋值给s、pprint(s, p) 1.3 元组的其他操作元组的下标访问元素、循环遍历、切片、加法和乘法运算等操作,都与列表相同。 2. 集合集合是Python中最常用的数据结构之一,用来存储不同元素。注意,集合中的元素是无序的。 2.1 集合的初始化创建集合用花括号或set()函数。注意:创建空集合只能用set(),不能用{},因为{}创建的是空字典,会在下一小节里介绍字典。 集合常见的初始化方式: 123456789101112131415basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'} # 会自动去除重复元素print(basket) # 重复的元素已经去除了s = set() # 初始化一个空列表print(s)a = [1, 2, 1, 3, 1]b = set(a) # 将列表转化成集合,一般是为了去重。c = list(b) # 将集合转化回列表print(b, c)x = "abracadabra"a = set(x) # 将字符串中的每个字符存到集合中b = str(a) # 注意,这里并不能将集合转化回原字符串,而是用格式化表示集合中的内容print(a, b) 2.2 集合的常用操作假设a表示一个集合。 12345len(a) 返回集合中包含的元素数量。a.add(x) 在集合中添加一个元素。a.remove(x) 删除集合中的x,如果x不存在,则报异常。a.discard(x) 删除集合中的x,如果x不存在,则不进行任何操作。x in a 判断x是否在a中。 例如: 1234567891011121314a = {1, 2, 3}print(len(a)) # 输出3a.add(4)print(a) # 输出 {1, 2, 3, 4},注意集合中的元素是无序的。a.remove(2)print(a) # 输出 {1, 3, 4}a.remove(5) # 因为5不存在,所以会报异常a.discard(5) # 因为5不存在,所以不进行任何操作print(a) # {1, 3, 4} 2.3 使用for循环遍历集合类似于列表,集合也可以用for ... in ...的形式遍历。例如: 1234a = {1, 2, 3}for x in a: # 循环遍历整个集合 print(x, end=' ') 3. 字典字典是Python中最常用的数据结构之一,用来存储映射关系。注意,字典中的元素是无序的。 不同于列表,字典是以key进行索引的,可以将每个key映射到某个value。key可以是任何不可变类型,常用可以作为key的类型有数字和字符串。列表因为是可变的,所以不能作为key。value可以是任意类型。 3.1 字典的初始化创建字典用花括号或dict()函数。 12345678910tel = {'jack': 4098, 'sape': 4139} # 创建一个字典print(tel) # 输出 {'jack': 4098, 'sape': 4139}a = dict() # 创建一个空字典a[123] = "abc" # 在字典中插入一个key-value对a[456] = "def" # 在字典中插入一个key-value对print(a) # 输出 {123: 'abc', 456: 'def'}b = list(a) # 将字典的关键字转化成列表print(b) # 输出[123, 456] 3.2 字典的常用操作假设a表示一个字典。 len(a):返回字典中的元素对数。 a[x]:获取关键字x对应的值,如果x不存在,会报异常。 a.get(x):获取关键字x对应的值,如果x不存在,会返回None,不会报异常。 a.get(x, y):获取关键字x对应的值,如果x不存在,会返回默认值y,不会报异常。 a[x] = y:在字典中插入一对元素,如果关键字x已存在,则将它之前映射的值覆盖掉。 del a[x]:删除关键字x对应的元素对,如果x不存在,会报异常。 x in a:检查字典中是否存在关键字x。 x not in a:检查字典中是否不存在关键字x。 a.keys():返回字典的所有key。 a.values():返回字典的所有value。 a.items():返回字典的所有由key和value组成的元组。 例如: 1234567891011121314151617181920a = {'abc': 1, 'def': 2, 'python': 3} # 初始化一个字典print(len(a)) # 输出3print(a['def']) # 输出2print(a.get('def')) # 输出2print(a.get('xyz', 5)) # 因为'xyz'不存在,所以输出默认值5a['hello'] = 4 # 插入一对元素 'hello' -> 4print(a) # 输出{'abc': 1, 'def': 2, 'python': 3, 'hello': 4}a['def'] = 5 # 更新'def'映射的值print(a['def']) # 输出5del a['python'] # 删除关键字'python'print(a) # 输出{'abc': 1, 'def': 5, 'hello': 4}print('hello' in a) # 输出Trueprint(a.keys()) # 输出dict_keys(['abc', 'def', 'hello'])print(a.values()) # 输出dict_values([1, 5, 4])print(a.items()) # 输出dict_items([('abc', 1), ('def', 5), ('hello', 4)]) 3.3 使用for循环遍历字典类似于列表,字典也可以用for ... in ...的形式遍历。例如: 1234567891011121314151617a = {'abc': 1, 'def': 2, 'python': 3} # 初始化一个字典for k in a: # 遍历key print(k, end=' ')print() # 输出回车for k in a.keys(): # 遍历key print(k, end=' ')print() # 输出回车for v in a.values(): # 遍历value print(v, end=' ')print() # 输出回车for k, v in a.items(): # 遍历key-value对 print("(%s, %d) " % (k, v), end=' ')print() # 输出回车 4. 作业扩展map()也可以用for ... in ...的形式遍历。例如:for x in map(int, input().split())可以遍历一行内用空格隔开的每个整数。","categories":[],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"}]},{"title":"Python字符串","slug":"Python字符串","date":"2023-10-18T08:18:06.000Z","updated":"2024-03-14T02:59:45.263Z","comments":true,"path":"2023/10/18/Python字符串/","link":"","permalink":"https://zade23.github.io/2023/10/18/Python%E5%AD%97%E7%AC%A6%E4%B8%B2/","excerpt":"","text":"1. 字符与整数的联系——ASCII码 2. 字符串常量的写法 3. 表示特殊字符——转义 4. 访问字符串中的每个字符 5. 使用循环语句遍历字符串 6. 字符串的切片操作 7. 字符串的复制 8. 字符串的运算 9. 字符串的常用操作 10. 更复杂的格式化输出 11. 作业题扩展内容 1. 字符与整数的联系——ASCII码每个常用字符都对应一个-128 ~ 127的数字,二者之间可以相互转化。注意:目前负数没有与之对应的字符。 ord()函数可以求一个字符的ASCII码。注意输入是一个字符,而不是字符串。 chr()函数可以将一个ASCII码转化成对应的字符。 12345c = 'a'print(ord(c))a = 66print(chr(a)) 常用ASCII值:'A'- 'Z'是65 ~ 90,'a' - 'z'是97 - 122,0 - 9是48 - 57。 注意:虽然字符可以跟整数相互转化,但在Python中,字符不能参与数值运算,这一点跟C++、Java等语言是不同的。 2. 字符串常量的写法在Python中,字符串既可以用单引号来表示,也可以用双引号来表示,二者完全相同。这一点跟C++、Java等编程语言是不同的,在这些编程语言中,用单引号来表示字符,用双引号来表示字符串。 12345a = "Hello World" # 双引号写法print(a)b = 'Hello World' # 单引号写法print(b) 两个或多个字符串常量并排写,会被自动合并,例如: 12a = "My " "name " "is yxc."print(a) # 输出:My name is yxc. 一个字符串如果包含多行,可以采用"""..."""或者'''...'''的初始化方式,字符串中将自动包含回车字符,例如: 1234a = """Usage: thingy [OPTIONS] -h Display this usage message -H hostname Hostname to connect to"""print(a) 会得到如下输出: 123Usage: thingy [OPTIONS] -h Display this usage message -H hostname Hostname to connect to 3. 表示特殊字符——转义当想在字符串中表示特殊字符时,一般可以在字符前加反斜杠\\。 常见需要转义的字符有: 转义字符 含义 ASCII码(十进制) \\n 回车 10 `` 代表一个反斜杠\\ 92 `“` 表示一个双引号 34 `‘` 表示一个单引号 39 例如: 1print("My name is:\\n\\"yxc!\\"") 会得到如下输出: 12My name is:"yxc!" 另外,如果想输出单引号,也可以用双引号来表示,反之亦然。例如: 12print("My name is 'yxc!'") # 输出:My name is 'yxc!'print('My name is "yxc!"') # 输出:My name is "yxc!" 4. 访问字符串中的每个字符可以通过下标读取字符串中的每个字符,下标从0开始,也可以是负数,负数下标表示的是除以字符串长度的余数对应的位置。 负数下标相当于将字符串首位相接,然后从0往前数。 如果字符串长度是 nn,那么下标只能取 −n∼n−1−n∼n−1 之间的整数,超出范围会报错。 注意:字符串中的每个字符不能修改。 例如: 1234a = "Hello World"print(a[0], ord(a[5])) # 输出H 32a[2] = 'x' # 会报错,字符串不能修改 5. 使用循环语句遍历字符串可以通过下标访问,例如: 1234s = "acwing"for i in range(6): print(s[i], end=' ')print() # 输出回车 可以通过for ... in ...直接遍历,例如: 123for c in "python": print(c, end=' ') # 注意c本身也是字符串类型print() # 输出回车 6. 字符串的切片操作字符串的切片操作会返回一个新字符串。用法: a[begin:end] 会返回包含a[begin], a[begin + 1], ..., a[end - 1]的字符串。 省略begin时,begin的默认值是0。 省略end时,end的默认值是字符串长度。 如果begin或end是负数,表示的是除以字符串长度后的余数。 例如: 1234567a = "ABCDE"print(a[1:4]) # 输出BCDprint(a[1:]) # 输出BCDEprint(a[:4]) # 输出ABCDprint(a[:]) # 输出ABCDEprint(a[-4:-1]) # 等价于print(a[1:4]) 注意:字符串的切片不支持写操作。 例如: 12a = "ABCDE"a[1:4] = "XY" # 会报错,字符串不能修改 7. 字符串的复制跟列表不同,字符串的每次复制操作,都会得到一个全新的字符串。 8. 字符串的运算 字符串的加法可以将两个字符串拼接起来,得到一个新字符串。 字符串乘以一个整数,可以将若干个自身拼接起来,得到一个新字符串。 字符串支持比较运算符,按字典序比较大小。即如果两个字符串相同,则表示相等;否则找到两个字符串从左到右数第一个不一样的字符,哪个字符串的字符的ASCII码小,哪个字符串的字典序就小;另外空字符比任何字符都小。 例如: 12345678910111213a = "Hello "b = "World"c = a + bprint(c) # 输出Hello Worldd = a * 3print(d) # 输出Hello Hello Helloe = a * 3 + "World"print(e) # 输出Hello Hello Hello Worldprint(a <= b) # 按字典序比较大小,输出Trueprint("123" > "22") # 按字典序比较大小,输出False 9. 字符串的常用操作假设s是一个字符串,则: len(s)返回字符串长度。 s.split(sep)返回一个字符串列表。如果给出了sep就按sep分隔;如果没给出,则会按空格分隔,但连续的空格会被视为单个分隔符,而且会忽略首尾的空白字符。 s.strip()将首尾的空白字符删除。 s.replace(old, new)将s中所有的old子串都改成new。 s.find("abc")查询某个子串在s中第一次出现的下标;如果不存在,则返回-1。 s.startswith(prefix)判断prefix是否为s的前缀。 s.endswith(suffix)判断suffix是否为s的后缀。 s.lower()将所有大写字母变成小写。 s.upper()将所有小写字母变成大写。 s.join(a),a是一个字符串列表,这个函数返回将a中的字符用s作为分隔符拼接起来的结果。 注意:返回的所有字符串都是新字符串,原字符串不变。 例如: 123456789101112131415161718s1 = "abc def xyz"print(len(s1)) # 输出11print(s1.split()) # 输出['abc', 'def', 'xyz']s2 = " abc abc "print(s2.strip()) # 输出abc abcprint(s2.replace("abc", "*")) # 输出 * *print(s2.find("abc"), s2.find("xyz")) # 输出2 -1s3 = "Abc deF"print(s3.startswith("Ab")) # 输出Trueprint(s3.endswith("deF")) # 输出Trueprint(s3.lower()) # 输出abc defprint(s3.upper()) # 输出ABC DEFs4 = ", "a = ["aa", "bb", "cc"]print(s4.join(a)) # 输出aa, bb, cc 10. 更复杂的格式化输出当需要用到更复杂的格式化输出时,现查即可。可以参考: 更复杂的输出格式 printf 风格的字符串格式化 11. 作业题扩展内容 作业的评测器会自动忽略每一行的行末空格,所以行末输出多余空格也视为正确。 s.isdigit():当字符串s不是空字符串,且包含的所有字符都是数字时返回True,否则返回False。 a, b = ["abc", "def"]这种写法可以将"abc"赋值给第一个变量a,将"def"赋值给第二个变量b。 s.rfind("abc")查询某个子串在s中最后一次出现的下标;如果不存在,则返回-1。 当不知道读入的具体行数时,可以采用如下方法一次性读取所有行: 1234from sys import stdinfor line in stdin.readlines(): print(line.strip()) # strip()是为了去掉行末的回车","categories":[],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"}]},{"title":"经济学——流动性陷阱","slug":"经济学——流动性陷阱","date":"2023-10-16T02:37:26.000Z","updated":"2024-03-07T02:16:13.691Z","comments":true,"path":"2023/10/16/经济学——流动性陷阱/","link":"","permalink":"https://zade23.github.io/2023/10/16/%E7%BB%8F%E6%B5%8E%E5%AD%A6%E2%80%94%E2%80%94%E6%B5%81%E5%8A%A8%E6%80%A7%E9%99%B7%E9%98%B1/","excerpt":"","text":"流动性陷阱——名词解释 白话解释 维基百科解释 换个视角看问题 国家层面 人民层面 流动性陷阱——名词解释白话解释流动性陷阱是一个经济学概念,出现在名义利率已经接近零或非常低,而央行进一步减少利率仍然不能刺激投资和消费的情况。在这种情境下,货币政策的传统工具,如购买国债以增加货币供应量,可能变得无效,因为商业银行和公众可能选择持有现金而不是投资或消费。这是因为他们预期未来的回报率很低,或对未来经济有所担忧。 维基百科解释流动性陷阱(英语:liquidity trap),是英国经济学家凯恩斯提出的概念,又称凯恩斯陷阱。 在凯恩斯主义经济学中,流动性陷阱是指一个当货币政策完全没法刺激经济的情况,无论降低利率抑或增加货币供应量也没有作用。典型的流动性陷阱在当预期经济不景气出现(例如通货紧缩、总需求不足、发生战争),使持有流动资产的人不愿意投资。 换个视角看问题 GPT4的回答 国家层面站在国家的角度: 财政政策:当货币政策变得无效时,国家可以使用财政政策,如增加公共支出、减税或直接向家庭提供资金,以刺激经济。 结构性改革:国家可以采取长期的结构性改革,如改善教育、健康、基础设施,以增加生产力和潜在产出。 消除不确定性:政府可以采取措施减少经济中的不确定性,这可能包括提供更清晰的经济前景、稳定的政策环境等。 合作:与其他国家合作,通过国际贸易和投资来促进增长。 人民层面站在人民的角度: 储蓄与投资:在经济不确定性和低回报期望的背景下,人们可能会倾向于增加储蓄。然而,他们也应考虑多样化投资,可能在国外或不同的资产类别,以获得更好的回报。 消费:尽管流动性陷阱可能导致对未来的担忧,但是长时间的压抑消费可能进一步伤害经济。如果有能力,人民可以考虑适度的消费。 教育与技能提升:在经济低迷的时期,提高自己的技能和教育背景可以帮助人们为未来更好的机会做好准备。 避免过度债务:在低利率环境下,尽管贷款更便宜,但人们应避免过度债务,以确保在经济形势变化时能够应对。 总之,流动性陷阱是一个复杂的经济问题,需要多方面的策略来应对。","categories":[],"tags":[{"name":"经济学小知识","slug":"经济学小知识","permalink":"https://zade23.github.io/tags/%E7%BB%8F%E6%B5%8E%E5%AD%A6%E5%B0%8F%E7%9F%A5%E8%AF%86/"}]},{"title":"Python函数","slug":"Python函数","date":"2023-10-12T10:22:54.000Z","updated":"2024-03-14T02:59:41.168Z","comments":true,"path":"2023/10/12/Python函数/","link":"","permalink":"https://zade23.github.io/2023/10/12/Python%E5%87%BD%E6%95%B0/","excerpt":"","text":"1. 函数基础 1.1 编写函数 1.2 调用函数 1.3 形参和实参 1.3.1 形参的初始化方式 1.3.2 带默认值的形参 1.3.3 其它参数写法 1.4 变量的作用域 2. 参数传递 2.1 值传递 2.2 引用传递 3.return语句 4.lambda表达式 5. 函数递归 Python中函数的用法非常多,80%的用法不常用,20%的用法常用。大家不要把精力浪费在背完所有用法上,而要把主要精力放到最常用的20%的用法和代码逻辑上,至于另外80%不常用的用法,边用边查就行。 1. 函数基础Python中一个典型的函数定义包括以下部分:关键字def、函数名称、由0个或多个形参组成的列表以及函数体。 1.1 编写函数我们来编写一个求阶乘的函数。例如: 12345def fact(n): res = 1 for i in range(1, n + 1): res *= i return res 函数名称是fact,给它传入一个n,会返回n的阶乘。return语句负责结束函数并返回res的值。 1.2 调用函数123print("我们要计算5的阶乘,答案是:")print(fact(5)) # 输出 120print("计算结束啦!") 函数的调用完成两项工作: 用实参初始化函数对应的形参 将控制权转移给被调用的函数 此时,代码原本的执行顺序被暂时中断,被调函数开始执行。等被调用函数执行完后,再继续执行之前的代码。 1.3 形参和实参实参指调用函数时传入的变量或常量,形参指定义函数时参数列表里的变量。 形参列表可以为空,例如: 1234def f(): print("Hello World")f() # 输出 Hello World 1.3.1 形参的初始化方式调用函数时会用实参去初始化形参,初始化的顺序有两种: 第一种是用位置实参来初始化形参。顾名思义,实参会按位置关系来初始化形参,第一个实参初始化第一个形参,第二个实参初始化第二个形参,依此类推。形参和实参的个数必须匹配。例如: 123456789def f(a, b, c, d): print("a =", a, end=", ") print("b =", b, end=", ") print("c =", c, end=", ") print("d =", d)f(1, True, "Python", 4.2) # 输出 a = 1, b = True, c = Python, d = 4.2f(1, True, "Python", 4.2, 3) # 会报错,因为实参个数多于形参f(1, True, "Python") # 会报错,因为实参个数少于形参 第二种是用关键字实参来初始化形参。此时实参不再按位置关系来初始化形参,而是按变量名初始化。例如: 12# f()的定义如上所述f(b=1, c=True, a="Python", d=4.2) # 输出 a = Python, b = 1, c = True, d = 4.2 两种方式也可以混合使用,但是位置实参一定要放到关键字实参之前。例如: 123# f()的定义如上所述f(1, 2, d="Python", c=4.2) # 输出 a = 1, b = 2, c = 4.2, d = Pythonf(1, b=3, "Python", d=4.2) # 会报错,因为位置实参位于关键字实参后面了。 1.3.2 带默认值的形参形参也可以设置默认值,但所有带默认值的形参必须是最后几个。当某些形参没有被初始化时,这些形参会使用默认值。例如: 123456789def f(a, b, c=3, d="Python"): print("a =", a, end=", ") print("b =", b, end=", ") print("c =", c, end=", ") print("d =", d)f(1, 2) # c和d没有被初始化,采用默认值。输出 a = 1, b = 2, c = 3, d = Pythonf(1, b=2, d="AcWing") # c没有被初始化,采用默认值。输出 a = 1, b = 2, c = 3, d = AcWing 1.3.3 其它参数写法其它参数写法用得不多,想了解的同学可以参考函数定义详解。 1.4 变量的作用域函数内定义的变量为局部变量,只能在函数内部使用。 当需要修改用全局变量时,需要用global关键字在函数内声明全局变量。例如: 12345678910111213x = 1def f(): global x # 在函数内声明全局变量 x = 666 y = 777 print(x, y)f() # 输出 666 777print(x) # 会发现全局变量x也被修改了print(y) # 会报错,因为y是局部变量,函数外无法使用 1.5 嵌套定义函数函数内部也可以定义函数。例如: 12345678def f(): def g(x): # 定义函数g() x += 1 print(x) g(5) # 调用函数g()f() # 输出6 1.6 pass语句当函数定义完但还不想实现时,可以用pass占位符,来避免出现语法错误。例如: 12def f(): pass 2. 参数传递2.1 值传递int、float、bool、字符串等采用值传递。 将实参的初始值拷贝给形参。此时,对形参的改动不会影响实参的初始值。例如: 12345678def f(y): y = 5 print(y)x = 10f(x)print(x) # 会发现x的值没变 2.2 引用传递列表采用引用传递。 将实参的引用传给形参,此时对形参的修改会影响实参的初始值。例如: 12345678def f(b): for i in range(len(b)): b[i] += 1a = [0, 1, 2, 3, 4]f(a)print(a) # 会发现列表a中的每个数加了1 3.return语句return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方,并返回结果。例如: 1234567891011121314def f(x): if x == 1: return # 不写返回值时,会返回None if x == 2: return 3 # 返回一个变量 if x == 3: return 4, 5 # 返回多个变量a = f(1)b = f(2)c, d = f(3)e = f(4) # 没写return时,也会返回Noneprint(a, b, c, d, e) # 输出 None 3 4 5 None 4.lambda表达式lambda关键字可以创建匿名函数,目的是为了简化代码。可以对比下面两种写法,会发现lambda表达式的写法更短一些。 常与sort()函数配合使用,例如: 1234pairs = [[1, "one"], [2, "two"], [3, "three"], [4, "four"]]pairs.sort(key=lambda pair: pair[1]) # 每个元素使用第二个变量比较大小print(pairs) # 输出:[[4, 'four'], [1, 'one'], [3, 'three'], [2, 'two']] 等价于下面的写法: 123456789pairs = [[1, "one"], [2, "two"], [3, "three"], [4, "four"]]def compare(pair): return pair[1]pairs.sort(key=compare) # 每个元素使用第二个变量比较大小print(pairs) # 输出:[[4, 'four'], [1, 'one'], [3, 'three'], [2, 'two']] 5. 函数递归在一个函数内部,也可以调用函数自身。这种写法被称为递归。 写递归函数可以从集合的角度来思考。理解递归函数的执行顺序可以用树的形式来思考。 例如,求解斐波那契数列第 nn 项可以采用如下写法: 1234567def fib(n): if n <= 2: return 1 return fib(n - 1) + fib(n - 2)print(fib(6)) # 输出 8","categories":[],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"}]},{"title":"github_workflow自动合并主线失败问题","slug":"githun-workflow自动合并主线失败问题","date":"2023-10-08T02:16:53.000Z","updated":"2023-10-08T02:37:06.373Z","comments":true,"path":"2023/10/08/githun-workflow自动合并主线失败问题/","link":"","permalink":"https://zade23.github.io/2023/10/08/githun-workflow%E8%87%AA%E5%8A%A8%E5%90%88%E5%B9%B6%E4%B8%BB%E7%BA%BF%E5%A4%B1%E8%B4%A5%E9%97%AE%E9%A2%98/","excerpt":"","text":"github_workflow自动提交在合并主线时出现的问题介绍看到 GitHub 上的一个好玩的项目,大意就是保持个人主页的提交栏保持尝绿,看起来很有意思,于是学着构建了一个 GitHub_actions_workflow 。仓库是用来每日清晨推送 bing 的当日壁纸的,仓库为:https://github.com/zade23/auto-green 但是部署之后发现 Actions 的 合并提交 动作一直失败,报错如下: 123456789remote: Permission to zade23/auto-green.git denied to github-actions[bot].fatal: unable to access 'https://github.com/zade23/auto-green.git/': The requested URL returned error: 403Error: Invalid exit code: 128 at ChildProcess.<anonymous> (/home/runner/work/_actions/ad-m/github-push-action/master/start.js:30:21) at ChildProcess.emit (node:events:513:28) at maybeClose (node:internal/child_process:1100:16) at Process.ChildProcess._handle.onexit (node:internal/child_process:304:5) { code: 128} 问题分析分析主要问题,应试这段 The requested URL returned error: 403 。 最终在 Stack Overflow 上找到了解决办法: Permission denied to github-actions[bot]. The requested URL returned error: 403 问题解决GitHub Actions 中自动提交功能必须在对应仓库下面手动设置允许 bot 合并提交的权限,否则合并动作就会被拒绝。 启动 actions 的 repository - Settings -> Action -> General -> Workflow permissions","categories":[],"tags":[]},{"title":"Python数据结构和算法","slug":"Python数据结构和算法","date":"2023-07-18T07:12:00.000Z","updated":"2024-03-14T02:58:36.019Z","comments":true,"path":"2023/07/18/Python数据结构和算法/","link":"","permalink":"https://zade23.github.io/2023/07/18/Python%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/","excerpt":"","text":"算法分析相关 复杂度计算 大 O 表示法 类 都是对数据的构成(状态)以及数据能做什么(行为)的描述。由于类的使用者只能看到数据项的状态和行为,因此类与抽象数据类型是相似的、 在面向对象编程范式中,数据项被称作 对象 一个对象就是类的一个实例。 1.内建原子数据结构 python是通过两种内建数据类型实现整数类型和浮点数类型的,相应的python类就是 int 和 float 。 列表列表 是零个或多个指向 Python 数据对象的引用的有序集合,通过在方括号内以逗号分隔的一系列值来表达。空列表就是 []。列表是异构的,这意味着其指向的数据对象不需要都是同一个类,并且这一集合可以被赋值给一个变量。下面的代码段展示了列表含有多个不同的 Python 数据对象。 元组 通常写成由括号包含并且以逗号分隔的一系列值。与序列一样,元组允许之前描述的任一操作。 列表提供的方法 方法名 用法 解释 append alist.append(item) 在列表末尾添加一个新元素 insert alist.insert(i,item) 在列表的第! i 个位置插入一个元素 pop alist.pop() 删除并返回列表中最后一个元素 pop alist.pop(i) 删除并返回列表中第 i 个位置的元素 sort alist.sort() 将列表元素排序 reverse alist.reverse() 将列表元素倒序排列 del del alist[i] 删除列表中第 i 个位置的元素 index alist.index(item) 返回 item 第一次出现时的下标 count alist.count(item) 返回 item 在列表中出现的次数 remove alist.remove(item) 从列表中移除第一次出现的 item 对应输出: 123456789101112131415161718192021222324252627282930313233343536>>> myList[1024, 3, True, 6.5]>>> myList.append(False)>>> myList[1024, 3, True, 6.5, False]>>> myList.insert(2,4.5)>>> myList[1024, 3, 4.5, True, 6.5, False]>>> myList.pop()False>>> myList[1024, 3, 4.5, True, 6.5]>>> myList.pop(1)3>>> myList[1024, 4.5, True, 6.5]>>> myList.pop(2)True>>> myList[1024, 4.5, 6.5]>>> myList.sort()>>> myList[4.5, 6.5, 1024]>>> myList.reverse()>>> myList[1024, 6.5, 4.5]>>> myList.count(6.5)1>>> myList.index(4.5)2>>> myList.remove(6.5)>>> myList[1024, 4.5]>>> del myList[0]>>> myList[4.5] 字符串字符串是零个或多个字母、数字和其他符号的有序集合。这些字母、数字和其他符号被称为 字符。常量字符串值通过引号(单引号或者双引号均可)与标识符进行区分。 123456789>>> "David"'David'>>> myName = "David">>> myName[3]'i'>>> myName*2'DavidDavid'>>> len(myName)5 字符串提供的方法 方法名 用法 解释 center astring.center(w) 返回一个字符串,原字符串居中,使用空格填充新字符串,使其长度为 w count astring.count(item) 返回 item 出现的次数 ljust astring.ljust(w) 返回一个字符串,将原字符串靠左放置并填充空格至长度 w rjust astring.rjust(w) 返回一个字符串,将原字符串靠右放置并填充空格至长度 w lower astring.lower() 返回均为小写字母的字符串 upper astring.upper() 返回均为大写字母的字符串 find astring.find(item) 返回 item 第一次出现时的下标 split astring.split(schar) 在 schar 位置将字符串分割成子串,不填则默认分割空格和换行符 列表和字符串的区别: 列表有 可修改性 ,字符串没有 集合集(set)是由零个或多个不可修改的 Python 数据对象组成的无序集合。集不允许重复元素,并且写成由花括号包含、以逗号分隔的一系列值。空集由 set() 来表示。集是异构的,并且可以通过下面的方法赋给变量。 Python 集支持的运算 运算名 运算符 解释 成员 in 询问集中是否有某元素 长度 len 获取集的元素个数 | aset | otherset 返回一个包含 aset 与 otherset 所有元素的新集 & aset & otherset 返回一个包含 aset 与 otherset 共有元素的新集 - aset - otherset 返回一个集,其中包含只出现在 aset 中的元素 <= aset <= otherset 询问 aset 中的所有元素是否都在 otherset 中 Python 集提供的方法 方法名 用法 解释 union aset.union(otherset) 返回一个包含 aset 和 otherset 所有元素的集 intersection aset.intersection(otherset) 返回一个仅包含两个集共有元素的集 difference aset.difference(otherset) 返回一个集,其中仅包含只出现在 aset 中的元素 issubset aset.issubset(otherset) 询问 aset 是否为 otherset 的子集 add aset.add(item) 向 aset 添加一个元素 remove aset.remove(item) 将 item 从 aset 中移除 pop aset.pop() 随机移除 aset 中的一个元素 clear aset.clear() 清除 aset 中的所有元素 12345678910111213141516171819202122232425262728293031>>> mySet{False, 4.5, 3, 6, 'cat'}>>> yourSet = {99,3,100}>>> mySet.union(yourSet){False, 4.5, 3, 100, 6, 'cat', 99}>>> mySet | yourSet{False, 4.5, 3, 100, 6, 'cat', 99}>>> mySet.intersection(yourSet){3}>>> mySet & yourSet{3}>>> mySet.difference(yourSet){False, 4.5, 6, 'cat'}>>> mySet - yourSet{False, 4.5, 6, 'cat'}>>> {3,100}.issubset(yourSet)True>>> {3,100}<=yourSetTrue>>> mySet.add("house")>>> mySet{False, 4.5, 3, 6, 'house', 'cat'}>>> mySet.remove(4.5)>>> mySet{False, 3, 6, 'house', 'cat'}>>> mySet.pop()False>>> mySet{3, 6, 'house', 'cat'}>>> mySet.clear()>>> mySet 字典字典 是无序结构,由相关的元素对构成,其中每对元素都由一个键和一个值组成。这种键–值对通常写成键:值的形式。字典由花括号包含的一系列以逗号分隔的键–值对表达,如下所示。 12>>> capitals = {'Iowa':'DesMoines','Wisconsin':'Madison'}>>> capitals{'Wisconsin':'Madison', 'Iowa':'DesMoines'} 可以通过键访问其对应的值,也可以向字典添加新的键–值对。访问字典的语法与访问序列的语法十分相似,只不过是使用键来访问,而不是下标。添加新值也类似。 1234567>>> capitals['Iowa'] = 'DesMoines'>>> capitals['Utah'] = 'SaltLakeCity'>>> capitals{'Utah':'SaltLakeCity', 'Wisconsin':'Madison', 'Iowa':'DesMoines'}>>> capitals['California']='Sacramento'>>> capitals{'Utah':'SaltLakeCity', 'Wisconsin':'Madison', 'Iowa':'DesMoines', 'California':'Sacramento'}>>> len(capitals)>>> 4 需要谨记,字典并不是根据键来进行有序维护的。第一个添加的键–值对('Utah':'SaltLakeCity')被放在了字典的第一位,第二个添加的键–值对('California':'Sacramento')则被放在了最后。 字典的运算keys、values 和 items 方法均会返回包含相应值的对象。可以使用 list 函数将字典转换成列表。 运算名 运算符 解释 [] myDict[k] 返回与 k 相关联的值,如果没有则报错 in key in adict 如果 key 在字典中,返回 True,否则返回 False del del adict[key] 从字典中删除 key 的键–值对 字典的方法get 方法有两种版本。如果键没有出现在字典中,get 会返回 None。然而,第二个可选参数可以返回特定值。 方法名 用法 解释 keys adict.keys() 返回包含字典中所有键的 dict_keys 对象 values adict.values() 返回包含字典中所有值的 dict_values 对象 items adict.items() 返回包含字典中所有键–值对的 dict_items 对象 get adict.get(k) 返回 k 对应的值,如果没有则返回 None get adict.get(k, alt) 返回 k 对应的值,如果没有则返回 alt 字典实例12345678910111213141516171819>>> phoneext={'david':1410, 'brad':1137}>>> phoneext{'brad':1137, 'david':1410}>>> phoneext.keys()dict_keys(['brad', 'david'])>>> list(phoneext.keys())['brad', 'david']>>> phoneext.values()dict_values([1137, 1410])>>> list(phoneext.values())[1137, 1410]>>> phoneext.items()dict_items([('brad', 1137), ('david', 1410)])>>> list(phoneext.items())[('brad', 1137), ('david', 1410)]>>> phoneext.get("kent")>>> phoneext.get("kent", "NO ENTRY")'NO ENTRY'>>> 输入和输出程序经常要和用户进行交互。 目前的大多数程序使用对话框作为要求用户提供某种输入的方式。尽管 Python 确实有方法来创建这样的对话框,但是可以利用更简单的函数。Python 提供了一个函数,它使得我们可以要求用户输入数据并且返回一个字符串的引用。这个函数就是 input。 提示字符串input 函数接受一个字符串作为参数。由于该字符串包含有用的文本来提示用户输入,因此它经常被称为 提示字符串。举例来说,可以像下面这样调用 input。 不论用户在提示字符串后面输入什么内容,都会被存储在 aName 变量中。使用 input 函数,可以非常简便地写出程序,让用户输入数据,然后再对这些数据进行进一步处理。例如,在下面的两条语句中,第一条要求用户输入姓名,第二条则打印出对输入字符串进行一些简单处理后的结果。 12345aName = input("Please enter your name ")print("Your name in all capitals is ", aName.upper(), "and has length", len(aName)) 需要注意的是,input 函数返回的值是一个字符串,它包含用户在提示字符串后面输入的所有字符。如果需要将这个字符串转换成其他类型,必须明确地提供类型转换。在下面的语句中,用户输入的字符串被转换成了浮点数,以便于后续的算术处理。 123sradius = input("Please enter the radius of the circle ")radius = float(sradius)diameter = 2 * radius 格式化字符串print 函数为输出 Python 程序的值提供了一种非常简便的方法。它接受零个或者多个参数,并且将单个空格作为默认分隔符来显示结果。通过设置 sep 这一实际参数可以改变分隔符。此外,每一次打印都默认以换行符结尾。这一行为可以通过设置实际参数 end 来更改。下面是一些例子。 12345678>>> print("Hello")Hello>>> print("Hello","World")Hello World>>> print("Hello","World", sep="***")Hello***World>>> print("Hello","World", end="***")Hello World*** Python 提供了另一种叫作 格式化字符串 的方式。格式化字符串是一个模板,其中包含保持不变的单词或空格,以及之后插入的变量的占位符。例如,下面的语句包含 is 和 years old.,但是名字和年龄会根据运行时变量的值而发生改变。 1print(aName, "is", age, "years old.") 使用格式化字符串,可以将上面的语句重写成下面的语句。 1print("%s is %d years old." % (aName, age)) 格式化字符串可用的类型声明% 是字符串运算符,被称作 格式化运算符。 表达式的左边部分是模板(也叫格式化字符串),右边部分则是一系列用于格式化字符串的值。 格式化字符串可以包含一个或者多个转换声明。转换字符告诉格式化运算符,什么类型的值会被插入到字符串中的相应位置。 在上面的例子中,%s 声明了一个字符串,%d 则声明了一个整数。其他可能的类型声明还包括 i、u、f、e、g、c 和 %。 字符 输出格式 d、i 整数 u 无符号整数 f m.dddd 格式的浮点数 e m.dddde+/-xx 格式的浮点数 E m.ddddE+/-xx 格式的浮点数 g 对指数小于-4 或者大于 5 的使用 %e,否则使用 %f c 单个字符 s 字符串,或者任意可以通过 str 函数转换成字符串的 Python 数据对象 % 插入一个常量 % 符号 格式化修改符 修改符 例子 解释 数字 %20d 将值放在 20 个字符宽的区域中 - %-20d 将值放在 20 个字符宽的区域中,并且左对齐 + %+20d 将值放在 20 个字符宽的区域中,并且右对齐 0 %020d 将值放在 20 个字符宽的区域中,并在前面补上 0 . %20.2f 将值放在 20 个字符宽的区域中,并且保留小数点后 2 位 (name) %(name)d 从字典中获取 name 键对应的值 1234567891011>>> price = 24>>> item = "banana">>> print("The %s costs %d cents" % (item,price))The banana costs 24 cents>>> print("The %+10s costs %5.2f cents" % (item,price))The banana costs 24.00 cents>>> print("The %+10s costs %10.2f cents" % (item,price))The banana costs 24.00 cents>>> itemdict = {"item":"banana","cost":24}>>> print("The %(item)s costs %(cost)7.1f cents" % itemdict)The banana costs 24.0 cents 控制结构Python提供的标准的标准控制语句有 while 语句以及 for 语句。 1234567891011>>> counter = 1>>> while counter <= 5:... print("Hello, world")... counter = counter + 1...Hello, worldHello, worldHello, worldHello, worldHello, world 看下面这个例子。 1while counter <= 10 and not done: 迭代语句只有在上面两个条件都满足的情况下才会被执行。变量 counter 的值需要小于或等于 10,并且变量 done 的值需要为 False(not False 就是 True),因此 True and True 的最后结果才是 True。 for在遍历每一个成员上非常的方便。例如: 12345678>>> for item in [1,3,6,2,5]:... print(item)...13625 列表解析式列表可以通过使用迭代结构和分支结构来创建。这种方式被称为 列表解析式。通过列表解析式,可以根据一些处理和分支标准轻松创建列表。 举例来说,如果想创建一个包含前 10 个完全平方数的列表,可以使用以下的 for 语句。 12345>>> sqlist = []>>> for x in range(1,11): sqlist.append(x*x)>>> sqlist[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] 进阶:使用列表解析式,只需一行代码即可创建完成。 123>>> sqlist = [x*x for x in range(1,11)]>>> sqlist[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] 进阶:变量 x 会依次取由 for 语句指定的 1 到 10 为值。之后,计算 x*x 的值并将结果添加到正在构建的列表中。列表解析式也允许添加一个分支语句来控制添加到列表中的元素。 123>>> sqlist = [x*x for x in range(1,11) if x%2 != 0]>>> sqlist[1, 9, 25, 49, 81] 例子:(力扣的原题:输出列表中非元音的字母并以大写形式返回) 12>>>[ch.upper() for ch in 'comprehension' if ch not in 'aeiou']['C', 'M', 'P', 'R', 'H', 'N', 'S', 'N'] 函数下面定义的简单函数会返回传入值的平方。 1234567>>> def square(n):... return n**2...>>> square(3)9>>> square(square(3))81 同样的,我们可以自己定义一个平方根函数 squareroot() 通过牛顿迭代法求解平方根 1234561. def squareroot(n):2. root = n/2 #initial guess will be 1/2 of n3. for k in range(20):4. root = (1/2)*(root + (n / root))5.6. return root 接下来,模拟调用这个函数 1234>>> squareroot(9)3.0>>> squareroot(4563)67.549981495186216 面向对象:定义 类算法分析相关复杂度计算大 O 表示法 如果可以通过观察循环结构和算法的方式快速判断出复杂度就算出师 异序词排序问题 1234567891011121314151617181920212223def anagramSolution1(s1, s2): alist = list(s2) pos1 = 0 StillOK = True while pos1 < len(s1) and stillOK: pos2 = 0 found = False while pos2 < len(alist) and not found: if s1[pos1] == alist[pos2]: found = True else: pos2 += 1 if found: alist[pos2] = None else: stillOK = False pos1 += 1 return stillOK 123456789101112131415161718def anagramSolution2(s1, s2): alist1 = list(s1) alist2 = list(s2) # 这里需要记住,常用的几类排序时间复杂度在 O(n^2) 或 O(nlogn) alist1.sort() alist2.sort() pos = 0 matches = True while pos < len(s1) and matches: if alist1[pos] == alist2[pos]: pos += 1 else: matches = False return matches 123456789101112131415161718192021def anagramSolution3(s1, s2): c1 = [0] * n c2 = [0] * n for i in range(len(s1)): pos = ord(s1[i]) - ord('a') c1[pos] += 1 for i in range(len(s2)): pos = ord(s2[i] - ord('a')) c2[pos] += 1 j = 0 stillOk = True while j < 26 and stillOK: if c1[j] == c2[j]: j += 1 else: stillOK = False return stillOk 第四个例子是最快的。因为没有循环嵌套,只有O(n)的复杂度。倘若需要考虑关于空间上的需求,那么第四个例子新开了额外的空间用于存储计数器,这种方式就是常说的:空间换时间的方法。","categories":[{"name":"Algorithm","slug":"Algorithm","permalink":"https://zade23.github.io/categories/Algorithm/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"}]},{"title":"完备区间判断下的二分查找万能模板","slug":"完备区间判断下的二分查找万能模板","date":"2023-07-07T06:57:15.000Z","updated":"2024-03-14T03:01:45.072Z","comments":true,"path":"2023/07/07/完备区间判断下的二分查找万能模板/","link":"","permalink":"https://zade23.github.io/2023/07/07/%E5%AE%8C%E5%A4%87%E5%8C%BA%E9%97%B4%E5%88%A4%E6%96%AD%E4%B8%8B%E7%9A%84%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E4%B8%87%E8%83%BD%E6%A8%A1%E6%9D%BF/","excerpt":"","text":"场景单调数列/数组 一定可以用二分;但如果非单调的情况,也有可能存在能够使用二分的情况 二分模板整数二分C++123456789101112131415161718192021222324252627#include <iostream>// 整数二分的模板bool check(int x) {/*......*/} // 检查x是否满足某种性质/* 目前发现整数二分的所有情况都可以被下面的两个板子涵盖*/// 2.1.1 区间被划分成 [l, mid] 和 [mid + 1, r]int bsearch_1(int l, int r) { while (l < r) { int mid = l + r >> 1; if (check(mid)) r = mid; else l = mid + 1; } return l}// 2.1.2 区间被划分成 [l, mid - 1] 和 [mid, r]int bsearch_2(int l, int r) { while (l < r) { int mid = l + r + 1 >> 1; if (chedk(mid)) l = mid; else r = mid - 1; }} Python1234567891011121314151617181920212223242526# 二分查找def check(x): # 判断x是否满足某种性质 if x > 0: return True else: return False# 2.1.1 区间被划分成 [l, mid] 和 [mid + 1, r]def bsearch_2(l, r): while l < r: mid = l + r >> 1 if (check(mid)): r = mid else: l = mid + 1 return l# 2.1.2 区间被划分成 [l, mid - 1] 和 [mid, r]def bsearch_1(l, r): while l < r: mid = l + r + 1 >> 1 if (check(mid)): l = mid else: r = mid - 1 return l 浮点数二分只有C++要考虑,高贵的Python不需要考虑。 1234567891011#include <iostream>double bsearch_3(double l, double r) { const double eps = 1e-6; while (r - l > eps) { double mid = (l + r) / 2; if (check(mid)) r = mid; else l = mid; } return l;}","categories":[{"name":"Algorithm","slug":"Algorithm","permalink":"https://zade23.github.io/categories/Algorithm/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"},{"name":"C++","slug":"C","permalink":"https://zade23.github.io/tags/C/"}]},{"title":"Python条件判断的优先级","slug":"Python条件判断的优先级","date":"2023-07-06T12:53:13.000Z","updated":"2024-03-14T02:59:07.047Z","comments":true,"path":"2023/07/06/Python条件判断的优先级/","link":"","permalink":"https://zade23.github.io/2023/07/06/Python%E6%9D%A1%E4%BB%B6%E5%88%A4%E6%96%AD%E7%9A%84%E4%BC%98%E5%85%88%E7%BA%A7/","excerpt":"","text":"背景做LeetCode的时候经常能够发现很多基本功不扎实的问题。这一次是在做一道简单题时候忽视的条件判断优先级的问题 题目连接:https://leetcode.cn/problems/count-the-number-of-vowel-strings-in-range 代码部分 问题代码12345678910class Solution: def vowelStrings(self, words: List[str], left: int, right: int) -> int: words = words[left : right + 1] n = len(words) print(n) res = 0 for i in range(n): if words[i][0] == 'a' or words[i][0] == 'e' or words[i][0] == 'i' or words[i][0] == 'o' or words[i][0] == 'u' and words[i][-1] == 'a' or words[i][-1] == 'e' or words[i][-1] == 'i' or words[i][-1] == 'o' or words[i][-1] == 'u': res += 1 return res 正确代码1234567891011class Solution: def vowelStrings(self, words: List[str], left: int, right: int) -> int: words = words[left : right + 1] n = len(words) print(n) res = 0 for i in range(n): if words[i][0] == 'a' or words[i][0] == 'e' or words[i][0] == 'i' or words[i][0] == 'o' or words[i][0] == 'u': if words[i][-1] == 'a' or words[i][-1] == 'e' or words[i][-1] == 'i' or words[i][-1] == 'o' or words[i][-1] == 'u': res += 1 return res 二者的差别就在如何衔接 or 和 and 之间的关系上。 具体分析分析错误代码的bug: 在第二段错误代码中,条件判断部分存在问题。以下是有问题的代码片段: 12if words[i][0] == 'a' or words[i][0] == 'e' or words[i][0] == 'i' or words[i][0] == 'o' or words[i][0] == 'u' and words[i][-1] == 'a' or words[i][-1] == 'e' or words[i][-1] == 'i' or words[i][-1] == 'o' or words[i][-1] == 'u': res += 1 问题出在逻辑运算符的优先级上。在Python中,and 运算符的优先级高于 or 运算符,因此该条件判断实际上被解释为: 12if words[i][0] == 'a' or words[i][0] == 'e' or words[i][0] == 'i' or words[i][0] == 'o' or (words[i][0] == 'u' and words[i][-1] == 'a') or words[i][-1] == 'e' or words[i][-1] == 'i' or words[i][-1] == 'o' or words[i][-1] == 'u': res += 1 由于 or 运算符的短路特性,只要 words[i][0] 的首字母为元音字母之一,整个条件判断就会被认为是True,导致res增加。而第二个部分 words[i][-1] == 'a'、words[i][-1] == 'e'等都是单独的条件,不会影响整个条件判断的结果。 修正该bug的方法是使用括号明确指定条件的分组,确保逻辑关系正确。以下是修正后的代码: 12if (words[i][0] == 'a' or words[i][0] == 'e' or words[i][0] == 'i' or words[i][0] == 'o' or words[i][0] == 'u') and (words[i][-1] == 'a' or words[i][-1] == 'e' or words[i][-1] == 'i' or words[i][-1] == 'o' or words[i][-1] == 'u'): res += 1 这样修改后,条件判断会首先检查首字母是否为元音字母,然后再检查末尾字母是否为元音字母,两个条件都满足时才会增加res的值。","categories":[{"name":"Algorithm","slug":"Algorithm","permalink":"https://zade23.github.io/categories/Algorithm/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"}]},{"title":"Hello World","slug":"hello-world","date":"2023-05-16T09:45:58.000Z","updated":"2024-03-11T08:34:34.956Z","comments":false,"path":"2023/05/16/hello-world/","link":"","permalink":"https://zade23.github.io/2023/05/16/hello-world/","excerpt":"","text":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1hexo new "My New Post" More info: Writing Run server1hexo server More info: Server Generate static files1hexo generate More info: Generating Deploy to remote sites1hexo deploy More info: Deployment","categories":[{"name":"whatever","slug":"whatever","permalink":"https://zade23.github.io/categories/whatever/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://zade23.github.io/tags/Hexo/"}]}],"categories":[{"name":"Git Workflow","slug":"Git-Workflow","permalink":"https://zade23.github.io/categories/Git-Workflow/"},{"name":"Deeplearning","slug":"Deeplearning","permalink":"https://zade23.github.io/categories/Deeplearning/"},{"name":"Algorithm","slug":"Algorithm","permalink":"https://zade23.github.io/categories/Algorithm/"},{"name":"whatever","slug":"whatever","permalink":"https://zade23.github.io/categories/whatever/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://zade23.github.io/tags/Python/"},{"name":"Git","slug":"Git","permalink":"https://zade23.github.io/tags/Git/"},{"name":"conda","slug":"conda","permalink":"https://zade23.github.io/tags/conda/"},{"name":"environment","slug":"environment","permalink":"https://zade23.github.io/tags/environment/"},{"name":"Docker","slug":"Docker","permalink":"https://zade23.github.io/tags/Docker/"},{"name":"PyTorch","slug":"PyTorch","permalink":"https://zade23.github.io/tags/PyTorch/"},{"name":"工具网站","slug":"工具网站","permalink":"https://zade23.github.io/tags/%E5%B7%A5%E5%85%B7%E7%BD%91%E7%AB%99/"},{"name":"经济学小知识","slug":"经济学小知识","permalink":"https://zade23.github.io/tags/%E7%BB%8F%E6%B5%8E%E5%AD%A6%E5%B0%8F%E7%9F%A5%E8%AF%86/"},{"name":"C++","slug":"C","permalink":"https://zade23.github.io/tags/C/"},{"name":"Hexo","slug":"Hexo","permalink":"https://zade23.github.io/tags/Hexo/"}]} \ No newline at end of file