python学习-1

基础

  • 输出:print()

  • 输入:input(),可以让用户输入字符串,并赋值,如:name = input(),提示符可以作为参数

  • 注释:#

  • 代码块:语句以:结尾时,缩进的语句视为代码块,4个空格的缩进

    a = 100
    if a >= 0:
        print(a)
    else:
        print(-a)
  • 数据类型:

    • 整型

    • 浮点数

    • 字符串:单引号或双引号声明字符串,r''表示''中的字符串不转义,'''...'''表示多行内容,其中...在交互命令行中使用

      • strip():去除字符串首尾的空格
    • 布尔值:False, True。布尔运算:not, and, or

    • 空值:None

    • 变量:直接使用变量名(python是动态语言)

    • 列表:

      • list(有序集合),类似于数组。使用len()获得list元素的个数,使用-1取到最后一个元素,使用负数可以从后获取元素。
        • append(value):追加元素到末尾
        • insert(index, value):把元素插入到指定的位置
        • pop():删除末尾的元素,list.pop(index)删除指定位置的元素
        • len():获取元素个数len(list)
        • +:两个list拼接
      • tuple(元组),类似数组,但是一旦初始化就不能修改
      # list定义
      >>> classmates = ['Michael', 'Bob', 'Tracy']
      # tuple定义
      >>> t = (1, 2)
      >>> t = ()
      >>> t = (1,) # 只有1个元素的tuple定义时必须加一个逗号,消除()运算符的歧义
      # 指针指向
      >>> t = ('a', 'b', ['A', 'B'])
      >>> t[2][0] = 'X'
      >>> t[2][1] = 'Y'
      >>> t
      ('a', 'b', ['X', 'Y'])
    • 字典:

      • dict,类似map,使用键-值(key-value)存储
        • in,判断dict中是否存在key
        • get(key[,returnValue]),判断dict中是否存在key,可以指定不存在时的返回值
        • pop(key),删除key值
      • set,类似无序集合,不储存value值,无重复key值
        • add(key):添加元素到set中,可以重复添加,但是没有效果
        • remove(key):删除元素
      # dict定义
      >>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
      # set定义
      >>> s = set([1, 2, 3])
      • 与list区别:
        • dict:查找和插入的速度极快,不会随着key的增加而变慢;需要占用大量的内存,内存浪费多。
        • list:查找和插入的时间随着元素的增加而增加;占用空间小,浪费内存很少。
  • 运算:+-*/( 除法结果是浮点数),//(地板除,两个整数相除仍然是整数,除不尽的保留整数部分),%(取余)

  • 编码:ord()获取字符的整数编码,chr()把编码转换为对应的字符,把string变为一字节为单位的bytesbytes类型的数据用带b前缀的单引号或双引号表示,bytes的每个字符都只占用一个字节,以Unicode表示的str通过encode()方法可以编码为指定的bytes。纯英文的str可以用ASCII编码为bytes,内容是一样的,含有中文的str可以用UTF-8编码为bytes。含有中文的str无法用ASCII编码,因为中文编码的范围超过了ASCII编码的范围,Python会报错。

    bytes中,无法显示为ASCII字符的字节,用\x##显示。

    从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法,如果bytes中包含无法解码的字节,decode()方法会报错。

    如果bytes中只有一小部分无效的字节,可以传入errors='ignore'忽略错误的字节。

    >>> '中文'.encode('utf-8')
    b'\xe4\xb8\xad\xe6\x96\x87'
    >>> b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')
    '中'

    要计算str包含多少个字符,可以用len()函数,如果换成byteslen()函数就计算字节数。

    文件开头要写如下语句,第一句告诉Linux/OS X系统,这是一个Python可执行程序,第二句Python解释器,按照UTF-8编码读取源代码。文本编辑器里要设置UTF-8 without BOM编码。

    #!/usr/bin/env python3 
    # -*- coding: utf-8 -*-
  • 格式化:

    • %。在字符串内部,%s表示用字符串替换,%d表示用整数替换,%f表示用浮点数替换,%x表示用十六进制整数替换,转义也使用%,如%%转义为%
    >>> 'Hello, %s' % 'world'
    'Hello, world'
    >>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
    'Hi, Michael, you have $1000000.'
    • format()替换占位符{0}{1}
    >>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
    'Hello, 小明, 成绩提升了 17.1%'
  • 条件判断: ifelif(else if缩写)else(注意冒号缩进)

  • 循环:

    • for...in循环list
    • while循环
    • break退出当前循环
    • continue跳出当前循环进行下一次循环
    • 注意不要滥用breakcontinue,分叉过多易出错

