阅读源码版本python 3.8.3

参考书籍<<Python源码剖析>>

参考书籍<<Python学习手册 第4版>>

官网文档目录介绍

  1. Doc目录主要是官方文档的说明。

  2. Include:目录主要包括了Python的运行的头文件。

  3. Lib:目录主要包括了用Python实现的标准库。

  4. Modules: 该目录中包含了所有用C语言编写的模块,比如random、cStringIO等。Modules中的模块是那些对速度要求非常严格的模块,而有一些对速度没有太严格要求的模块,比如os,就是用Python编写,并且放在Lib目录下的

  5. Objects:该目录中包含了所有Python的内建对象,包括整数、list、dict等。同时,该目录还包括了Python在运行时需要的所有的内部使用对象的实现。

  6. Parser:该目录中包含了Python解释器中的Scanner和Parser部分,即对Python源码进行词法分析和语法分析的部分。除了这些,Parser目录下还包含了一些有用的工具,这些工具能够根据Python语言的语法自动生成Python语言的词法和语法分析器,将python文件编译生成语法树等相关工作。

  7. Programs目录主要包括了python的入口函数。

  8. Python:目录主要包括了Python动态运行时执行的代码,里面包括编译、字节码解释器等工作。

1. Run Python文件的启动流程

Python启动是由Programs下的python.c文件中的main函数开始执行

  1. /* Minimal main program -- everything is loaded from the library */
  2. #include "Python.h"
  3. #include "pycore_pylifecycle.h"
  4. #ifdef MS_WINDOWS
  5. int
  6. wmain(int argc, wchar_t **argv)
  7. {
  8. return Py_Main(argc, argv);
  9. }
  10. #else
  11. int
  12. main(int argc, char **argv)
  13. {
  14. return Py_BytesMain(argc, argv);
  15. }
  16. #endif
  1. int
  2. Py_Main(int argc, wchar_t **argv) {
  3. ...
  4. return pymian_main(&args);
  5. }
  6. static int
  7. pymain_main(_PyArgv *args)
  8. {
  9. PyStatus status = pymain_init(args); // 初始化
  10. if (_PyStatus_IS_EXIT(status)) {
  11. pymain_free();
  12. return status.exitcode;
  13. }
  14. if (_PyStatus_EXCEPTION(status)) {
  15. pymain_exit_error(status);
  16. }
  17. return Py_RunMain();
  18. }

1.1 初始化关键流程

  • 初始化一些与配置项 如:开启utf-8模式,设置Python内存分配器
  • 初始化pyinit_core核心部分
    • 创建生命周期 pycore_init_runtime, 同时生成HashRandom
    • 初始化线程和解释器并创建GIL锁 pycore_create_interpreter
    • 初始化所有基础类型,list, int, tuple等 pycore_init_types
    • 初始化sys模块 _PySys_Create
    • 初始化内建函数或者对象,如map, None, True等 pycore_init_builtins
      • 其中包括内建的错误类型初始化 _PyBuiltins_AddExceptions

Python3.8 对Python解释器的初始化做了重构PEP 587-Python初始化配置

