######################################## 基本概念 ######################################## 编码与解析器 **************************************** #. 对于不使用 Python 默认编码的用户来说,可以在文件开头处添加 ``# -*- coding:utf-8 -*-`` 来指明文件的编码 .. note:: Python3 的默认编码为是 utf-8,因此,Python3 的脚本文件无需添加该行代码 #. 对于 Linux 用户来说,由于脚本的默认解析器为 bash,为了方便执行 Python 脚本,可以在文件开头处添加 ``#/usr/bin/python3`` 来指明脚本文件的默认解析器 鸭子类型 **************************************** Python 是一种动态类型,与 C++ 等静态语言不同的是,它关注的是对象的行为而不是类型。动态语言常用的类型为 ``鸭子类型`` 鸭子类型可以概括为:一只鸟走起来像鸭子、游起泳来像鸭子、叫起来也像鸭子,那它就可以被当做鸭子 C++ 等静态语言,通过类型来构建整个语言体系,通过公有继承来维护 is-a 关系。而 Python 通过行为来构建整个语言体系,通过让对象拥有相似的行为来保证 is-a 关系。 例如对于元组来说,其可迭代,可打印,可索引(打印时元素被小括号包裹) 而对于 sqlalchemy 从数据库查到的结果来说,其可迭代,可打印,可索引(打印时元素被小括号包裹)。那么我们就认为 sqlalchemy 的结果就是元组。也可以通过类型转换转换为元组(该行为为约定) - 在 Python 中,协议就是接口,而不是 interface .. seealso:: - `Python里有类似Java的接口(interface)吗? `_ 列表和元组 **************************************** 和 C++ 不一样的是,Python 中的列表和元组都是异质容器。因此它们的唯一区别就是列表是可变的,元组是不可变的 字典 **************************************** Python 自 3.5 以后的字典是有序的,所谓有序是指字典索引出来的顺序和插入的顺序相同。 Python 以前字典的底层数据结构是一个哈希表,之后改成了一个“紧缩字典”。并不是和 C++ 一样的红黑树 .. seealso:: - `[Python-Dev] Python 3.6 dict becomes compact and gets a private version; and keywords become ordered `_ 类 **************************************** Python 使用命名约定来决定一个类的属性。以下划线开头的函数是保护函数,以双下划线开头的是私有函数。请注意,上述只是一些命名约定,Python 使用 Name Mangling 的方式将上述形式的函数转换为对此类的调用 例如: .. code-block:: python class A: def _printA(self): print("yes") def __printA1(self): print("no") class B(A): def printB(self): self._printA() def printB1(self): self._A__printA1() a = A() a._printA() a._A__printA1() b = B() b.printB() b.printB1() 如上,以双下划线开头的函数将会视为私有函数,其函数名将被转换为 ``_ClassName__method_name`` 的形式。可以手动调用此私有函数 此外,还有以下划线开头和下划线结尾的魔法函数,这些函数一般是保留的函数,例如: +------+--------------------------------------------+ | 名称 | 作用 | +======+============================================+ | init | 初始化类对象 | +------+--------------------------------------------+ | str | 将类对象转为字符串 | +------+--------------------------------------------+ | repr | 打印对象 | +------+--------------------------------------------+ | cmp | 比较大小 | +------+--------------------------------------------+ | len | 返回对象长度 | +------+--------------------------------------------+ | iter | 返回一个迭代器 | +------+--------------------------------------------+ | new | 先于 init 调用的函数,用来返回一个类的实例 | +------+--------------------------------------------+ | call | 仿函数 | +------+--------------------------------------------+ | add | 加法 | +------+--------------------------------------------+ | radd | 反向加法 | +------+--------------------------------------------+ | del | 析构函数 | +------+--------------------------------------------+ 还有一些特殊类需要实现的函数: 配合 with 语句的类需要实现: +-------+------------------------------------+ | 名称 | 作用 | +=======+====================================+ | enter | 进入函数时执行的任务 | +-------+------------------------------------+ | exit | 退出时执行的任务(包括因异常退出) | +-------+------------------------------------+ .. seealso:: - `Python中私有和受保护方法的继承 - Thinbug `_ 模块 **************************************** 导入模块可以使用 import 语句,另一种方式是手动读取文件: .. code-block:: python from importlib.machinery import SourceFileLoader config = SourceFileLoader('config', config_file).load_module() Python 模块搜索的顺序是: #. 当前路径 #. PYTHONPATH 下的路径 #. PATH 中的路径 此路径被储存到 sys.path 变量中 Python 中的每个文件就是一个模块。模块的导入只是简单的在导入位置执行一遍模块。尽管你可以选择导入哪些部分,但是实际上所有部分都会被执行,只是不需要的被舍弃了而已。例如: .. code-block:: python #!/usr/bin/python3 print("yes") def func(): print("func") 任何导入此模块的文件都会打印“yes” 模块可以被多次 import,但是只会被导入一次 包 **************************************** 在文件夹中创建 ``__init__.py`` 可以将当前文件夹变成一个包。此文件可以是空的,但是这样导入的时候需要指定模块 另一种方便的方式是在文件中声明需要导入的包,这样直接使用 ``import * from PACKAGE`` 就行了 装饰函数 **************************************** 装饰函数在以前学习的时候一直不懂什么意思,C++ 写的多了现在感觉也就一般般。只是将装饰器函数的参数是函数指针罢了。 先来一个简单的例子: .. code-block:: python def funA(fn): print("yes") return fn @funA def funB(): print('c') funB() 如图,装饰器 *funA* 传入的是一个函数指针,传出的也是一个函数指针(或者是一个可调用对象)。 装饰器的功能等价于 .. code-block:: python funB = funA(funB) 因此,如果只是 *funB* 的形式,只是拿了一下 *funA(funB)* 的返回值,需要继续调用的话使用 *funB* (你也可以在 funA 内自动调用 funB,这里只是为了格式上的统一) 然后就是传入参数的问题: .. code-block:: python def funA(fn): print("yes") return fn @funA def funB(a): print('c') print(a) funB(10) 没什么好说的,不明白为什么网上这么多文章要里面再嵌套一个函数,仿 js 来个闭包吗?但是 Python 中的闭包并不能修改局部变量的值 如果按照网上的来的话,形式是: .. code-block:: python def funA(fn): # 定义一个嵌套函数 def say(arc): print("hello",arc) return say @funA def funB(arc): print("funB():", a) funB("world") 唯一需要注意的就是 say 和 funB 的参数要一样,毕竟上述语句等价于: .. code-block:: python funB = lambda fn: say 也就是说 funB 修饰后指向的实际上是 say 函数。要求参数一样自然也就没什么奇怪的了 类 **************************************** 私有成员 ======================================== Python 类中类似 ``_xxx`` 和 ``__xxx`` 这样的函数为私有函数,否则为公有函数。 私有函数依然可以被调用,但是并不推荐 构造函数 ======================================== Python 与构造函数相关的函数有两个: ``__new__`` 和 ``__init__`` - __new__ 函数在对象创建前访问,返回类的实例 - __init__ 函数在对象创建后访问,用来初始化类成员 析构函数 ======================================== 在通过 ``del object`` 表达式来销毁一个对象、或者 Python 自动销毁一个对象时,将会调用类的 ``__del__`` 函数。 需要注意的是:Python 采用 ``引用记数`` 的方式来决定对象是否真的应该被销毁,del 操作符的作用是使引用计数减一,但是只有当计数为零时,Python 才会真的调用析构函数。 运算符重载 ======================================== 运算符重载分为两种:左手运算符和右手运算符 - 左手运算符是默认的重载运算符,意味着类对象出现在操作符左边 - 右手运算符是带有 ``r`` 前缀的运算符重载函数,意味着类对象出现在操作符右边 ====================== ================ ========= 函数名 运算符表达式 解释 ====================== ================ ========= __add__(self,rhs) self + rhs 加法 __radd__(self,rhs) rhs + self 右手加法 __sub__(self,rhs) self - rhs 减法 __mul__(self,rhs) self * rhs 乘法 __truediv__(self,rhs) self / rhs 除法 __floordiv__(self,rhs) self //rhs 整除 __mod__(self,rhs) self % rhs 求余 __pow__(self,rhs) self \**rhs 幂运算 ====================== ================ ========= 上述中右手运算符仅仅列出了加法。 例: .. code-block:: python class Number: def __add__(self, other): print("左手加法") def __radd__(self, other): print("右手加法") number = Number() number + 1 # 调用 __add__ 1 + number # 调用 __radd__