作用域

当变量在Module(模块)、Class(类)、def(函数)中定义的时候,才会有作用域的概念。

在if-elif-else、for-else、while、try-except\try-finally等关键字的语句块中并不会产成作用域。

作用域类型

在Python中,使用一个变量时并不严格要求需要预先声明它,但是在真正使用它之前,它必须被绑定到某个内存对象(被定义、赋值);这种变量名的绑定将在当前作用域中引入新的变量,同时屏蔽外层作用域中的同名变量。

局部作用域(Local)

包含在def关键字定义的语句块中,即在函数中定义的变量。每当函数被调用时都会创建一个新的局部作用域。

在局部作用域中声明全局变量需要使用global关键字

嵌套作用域(Enclosing)

在局部作用域中的def关键字中,嵌套作用域的上一层是局部作用域。闭包中的作用域。

全局作用域(Global)

在模块层次中定义的变量,每一个模块都是一个全局作用域。

注意:全局作用域的作用范围仅限于单个模块文件内

内置作用域(Built-in)

系统内固定模块里定义的变量,如预定义在builtin 模块内的变量。

变量名解析LEGB法则

搜索变量名的优先级:局部作用域 > 嵌套作用域 > 全局作用域 > 内置作用域

LEGB法则: 当在函数中使用未确定的变量名时,Python会按照优先级依次搜索4个作用域,以此来确定该变量名的意义。首先搜索局部作用域(L),之后是上一层嵌套结构中def或lambda函数的嵌套作用域(E),之后是全局作用域(G),最后是内置作用域(B)。按这个查找原则,在第一处找到的地方停止。如果没有找到,则会出发NameError错误。

当在当前作用域中的给变量赋值时,该变量将成为该作用域的局部变量,并在外部范围中隐藏任何类似命名的变量

Python中的模块代码在执行之前,并不会经过预编译,但是模块内的函数体代码在运行前会经过预编译,因此不管变量名的绑定发生在作用域的那个位置,都能被编译器知道。Python虽然是一个静态作用域语言,但变量名查找是动态发生的,直到在程序运行时,才会发现作用域方面的问题

函数

数学运算
  • 绝对值:abs(num)
  • 阶乘:fact(n)
  • 最大值:max(num1,num2,num3...),接受任意多个参数,并返回最大值
  • 范围:range(num),[0, num)区间的整数
  • 加法:sum()
数据类型转换
  • 整型:int(value),把其他数据类型转换为整数
  • 浮点型:float(value),把其他数据类型转换为浮点数
  • 布尔值:bool(value),把其他数据类型转换为布尔值
  • 字符串:str(value),把其他数据类型转换为字符
  • 十六进制:hex(value),把一个整数转换为十六进制表示的字符串
  • 数据类型检查:isinstance(num, (type1, type2)): 对参数num做检查,只允许type1和type2类型的参数
函数定义 def
# 不变参数
def my_abs(x): # 可以设置参数的默认值,默认参数必须指向不变对象
    if x >= 0:
        return x
    else:
        return -x
    
# 可变参数
def calc(*numbers): # numbers就是tuple数据类型
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum
# 使用
calc(1, 3, 5, 7)
# 或
nums = [1, 2, 3]
calc(*nums)

# 关键字参数
def person(name, age, **kw): # kw接收的是一个dict数据类型
    print('name:', name, 'age:', age, 'other:', kw)
# 使用
person('Michael', 30) # name: Michael age: 30 other: {}
person('Adam', 45, gender='M', job='Engineer') # name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
# 输出同上
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **extra) # **extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数

# 命名关键字参数,命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数。
def person(name, age, *, city, job):
    print(name, age, city, job)