1.2 run 相关源码阅读

  1. int
  2. Py_RunMain(void)
  3. {
  4. int exitcode = 0;
  5. pymain_run_python(&exitcode); //执行python脚本
  6. if (Py_FinalizeEx() < 0) { // 释放资源
  7. /* Value unlikely to be confused with a non-error exit status or
  8. other special meaning */
  9. exitcode = 120;
  10. }
  11. pymain_free(); // 释放资源
  12. if (_Py_UnhandledKeyboardInterrupt) {
  13. exitcode = exit_sigint();
  14. }
  15. return exitcode;
  16. }
  17. static void
  18. pymain_run_python(int *exitcode)
  19. {
  20. // 获取一个持有GIL锁的解释器
  21. PyInterpreterState *interp = _PyInterpreterState_GET_UNSAFE();
  22. /* pymain_run_stdin() modify the config */
  23. ... // 添加sys_path等操作
  24. if (config->run_command) {
  25. // 命令行模式
  26. *exitcode = pymain_run_command(config->run_command, &cf);
  27. }
  28. else if (config->run_module) {
  29. // 模块名
  30. *exitcode = pymain_run_module(config->run_module, 1);
  31. }
  32. else if (main_importer_path != NULL) {
  33. *exitcode = pymain_run_module(L"__main__", 0);
  34. }
  35. else if (config->run_filename != NULL) {
  36. // 文件名
  37. *exitcode = pymain_run_file(config, &cf);
  38. }
  39. else {
  40. *exitcode = pymain_run_stdin(config, &cf);
  41. }
  42. ...
  43. }
  44. /* Parse input from a file and execute it */ //Python/pythonrun.c
  45. int
  46. PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
  47. PyCompilerFlags *flags)
  48. {
  49. if (filename == NULL)
  50. filename = "???";
  51. if (Py_FdIsInteractive(fp, filename)) {
  52. int err = PyRun_InteractiveLoopFlags(fp, filename, flags); // 是否是交互模式
  53. if (closeit)
  54. fclose(fp);
  55. return err;
  56. }
  57. else
  58. return PyRun_SimpleFileExFlags(fp, filename, closeit, flags); // 执行脚本
  59. }
  60. // 执行python .py文件
  61. int
  62. PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
  63. PyCompilerFlags *flags)
  64. {
  65. ...
  66. if (maybe_pyc_file(fp, filename, ext, closeit)) {
  67. FILE *pyc_fp;
  68. /* Try to run a pyc file. First, re-open in binary */
  69. ...
  70. v = run_pyc_file(pyc_fp, filename, d, d, flags);
  71. } else {
  72. /* When running from stdin, leave __main__.__loader__ alone */
  73. ...
  74. v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
  75. closeit, flags);
  76. }
  77. ...
  78. }
  79. PyObject *
  80. PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
  81. PyObject *locals, int closeit, PyCompilerFlags *flags)
  82. {
  83. ...
  84. // // 解析传入的脚本,解析成AST
  85. mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
  86. flags, NULL, arena);
  87. ...
  88. // 将AST编译成字节码然后启动字节码解释器执行编译结果
  89. ret = run_mod(mod, filename, globals, locals, flags, arena);
  90. ...
  91. }
  92. // 查看run_mode
  93. static PyObject *
  94. run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
  95. PyCompilerFlags *flags, PyArena *arena)
  96. {
  97. ...
  98. // 将AST编译成字节码
  99. co = PyAST_CompileObject(mod, filename, flags, -1, arena);
  100. ...
  101. // 解释执行编译的字节码
  102. v = run_eval_code_obj(co, globals, locals);
  103. Py_DECREF(co);
  104. return v;
  105. }

1.3 字节码查看案例

新建test.py

  1. def show(a):
  2. return a
  3. if __name__ == "__main__":
  4. print(show(10))

执行命令: python3 -m dis test.py

  1. λ ppython3 -m dis test.py
  2. 3 0 LOAD_CONST 0 (<code object show at 0x000000E7FC89E270, file "test.py", line 3>)
  3. 2 LOAD_CONST 1 ('show')
  4. 4 MAKE_FUNCTION 0
  5. 6 STORE_NAME 0 (show)
  6. 7 8 LOAD_NAME 1 (__name__)
  7. 10 LOAD_CONST 2 ('__main__')
  8. 12 COMPARE_OP 2 (==)
  9. 14 POP_JUMP_IF_FALSE 28
  10. 8 16 LOAD_NAME 2 (print)
  11. 18 LOAD_NAME 0 (show)
  12. 20 LOAD_CONST 3 (10)
  13. 22 CALL_FUNCTION 1
  14. 24 CALL_FUNCTION 1
  15. 26 POP_TOP
  16. >> 28 LOAD_CONST 4 (None)

左边3, 7, 8表示 test.py中的第一行和第二行,右边表示python byte code

Include/opcode.h 发现总共有 163 个 opcode, 所有的 python 源文件(Lib库中的文件)都会被编译器翻译成由 opcode 组成的 pyx 文件,并缓存在执行目录,下次启动程序如果源代码没有修改过,则直接加载这个pyx文件,这个文件的存在可以加快 python 的加载速度。普通.py文件如我们的test.py 是直接进行编译解释执行的,不会生成.pyc文件,想生成test.pyc 需要使用python内置的py_compile模块来编译该文件,或者执行命令python3 -m test.py python生成.pyc文件

