#!/usr/bin/env python # -*- coding: utf-8 -*-
Python的数据类型有
- 整数
- 浮点数
- 字符串
'str1'
/"str2"
/''' str3'''
/"""str4"""
- 布尔值: True / False
- 空值: None , 是一个特殊的空值。
- 常量: 通常用全部大写的变量名表示常量, 只是一个习惯,值可以被改变
变量
a = 5 <= 3 or True
a = 10 % 3 + 10 // 3 #地板除得到整数
a = 10 % 3 + 10 / 3 #除法得到浮点数
a = 2**3 #计算乘方
a = 'ABC'
b = a
a = 'XYZ' #输出b: ABC, a和b都是地址
PI = 3.1415
name = input('Please enter your name: ')
print(name)
#b表示bytes
print('ABC'.encode('ascii'))
print('ABC'.encode('utf-8'))
print('中文'.encode('utf-8'))
a = b'\xe4\xb8\xad\xff'.decode('utf-8',errors='ignore')
print(a)
print(len(a),len(b'\xe4\xb8\xad'),len('中'.encode('utf-8')))
print('中文测试正常')
r
表示内部字符默认不转义
print(r'seek\t\n\\')
print('''line1
line2
line3''')
print(r'''line1
line2
line3''')
#格式化方式1
print('age: %d, name: %s, stunum: %04d, ave_score:%.2f' % (25, '然然', 32, 73.1415926))
#格式化方式2: format()
print('{0}成绩提升了{1:.2f}%'.format('童童', 5.2316))
print('my name is {0}, I am {1} years old. Please call me {0}'.format('陈明',12))
#格式化方式3: f-string
r = 2.5
s = 3.14 * r ** 2
print(f'The area of a circle with radius {r} is {s:.2f}')
占位符 | 替换内容 |
---|---|
%d | 整数 |
%f | 浮点数 |
%s | 字符串 |
%x | 十六进制整数 |
替换字符
a = 'abc'
b = a.replace('a', 'A')
条件语句
height = float(input('height: '))
weight = float(input('weight: '))
bmi = weight / height**2
if bmi < 18.5:
print('过轻')
elif bmi >= 18.5 and bmi < 25:
print('正常')
elif bmi >= 25 and bmi < 28:
print('过重')
else:
print('肥胖')
if age >= 18:
#什么都不做
pass
for循环
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)
sum = 0
for x in range(101):
sum = sum + x
if sum == 5050:
print('Gauss is correct.')
else:
print('Gauss is wrong.')
range() 函数左闭右开
while循环
n = 201
pow = 1
while n > 0:
n = n - 1
if n % 5 == 0:
continue
pow = pow * n
print(pow)
dictionary 字典,对应于其他语言的map结构
#创建dic
ad = {'mar': 13, (1,2):55,'wat': 67}
#判断key是否存在
'mar' in ad
#取值
ad['hap'] #不存在抛异常
ad.get('mar') #不存在返回None
ad.get('mar',-1) #不存在返回指定值-1
#增加key
ad['hap'] = 23
#删除key
ad.pop('mar')
print(ad)
# 遍历
a = {'a': 1, 'b': 2, 'c': 3}
# 方式一:
for key in a:
print(key+':'+a[key])
# 方式二:
for key in a.keys():
print(key+':'+a[key])
# 方式三:
for key,value in a.items():
print(key+':'+value)
#方式四:
for (key,value) in a.items():
print(key+':'+value)
# 遍历value值:
for value in a.values():
print(value)
# 遍历字典项
for item in a.items():
print(item)
打印结果:
('a', '1')
('b', '2')
('c', '3')
set 集合
s = set([2,2,3,(1,2),5]) #要创建一个set,需要提供一个list作为输入集合
s.add(4) #添加元素
s.remove(4) #删除元素
se = set([5,2,(2,3)])
print(se & s) #set做交集
print(se | s) #set做并集
list 列表
roommate = ['suhong','ziheng','siyuan'] #创建list
len(roommate) #获取list中元素个数
print(roommate[2]) #取list中元素
print(roommate[-1]) #取list中元素
roommate.append('xinran') #追加元素
roommate.insert(1,['panpan','xinran']) #指定位置添加元素
roommate.pop() #删除末尾元素
roommate.pop(i) #删除索引i处的元素
del roommate[i] #删除索引i处的元素
roommate.sort() #对list进行排序,roommate原地排序,不生成新的列表
new_roommate = sorted(roomate) #对list进行排序,产生一个新的列表
# 遍历
for r in roommate:
print(r)
for i in range(roommate):
print(roommate[i])
tuple 元组,不可变
she = ('suhong',) #创建tuple
she1 = ('suhong') #这不是创建tuple
she2 = () #创建空tuple
roommate = ('suhong', 'siyuan', ['panpan', 'ziheng'])
roommate[2][1] = 'bianbian'
print(roommate)
print(len(roommate))
对函数的理解: 函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:
a = abs # 变量a指向abs函数
a(-1) # 所以也可以通过a调用abs函数
#定义一个函数
def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x >= 0:
return x
else:
return -x
#定义空函数
def nop():
pass
#定义函数返回多个值
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
x, y = move(100, 100, 60, math.pi / 6) #返回多值实际上是返回tuple
def move(x, y, step, angle=0):
-
位置参数 : x,y,step叫做位置参数。
-
默认参数 : angle=0 叫默认参数。定义默认参数要牢记一点:默认参数必须指向不变对象!
def add_end(L=[]): #这种定义指向了可变对象,错误 def add_end(L=None): #这种定义执行不变对象,正确
-
可变参数: 传入一列值或一个list,tuple
def calc(*numbers): sum = 0 for n in numbers: sum = sum + n * n return sum calc(1, 2) nums = [1, 2, 3] calc(*nums) #在list或tuple前面加一个*号,表示list的所有元素作为可变参数传进去
可变参数在函数调用时自动组装为一个tuple
-
关键字参数
def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw) person('Michael', 30) person('Bob', 35, city='Beijing') extra = {'city': 'Beijing', 'job': 'Engineer'} person('Jack', 24, **extra)
关键字参数在函数内部自动组装为一个dic
-
命名关键字参数
def person(name, age, *, city, job): print(name, age, city, job) def man(name, age, *, city='Beijing', job): print(name, age, city, job) #函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了 def person(name, age, *args, city, job): print(name, age, args, city, job)
如果没有可变参数,就必须加一个
*
作为特殊分隔符。如果缺少*
其中默认参数、可变参数、关键字参数都可传可不传;位置参数和命名关键字参数必须传入。
参数定义的顺序必须是:位置参数、默认参数、可变参数、命名关键字参数和关键字参数。
对于任意函数,都可以通过类似
func(*args, **kw)
的形式调用它,无论它的参数是如何定义的。使用参考廖雪峰博客函数的参数
def fact(n):
if n==1:
return 1
return n * fact(n - 1)
L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
L[0:3] #取前3个元素,等同于 L[:3]
L[-2:] #从倒数第2个元素开始,取到 L[len-2],L[len-1]
L[-3:-1] #从倒数第2个元素开始,到倒数第1个元素,取到 L[len-3],L[len-2]
L[-1:-3:-1] #从最后一个元素开始,倒数取,取到 L[len-1],L[len-2],等同于L[:-3:-1]
tuple也是一种list,唯一区别是tuple不可变
(0, 1, 2, 3, 4, 5)[:3]
字符串'xxx'
也可以看成是一种list
'ABCDEFG'[::2]
Python没有针对字符串的substring截取函数,只需要切片一个操作就可以完成
d = {'a': 1, 'b': 2, 'c': 3}
for key in d:
print(key)
for value in d.values()
for k, v in d.items()
只要作用于一个可迭代对象,for
循环就可以正常运行。判断可迭代对象
from collections.abc import Iterable
isinstance('abc', Iterable)
对list
实现循环下标
for i, value in enumerate(['A', 'B', 'C']):
print(i, value)
for
循环可以同时引用多个变量
for x, y in [(1, 1), (2, 4), (3, 9)]:
print(x, y)
未来代替繁琐的for循环
[x * x for x in range(1, 11)]
[x * x for x in range(1, 11) if x % 2 == 0]
[m + n for m in 'ABC' for n in 'XYZ']
d = {'x': 'A', 'y': 'B', 'z': 'C' }
[k + '=' + v for k, v in d.items()]
L = ['Hello', 'World', 'IBM', 'Apple']
[s.lower() for s in L]
常用例子: 列出当前目录下的所有文件和目录名
import os
[d for d in os.listdir('.')]
在for
后面的if
是一个筛选条件,不能带else
.for
前面的部分是一个表达式,它必须根据x
计算出一个结果,必须带 else
[x if x % 2 == 0 else -x for x in range(1, 11)]
Python中,这种一边循环一边计算的机制,称为生成器:generator。
创建生成器
L = [x * x for x in range(10)] #这是1个list
g = (x * x for x in range(10)) #这是1个生成器
通过next()
函数获得generator
next(g)
generator保存的是算法,每次调用next(g)
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。
实际上最常用到for取生成器的元素
g = (x * x for x in range(10))
for n in g:
print(n)
经常将函数作为生成器
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>
#取fib的值
while True:
try:
x = next(f)
print('f:', x)
except StopIteration as e:
print('Generator return value:', e.value)
break
fib
函数是一个生成器。
如果一个函数定义中包含
yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator
generator和函数的执行流程不一样。函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。即 普通函数调用直接返回结果;generator函数的“调用”实际返回一个generator对象
凡是可作用于for
循环的对象都是Iterable
类型;
凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。详细参考廖雪峰博客迭代器
高阶函数: 一个函数就可以接收另一个函数作为参数.
def add(x, y, f):
return f(x) + f(y)
print(add(-5, 6, abs))
map()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。
def f(x):
return x * x
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
list(r)
list() 函数将迭代器转换为list。
reduce
把结果和序列的下一个元素做累积计算。效果等价于下面
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
from functools import reduce
def fn(x, y):
return x * 10 + y
reduce(fn, [1, 3, 5, 7, 9])
一个例子: str
转换为int
的函数:
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()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
Python内置的sorted()
函数就可以对list进行排序
sorted([36, 5, -12, 9, -21])
sorted()
函数是一个高阶函数,接收一个key
函数来实现自定义的排序
sorted([36, 5, -12, 9, -21], key=abs) #结果为[5, 9, -12, -21, 36]
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower) #忽略大小写的排序
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) #逆序排序
不需要立刻计算,而是在后面的代码中,根据需要再计算。
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()#调用函数f时,才真正计算求和
说明: 内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
闭包: 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。参考廖雪峰博客闭包
关键字lambda
表示匿名函数,冒号前面的x
表示函数参数。
list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
匿名函数lambda x: x * x
实际上就是
def f(x):
return x * x
匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果。
也可以把匿名函数作为返回值返回,比如:
def build(x, y):
return lambda: x * x + y * y
在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)
本质上,decorator就是一个返回函数的高阶函数。我们要定义一个能打印日志的decorator:
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
函数对象有一个
__name__
属性,可以拿到函数的名字
借助Python的@语法,把decorator置于函数的定义处
@log
def now():
print('2015-3-25')
now()
调用now()
函数,不仅会运行now()
函数本身,还会在运行now()
函数前打印一行日志.把@log
放到now()
函数的定义处,相当于执行了语句:
now = log(now)
由于log()
是一个decorator,返回一个函数,所以,原来的now()
函数仍然存在,只是现在同名的now
变量指向了新的函数,于是调用now()
将执行新函数,即在log()
函数中返回的wrapper()
函数。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
#wrapper.__name__=func.__name__
return wrapper
return decorator
@log('execute')
def now():
print('2015-3-25')
now()
print(now.__name__)
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
now = log('execute')(now)
面向对象(OOP)的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。
functools.partial
就是帮助我们创建一个偏函数的.
对于一个字符串转2进制整数的函数:
int('12345', base=2) #每次都传入int(x, base=2)非常麻烦
int2('1000000')
#于是定义
def int2(x, base=2):
return int(x, base)
使用偏函数,创建新函数
import functools
int2 = functools.partial(int, base=2)
int2('1000000')
当函数的参数个数太多,需要简化时,使用functools.partial
可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a test module '
__author__ = 'Michael Liao'
import sys
def test():
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()
sys
模块有一个argv
变量,用list存储了命令行的所有参数。argv
至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:
运行python3 hello.py
获得的sys.argv
就是['hello.py']
;
运行python3 hello.py Michael
获得的sys.argv
就是['hello.py', 'Michael']
。
当我们在命令行运行hello
模块文件时,Python解释器把一个特殊变量__name__
置为__main__
,而如果在其他地方导入该hello
模块时,if
判断将失败,因此,这种if
测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。
在Python中,是通过_
前缀来实现的。
正常的函数和变量名是公开的(public),可以被直接引用,比如:abc
,x123
,PI
等;
类似__xxx__
这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__
,__name__
就是特殊变量,hello
模块定义的文档注释也可以用特殊变量__doc__
访问,我们自己的变量一般不要用这种变量名;
类似_xxx
和__xxx
这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc
,__abc
等;
private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。
private函数或变量不应该被别人引用,那它们有什么用呢?请看例子:
def _private_1(name):
return 'Hello, %s' % name
def _private_2(name):
return 'Hi, %s' % name
def greeting(name):
if len(name) > 3:
return _private_1(name)
else:
return _private_2(name)
我们在模块里公开greeting()
函数,而把内部逻辑用private函数隐藏起来了,这样,调用greeting()
函数不用关心内部的private函数细节,这也是一种非常有用的代码封装和抽象的方法,即:
外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。
安装第三方模块,是通过包管理工具pip完成的
默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys
模块的path
变量中:
import sys
sys.path
如果我们要添加自己的搜索目录,有两种方法:
一是直接修改sys.path
,添加要搜索的目录:
import sys
sys.path.append('/Users/michael/my_py_scripts')
这种方法是在运行时修改,运行结束后失效。
第二种方法是设置环境变量PYTHONPATH
,该环境变量的内容会被自动添加到模块搜索路径中。设置方式与设置Path环境变量类似。
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'
bart = Student()
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量
self
和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.age = 8
>>> bart.age
8
>>> lisa.age
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'
-
两个下划线
__
表示私有把属性的名称前加上两个下划线
__
,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问.class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print('%s: %s' % (self.__name, self.__score)) def get_name(self): return self.__name def get_score(self): return self.__score def set_score(self, score): if 0 <= score <= 100: self.__score = score else: raise ValueError('bad score')
不能直接访问
__name
是因为Python解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
变量.bart._Student__name
禁用这种错误写法
bart.__name = 'New Name' # 设置__name变量。 错误!
-
以双下划线开头,并且以双下划线结尾
以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用
__name__
、__score__
这样的变量名。 -
以一个下划线开头
实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
def run_twice(animal):
animal.run()
animal.run()
run_twice(Animal())
run_twice(Dog())
run_twice(Cat())
class Timer(object):
def run(self):
print('Start...')
run_twice(Timer())
前面是继承的写法。
后面是动态语言“file-like object“的“鸭子类型”: 则不一定需要传入Animal
类型。我们只需要保证传入的对象有一个run()
方法就可以了.
>>> 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
对于class的继承关系来说,使用type()
就很不方便。我们要判断class的类型,可以使用isinstance()
函数。
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
>>> isinstance(d, Dog) and isinstance(d, 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(myObj)
的话,就自己写一个__len__()
方法:
>>> class MyDog(object):
... def __len__(self):
... return 100
...
>>> dog = MyDog()
>>> len(dog)
100
配合getattr()
、setattr()
以及hasattr()
,我们可以直接操作一个对象的状态
>>> 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的错误:
>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'
可以传入一个default参数,如果属性不存在,就返回默认值:
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
也可以获得对象的方法:
>>> 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
创建类属性
class Student(object):
name = 'Student'
在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
多重继承、定制类、元类
定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。
尝试给实例绑定一个方法:
>>> def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25
给class绑定方法:
>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__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__
Python内置的@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
把一个getter方法变成属性,只需要加上@property
就可以了,此时,@property
本身又创建了另一个装饰器@score.setter
,负责把一个setter方法变成属性赋值
>>> 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方法,不定义setter方法就是一个只读属性
要特别注意:属性的方法名不要和实例变量重名。
class Dog(Mammal, Runnable):
pass
多重继承设计叫做MixIn。MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
try:
print('try...')
r = 10 / int('2')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
else:
print('no error!')
finally:
print('finally...')
print('END')
没有错误发生,可以在except
语句块后面加一个else
,当没有错误发生时,会自动执行else
语句。
Python的错误其实也是class,所有的错误类型都继承自BaseException
,所以在使用except
时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。
Python内置的logging
模块可以非常容易地记录错误信息:
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')
通过配置,logging
还可以把错误记录到日志文件里。
用raise
语句抛出一个错误的实例
class FooError(ValueError):
pass
def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n
foo('0')
-
使用print()函数
-
用断言(assert):如果断言失败,
assert
语句本身就会抛出AssertionError
def foo(s): n = int(s) assert n != 0, 'n is zero!' return 10 / n def main(): foo('0')
启动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)
logging 有
debug
,info
,warning
,error
等几个级别,当我们指定level=INFO
时,logging.debug
就不起作用了。同理,指定level=WARNING
后,debug
和info
就不起作用了。 -
Python的调试器pdb
-
IDE工具
mydict.py
代码
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
编写单元测试,我们需要引入Python自带的unittest
模块,编写mydict_test.py
import unittest
from mydict import Dict
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):
value = d['empty']
def test_attrerror(self):
d = Dict()
with self.assertRaises(AttributeError):
value = d.empty
编写单元测试时,我们需要编写一个测试类,从unittest.TestCase
继承。
以test
开头的方法就是测试方法,不以test
开头的方法不被认为是测试方法,测试的时候不会被执行。
对每一类测试都需要编写一个test_xxx()
方法。由于unittest.TestCase
提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的。最常用的断言就是assertEqual()
:
self.assertEqual(abs(-1), 1) # 断言函数返回的结果与1相等
另一种重要的断言就是期待抛出指定类型的Error,比如通过d['empty']
访问不存在的key时,断言会抛出KeyError
:
with self.assertRaises(KeyError):
value = d['empty']
最简单的运行方式是在mydict_test.py
的最后加上两行代码:
if __name__ == '__main__':
unittest.main()
这样就可以把mydict_test.py
当做正常的python脚本运行:
$ python mydict_test.py
另一种方法是在命令行通过参数-m unittest
直接运行单元测试:
$ python -m unittest mydict_test
这是推荐的做法,因为这样可以一次批量运行很多单元测试,并且,有很多工具可以自动来运行这些单元测试。
class TestDict(unittest.TestCase):
def setUp(self):
print('setUp...')
def tearDown(self):
print('tearDown...')
这两个方法会分别在每调用一个测试方法的前后分别被执行。比如测试前需要启动数据库,可以在setUp()
方法中连接数据库。
# mydict2.py
class Dict(dict):
'''
Simple dict but also support access as x.y style.
>>> d1 = Dict()
>>> d1['x'] = 100
>>> d1.x
100
>>> d1.y = 200
>>> d1['y']
200
>>> d2 = Dict(a=1, b=2, c='3')
>>> d2.c
'3'
>>> d2['empty']
Traceback (most recent call last):
...
KeyError: 'empty'
>>> d2.empty
Traceback (most recent call last):
...
AttributeError: 'Dict' object has no attribute 'empty'
'''
def __init__(self, **kw):
super(Dict, self).__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
if __name__=='__main__':
import doctest
doctest.testmod()
运行python mydict2.py
:
$ python mydict2.py
什么输出也没有。这说明我们编写的doctest运行都是正确的。