# 命名关键字参数必须传入参数名
person('Jack', 24, city='Beijing', job='Engineer')
# 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了
def person(name, age, *args, city, job):
    print(name, age, args, city, job)
    
# 参数组合
# 参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

空函数pass

def nop():
    pass

pass可以使用在其他语句中,先让代码能运行起来

raise抛出错误

def my_abs(x):
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x

函数返回多值时,就是返回的一个tuple,函数没有return返回时,自动`return None``

高级特性

切片(Slice):
>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
>>> L[0:3] # L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3,第一个索引为0可以省略
# ['Michael', 'Sarah', 'Tracy']
>>> L[1:3] # ['Sarah', 'Tracy']
>>> L[-2:] # ['Bob', 'Jack']
>>> L[-2:-1] # ['Bob']
>>> L[:] # ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']

tuple切片的结果仍为tuple

迭代(for...in):

for...in可以用在可迭代对象上,如:list,set,dict,tuple,str

迭代dict,默认情况下,迭代的是key,如果要迭代value,可以使用for calue in d.values(),如果要同时迭代keyvalue,可以用for k, v in d.items()

判断一个对象是可迭代对象方法(通过collections模块的Iterable类型判断)

>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False

通过索引循环list,python内置的enumerate函数可以把一个list变成索引-元素对

for i, value in enumerate(['A', 'B', 'C']):
    print(i, value)
# 同时引用两个变量
for x, y in [(1, 1), (2, 4), (3, 9)]:
    print(x, y)
列表生成式
>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

>>> L = []
>>> for x in range(1, 11):
...    L.append(x * x)
...
>>> L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# 列表生成式
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# 筛选出仅偶数的平方
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
# 全排列
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
# 两个变量
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']
# 把list中所有的字符串变为小写
>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']
生成器(generator)

一边循环一边计算的机制

创建生成器:

# 创建list
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 创建generator
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

通过next()函数获得generator的下一个返回值,或者使用for循环打印

如果一个函数定义中包含yield关键字,则该函数是一个generator

generator与函数的区别:

函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

使用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue

# 定义斐波那契generator
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
g = fib(6)
while True:
    try:
        x = next(g)
        print('g:', x)
    except StopIteration as e:
        print('Generator return value:', e.value)
        break
迭代器(Iterator)

可以被next()函数调用并不断返回下一个值的对象称为迭代器,Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算,不能提前知道序列的长度。

Iterable变成Iterator可以使用iter()函数

IterableIterator的区别:

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

函数式编程

高阶函数

变量可以指向函数

函数也可以作为参数

map(function, iterable)函数

map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

>>> def f(x):
...     return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]) # 返回结果是Iterator
>>> list(r) # list()函数把整个序列都计算出来并返回一个list
[1, 4, 9, 16, 25, 36, 49, 64, 81]
# 简化
>>> list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
reduce(function, iterable)函数

reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,将结果继续和序列的下一个元素做累积计算

# 把序列变成整数
>>> from functools import reduce
>>> def fn(x, y):
...     return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579

str转换成正整数

from functools import reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def str2int(s):
    def fn(x, y):
        return x * 10 + y
    def char2num(s):
        return DIGITS[s]
    return reduce(fn, map(char2num, s))

# lambda函数进一步简化
from functools import reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def char2num(s):
    return DIGITS[s]

def str2int(s):
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))
filter(function, iterable)函数

filter求素数

def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n
def _not_divisible(n):
    return lambda x: x % n > 0
def primes():
    yield 2
    it = _odd_iter() # 初始序列
    while True:
        n = next(it) # 返回序列的第一个数
        yield n
        it = filter(_not_divisible(n), it) # 构造新序列
        
# 打印1000以内的素数:
for n in primes():
    if n < 1000:
        print(n)
    else:
        break
sorted()函数
# 对list进行排序
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
# 接收一个key函数来实现自定义的排序
# 按绝对值大小排序
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
# 忽略大小写排序
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
# 反向排序
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
返回函数

函数作为返回值(闭包:参数和变量都保存在返回的函数中)

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum
f = lazy_sum(1, 3, 5, 7, 9)
f() # 25, 返回计算求和的结果
闭包

当一个函数返回了一个函数后,其内部的局部变量还被新函数引用

注意:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

解决方案:

如果一定要引用循环变量,方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变

def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
    return fs
# 结果
>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9
匿名函数(lambda)
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))

关键字lambda表示匿名函数,冒号前面的x表示函数参数。

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

装饰器(Decorator)

函数对象有一个__name__属性,可以拿到初始函数的名字

# 打印日志的装饰器
def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
# 使用@语法,将@log放到now()函数的定义处,相当于执行了语句:now = log(now)
@log
def now():
    print('2015-3-25')
now()
# 结果
# call now():
# 2015-3-25

# 自定义log文本
def log(text):
    def decorator(func):
        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')
# 结果
# execute now():
# 2015-3-25

# 上述代码bug:now.__name__变为'wrapper',因此依赖函数签名的代码执行会出现错误
import functools

def log(func):
    @functools.wraps(func) # 把原始函数的属性复制到wrapper
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

偏函数(Partial function)

functools.partial就是帮助我们创建一个偏函数的,作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

创建偏函数时,可以接受函数对象、*args**kw这三个参数

# 不使用functools.partial,定义int2
def int2(x, base=2):
    return int(x, base)

# 使用functools.partial
import functools
int2 = functools.partial(int, base = 2) # 固定了int()函数的关键字参数base,相当于kw={'base': 2}

max2 = functools.partial(max, 10) # 会把10作为*args的一部分自动加到左边,可变参数
max2(5, 6, 7) # 相当于args = (10,5,6,7);max(*args)
# 结果 10
模块

模块名避免冲突,命名包名。每个包目录下都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany

注意:不能和Python自带的模块名称冲突

hello.py:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module ' # 表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;

__author__ = 'Michael Liao' # 作者名

import sys

def test():
    # argv变量,用list存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py文件的名称
    args = sys.argv 
    if len(args)==1:
        print('Hello, world!')
    elif len(args)==2:
        print('Hello, %s!' % args[1])
    else:
        print('Too many arguments!')

if __name__=='__main__':
    test()

当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。

公有变量(public):ab

私有变量(private):_c__d(单下划线开头的变量,能够被直接引用,但是不建议直接使用,python没有机制阻止使用私有变量)

特殊变量:__xxx__,可以直接被引用,但是有特殊用途

模块搜索路径

默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中:

>>> import sys
>>> sys.path
['', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', ..., '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages']

如果我们要添加自己的搜索目录,有两种方法:

一是直接修改sys.path,添加要搜索的目录:

>>> import sys
>>> sys.path.append('/Users/michael/my_py_scripts')

这种方法是在运行时修改,运行结束后失效。

第二种方法是设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。设置方式与设置Path环境变量类似。注意只需要添加你自己的搜索路径,Python自己本身的搜索路径不受影响。

面向对象编程

类和实例

创建类

class ClassName(object): # object表示从哪个类继承的,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类
    pass

创建实例,通过__init__方法,在创建实例的时候,把属性绑上去

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score
        
# 使用
bart = Student('Bart Simpson', 59)

__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

访问限制

两个下划线__开头定义的变量,就会编程私有变量,只有内部可以访问

以双下划线开头和双下划线结尾的是特殊变量,特殊变量是可以直接访问的。

要写getter和setter方法去控制对象属性的获取和设置。

继承和多态

继承,使用class className(extendClass),被继承的class称为基类、父类或超类。

动态语言的“鸭子类型”,它并不要求严格的继承体系。动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。

Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。

多重继承(MixIn)
class Bat(Mammal, Flyable):
    pass
获取对象信息
type()

判断对象类型:type(),返回对应的对象类型

>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False
# 判断一个对象是否是函数
>>> import types
>>> def fn():
...     pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
isinstance()

判断class类型,继承关系,也可以判断到父类和超类。

>>> isinstance(h, Dog)
True
>>> isinstance(h, Animal) # Dog类继承与=于Animal类
True

常用判断多个类型

dir()

获取一个对象的所有属性和方法,返回一个包含字符串的list

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:

>>> len('ABC')
3
>>> 'ABC'.__len__()
3

自定义的类,如果想使用len()方法,需要自己写一个__len__()方法

配合getattr()setattr()以及hasattr(),我们可以直接操作一个对象的状态:

>>> class MyObject(object):
...     def __init__(self):
...         self.x = 9
...     def power(self):
...         return self.x * self.x
...
>>> obj = MyObject()

测试该对象的属性:

>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19

获取不存在的属性,会抛出AttributeError的错误

获取对象的方法:

>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81
实例属性和类属性

由于Python是动态语言,根据类创建的实例可以任意绑定属性。

使用__slots__

限制class实例能添加的属性

# 定义
class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
    
# 使用
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

注意: __slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

@property

把一个方法变成属性调用。

# 定义
class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value
        
# 使用
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!

把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值。

定制类
__str__()

返回用户看到的字符串

__repr__()

返回开发者看到的字符串,为调试服务的

__iter__()

如果一个类想被用于for ... in循环,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化两个计数器a,b

    def __iter__(self):
        return self # 实例本身就是迭代对象,故返回自己

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b # 计算下一个值
        if self.a > 100000: # 退出循环的条件
            raise StopIteration()
        return self.a # 返回下一个值
    
# 使用
>>> for n in Fib():
...     print(n)
...
1
1
2
3
5
...
46368
75025
__getitem__()

可以像list那样按照下标取出元素

class Fib(object):
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a + b
        return a
    
# 使用
>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2

切片功能需要做参数判断

__setitem__()方法,把对象视作listdict来对集合赋值,__delitem__()方法,用于删除某个元素。如果要使对象用起来像Iterator需要自定义多个Iterator的方法。

__getattr__()

动态返回一个属性,当调用的不存在的属性时,Python解释器会试图调用__getattr__(self, 'attributeName')来尝试获得属性。如果查找的属性不存在并且在__getattr__中没有设置,则会默认返回None

class Student(object):

    def __init__(self):
        self.name = 'Michael'

    def __getattr__(self, attr):
        if attr=='score':
            return 99
            
# 使用
>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99

要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:

class Student(object):

    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25
        raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)

这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段。

使用场景:例如动态设置url调用SDK的API

__call__()

直接对实例进行调用。判断一个对象是否能被调用,能被调用的对象就是一个Callable对象。

class Student(object):
    def __init__(self, name):
        self.name = name

    def __call__(self):
        print('My name is %s.' % self.name)
        
# 使用
>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.
枚举类
from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

# 使用
for name, member in Month.__members__.items():
    # value属性则是自动赋给成员的int常量,默认从1开始计数
    print(name, '=>', member, ',', member.value)

如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:

from enum import Enum, unique

@unique # 检查有没有重复值
class Weekday(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6
使用元类
type()

可以查看一个类型或变量的类型。一个class的类型是type,一个实例的类型就是其相对应的class

动态语言中,函数和类的定义是运行时动态创建的。python中创建class的方法就是使用type()函数。

type()函数既可以返回一个对象的类型,又可以创建出新的类型。

>>> def fn(self, name='world'): # 先定义函数
...     print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>

要创建一个class对象,type()函数依次传入3个参数:

  1. class的名称;
  2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
  3. class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello
元类(metaclass

控制类的创建行为。先定义metaclass,就可以创建类,最后创建实例。

metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。

很少用到。

# 给自定义的MyList增加一个add方法
# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

有了ListMetaclass,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字参数metaclass

class MyList(list, metaclass=ListMetaclass):
    pass

当我们传入关键字参数metaclass时,魔术就生效了,它指示Python解释器在创建MyList时,要通过ListMetaclass.__new__()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。

__new__()方法接收到的参数依次是:

  1. 当前准备创建的类的对象;
  2. 类的名字;
  3. 类继承的父类集合;
  4. 类的方法集合。

测试一下MyList是否可以调用add()方法:

>>> L = MyList()
>>> L.add(1)
>> L
[1]

ORM(对象-关系映射):把关系数据库的一行映射为一个对象,也就是一个类对应一个表。

要编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。

错误、调试和测试

错误处理

try...except...finally...

finally不管成功还是异常都会执行。

except后添加else代码块,当没有错误发生时。会执行else的内容

所有的错误类型都继承自BaseException

记录错误(logging)

Python内置的logging模块可以非常容易地记录错误信息:

# err_logging.py

import logging

def foo(s):
    return 10 / int(s)

def bar(s):
    return foo(s) * 2

def main():
    try:
        bar('0')
    except Exception as e:
        logging.exception(e)

main()
print('END')

同样是出错,但程序打印完错误信息后会继续执行,并正常退出:

# 使用
$ python3 err_logging.py
ERROR:root:division by zero
Traceback (most recent call last):
  File "err_logging.py", line 13, in main
    bar('0')
  File "err_logging.py", line 9, in bar
    return foo(s) * 2
  File "err_logging.py", line 6, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero
END
抛出错误(raise)

使用raise抛出错误

python内置的错误类型如:ValueError,TypeError

# err_reraise.py

def foo(s):
    n = int(s)
    if n==0:
        raise ValueError('invalid value: %s' % s)
    return 10 / n

def bar():
    try:
        foo('0')
    except ValueError as e:
        print('ValueError!')
        raise

bar()

raise语句如果不带参数,就会把当前错误原样抛出。

调试
打印(print()
断言(assert
def foo(s):
    n = int(s)
    assert n != 0, 'n is zero!' # 'n != 0'应为true,断言失败会抛出AssertionError
    return 10 / n

def main():
    foo('0')
    
# 使用
$ python err.py
Traceback (most recent call last):
  ...
AssertionError: n is zero!

启动Python解释器时可以用-O(大写的’O’)参数来关闭assert,关闭后,你可以把所有的assert语句当成pass来看。

$ python -O err.py
Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero
logging

logging.info()就可以输出一段文本。需要进行配置。

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

指定记录信息的级别,有debuginfowarningerror等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。

pdb

让程序以单步方式运行,可以随时查看运行状态。

启动python代码文件,python -m pdb err.py

输入命令l来查看代码,输入命令n可以单步执行代码,任何时候都可以输入命令p 变量名来查看变量,输入命令q结束调试,退出程序。

pdb.set_trace(), 不需要单步执行。

# err.py
import pdb

s = '0'
n = int(s)
pdb.set_trace() # 运行到这里会自动暂停,设置断点
print(10 / n)

程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者用命令c继续运行:

$ python err.py 
> /Users/michael/Github/learn-python3/samples/debug/err.py(7)<module>()
-> print(10 / n)
(Pdb) p n
0
(Pdb) c
Traceback (most recent call last):
  File "err.py", line 7, in <module>
    print(10 / n)
ZeroDivisionError: division by zero
单元测试

引入Python自带的unittest模块

测试类配合断言使用:

源文件:

class Dict(dict):

    def __init__(self, **kw):
        super().__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

相对应的测试类:

import unittest

from mydict import Dict
# 以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。
class TestDict(unittest.TestCase):

    def test_init(self):
        d = Dict(a=1, b='test')
        self.assertEqual(d.a, 1)
        self.assertEqual(d.b, 'test')
        self.assertTrue(isinstance(d, dict))

    def test_key(self):
        d = Dict()
        d['key'] = 'value'
        self.assertEqual(d.key, 'value')

    def test_attr(self):
        d = Dict()
        d.key = 'value'
        self.assertTrue('key' in d)
        self.assertEqual(d['key'], 'value')

    def test_keyerror(self):
        d = Dict()
        with self.assertRaises(KeyError): # 访问不存在的key时,断言会抛出KeyError
            value = d['empty']

    def test_attrerror(self):
        d = Dict()
        with self.assertRaises(AttributeError): 
            # 访问不存在的key时,我们期待抛出AttributeError
            value = d.empty
setUptearDown

setUp():调用一个测试方法前执行

tearDown():调用一个测试方法后执行

安装第三方库

安装pip库管理后,使用pip install xxx

安装常用模块,可以直接使用Anaconda,这是一个基于Python的数据处理和科学计算平台,它已经内置了许多非常有用的第三方库(国内镜像)。下载后直接安装

参考链接