1.4 python中的code对象

字节码在python虚拟机中对应的是PyCodeObject对象, .pyc文件是字节码在磁盘上的表现形式。python编译的过程中,一个代码块就对应一个code对象,那么如何确定多少代码算是一个Code Block呢? 编译过程中遇到一个新的命名空间或者作用域时就生成一个code对象,即类或函数都是一个代码块,一个code的类型结构就是PyCodeObject, 参考Junnplus

  1. /* Bytecode object */
  2. typedef struct {
  3. PyObject_HEAD
  4. int co_argcount; /* #arguments, except *args */ // 位置参数的个数,
  5. int co_posonlyargcount; /* #positional only arguments */
  6. int co_kwonlyargcount; /* #keyword only arguments */
  7. int co_nlocals; /* #local variables */
  8. int co_stacksize; /* #entries needed for evaluation stack */
  9. int co_flags; /* CO_..., see below */
  10. int co_firstlineno; /* first source line number */
  11. PyObject *co_code; /* instruction opcodes */
  12. PyObject *co_consts; /* list (constants used) */
  13. PyObject *co_names; /* list of strings (names used) */
  14. PyObject *co_varnames; /* tuple of strings (local variable names) */
  15. PyObject *co_freevars; /* tuple of strings (free variable names) */
  16. PyObject *co_cellvars; /* tuple of strings (cell variable names) */
  17. /* The rest aren't used in either hash or comparisons, except for co_name,
  18. used in both. This is done to preserve the name and line number
  19. for tracebacks and debuggers; otherwise, constant de-duplication
  20. would collapse identical functions/lambdas defined on different lines.
  21. */
  22. Py_ssize_t *co_cell2arg; /* Maps cell vars which are arguments. */
  23. PyObject *co_filename; /* unicode (where it was loaded from) */
  24. PyObject *co_name; /* unicode (name, for reference) */
  25. PyObject *co_lnotab; /* string (encoding addr<->lineno mapping) See
  26. Objects/lnotab_notes.txt for details. */
  27. void *co_zombieframe; /* for optimization only (see frameobject.c) */
  28. PyObject *co_weakreflist; /* to support weakrefs to code objects */
  29. /* Scratch space for extra data relating to the code object.
  30. Type is a void* to keep the format private in codeobject.c to force
  31. people to go through the proper APIs. */
  32. void *co_extra;
  33. /* Per opcodes just-in-time cache
  34. *
  35. * To reduce cache size, we use indirect mapping from opcode index to
  36. * cache object:
  37. * cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
  38. */
  39. // co_opcache_map is indexed by (next_instr - first_instr).
  40. // * 0 means there is no cache for this opcode.
  41. // * n > 0 means there is cache in co_opcache[n-1].
  42. unsigned char *co_opcache_map;
  43. _PyOpcache *co_opcache;
  44. int co_opcache_flag; // used to determine when create a cache.
  45. unsigned char co_opcache_size; // length of co_opcache.
  46. } PyCodeObject;
Field Content Type
co_argcount Code Block 的参数个数 PyIntObject
co_posonlyargcount Code Block 的位置参数个数 PyIntObject
co_kwonlyargcount Code Block 的关键字参数个数 PyIntObject
co_nlocals Code Block 中局部变量的个数 PyIntObject
co_stacksize Code Block 的栈大小 PyIntObject
co_flags N/A PyIntObject
co_firstlineno Code Block 对应的 .py 文件中的起始行号 PyIntObject
co_code Code Block 编译所得的字节码 PyBytesObject
co_consts Code Block 中的常量集合 PyTupleObject
co_names Code Block 中的符号集合 PyTupleObject
co_varnames Code Block 中的局部变量名集合 PyTupleObject
co_freevars Code Block 中的自由变量名集合 PyTupleObject
co_cellvars Code Block 中嵌套函数所引用的局部变量名集合 PyTupleObject
co_cell2arg N/A PyTupleObject
co_filename Code Block 对应的 .py 文件名 PyUnicodeObject
co_name Code Block 的名字,通常是函数名/类名/模块名 PyUnicodeObject
co_lnotab Code Block 的字节码指令于 .py 文件中 source code 行号对应关系 PyBytesObject
co_opcache_map python3.8新增字段,存储字节码索引与CodeBlock对象的映射关系 PyDictObject

