基本概念

编码与解析器

  1. 对于不使用 Python 默认编码的用户来说,可以在文件开头处添加 # -*- coding:utf-8 -*- 来指明文件的编码

    备注

    Python3 的默认编码为是 utf-8,因此,Python3 的脚本文件无需添加该行代码

  2. 对于 Linux 用户来说,由于脚本的默认解析器为 bash,为了方便执行 Python 脚本,可以在文件开头处添加 #/usr/bin/python3 来指明脚本文件的默认解析器

鸭子类型

Python 是一种动态类型,与 C++ 等静态语言不同的是,它关注的是对象的行为而不是类型。动态语言常用的类型为 鸭子类型

鸭子类型可以概括为:一只鸟走起来像鸭子、游起泳来像鸭子、叫起来也像鸭子,那它就可以被当做鸭子

C++ 等静态语言,通过类型来构建整个语言体系,通过公有继承来维护 is-a 关系。而 Python 通过行为来构建整个语言体系,通过让对象拥有相似的行为来保证 is-a 关系。

例如对于元组来说,其可迭代,可打印,可索引(打印时元素被小括号包裹) 而对于 sqlalchemy 从数据库查到的结果来说,其可迭代,可打印,可索引(打印时元素被小括号包裹)。那么我们就认为 sqlalchemy 的结果就是元组。也可以通过类型转换转换为元组(该行为为约定)

  • 在 Python 中,协议就是接口,而不是 interface

列表和元组

和 C++ 不一样的是,Python 中的列表和元组都是异质容器。因此它们的唯一区别就是列表是可变的,元组是不可变的

字典

Python 自 3.5 以后的字典是有序的,所谓有序是指字典索引出来的顺序和插入的顺序相同。

Python 以前字典的底层数据结构是一个哈希表,之后改成了一个“紧缩字典”。并不是和 C++ 一样的红黑树

Python 使用命名约定来决定一个类的属性。以下划线开头的函数是保护函数,以双下划线开头的是私有函数。请注意,上述只是一些命名约定,Python 使用 Name Mangling 的方式将上述形式的函数转换为对此类的调用

例如:

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

退出时执行的任务(包括因异常退出)

模块

导入模块可以使用 import 语句,另一种方式是手动读取文件:

from importlib.machinery import SourceFileLoader
config = SourceFileLoader('config', config_file).load_module()

Python 模块搜索的顺序是:

  1. 当前路径

  2. PYTHONPATH 下的路径

  3. PATH 中的路径

此路径被储存到 sys.path 变量中

Python 中的每个文件就是一个模块。模块的导入只是简单的在导入位置执行一遍模块。尽管你可以选择导入哪些部分,但是实际上所有部分都会被执行,只是不需要的被舍弃了而已。例如:

#!/usr/bin/python3

print("yes")


def func():
   print("func")

任何导入此模块的文件都会打印“yes”

模块可以被多次 import,但是只会被导入一次

在文件夹中创建 __init__.py 可以将当前文件夹变成一个包。此文件可以是空的,但是这样导入的时候需要指定模块

另一种方便的方式是在文件中声明需要导入的包,这样直接使用 import * from PACKAGE 就行了

装饰函数

装饰函数在以前学习的时候一直不懂什么意思,C++ 写的多了现在感觉也就一般般。只是将装饰器函数的参数是函数指针罢了。

先来一个简单的例子:

def funA(fn):
   print("yes")
   return fn


@funA
def funB():
   print('c')


funB()

如图,装饰器 funA 传入的是一个函数指针,传出的也是一个函数指针(或者是一个可调用对象)。

装饰器的功能等价于

funB = funA(funB)

因此,如果只是 funB 的形式,只是拿了一下 funA(funB) 的返回值,需要继续调用的话使用 funB (你也可以在 funA 内自动调用 funB,这里只是为了格式上的统一)

然后就是传入参数的问题:

def funA(fn):
   print("yes")
   return fn


@funA
def funB(a):
   print('c')
   print(a)


funB(10)

没什么好说的,不明白为什么网上这么多文章要里面再嵌套一个函数,仿 js 来个闭包吗?但是 Python 中的闭包并不能修改局部变量的值

如果按照网上的来的话,形式是:

def funA(fn):
   # 定义一个嵌套函数
   def say(arc):
      print("hello",arc)
   return say

@funA
def funB(arc):
   print("funB():", a)
funB("world")

唯一需要注意的就是 say 和 funB 的参数要一样,毕竟上述语句等价于:

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

幂运算

上述中右手运算符仅仅列出了加法。

例:

class Number:
    def __add__(self, other):
        print("左手加法")
    def __radd__(self, other):
        print("右手加法")

number = Number()
number + 1 # 调用 __add__
1 + number # 调用 __radd__