基本概念
编码与解析器
对于不使用 Python 默认编码的用户来说,可以在文件开头处添加
# -*- coding:utf-8 -*-来指明文件的编码备注
Python3 的默认编码为是 utf-8,因此,Python3 的脚本文件无需添加该行代码
对于 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 模块搜索的顺序是:
当前路径
PYTHONPATH 下的路径
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__