1.4.1 LOAD_CONST

  1. // Python\ceval.c
  2. PREDICTED(LOAD_CONST); -> line 943: #define PREDICTED(op) PRED_##op:
  3. FAST_DISPATCH(); -> line 876 #define FAST_DISPATCH() goto fast_next_opcode

额外收获: c 语言中 ##和# 号 在marco 里的作用可以参考 这篇

在宏定义里, ## 被称为连接符(concatenator) , a##b 表示将ab连接起来

a 表示把a转换成字符串,即加双引号,

所以LONAD_CONST这个指领根据宏定义展开如下:

  1. case TARGET(LOAD_CONST): {
  2. PRED_LOAD_CONST:
  3. PyObject *value = GETITEM(consts, oparg); // 获取一个PyObject* 指针对象
  4. Py_INCREF(value); // 引用计数加1
  5. PUSH(value); // 把刚刚创建的PyObject* push到当前的frame的stack上, 以便下一个指令从这个 stack 上面获取
  6. goto fast_next_opcode;

1.5 main_loop

  1. // Python\ceval.c
  2. main_loop:
  3. for (;;) {
  4. ...
  5. switch (opcode) {
  6. /* BEWARE!
  7. It is essential that any operation that fails must goto error
  8. and that all operation that succeed call [FAST_]DISPATCH() ! */
  9. case TARGET(NOP): {
  10. FAST_DISPATCH();
  11. }
  12. case TARGET(LOAD_FAST): {
  13. PyObject *value = GETLOCAL(oparg);
  14. if (value == NULL) {
  15. format_exc_check_arg(PyExc_UnboundLocalError,
  16. UNBOUNDLOCAL_ERROR_MSG,
  17. PyTuple_GetItem(co->co_varnames, oparg));
  18. goto error;
  19. }
  20. Py_INCREF(value);
  21. PUSH(value);
  22. FAST_DISPATCH();
  23. }
  24. case TARGET(LOAD_CONST): {
  25. PREDICTED(LOAD_CONST);
  26. PyObject *value = GETITEM(consts, oparg);
  27. Py_INCREF(value);
  28. PUSH(value);
  29. FAST_DISPATCH();
  30. }
  31. ...
  32. }
  33. }

在 python 虚拟机中,解释器主要在一个很大的循环中,不停地读入 opcode, 并根据 opcode 执行对应的指令,当执行完所有指令虚拟机退出,程序也就结束了

1.6 总结

过程描述:

  1. python先把代码(.py文件)编译成字节码,交给字节码虚拟机,然后虚拟机会从编译得到的PyCodeObject对象中一条一条执行字节码指令,并在当前的上下文环境中执行这条字节码指令,从而完成程序的执行。Python虚拟机实际上是在模拟操作中执行文件的过程。PyCodeObject对象中包含了字节码指令以及程序的所有静态信息,但没有包含程序运行时的动态信息——执行环境(PyFrameObject),后面会继续记录执行环境的阅读。
  2. 从整体上看:OS中执行程序离不开两个概念:进程和线程。python中模拟了这两个概念,模拟进程和线程的分别是PyInterpreterStatePyTreadState。即:每个PyThreadState都对应着一个帧栈,python虚拟机在多个线程上切换(靠GIL实现线程之间的同步)。当python虚拟机开始执行时,它会先进行一些初始化操作,最后进入PyEval_EvalFramEx函数,内部实现了一个main_loop它的作用是不断读取编译好的字节码,并一条一条执行,类似CPU执行指令的过程。函数内部主要是一个switch结构,根据字节码的不同执行不同的代码

2. Python中的Frame

如上所说,PyCodeObject对象只是包含了字节码指令集以及程序的相关静态信息,虚拟机的执行还需要一个执行环境,即PyFrameObject,也就是对系统栈帧的模拟。

2.1 堆和栈的认识

堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用。一个对象的大小是不可估计的,或者说是可以动态变化的,但是在栈中,一个对象只对应了一个4btye的引用(堆栈分离的好处)

内存中的堆栈和数据结构堆栈不是一个概念,可以说内存中的堆栈是真实存在的物理区,数据结构中的堆栈是抽象的数据存储结构。

内存空间在逻辑上分为三部分:代码区,静态数据区和动态数据区,动态数据区有分为堆区和栈区

  • 代码区:存储的二进制代码块,高级调度(作业调度)、中级调度(内存调度)、低级调度(进程调度)控制代码区执行代码的切换
  • 静态数据区:存储全局变量,静态变量,常量,系统自动分配和回收。
  • 动态数据区:
    • 栈区(stack):存储运行方法的形参,局部变量,返回值,有编译器自动分配和回收,操作类似数据结构中的栈
    • 堆区(heap):new一个对象的引用或者地址存储在栈区,该地址指向指向对象存储在堆区中的真实数据。如c中的malloc函数,python中的Pymalloc

2.2 PyFrameObject对象

  1. typedef struct _frame{
  2. PyObject_VAR_HEAD //"运行时栈"的大小是不确定的, 所以用可变长的对象
  3. struct _frame *f_back; //执行环境链上的前一个frame,很多个PyFrameObject连接起来形成执行环境链表
  4. PyCodeObject *f_code; //PyCodeObject 对象,这个frame就是这个PyCodeObject对象的上下文环境
  5. PyObject *f_builtins; //builtin名字空间
  6. PyObject *f_globals; //global名字空间
  7. PyObject *f_locals; //local名字空间
  8. PyObject **f_valuestack; //"运行时栈"的栈底位置
  9. PyObject **f_stacktop; //"运行时栈"的栈顶位置
  10. //...
  11. int f_lasti; //上一条字节码指令在f_code中的偏移位置
  12. int f_lineno; //当前字节码对应的源代码行
  13. //...
  14. //动态内存,维护(局部变量+cell对象集合+free对象集合+运行时栈)所需要的空间
  15. PyObject *f_localsplus[1];
  16. } PyFrameObject;

如果你想知道 PyFrameObject 中每个字段的意义, 请参考 Junnplus' blog 或者直接阅读源代码,了解frame的执行过程可以参考zpoint'blog.

名字空间实际上是维护着变量名和变量值之间关系的PyDictObject对象。

f_builtins, f_globals, f_locals名字空间分别维护了builtin, global, local的name与对应值之间的映射关系。

每一个 PyFrameObject对象都维护了一个 PyCodeObject对象,这表明每一个 PyFrameObject中的动态内存空间对象都和源代码中的一段Code相对应。

2.2.1 栈帧的获取,工作中会用到

可以通过sys._getframe([depth]), 获取指定深度的PyFrameObject对象

  1. >>> import sys
  2. >>> frame = sys._getframe()
  3. >>> frame
  4. <frame object at 0x103ab2d48>

2.2.2 python中变量名的解析规则 LEGB

Local -> Enclosed -> Global -> Built-In

  • Local 表示局部变量

  • Enclosed 表示嵌套的变量

  • Global 表示全局变量

  • Built-In 表示内建变量

如果这几个顺序都取不到,就会抛出 ValueError

可以在这个网站python执行可视化网站,观察代码执行流程,以及变量的转换赋值情况。

3. 额外收获

意外收获: 之前知道pythonGIL , 遇到I/O阻塞时会释放gil,现在从源码中看到了对应的流程

  1. if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
  2. /* Give another thread a chance */
  3. if (_PyThreadState_Swap(&runtime->gilstate, NULL) != tstate) {
  4. Py_FatalError("ceval: tstate mix-up");
  5. }
  6. drop_gil(ceval, tstate);
  7. /* Other threads may run now */
  8. take_gil(ceval, tstate);
  9. /* Check if we should make a quick exit. */
  10. exit_thread_if_finalizing(runtime, tstate);
  11. if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
  12. Py_FatalError("ceval: orphan tstate");
  13. }
  14. }
  15. /* Check for asynchronous exceptions. */

