基本概念

编码与解析器

  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 的结果就是元组。也可以通过类型转换转换为元组(该行为为约定)

列表和元组

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

对列表解引用的语义想到与将列表在代码中展开。例如:

a = [1, 2, 3]

func(*a) # 想当于 func(1, 2, 3)

字典

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

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

  • [Python-Dev Python 3.6 dict becomes compact and gets a private version; and keywords become ordered]

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 与构造函数相关的函数有两个: newinit

  • 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__

延迟计算

可以延迟计算一个属性:

import math

class Lazyproperty:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            value = self.func(instance)
            setattr(instance, self.func.__name__, value)
            return value


class Circle:
    def __init__(self, radius):
        self.radius = radius

    @Lazyproperty
    def area(self):
        print('Computing area')
        return math.pi * self.radius ** 2

    @Lazyproperty
    def perimeter(self):
        print('Computing perimeter')
        return 2 * math.pi * self.radius


c = Circle(1)
print(c.area)
print(c.perimeter)

另个方式是手动实现:

class OnlineIterm:
    def __init__(self, info, req):
        self.__info = info # __info 可能为 dict|str
        self.req = req
        self.info = {} # 保存类型信息

    def __getattribute__(self, __name: str) -> Any:
        if __name != "info":
            return object.__getattribute__(self, __name)

        if object.__getattribute__(self, "info") != {}:
            return object.__getattribute__(self, __name)

        if not hasattr(self.__info, "__call__"):
            object.__setattr__(self, "info", self.__info)
            return object.__getattribute__(self, __name)

        object.__setattr__(self, "info", self.__info())
        return self.info

这样的好处是不会丢失类型信息。

在手动实现时需要注意使用 self.attr 会调用 self.getattribute,这可能会导致无限迭代(迭代太深 python 会报错的)

pypy

pypy 是 Python 的 JIT 实现,使用方式如下 :

pacman -S pypy3
pypy3 -m ensurepip
pypy3 -m pip install requests

默认情况下,pypy 的包被安装到 ~/.local/lib/pypyxx/ 中。pypy3-pip 的配置文件和 cpython 的配置文件相同

numba

numba 可以用于加速 Python 代码。numba 将一部分 Python 代码翻译成机器码从而加快速度。numba 尤其擅长处理循环:

import numba

@numba.jit(nopython=True, parallel=True)
def logistic_regression(Y, X, w, iterations):
    for i in range(iterations):
        w -= np.dot(((1.0 /
              (1.0 + np.exp(-Y * np.dot(X, w)))
              - 1.0) * Y), X)
    return w

生成器

如果函数中存在 yield,那么函数的返回值就是生成器类型。生成器迭代器的一个子类。但是提供了比迭代器更多的功能。一个生成器的类型注释为 :

Generator[YieldType, SendType, ReturnType]

迭代器语法为 :

return_val yield value

其中,return_val 是 SendType,value 是 YieldType。而如果函数后面存在 return,那么 return 后面的值是 ReturnType

  • 如果一个函数既没有 SendType,也没有 ReturnType,那么可以将函数签名改为 Iterator[YieldType]

  • 生成器函数中的 return 只是用来标识迭代器已经终止。它的值不会被返回给调用者

一个代码示例为:

from typing import Generator
import logging

logging.basicConfig(level=logging.DEBUG, format="%(lineno)s: %(message)s")


def test_generator() -> Generator[int, float, str]:
    for i in range(5):
        b = yield i
        logging.debug(b)

    return "a"


a = test_generator()

logging.debug(next(a))  # i = 0
logging.debug(a.send(10))  # i = 1
logging.debug(a.send(20))  # i = 2
logging.debug(a.send(30))  # i = 3
logging.debug(a.send(40))  # i = 4
logging.debug(next(a))  # Exception: StopIteration: a

可以看到,next 和 send 都会将函数执行到下一个 yield 的位置,并将 YieldType 返回。但是 send 可以向函数发送一个值

可以将 send 简单地用作推进生成器进展,其作用与 next 相同。即使函数不使用 yield 的返回值也一样。

对生成器解引用会得到一个 展开 的列表

例如:

from typing import Generator


def test_generator() -> Generator[int, None, None]:
    for i in range(10):
        yield i


a = test_generator()

print(*a) # 这里相当于 print(0,1,2,3,4,5,6,7,8)
Last moify: 2023-02-04 01:03:05
Build time:2025-07-18 09:41:42
Powered By asphinx