Python笔记 -- 整理自廖雪峰教程


编码问题

  • Python3中的字符串在内存中是以Unicode编码的

    • ord() 获取字符串的编码整数表示
    • chr()把编码转化为对应的字符
    • bytes类型的数据用前缀b表示 b'ABC'
    • 可以用encode()指定编码类型 'ABC'.encode('ascii')
    • 可以用decode()对字节流数据解码 b'ABC'.decode('ascii')
    • 选择errors='ignore'可以无视decode时错误的字节
  • 同C语言, 使用%可以格式化字符串 'hello %s' % 'world'

    • 输出%需要转译 %%
    • format()也有类似的作用
    • 还有一种以f-string格式
      • f'pi eq {r:.4f}' {}内变量会被替换输出

列表

  • list

    • 越界访问会抛出IndexError错误
    • 追加到末尾 list.append()
    • 指定插入位置list.insert(idx, data)
    • 删除末尾元素list.pop() / list.pop(idx)
    • 替换元素list[idx] = data
  • tuple 元组

    • tuple一旦初始化后便不能修改, 使用()初始化
    • ('a', 'b', ['X']) 中, 列表中元素是可变的, tuple的不变是指其指向元素不变
  • input()得到的结果的str类型的

非顺序结构

  • dict

    • 初始化格式 { key:value }
    • 判断key是否存在
      • key in dict : 返回True/False
      • dict.get(key) : 不存在返回None
    • 删除 dict.pop(key)
    • dict中的key必须是不可变对象
    • 底层原理为hash表
  • set

    • 初始化 s = set()
    • 插入元素set.add()
    • 删除元素set.remove()
    • 求交集 set1 & set2
    • 求并集 set1 | set2
    • 底层原理也是hash表
  • 对不可变对象的操作

a = 'abc'
a.replace('a', 'A') # 'Abc'
a # 'abc'
  • 对于不变对象来说, 调用对象自身的任意方法, 也不会改变该对象自身的内容, 而是创建一个新的对象并返回

函数

  • 函数名其实就指向一个函数对象的引用, 把函数名赋予一个变量, 等价于给该函数起了一个别名

  • pass占位符

  • 参数类型检查

isinstance(x, (int, float)) # 检查x是否是int或者float类型
  • 函数的多返回值实际是一种假象, 实际上是返回了一个tuple

  • 支持默认参数, 可变参数, 关键字参数; 还调用支持不按顺序提供参数, 但是要指明参数名

def add_end(L=[]):
    L.append('End')
    return L
add_end() # ['End']
add_end() # ['End', 'End']

因为默认参数也是一个变量, 每次调用时, 如果改变了默认参数, 则会影响下一次调用

  • 可变参数格式, 实际传入的是一个tuple
def calc(*args):
    return sum([i*i for i in args])
calc(1,2,3,4) # 30
  • 关键字参数: 传入关键字参数时会在函数内部自动组装为一个dict
def person(name, **kw):
    print('name:', name, 'other:', kw)
    person('XiaoMing', age = 12)

若需要限制关键字参数的名字, 可使用命名关键字参数(可为默认参数)

def person(name, *, age):
    print('name:', name, 'age:', age)

person('XiaoMing') # error
person('XiaoMing', 12) # error
person('XiaoMing', age = 12) # ok
  • 混合使用时, 参数的定义顺序必须是: 必选参数, 默认参数, 可变参数, 命名关键字参数和关键参数

  • 特别地, 任意函数都可以通过类似func(*args, **kw)调用

args = (1,2,3,4)
kw = {'a':12, 'b':[1,2]}
f1(*args, **kw)

高级特性

  • 切片

    • [a:b] 取[a, b)的内容
    • 字符串也可以切片
  • 无论有无下标, 都可以迭代

d = {'a':1, 'b':2}
for key in d : print(key)
for k, v in d.items() : print(k ," ", v)

注意字典的迭代顺序不一定按照定义的顺序, 默认迭代参数为key

  • 通过索引迭代
