Mengzelev's Blog

python学习笔记

Word count: 8,821 / Reading time: 37 min
2019/01/23 Share

持续更新中
参考:

数据类型

Python是动态语言,即变量本身类型不固定,不需要提前声明变量类型的
Python有三大数据类型:整数、浮点数、字符串

整数

整数可以是任意大小的

浮点数

浮点数可以是任意精度的【但是运算也会出现误差】

字符串

  • 字符串用单引号或双引号括起来
  • 会涉及到转义符的问题
  • 在字符串前加r表示让所有的转义符都不转
  • 字符串拼接:+

几个骚方法

  • title()首字母大写
  • upper()全部大写
  • lower()全部小写
  • rstrip()暂时删除字符串右端的空白

    1
    2
    3
    4
    5
    6
    >>> s = 'python '
    >>> s.rstrip()
    'python'
    >>> s
    'python '
    >>> s = rstrip() #永久删除右端空格
  • lstrip()暂时删除字符串左端的空白

  • strip()同时暂时删除字符串两端空白

注释

  • Python中使用#注释掉一整行
  • PyCharm中可以使用Ctrl+/

类型转换函数

  • int(x)x转化为整数
  • float(x)x转化为浮点数
  • str(x)x转化为字符串

编码问题

在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。

  • Python 3的字符串是以Unicode编码的
  • ord()获取字符的整数表示,chr()把编码转换为对应的字符
  • 字符串类型是str,加前缀b'可以转换为bytes类型
  • Unicode表示的str通过encode()方法可以编码为bytes类型,反过来bytes类型可以通过decode()方法解码为str类型

    1
    2
    3
    4
    5
    6
    7
    8
    >>> 'ABC'.encode('ascii')
    b'ABC'
    >>> '中文'.encode('utf-8')
    b'\xe4\xb8\xad\xe6\x96\x87'
    >>> b'ABC'.decode('ascii')
    'ABC'
    >>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
    '中文'
  • len()函数返回字符串长度或bytes字节数

  • 始终坚持使用UTF-8对strbytes进行转换
  • 开头通常需要加上

    1
    2
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
  • 格式化字符串%,用法同C的printf

布尔值

  • 值为TrueFalse
  • 运算andornot

空值

  • None,是一个特殊变量

常量

  • 习惯用全部大写的变量名表示常量
  • 然而并不能保证该变量不会改变,不像C有const

list 列表

  • []表示list
  • []为下标索引,从0开始,同C
  • [-1]表示最后一个元素,负号索引-n就表示倒数第n个这也太nb了吧
  • list里的数据类型可以不同
  • list可以嵌套

list方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
len(mylist) #获得list元素的个数 
mylist.append('a') # 往list中追加元素到末尾
mylist.insert(1, 'a') # 把元素插入到指定位置
mylist.pop() # 删除list末尾的元素
mylist.pop(1) # 删除指定位置的元素
#pop返回值为被删除的元素
del mylist[1] #使用del可以删除任何位置的列表元素,条件是知道索引
mylist.remove() #按值删除元素,只删除第一个指定的值
# `pop和`del`的选择:删除后是否还要使用该元素
mylist.sort() #永久排序
mylist.sort(reverse = True) #倒序排序
sorted(mylist) #暂时排序,也可以加入倒序参数
mylist.reverse() #永久倒置
mylist[1:3] #list切片,同MATLAB,首尾可缺省
copy_list = mylist[:] #通过切片赋值list

切片

1
2
3
4
5
6
7
L[head : tail :step]
L[:3] #取出从第0个到第3个元素
L[1:3] #取出从第1个到第3个元素
L[:10:2] #前10个数,每2个取一个
L[::5] #所有数,每5个取一个
L[:] #原样复制一个list
'ABCDEFG'[1:3] #字符串也可以看成List

列表生成式

1
2
3
4
squares = [value ** 2 for value in range(1,11)]
squares = [value ** 2 for value in range(1,11) if value % 2 == 0]
[m + n for m in 'ABC' for n in 'XYZ'] #两两组合双重循环
[s.lower() for s in L] #将一个list中所有字符串变成小写

上述代码等价于对squares这个list执行了一个for循环,每个元素都平方(**是乘方的意思)
自然语言编程指日可待

生成器generator

  • lits保存的是数据,generator保存的是算法
  • 使用next()函数可以获得generator的下一返回值
  • generator也是可迭代对象
  • 定义generator
    • 把list的[]变成()
    • 使用yield关键字把函数变成generator
  • 变成generator的函数,每次调用next()时执行,遇到yield语句返回,再次执行时