参考:

python 源码分析 基本篇

python虚拟机运行原理

python3 源码阅读-虚拟机运行原理的更多相关文章

  1. Robotium源码分析之运行原理

    从上一章<Robotium源码分析之Instrumentation进阶>中我们了解到了Robotium所基于的Instrumentation的一些进阶基础,比如它注入事件的原理等,但Rob ...

  2. Python3 源码阅读-深入了解Python GIL

    今日得到: 三人行,必有我师焉,择其善者而从之,其不善者而改之. 今日看源码才理解到现在已经是2020年了,而在2010年的时候,大佬David Beazley就做了讲座讲解Python GIL的设计 ...

  3. Python3 源码阅读 - 垃圾回收机制

    Python的垃圾回收机制包括了两大部分: 引用计数(大部分在 Include/object.h 中定义) 标记清除+隔代回收(大部分在 Modules/gcmodule.c 中定义) 1. 引用计数 ...

  4. angular源码阅读,依赖注入的原理:injector,provider,module之间的关系。

    最开始使用angular的时候,总是觉得它的依赖注入方式非常神奇. 如果你跳槽的时候对新公司说,我曾经使用过angular,那他们肯定会问你angular的依赖注入原理是什么? 这篇博客其实是angu ...

  5. 老李推荐:第5章7节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles

    老李推荐:第5章7节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles   poptest是国内唯一一家培养测试开 ...

  6. 老李推荐:第5章6节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 初始化事件源

    老李推荐:第5章6节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 初始化事件源   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试 ...

  7. 老李推荐:第5章3节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动脚本

    老李推荐:第5章3节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 启动脚本   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性 ...

  8. 老李推荐:第5章5节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 获取系统服务引用

    老李推荐:第5章5节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 获取系统服务引用   上一节我们描述了monkey的命令处理入口函数run是如何调用optionP ...

  9. 老李推荐:第5章2节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动流程概览

    老李推荐:第5章2节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 启动流程概览   每个应用都会有一个入口方法来供操作系统调用执行,Monkey这个应用的入口方法就 ...