List = ['a', 'b']
for i, v in enumerate(List):
    print(i, " ", v)
  • 列表生成式
[x*x for x in range(1,8) if x % 2] # [1, 9, 25, 49]
[n+m for n in 'AB' for m in 'CD'] # ['AC', 'AD', 'BC', 'BD']

for前方必须是一个表达式, 故前方的if必须接else; 而for后方的if不能接else, 因为它表示for循环的过滤条件

  • 生成器(generator): 循环过程中不断推算出后续元素而不生成完整列表
(x*x for x in range(10))

其保存的是算法, 一个典型案例是斐波拉契数列

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b # 这样, fib就是一个generator function了
        a, b = b, a + b
        n = n + 1
    return 'done'

for n in fib(10):
    print(n)

生成器可以通过next()函数不断获得下一个返回值, 当已经取得最后一个返回值时, 若再次调用next()函数, 会抛出StopIteration错误

  • 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
  • 凡是可作用于for循环的对象都是Iterable类型;
  • 集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

函数式编程

  • 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数

  • Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

高阶函数

  • 函数也是一个变量, 可以赋值

  • map()reduce(), 其中reduce在包functools中

    • map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。`
list(map(str,[1,2,3])) # ['1', '2', '3']
  • reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算
from functools import reduce
reduce(lambda a,b: a+b, [1,2,3,4]) # 10
  • 两者结合
def normalize(names):
    return reduce(
        lambda n1,n2: n1+n2, map(
            lambda name: [name[0].upper()+name[1:]] 
            if len(name)>0 else [], names))

normalize(['asv','vas','aqwe',''])
  • filter()与map()类似, 但是其根据传入函数的返回值决定是否保留该元素, 常用于过滤序列
def not_empty(s):
    return s and s.strip()
  
list(filter(not_empty, ['A', '', 'B', None, 'C', '  '])) # ['A', 'B', 'C']
def primes():
    def _odd_iter():
        n = 1
        while True:
            n = n + 2
            yield n

def _not_divisible(n):
    return lambda x: x % n > 0
    yield 2
    it = _odd_iter() # 初始序列
    while True:
        n = next(it) # 返回序列的第一个数
        yield n
        it = filter(_not_divisible(n), it) # 构造新序列

for n in primes():
    if n < 100:
        print(n)
    else:
        break
  • sorted() 排序

    • 返回排序后列表, 原参数列表不变

    • 其也是个高阶函数, 接受一个key函数实现自定义排序

sorted([36, 5, -12, 9, -21], key=abs) 
# [5, 9, -12, -21, 36]
  • 默认为升序, 设置参数reverse=True, 实现降序排序

  • 高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回

  • 关于闭包(Closure)

    • 内部函数可以引用外部函数的参数和局部变量,当外部函数将内部函数返回时,相关参数和变量都保存在返回的函数中

    • 每次调用外部函数都会返回一个新的函数

    • 返回的函数不会立即执行, 而是等到调用了再执行, 期间保存在其内部参数是可能发生变化的; 因此返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

    • 若真的有返回内部参数是循环变量函数的需求, 可在内部函数中再定义一个函数

def count():
    def f(j):
        def g():
          return j*j
        return g

    fs = []
    for i in range(1, 4):
        fs.append(f(i)) 
    return fs
  • nonlocal: 声明变量为外部变量

匿名函数

  • lambda表达式
  • 只能有一个表达式, 返回值就是该表达式的结果

装饰器

  • 由于函数也是一个对象, 故也可以有属性, 比如__name__

  • 在代码运行期间动态增加功能的方式,称之为装饰器(decorator)

import functools
def log(func):
    @functools.wraps(func) # 防止传入的func签名变为wrapper
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
    
@log
def now():
    print('2015-3-25')
  • decorator本身需要传入参数, 则可定义为
import functools
def log(text):
    def decorator(func):
        @functools.wraps(func) # 防止传入的func签名变为wrapper
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

@log('execute')
def now():
    print('2015-3-25')

偏函数

  • 类似C++中的bind
import functools
int2 = functools.partial(int, base=2)
int2('1000000')

面向对象编程

  • 一个.py文件称为一个模块(Module)

  • 一个文件夹是一个包(package), 每个包必须有一个__init__.py文件

    • 该文件可以为空, 其本身就是一个模块, 模块名为对应包名
  • 面向对象最重要的概念就是类(Class)实例(Instance)

  • 定义类格式:

class Student(object):
    pass

? 表示继承自某一个类, 如果无合适类, 默认继承object类

  • 类的初始化方法为__init__(self, *args)

  • 在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问

  • 在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量

    • 双下划线开头的实例变量不一定不能从外部访问, 不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量
  • type()对象类型判断, isinstance()对象class类型判断

  • dir()获取对象所有属性与方法

  • __str__(self) 设置print时显示的字符串, __repr__(self)直接调用时显示的字符串, 前者一般是用户看到的, 后者是开发者看到的

  • __iter__(self)返回一个迭代对象, for循环可以不断通过__next__(self)方法获取循环的下一个值, 直到遇到StopIteration错误

  • __getitem__(self, n)设定下标访问操作, 注意n有可能是一个切片对象slice(即0:3之类的)

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int): # n是索引
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice): # n是切片, 这里未处理负数情况
            start = n.start
            stop = n.stop
            if start is None:
              start = 0
              a, b = 1, 1
              L = []
              for x in range(stop):
                if x >= start:
                L.append(a)
                a, b = b, a + b
            return L
  • __getattr__(self, attr) 找不到某属性时调用该方法

错误处理

  • try...except...finally处理错误
try:
    r = 10 / int('2')
except ValueError as e:
    print('ValueError:', e)
except ZeroDivisionError as e:
    print('ZeroDivisionError:', e)
else:
    print('no error!')
finally:
    print('finally...')
  • Python所有的错误都是从BaseException类派生的

  • 错误栈: 如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获

  • 记录错误但程序继续执行: 内置的logging模块

import logging
try:
    bar('0')
except Exception as e:
    logging.exception(e)
  • 使用raise抛出错误
def foo(s):
    n = int(s)
    if n==0:
        raise FooError('invalid value: %s' % s)
        return 10 / n


def bar():
    try:
        foo('0')
    except ValueError as e:
        print('ValueError!')
        raise # 捕获错误目的只是记录一下,便于后续追踪。但是,由于当前函数不知道应该怎么处理该错误,所以,最恰当的方式是继续往上抛,让顶层调用者去处理。    
# 只有在必要的时候才定义我们自己的错误类型。如果可以选择Python已有的内置的错误类型(比如ValueError,TypeError),尽量使用Python内置的错误类型


## 调试

- 使用`断言(assert)`, 断言失败,assert语句本身会抛出AssertionError

```python
def foo(s):
    n = int(s)
    assert n != 0, 'n is zero!'
    return 10 / n
  • 启动Python解释器时可以用-O参数来关闭assert, python -O err.py

  • 使用logging模块处理错误

import logging
logging.basicConfig(level=logging.INFO)

s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)

? 允许指定记录信息的级别,有debug,info,warning,error等几个级别

序列化

  • 使用pickle模块实现序列化
import pickle
d = dict(name='Bob', age=20, score=88)
pickle.dumps(d)
  • 写入和读取序列化文件
f = open('dump.txt', 'wb')
d = dict(name='Bob', age=20, score=88)
pickle.dump(d, f)
f.close()

f = open('dump.txt', 'rb')
d = pickle.load(f)
f.close()
d
  • 更通用的序列化 JSON, XMK ...

  • Python中json模块提供的完善的转化

import json
d = dict(name='Bob', age=20, score=88)
json.dumps(d)

json_str = '{"age": 20, "score": 88, "name": "Bob"}'
json.loads(json_str)
  • 对Class实现序列化 json的dumps方法还提供了许多可选操作
json.dumps(s, default=lambda obj: obj.__dict__) # 偷懒写法
# 标准的写法是自定义转化函数
json.dumps(s, default=student2dict)
json.loads(json_str, object_hook=dict2student)