1
2
3
4
5
>>> g = (x * x for x in range(10)) #把list的[]变成()就可以得到生成器
>>> g
<generator object <genexpr> at 0x1022ef630>
>>> for n in g:
print(n)
1
2
3
4
5
6
7
8
9
10
11
#斐波那契数列
def fib(max):
n, a, b = 0, 0, 1
while(n < max):
yield b
a, b = b, a + b
#相当于(a,b) = (b, a+b)
n = n + 1
return 'done'
>>> for n in fib:
print(n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#杨辉三角生成器
def triangles():
L = [1,]
while True:
yield L
L = [1,] + [L[i-1] + L[i] for i in range(1, len(L))] + [1,]


n = 0
results = []
for t in triangles():
print(t)
results.append(t)
n = n + 1
if n == 10:
break

tuple 元组

  • 与list类似,但是一旦初始化就不能修改,类似于C的enum
  • 不可变的tuple使代码更安全
  • 注:定义一个元素的tuple时需要加一个逗号,
  • tuple的不变是指向不变,即给元组变量赋值是合法的
1
2
3
4
t = () #定义空tuple
t = (1,) #定义单元素tuple
dim = (200, 5)
dim = (400, 10) #合法,相当于重新定义了整个元组

分支语句if

  • 除了不要括号、冒号换行、缩进代替大括号外,其他都和C语言一样
  • else if可以缩写为elif
  • if mylist 可以检验列表是否非空
  • if a in mylist你以为我是自然语言编程其实我只是检查某个元素在不在列表里哒
1
2
3
4
5
6
7
8
9
# -*- coding: utf-8 -*-
s = input('birth:')
birth = int(s) //将字符串输入转化为数字类型
if birth >= 2000:
print('00后')
elif birth < 1960:
print('60前')
else:
print('00前')

循环语句

for

python有2种循环

  • for x in ...循环,依次把list或者tuple中的每个元素迭代出来
  • range(m,n,step)函数生成从m开始到n的整数序列,步长为step,m缺省为0,step缺省为1
1
2
3
for value in range(1,6): #行尾要有冒号
print(value) #别忘了缩进对齐
numbers = list(range(1,11,2)) #list()函数将参数转换为列表

类似C语言的循环

1
2
3
4
5
6
for i, value in enumerate(['A','B','C']):
print(i, value)
# enumerate可以将list变成索引-元素对,相当于数组
for x,y in [(1,1),(2,4),(3,9)]:
print(x,y)
#同时对两个变量进行迭代

迭代对象

for可以作用于所有可迭代对象

1
2
3
4
5
6
7
8
9
10
11
12
# 可以通过isinstance()函数判断一个对象是否为Iterable(可迭代对象)
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

迭代器

  • 可以被next()调用并不断返回下一个值的对象成为迭代器Iterator
  • Iterator对象表示的是一个数据流
  • 生成器都是Iterator对象
  • Iterable可迭代对象不一定是Iterator
1
2
3
4
5
6
7
8
9
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

while

1
2
3
4
5
6
ans = 0;
n = 99;
while n > 0:
ans += n
n -= 2 # 没有n--这种语法糖了
print(ans)

breakcontinue都可以像以前一样用

字典dict

  • dict就相当于C++里的map,功能类似于Hash Table
  • 是关键字-值对(key-value),key和value只是名字,并不规定类型
  • dict相比于list,是在用空间换时间
  • dict的key必须是不可变对象

创建dict

注意dict的初始化使用的{}花括号

1
2
d = {'Michael':95, 'Bob':75, 'Tracy':85} #将名字和成绩对应起来,项比较多的时候可以加上换行
d['Adam'] = 67 #直接通过关键字索引放入数据
  • 多次对一个关键字放入数据,后面的值会把前面的冲掉
  • 访问了不存在的关键字时会报错
1
2
'Thomas' in d #返回布尔值,判断某关键字是否在该字典中
del d['Bob'] #删除指定关键字

dict方法

1
2
d.get('Thomas',-1) #寻找某个关键字的值,-1是规定的不存在时的返回值,缺省时返回None,Python交互环境不显示结果
d.pop('Bob') #删除一个关键字

遍历dict

1
2
3
4
5
6
7
8
for key,value in d.items(): # 使用items()方法可以访问所有条目
print(key + ":" + value)
# 需要声明两个变量,命名任意

for key in d.keys(): # 使用keys()方法可以访问所有的关键字
print(key.title())
#遍历字典时默认遍历所有key
#同理有values()方法

集合set

  • 无序、无重复元素的集合
  • 不能放入可变对象

创建set

1
s = set([1,1,2,2,3]) #需要提供一个list作为输入,重复元素自动过滤

set方法

1
2
s.add(4) #添加元素
s.remove(4) #删除元素

set操作

1
2
s1 & s2 #交集
s1 | s2 #并集

可变对象与不可变对象

不可变对象:调用对象自身的方法不会改变该对象自身的内容,而是会创建新的对象并返回,如str
可变对象:恰恰相反,如list

函数

  • 函数名可以像变量一样赋值【太骚了

定义函数

1
2
3
4
5
def my_abs(x): #行尾加个冒号
if x = 0:
return x
else:
return -x

空操作

1
2
3
def nop():
pass
# pass可以用作占位符,没想好写什么但函数可以先运行起来

参数类型检查

1
2
3
4
5
6
7
def my_abs(x):
if not isinstance(x,(int, float)): #数据类型检查
raise TypeError('bad operand type') #异常处理(后续会提到)
if x >= 0:
return x
else:
return -x

返回多值

这也太骚了

1
2
3
4
5
6
import math

def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny

事实上返回的是一个tuple

参数

  • 位置参数:普通意义上的参数
  • 默认参数:有缺省值的参数,如def power(x,n=2)
    • 必选参数在前,默认参数在后
    • 多个默认参数时,可以按顺序调用,也可以将需要修改的参数值的名称写好,不写的使用默认值,如enroll('Adam', 'M',city='Tianjin')
    • 默认参数必须指向不变对象,例如默认参数为list时用None代替[]
  • 可变参数:参数个数不确定时,可以使用list或tuple传参,带*表示可变参数

    • 参数在函数调用时自动组装为tuple
      1
      2
      3
      4
      5
      6
      7
      8
      def cal(*numbers): #加*表示可变参数
      ans = 0
      for n in numbers:
      ans += n * n
      return ans
      #传参
      >>> cal(1,2) #传递变量
      >>> cal(*number) #传递list和tuple
  • 关键字参数:允许传入0个或任意个含参数名的参数

    • 参数在内部自动组装为dict
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
          def person(name, age, **kw): #表示接受关键字参数`kw`
      print('name:', name, 'age:', age, 'other:', kw)
      #调用
      >>> person('Michael', 30) #可以只传入必选参数
      >>> person('Adam', 45, gender='M', job='Engineer')
      >>> person('Adam', 45, **extra) #将现成的dict作为参数
      + 命名关键字参数:在函数内部检查传入了哪些参数
      + 为了限制调用者可以传入的参数名,同时可以提供默认值。
      + 必须传入参数名,否则调用将报错
      + 命名关键字参数可以有缺省值
      + 使用时,如果没有可变参数,必须加一个`*`作为分隔符。如果缺少*,Python解释器将无法识别位置参数和命名关键字参数
      ```py
      def person(name, age, **kw):
      if 'city' in kw: #有city参数
      pass
      if 'job' in kw: #有job参数
      pass
      def person(name, age, *, city, job):
      #限制关键字参数的名字,分隔符*后的参数被视为命名关键字参数
      def person(name, age, *args, city, job):
      #有可变参数时后面的命名关键字参数不再需要分隔符*
  • 参数定义顺序:必选参数、默认参数、可变参数、命名关键字参数和关键字参数

  • 对于任何函数,都可以通过类似func(*args, **kw)的形式调用

P.S.

  • 可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3))
  • 关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})
  • *args**kw是习惯写法

递归

1
2
3
4
5
def fact(n):
if n == 1:
return n
else:
return n * fact(n-1)

函数式编程

  • 函数名也是变量
  • 有点C里面函数指针的感觉
1
2
3
>>> f = abs
>>> f(-10)
10

高阶函数

  • 接收函数作为参数的函数叫做高阶函数

map

  • map(f,L)f为函数,L为一个list,表示将f作用于L的每一个元素上
1
2
3
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]

reduce

  • reduce(f,L),把结果继续和序列的下一个元素做累计计算
  • reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
  • 使用时需要加上from functools import reduce
1
2
3
4
5
6
7
>>> from functools import reduce
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
#求和运算可以直接用sum()函数

map()reduce()结合使用可以用很少的代码写出str2int函数

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

from functools import reduce

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

filter

  • 用于过滤list
  • 把传入的函数依次作用于某个元素,根据返回值是True还是False决定保留还是丢弃该元素
  • 返回的是一个惰性序列,需要用list()函数将所有结果组织为list
1
2
3
4
5
6
7
#筛选回文数
def is_palindrome(n):
s = str(n)
rev = s[::-1] #使用切片倒转字符串
return s == rev

output = filter(is_palindrome, range(1, 1000))

sorted

  • 可以直接对全是数据的list排序,sort(L)返回升序排序的L
  • 可以接受一个key函数来实现自定义的排序,sort(L,key)
    • key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序
    • 然后sorted()函数按照keys进行排序,并按对应关系返回原list的相应元素
    • 要进行反向排序可以传入第三个参数reverse=True
1
2
3
4
5
6
7
8
9
10
11
def by_name(t):
return t[0] #按姓名字母顺序排序

def by_score(t):
return -t[1] #按分数降序排序

L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
L1 = sorted(L, key=by_name)
L2 = sorted(L, key=by_score)
print(L1)
print(L2)

返回函数

  • 函数作为返回值的函数
  • “闭包”程序结构:相关参数和变量都包含在返回的函数中
  • 返回的函数不会被立刻执行,而是直到调用了才执行
  • 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量
  • 有啥卵用:保存当前的运行环境。被引用的自由变量与函数同在,即使离开了创造它的环境也不例外
  • 将外部函数的变量与内部函数绑定

匿名函数

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

    1
    2
    3
    4
    lambda x : x * x
    #相当于
    def f(x):
    return x * x
  • 只能有一个表达式,不用写return,返回值就是该表达式的结果

  • 好处:没有名字,不用担心和其他函数名冲突
  • 匿名函数也是函数对象,可以把匿名函数赋值给一个变量,再利用变量来调用该函数【有毒吗,不能直接def吗
  • 匿名函数也可以作为返回值返回
1
L = list(filter((lambda x : x%2==1), range(1,20)))

装饰器

  • 函数对象有个__name__属性,可以获得函数的名字
  • 在代码运行期间动态增加函数的功能的方式成为装饰器(decorator)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import functools
def log(func):
@functools.wraps(func) # 把原始函数的属性复制到wrapper()函数中
def wrapper(*args, **kw):
print('call %s():' % func.__name__) # 先打印日志
return func(*args, **kw) # 再调用原函数
return wrapper

@log # @语法,相当于执行now=log(now)
def now():
print("2019-2-6")

>>> now()
call now():
2019-2-6

偏函数

  • 把一个函数的某些参数固定住(设置一个参数的默认值),返回一个新的函数,可以直接调用这个新的函数
  • functools.partial(f,*args,**kw)
1
2
int2 = functools.partial(int, base=2) 
#固定转化为二进制的函数

模块

  • 一个.py文件就是一个模块
  • 可以避免函数名和变量名冲突
  • 包下的是模块,包中必包含init.py模块
  • 注意模块命名不要与python原有模块冲突
  • 模块名为包名.模块名
  • 类似于C的库

标准模块文件

1
2
3
4
5
6
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'Michael Liao'
  • 第一行:标准注释,表示该文件可以直接在Unix/Linux/Mac上运行
  • 第二行:标准注释,表示该文件本身使用标准UTF-8编码
  • 第四行:模块文档注释,任何模块代码的第一个字符串都被视为模块的文档注释
  • 第六行:作者标注,使用__author__变量把作者写进去

使用模块

  • 导入模块:import 模块名

函数和变量的作用域

  • 正常的函数和变量名是公开的(public),可以直接被引用
  • 类似__xxx__的变量是特殊变量,可以被直接引用,但一般不建议这么做
    • 例如,如果调用len()试图获取一个对象的长度,会自动调用该类的__len__方法
  • 类似_xxx__xxx的函数或变量是非公开的(private),不应该被引用,但是Python不能完全限制它们的引用
  • 外部不需要引用的函数和变量全部定义为private,只有外部需要引用的函数才定义为public

安装第三方模块

1
pip install [模块名]

OOP

类的定义

1
2
class Student(object):
# Student是类名
  • 类名一般首字母大写
  • 括号中标明了从哪个类继承下来,所有类最终都继承自object(同Java)

实例

  • 创建实例:类名+(),如bart = Student()
  • 可以自由地给一个实例变量绑定属性
  • 实例的初始化可以使用__init__函数

    • 将一些必须绑定的属性写上去
    • __init__方法的第一个参数永远是self,需要声明但无需传递(类似于C的this)
    • 不就是构造函数吗
      1
      2
      3
      4
      5
      6
      class Student(object):
      def __init__(self, name, score):
      self.name = name
      self.score = score
      # 创建实例
      bart = Student('Bart Simpson',59)
  • 定义方法除了第一个参数是self外,与定义函数无异

访问限制

  • 私有变量:名称前加两个下划线__xxx,外部无法访问(其实只是Python解释器把它解释为了另一个名字)
  • 单下划线实例变量名:“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”

继承和多态

  • 基本类似Java的继承
  • 子类继承父类的方法
  • 子类和父类可以有相同的方法,但子类的方法会覆盖父类的方法
  • “鸭子类型”:一个对象只要“看起来像鸭子,走起路来像鸭子”,就可以被看成鸭子
    • 不要求严格的继承体系,调用方法时只要保证对象有这样的方法

获取对象信息

  • type()函数:判断对象、函数类型,返回对应的Class类型,可以用if语句比较两个变量的类型是否相同

    • 判断基本数据类型可以直接写intstr
    • 判断一个对象是否是函数可以使用types模块中定义的常量
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      >>> type(123) == int
      True
      >>> 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()可以用来判断继承关系

    • 能用type()判断的基本类型也可以用isinstance()判断
    • 优先使用isinstance()
  • dir()可以获得一个对象的所有属性和方法,返回一个包含字符串的list

    1
    2
    >>> dir('ABC')
    ['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
    • 配合getattr()setattr()hasattr()可以直接操作一个对象的状态

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      >>> 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
      >>> 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
      • 只有在不知道对象具体信息时,才会去获取对象的信息
        1
        2
        3
        4
        5
        6
        7
         #正确用法
        def readImage(fp):
        if hasattr(fp, 'read'):
        return readData(fp)
        return None
        # 假设我们希望从文件流fp中读取图像
        # 我们首先要判断该fp对象是否存在read方法

实例属性和类属性

  • 给实例绑定属性:通过实例变量或self变量
  • 类本身需要绑定属性:直接在class中定义
    • 类的所有实例都可以访问类属性
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      class Student(object):
      count = 0

      def __init__(self, name):
      self.name = name
      Student.count += 1 # 类的属性增加一,而非实例的属性self.countA

      bart = Student('Bart')
      print(Student.count)
      lisa = Student('Lisa')
      print(Student.count)

slots

  • 实例可以绑定属性和方法

    • 绑定方法需要使用到types模块中的MethodType`方法

      1
      2
      3
      4
      5
      >>> from types import MethodType
      >>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
      >>> s.set_age(25) # 调用实例方法
      >>> s.age # 测试结果
      25
    • 以上方法绑定的方法对其他的实例是不起作用的

  • 给类绑定方法可以给所有实例绑定方法,是一种动态绑定,绑定后所有实例均可调用

    1
    2
    3
    4
    >>> def set_score(self, score):
    ... self.score = score
    ...
    >>> Student.set_score = set_score
  • 使用__slots__可以限制实例的属性

    • 仅对当前类的实例起作用,对继承的子类是不起作用的
    • 如果在子类中也定义__slots__,则子类实例允许定义的属性 = 自身的__slots__ + 父类的__slots__

@property

  • @property是装饰器,负责把一个setter方法变成属性赋值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Student(object)
    @property
    def score(self):
    return self._score # getter属性

    @score.setter
    def score(self, value): # setter属性
    if not isinstance(value, int):
    raise ValueError('score must be an integer')
    if value < 0 or value > 100:
    raise ValueError('score must between 0 and 100')
    self._score = value
    • @property使得对实例属性操作时,通过getter和setter方法来实现
    • 是定义getter方法就相当于定义了一个只读属性

多重继承

  • 一个子类可以同时获得多个父类的所有功能

    1
    2
    class Dog(Mammal, Runnable):
    pass
  • MixIn:除主线之外的继承关系

    • 为了更好地看出继承关系,可以把主线外的继承类命名为xxxMixIn,如RunnableMixIn
    • 这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类
    • Java是只允许单一继承的,不允许MixIn的继承

定制类

上文提到的 __len__方法能让class作用于len()函数,就是一种定制类的方法

str

  • __str__方法可以改变类的实例的打印方式,类似于Java的toString

    1
    2
    3
    4
    5
    6
    7
    8
    class Student(object):
    def __init__(self):
    self.name = name
    def __str__(self):
    return 'Student object (name: %s)' % self.name

    >>> print(Student('Michael'))
    Student object (name: Michael)

repr

  • __repr__方法 直接显示变量调用(在交互模式下敲出变量名)
    • __str__()方法返回用户看到的字符串,__repr__()返回程序开发者看到的字符串,为调试服务
    • 通常__str__()__repr__()代码都是一样的,有个偷懒的写法
1
2
3
def __str__(self):
pass
__repr__ = __str__

iter

  • __iter__方法返回一个迭代对象,让类可以被用用于for ... in循环

    • 配合__next__方法,for循环会不断调用该迭代对象的__next__()方法得到循环的下一个值,直到遇到StopIteration错误退出循环

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      class Fib(object):
      def __init__(self):
      self.a,self.b = 0,1

      def __iter__(self):
      return self

      def __next__(self):
      self.a,self.b = self.b, self.a+self.b
      if self.a > 100000:
      raise StopInteration()
      return self.a

      >>> for n in Fib():
      print(n)
      1
      1
      2
      3
      5
      ...
      46368
      75025

getitem

  • __getitem__方法使类可以像list那样根据下标取出元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class Fib(object):
    def __getitem__(self, n):
    if(isinstance(n, int)): # n是索引
    a,b = 1,1
    for x in range(1, n):
    a,b = b,a+b
    return a
    if(isinstance(n, slice)): # n是切片
    start = n.start
    stop = n.start
    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

    >>> f = Fib()
    >>> f[0]
    1
    >>> f[100]
    573147844013817084101
    >>> f[0:5]
    [1, 1, 2, 3, 5]
    • 以上__getitem__()方法没有对步长和负数做处理,因此要正确实现一个__getitem__()还是有很多工作要做的
    • 如果把对象看成dict__getitem__()的参数也可能是一个可以作key的object,例如str,与之对应的还有__setitem____delitem__

getattr

  • __getattr__方法能够动态返回一个属性,当调用不存在的属性时,会试图调用__getattr__(self,属性)来尝试获得属性

    • 只有在没有找到属性的情况下才会调用__getattr__,已有的属性是直接获取的

      1
      2
      3
      4
      5
      6
      class Student(object):

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

call

  • __call__使实例自身能被当作函数调用
1
2
3
4
5
6
7
8
9
10
class Student(object):
def __init__(self, name):
self.name = name

def __call__(self):
print('My name is %s.' % self.name)

>>> s = Student('Michael')
>>> s()
My name is Michael
  • 通过callable函数可以判断一个对象是否能被调用
1
2
3
4
5
6
7
8
9
10
>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False

枚举类Enum

1
2
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
  • 这样就得到了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量
  • 也可以枚举该类的所有成员

    1
    2
    from name,member in Month.__members.items()
    print(name, '=>', member, ',', member.value)
  • value属性是自动赋给int常量,默认从1开始计数(居然不是从0开始)

  • Enum派生自定义类可以精确控制枚举类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from enum import Enum

    @unique #装饰器,帮忙检查有无重复值
    class Weekday(Enum): # Enum的子类
    Sun = 0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

    # 访问
    Weekday.Mon
    Weekday['Tue']
    Weekday(1)

type()函数

  • 动态语言的函数和类不是编译时定义的,而是运行时动态创建的
  • type()函数可以查看一个类型或变量的类型
  • type()函数可以创建出新的类型,而无需通过class定义

    1
    2
    3
    4
    5
    6
    def fn(self, name='world'):
    print('Hello, %s.' % name)
    Hello = type('Hello', (object,), dict(hello=fn)) #创建Hello类
    >>> h = Hello()
    >>> h.hello()
    Hello, world.
  • type()函数需依次传入3个参数

    • class的名称
    • 继承的父类的集合,注意单元素tuple的写法
    • class的方法名称与函数绑定

metaclass()函数

  • 先定义metaclass(元类),就可以创建类,然后创建实例
  • 可以把类看成metaclass创建出来的实例
  • metaclass正常情况下不会用到所以看不懂也没关系
1
2
3
4
5
6
7
8
9
# 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)

# 使用metaclass定制类
class MyList(list, metaclass=ListMetaclass):
pass
  • 创建Mylist时,通过ListMetaclass.__new__()来创建
  • 可以用来修改类的定义,例如加上新的方法(样例中在MyList类中增加了add方法)
  • __new__()方法接收的参数依次是
    • 当前准备创建的类的对象
    • 类的名字
    • 类继承的父类的集合
    • 类的方法的集合
  • 应用上与SQL相结合的较多,等用到的时候再看了

错误、调试和测试

try

  • 当我们认为某些代码可能会出错时,可以用try来运行这段代码
  • 如果出错,后续代码不会继续执行,而是直接跳转至错误处理代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    try:
    print('try...')
    r = 10 / 0
    print('result:', r)
    # 错误处理代码
    except ZeroDivisionError as e: # 可以有多个except
    print('except:', e)
    finally: # finally无论如何都会被执行,表示执行结束
    print('finally...')
    print('END')
  • except后可加else语句块,当没有错误发生时会执行else语句块

  • try...except可以跨越多层调用,被调用者出错,调用者也能捕捉到
  • Python的错误也是类,所有错误类型都继承自BaseException
  • 常见的错误类型和继承类型

记录错误

  • python有内置的logging模块,可以记录错误信息
  • 程序打印完错误信息后会继续执行并正常退出
1
2
3
4
try:
bar('0')
except Exception as e:
logging.exception(e)

抛出错误

  • 根据需要,可以定义一个错误的类,选择好继承关系,用raise语句抛出一个错误的实例
  • 只有在必要的时候才定义我们自己的错误类型。如果可以选择Python已有的内置的错误类型,尽量使用Python内置的错误类型
  • 当前函数不知道如何处理该错误时,还可以在打印错误后再往上抛,让顶层调用者去处理
    • raise语句如果不带参数,就会将当前错误原样抛出
    • exceptraise一个Error,可以将错误转化成另一种类型

调试

  • print调试法
  • assert调试法

    1
    2
    assert n != 0, 'n is zero!'
    # 表达式应该为True,否则输出AssertionError+后接的字符串
    • 启动python解释器时可以用-O参数来关闭assert
  • log调试法(Python管这叫logging)

    1
    2
    3
    4
    import logging
    logging.basicConfig(level=logging.INFO) # 配置
    logging.info('n = %d' % n)
    # logging.info: 输出信息
    • 可以指定DEBUG>INFO>WARNING>ERROR几个不同级别(level)的信息,级别越高越先被屏蔽,e.g.level=INFODEBUG的logging就不起作用了
  • pdb调试法(命令同gdb)
    • l: 查看代码
    • n: 单步执行
    • p 变量名: 打印变量
    • c: 继续执行
  • pdb.set_trace()在程序中设置断点,运行时程序会自动暂停并进入pdb

    1
    2
    3
    4
    5
    6
    import pdb

    s = '0'
    n = int(s)
    pdb.set_trace() # 运行到这里会自动暂停
    print(10 / n)
  • PyCharm调试法

I/O

文件读写

读文件

1
2
3
4
f = open('/path/to/file', 'r')
# 失败时会返回IOError
f.read() #一次性读取全部内容到内存,用一个str对象表示
f.close() #关闭文件,回收资源

为了保证无论是否出错都能正确地关闭文件,我们可以使用try ... finally来实现:

1
2
3
4
5
6
try:
f = open('/path/to/file', 'r')
print(f.read())
finally:
if f:
f.close()

嫌长可以简化为with语句

1
2
3
with open('/path/to/file', 'r') as f:
print(f.read())
#隐式调用了f.close()

不想一次读取所有文件

1
2
3
f.read(size) # 读取一定size个字节的内容
f.readline() # 一次读取一行
f.readlines() # 一次读取所有行并返回list

还可以有效防止内存爆炸

内存的字节流,网络流,自定义流等等都是file-like Object,只需要写个read()方法就能用

二进制文件需要用'rb'模式打开

读取默认使用UTF-8编码,需要编码转换的时候要给open()传入encoding参数,errors参数表示出现错误后怎么处理,一般选择忽略

1
f = open('/Users/michael/gbk.txt', 'r', encoding='gbk',errors='ignore')

写文件

和读一样,把r改成w(跟C也差不多)

StringIO

在内存中读写str

1
2
3
4
5
6
7
8
9
10
>>> from io import StringIO
>>> f = StringIO() # 创建StringIO对象
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue()) #getvalue()用于获得写入后的str
hello world!