随机推荐

  1. 【雕爷学编程】Arduino动手做(5)---热敏温度传感器模块

    37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器模块,依照实践(动手试试)出真知的理念,以学习和交流为目的,这里准备逐 ...

  2. ES6常见面试题

    1.es5和es6的区别,说一下你所知道的es6 ECMAScript5,即ES5,是ECMAScript的第五次修订,于2009年完成标准化 ECMAScript6,即ES6,是ECMAScript ...

  3. 最香远程开发解决方案!手把手教你配置VS Code远程开发工具,工作效率提升N倍

    文章每周持续更新,原创不易,「三连」让更多人看到是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) 今天和大家分享远程开发工具,分享一下我平常是如何用 V ...

  4. COCO数据集提取特定多个类并在YOLO-V3上训练

    先占个地方,有空再写 ` import os Dir = './coco_class_6/Annotations/val2014' ImageDir = './coco_class_6/images/ ...

  5. javaScript(原型链)

    在了解javaScript的原型链之前,我们得先来看一下原型是什么. 在javaScript中,所有的函数都会有着一个特别属性:prototype(显示原型属性):当我们运行如下代码时: functi ...

  6. adb常用命令食用方法

    一.什么是adb? adb是Android Debug Bridge的缩写,即安卓调试桥:那什么是安卓调试桥?简单来说,就是一个通用命令行工具,允许计算机与模拟器或连接的安卓设备之间进行通信,提供各种 ...

  7. Kubernetes Ingress简单入门

    作者:Nick Ramirez 原文链接:https://thenewstack.io/kubernetes-ingress-for-beginners/ 本文转载自Rancher Labs 不知道你 ...

  8. .NET Core 反射获取所有控制器及方法上特定标签

    .NET Core 反射获取所有控制器及方法上特定标签 有个需求,就是在. NET Core中,我们想在项目 启动时,获取LinCmsAuthorizeAttribute这个特性标签所有出现的地方,把 ...

  9. python的转义

    print('"I\'m OK"') print("I'm OK") print('"I"\'m \"OK"') &qu ...

  10. Java中的集合(二)单列集合顶层接口------Collection接口

    Java中的集合(二)单列集合顶层接口------Collection接口 Collection是一个高度封装的集合接口,继承自Iterable接口,它提供了所有集合要实现的默认方法.由于Iterab ...