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
中是否存在keyget(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
变为一字节为单位的bytes
,bytes
类型的数据用带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()
函数,如果换成bytes
,len()
函数就计算字节数。文件开头要写如下语句,第一句告诉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%'
条件判断:
if
、elif(else if缩写)
、else
(注意冒号缩进)循环:
for...in
循环listwhile
循环break
退出当前循环continue
跳出当前循环进行下一次循环- 注意不要滥用
break
和continue
,分叉过多易出错
作用域
当变量在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()
,如果要同时迭代key
和value
,可以用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
错误,返回值包含在StopIteration
的value
中
# 定义斐波那契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()
函数
Iterable
与Iterator
的区别:
凡是可作用于for
循环的对象都是Iterable
类型;
凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如list
、dict
、str
等是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):a
、b
私有变量(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__()
方法,把对象视作list
或dict
来对集合赋值,__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个参数:
- class的名称;
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- 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__()
方法接收到的参数依次是:
- 当前准备创建的类的对象;
- 类的名字;
- 类继承的父类集合;
- 类的方法集合。
测试一下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)
指定记录信息的级别,有debug
,info
,warning
,error
等几个级别,当我们指定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
setUp
与tearDown
setUp()
:调用一个测试方法前执行
tearDown()
:调用一个测试方法后执行
安装第三方库
安装pip库管理后,使用pip install xxx
安装常用模块,可以直接使用Anaconda,这是一个基于Python的数据处理和科学计算平台,它已经内置了许多非常有用的第三方库(国内镜像)。下载后直接安装