可以用一个str初始化StringIO,然后,像读文件一样读取

BytesIO

操作内存中的二进制数据

1
2
3
4
5
6
>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87

操作系统命令

Python内置的os模块可以直接调用操作系统提供的接口函数

文件名操作只对字符串进行,并不要求文件真实存在

shutil中可以找到很多对os的补充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> import os
>>> os.name #查看操作系统名称
>>> os.environ # 查看操作系统中所有的环境变量
>>> os.environ.get('key') # 获取某个环境变量的值
>>> os.path.abspath('.') # 查看当前目录的绝对路径:
'/Users/michael'
>>> os.path.join('/Users/michael', 'testdir')
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
'/Users/michael/testdir'
# 然后创建一个目录:
>>> os.mkdir('/Users/michael/testdir')
# 删掉一个目录:
>>> os.rmdir('/Users/michael/testdir')
# 拆分路径,把当前文件名与目录分开
>>> os.path.split('/Users/michael/testdir/file.txt')
('/Users/michael/testdir', 'file.txt')
# 拆分文件扩展名
>>> os.path.splitext('/path/to/file.txt')
('/path/to/file', '.txt')
# 对文件重命名:
>>> os.rename('test.txt', 'test.py')
# 删掉文件:
>>> os.remove('test.py')

过滤文件
不都是一行命令行就能搞定的吗

1
2
3
4
>>> [x for x in os.listdir('.') if os.path.isdir(x)]
['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Applications', 'Desktop', ...]
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']`
['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']

还有psutil模块提供了对cpu信息、内存和磁盘使用信息的访问

序列化

把变量从内存中变成可存储或传输的过程叫作序列化(picking).序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。
反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。

用到了再看吧

CATALOG
  1. 1. 数据类型
    1. 1.1. 整数
    2. 1.2. 浮点数
    3. 1.3. 字符串
      1. 1.3.1. 几个骚方法
    4. 1.4. 注释
    5. 1.5. 类型转换函数
      1. 1.5.1. 编码问题
    6. 1.6. 布尔值
    7. 1.7. 空值
    8. 1.8. 常量
    9. 1.9. list 列表
      1. 1.9.1. list方法
    10. 1.10. 切片
      1. 1.10.1. 列表生成式
      2. 1.10.2. 生成器generator
    11. 1.11. tuple 元组
  2. 2. 分支语句if
  3. 3. 循环语句
    1. 3.1. for
      1. 3.1.1. 类似C语言的循环
      2. 3.1.2. 迭代对象
      3. 3.1.3. 迭代器
    2. 3.2. while
  4. 4. 字典dict
    1. 4.1. 创建dict
    2. 4.2. dict方法
    3. 4.3. 遍历dict
  5. 5. 集合set
    1. 5.1. 创建set
    2. 5.2. set方法
    3. 5.3. set操作
    4. 5.4. 可变对象与不可变对象
  6. 6. 函数
    1. 6.1. 定义函数
      1. 6.1.1. 空操作
      2. 6.1.2. 参数类型检查
      3. 6.1.3. 返回多值
    2. 6.2. 参数
    3. 6.3. 递归
  7. 7. 函数式编程
    1. 7.1. 高阶函数
      1. 7.1.1. map
      2. 7.1.2. reduce
      3. 7.1.3. filter
      4. 7.1.4. sorted
    2. 7.2. 返回函数
    3. 7.3. 匿名函数
    4. 7.4. 装饰器
    5. 7.5. 偏函数
  8. 8. 模块
    1. 8.1. 标准模块文件
    2. 8.2. 使用模块
    3. 8.3. 函数和变量的作用域
    4. 8.4. 安装第三方模块
  9. 9. OOP
    1. 9.1. 类的定义
    2. 9.2. 实例
    3. 9.3. 访问限制
    4. 9.4. 继承和多态
    5. 9.5. 获取对象信息
    6. 9.6. 实例属性和类属性
    7. 9.7. slots
    8. 9.8. @property
    9. 9.9. 多重继承
    10. 9.10. 定制类
      1. 9.10.1. str
      2. 9.10.2. repr
      3. 9.10.3. iter
      4. 9.10.4. getitem
      5. 9.10.5. getattr
      6. 9.10.6. call
    11. 9.11. 枚举类Enum
    12. 9.12. type()函数
    13. 9.13. metaclass()函数
  10. 10. 错误、调试和测试
    1. 10.1. try
    2. 10.2. 记录错误
    3. 10.3. 抛出错误
  11. 11. 调试
  12. 12. I/O
    1. 12.1. 文件读写
      1. 12.1.1. 读文件
      2. 12.1.2. 写文件
      3. 12.1.3. StringIO
      4. 12.1.4. BytesIO
      5. 12.1.5. 操作系统命令
      6. 12.1.6. 序列化