第1章 函数式编程
1-1 Python之函数式编程简介
- 函数式:functional,是一种编程范式;
- 函数式编程特点:
- 把计算视为函数而非指令;
- 纯函数式编程:不需要变量,没有副作用,测试简单;
- 支持高阶函数,代码简洁;
- Python的函数式编程特点:
- 不是纯函数式编程,允许有变量
- 支持高阶函数,函数可以作为变量传入
- 支持闭包,可以返回函数;
- 有限度地支持匿名函数
1-2 Python之高阶函数
- 变量可以指向函数,直接调用该变量,和调用函数的效果一样;
- 函数名其实是指向函数的变量,和普通的变量名没有区别,只是指向了一个函数对象;
- 高阶函数:能接收函数做参数的函数
1-3 Python之把函数作为参数
- 定义函数:
def add(x, y, f):
return f(x) + f(y)
add(-5,9,abs) = abs(-5) + abs(9) = 14
- 定义的函数add,其参数 x、y、f 都可以任意传入,若 f 传入其他函数,就可以得到不同的返回值。
1-4 Python之map()函数
- 概述:指定规则作用于list中每个元素,生成新list的高阶函数;
- 定义:接收一个含1个参数的函数f和一个list,将函数f依次作用于list的每个元素,得到新list。
1-5 Python之reduce()函数
- 概述:指定规则使list内部元素交互运算并返回结果值的高阶函数;
- 定义:接收一个含2个参数的函数f、一个list、一个可选参数,将list中的元素逐个调用函数f(第i个元素和第(i+1)个元素交互结果再与第(i+3)个元素交互)进行交互运算,返回结果值,可选参数表示初始计算值。
1-6 Python之filter()函数
- 概述:指定判断规则对list中元素进行筛选的高阶函数;
- 定义:接收一个判断函数f和一个list,对list中每个元素进行判断,返回由符合条件的元素组成的新list;
-
math.sqrt(x)
:返回的是浮点型数据,无法根据其运算结果进行整数判断;
-
str.strip(x)
:删除字符串str开头、结尾处的字符串x。若x为空则默认删除空白符(包括‘\n’ , ‘\r’ , ‘\t’, ‘ ’)。
1-7 Python之自定义排序函数
- 比较函数的定义:传入2个待比较的元素 x 和 y,若 x 应该排在 y 前面则返回 -1;若 x 应该排在 y 后面则返回1;若 x 与 y 相等则返回0;
-
sorted()
也是一个高阶函数,通过调用一个比较函数可以实现自定义排序。
1-8 Python之返回函数
- Python函数不仅可以返回int、str、list、dict等数据类型,还可以返回函数;
- 区分返回函数和返回值;
def myabs():
return abs # 返回函数
def myabs2(x):
return abs(x) # 返回函数调用的结果,返回值是一个数值
- demo:调用A时,返回函数a,调用a时,返回值;
def A(n):
def a():
return n*n
return a
>>> A(2)
<function A.<locals>.a at 0x1085fcea0>
>>> A(2)()
4
- 返回函数可以把一些计算延迟执行,因为调用时返回的是内层的函数,如demo中,调用A(2)时返回的是函数 a ;
- 返回函数时,切记:该函数并未执行、返回函数中不能引用任何可能会变化的变量。
1-9 Python之闭包
- 闭包:内层函数引用了外层函数的局部变量或参数,然后返回内层函数的情况;
- 闭包特点:确保引用的局部变量在函数返回后不能变。
1-10 Python之匿名函数
- 匿名函数用
lambda
表示,后面紧跟变量+冒号(:)
,冒号后是表达式。使用匿名函数可以不必定义函数名,直接创建一个函数对象,简化代码;
- 匿名函数有个限制,只能有一个表达式,不写return,返回值即为表达式的结果;
- 示例:
reduce(lambda x,y:x*y,[1,2,3,4,5,6,7,8,9]) = 362880
f = lambda x, y: x*y
f(3,4) = 12
def build(x,y):
return lambda x,y:x*y
1-11 Python之装饰器
- 应用场景:定义了一个函数,想在运行时动态增加功能,但又不想改动函数本身的代码时,借助装饰器可以实现;
- 装饰器
(decorator)
本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新函数;
- Python内置的
@
语法可以简化装饰器的调用,@+函数名
表示装饰器;
- 装饰器的特点:极大地简化代码,避免每个函数编写重复性代码;
- 示例:传入一个函数func,返回一个函数wrapper,wrapper函数就是装修后的函数func。
def log(func):
def wrapper():
print('call %s():' % func.__name__)
return func()
return wrapper
1-12 Python之无参数装饰器
# 无参数装饰器,即装饰器自身不带参数
import time #导入包
def performance(f): #定义一个装饰器,本例将函数名factorial传入给参数 f
def wrapper(*args,**kw): #定义装饰器返回的函数wrapper,wrapper为装饰过的函数factorial,其参数(*args,**kw)为函数factorial的参数n,本例中的设置表示可以接受任意类型和个数的参数
t1 = time.time() #记录开始时间,并赋值给t1
r = f(*args,**kw) # f 即为原函数factorial,即f(*args,**kw)执行的是factorial(n),本例中为:factorial(10),最后赋值给 r
t2 = time.time() #记录结束时间,并赋值给t2
print ('call %s() in %fs' % (f.__name__, (t1 - t2))) 打印 f 函数的名字,2个时间戳的差值为浮点型数值,用%fs匹配
return r #返回 r 即为返回factorial(n)
return wrapper #装饰器每次返回新函数,即为装饰过的函数factorial
@performance #填加装饰器performance,且未带参数
def factorial(n): #定义的函数
return reduce(lambda x,y: x*y, range(1, n+1)) #函数返回的表达式
print (factorial(10)) #打印函数,参数为10
call factorial() in -0.003976s
3628800
1-13 Python之带参数装饰器
# 装饰器本身带参数,首先需要定义一个接受参数的decorator函数,即比正常的装饰器多一层
import time #导入包
def performance(unit): #首先定义一个接受装饰器自身参数的函数
def perf_decorator(f):
def wrapper(*args,**kw):
t1 = time.time()
r = f(*args,**kw)
t2 = time.time()
t = (t2 - t1)*1000 if unit == 'ms' else (t2- t1)
print ('call %s() in %f%s' % (f.__name__,t,unit))
return r
return wrapper
return perf_decorator
@performance('ms') #填加装饰器performance,带参数,传入一个字符串'ms'
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print (factorial(10))
call factorial() in 5.087137ms
3628800
1-14 Python之完善装饰器decorator
- 装饰器
@decorator
可以动态实现函数功能的增加,但经过@decorator装饰过的函数,与原函数会有些不同;
- 以1-13的例子为例,函数factorial经过@decorator后,返回的是新函数wrapper,而不再是factorial,这样会改变函数factorial的name/doc等属性,若使调用者看不出一个函数经过了@decorator装饰,需要把原函数的一些属性复制到新函数中,如下:
def log(f):
def wrapper(*args, **kw):
print ('call...')
return f(*args, **kw)
wrapper.__name__ = f.__name__
wrapper.__doc__ = f.__doc__
return wrapper
- 这样写decorator会很麻烦,而且很难把原有函数的所有必要属性都一个一个复制到新函数上,Python内置了
functools()
,通过functools.wraps
可以自动化完成这个 '复制' 任务;
def log(f):
@functools.wraps(*args,**kw)
def wrapper(*args, **kw):
print ('call...')
return f(*args, **kw)
return wrapper
- 使用
functools()
时,需要明确:由于我们把原函数的参数改为了(*args,**kw)
,因此无法获取原函数的原始参数信息,即便我们采用固定参数来装饰只有一个参数的函数,也可能改变原函数的参数名,因为新函数wrapper的参数名始终是 'x',原函数定义的参数名不一定叫 'x'。
def log(f):
@functools.wraps(f)
def wrapper(x):
print ('call...')
return f(x)
return wrapper
1-15 Python之偏函数
- 当一个函数有很多参数时,调用者需要提供多个参数,若能减少参数个数,就可以简化调用者的负担;
- Python内置了
functools.partial
可以创建偏函数,即把一个参数多的函数变成一个参数少的新函数,少的参数在创建时指定默认值,这样新函数调用的难度就降低了,如下:
int2 = functools.partial(int, base=2) # 创建一个默认进制数为二进制的int函数;
第2章 模块
2-1 Python之模块
- 在Python中,一个
.py
文件就是一个模块(module),可以实现特定的功能;
- 模块特点:提高代码的可维护性;编写代码不必从零开始;复用性强,可以被其它地方调用;
- 使用模块可以避免函数名和变量名冲突,相同名字的函数和变量可以放在不同的模块中;
- 包:按目录的方式来组织模块的方法。可以避免模块冲突,相当于创建了一个顶层包名,只要包名不冲突,模块就不会冲突;
- 每个包目录下必须有一个
__init__.py
文件,否则Python会将该目录视为普通目录,而不是包。init.py可以是空的,也可以有代码,它本身就是一个模块。
2-2 Python之导入模块
- 使用一个模块,必须先导入该模块,导入方式有2种
import package_name
from package import function_name1, function_name2
-
方式一:
import package_name
,可以访问package_name中定义的所有公开函数、类和变量,通过package_name.function_name的方式调用,不会存在冲突。缺点:导入了package_name中所有的function,可能只会用到其中某几个;
-
方式二:
from package import function_name1,function_name2
,仅将自己需要的function导入,可以直接调用function_name1或function_name2。缺点:直接使用函数名可能会存在函数名冲突,可以在导入时给函数起别名,如 from package import function_name1 as func。
2-3 Python之动态导入模块
- 若导入的模块不存在,Python解释器会报错
ImportError
;
- 有时候两个不同的模块(或版本不同的两个模块),如StringIO和cStringIO都提供了StringIO这个功能,但StringIO是由纯Python代码编写的,而cStringIO的部分函数是用C语言编写的,运行速度会更快,利用’ImportError’可以动态地导入模块。
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
2-4 Python之使用future
- 当Python新版本中增加了新特性时,会将该特性添加到Python旧版本的future模块中,以便旧代码在旧版本中测试新特性;
- 例子:Python2.x版本中,整数除法运算的结果仍为整数,但Python3.x版本中,整数除法的运算结果可以是浮点型数据,在Python2.x中导入3.x的新特性。
from __future__ import division
print (10 / 3)
3.3333333333333335
2-5 Python之导入第三方模块
- Python提供了2种管理第三方模块的工具:easy_install 和 pip,后者是python官方推荐的管理工具,已经内置到了python中;
- 语句:
pip install package_name
可以通过pipy.python.org网站来根据关键字进行查找三方模块的名字。
2-6 Python之模块作用域
- 模块中会定义很多函数和变量,有些我们希望给别人调用,有些我们希望只在模块内部使用,Python中通过通过 _ 前缀来实现;
- 正常的函数和变量都是公开的(public),可以直接调用;
- 类似
\_\_xxx__
这样的变量是特殊变量,可以直接调用,但有特殊用途;
- 类似
\_xxx
和\__xxx
这样的变量或函数是非公开的(private),不应该直接被引用。不应该被直接引用而不是不能被直接引用,因为Python并没有一种方法可以完全限制访问private函数,只是从编程习惯上不应该引用private函数或变量;
- 外部需要调用的函数定义成public的,外部不需要调用的函数定义成private的。调用public函数而不必关心内部private函数的细节,这是一种非常有用的代码封装和抽象的方法。
第3章 面向对象编程基础
3-1 Python之面向对象编程
- 面向对象编程是一种程序设计范式,把程序看做不同对象的相互调用,是对现实世界建立的对象模型;
- 面向对象编程的基本思想:类和实例,类用于定义抽象类型,实例是根据类的定义创建的;
- 在Python中,用
Class
定义类,用类名和函数调用创建实例;
3-2 Python之创建类和实例
- Python中,用
Class
关键字来定义类,按编程习惯一般将类名首字母大写,紧接着是(object): 表示该类是从哪个类继承下来的;
- 创建实例,使用
类名()
类似函数调用的形式创建;
- 创建一个类Person和2个实例
class Person(object):
pass
xiaoming = Person()
xiaohong = Person()
3-3 Python创建实例属性
- 由于Python是动态语言,对每一个实例,都可以直接给他们的属性赋值,如:给xiaoming这个实例加上name/gender/birth属性;
xiaoming = Person()
xiaoming.name = ‘Xiao Ming’
xiaoming.gender = ‘Male’
xiaoming.birth = ‘1989-10-01’
- 同一个类的不同实例,可以添加不同的属性,如:给xiaohong加上name/school/grade属性;
xiaohong = Person()
xiaohong.name = ‘Xiao Hong’
xiaohong.school = ’No.1 High School’
xiaohong.grade = 2
- 除了上述的方法,还可以通过
setattr(对象,属性,属性的值)
对实例赋值,如:setatrr(xiaoming,gender,male)
,相当于xiaoming.gender = ‘Male’
;
- 实例的属性可以像普通的变量一样进行运算操作:
xiaoming.age = xiaohong.age + 1
。
3-4 Python之初始化实例属性
- 类起到了模版的作用,在定义类的时候,可以通过特殊方法
__init__
把一些必须的实例属性直接加上,即初始化实例属性;
-
__init__
方法的第一个参数永远是self
,表示创建的实例本身,在其内部,可以把各种属性绑定到self,但是在创建实例时,不能传入空参数,必须传入与__init__
方法匹配的参数,除了self本身,Python解释器会自动把实例变量传进去;
class Person(object):
def __init__(self,name,gender,age)
self.name = name
self.gender = gender
self.age = age
xiaoming = Person(‘xiaoming’,‘Male’,18)
- 和普通函数相比,类中定义的函数的不同点:第一个参数永远是实例变量自身self,且调用时不必传入参数。
3-5 Python之实例属性的访问限制
- Python对属性权限的控制是通过属性名来实现的,不希望被外部访问的属性,在属性名前添加双下划线
(__)
即可,通常单下划线开头在子类中使用,双下划线开头不能在子类中使用。
class Person(object):
def __init__(self,name):
self.name = name
sefl.__age = age
p = Person(‘Bob’)
print (p.name)
-- > Bob
print (p.__age)
Error
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute ‘__age'
3-6 Python之创建类属性
- 类是模版,而实例则是根据类创建的对象,绑定在一个实例上的属性不会影响其它实例的属性;
- 类本身也是对象,在类上绑定一个属性,则该类的所有实例都可以访问类属性。每个实例各自拥有相互独立的属性,而类属性有且共有一份;
- 在定义类时可以定义类属性,因为类属性是直接绑定在类上的,所以访问类属性不需要创建实例,可以直接访问;
class Person(object):
address = ‘Earth’
def __init__(self,name)
self.name = name
print Person.address
-- > 'Earth'
- Python是动态语言,类属性也可以动态地添加或修改,因为类属性只有一份,当类属性发生变化时所有实例访问的类属性也发生了变化。
3-7 Python之类和实例的属性名冲突
- 修改类属性会导致所有实例访问到的类属性发生改变,若在实例中直接对类属性进行赋值,则该实例会增加一个和类属性同名的属性,但是类属性不会发生变化,当实例属性与类属性同名时,实例属性优先级高,会屏蔽掉该实例对类属性的访问;
- 尽量不要在实例中修改类属性,因为它实际上并没有修改类属性,而只是给该实例绑定了一个实例属性。
3-8 Python之定义实例方法
- 在类的内部,除了可以定义实例的属性,还可以定义实例的方法(函数);
- 类中定义的实例方法就是定义的函数,其第一个参数永远是self,指向调用该方法的实例本身,其它的参数和普通函数的参数一样。带下划线的
__init__
可以看作是一个特殊的实例方法;
- 实例方法必须在实例上调用,在实例方法内部,可以访问所有的实例属性,若外部需要访问私有属性,可以通过方法调用获得,这种数据封装的形式,除了能保护内部数据一致性外,还可以简化外部调用的难度。
3-9 Python之类中方法也是属性
- 在类中定义的实例方法是一个绑定在实例上的函数对象,只能在实例上调用,其实也是实例的属性,可以动态地为实例添加;
- 借助
types.MethodType()
可以将外部定义的函数变为类中的实例方法,语法:types.MethonTypes(外部定义的函数名,实例名,类名)
;
import types
#外部定义的函数
def fn_get_grade(self):
if self.score >= 80:
return ‘A’
if self.score >= 60:
return ‘B’
else:
return ‘C’
class Person(object):
def __init__(self,name,score):
self.name = name
self.score = score
p1 = Person(‘Bob’,90)
p1.get_grade() = types.Method.Types(fn_get_grade,p1,Person)
print (p1.get_grade())
-- > A
- 给一个实例动态地添加方法并不常见,直接在class中定义更直观。
3-10 Python之类方法
- 和属性类似,方法也分实例方法和类方法,在Class中定义的全部是实例方法,实例方法的第一个参数是self,即实例变量自身;
- 通过标记一个@classmethod将定义的方法绑定到类上,而非实例方法,类方法的第一个参数将传入类本身,通常将参数名命名为
cls
;
- 因为是在类上调用,类方法无法获得任何实例变量,只能获得类的引用。
class Person(object):
count = 0
@classmethod
def how_many(cls):
return cls.count #cls.count相当于Person.count
def __init__(self, name):
self.name = name
Person.count = Person.count + 1
print (Person.how_many())
-- > 0
p1 = Person('Bob')
print Person.how_many()
-- > 1
第4章 类的继承
4-1 Python之什么是继承
- 定义一个新类时,不必从头编写,可以从现有的类中继承,获得现有类的所有功能,新类只需要编写现有类缺少的功能;
- 被继承的类称为:父类、基类、超类;新建的类称为:子类、派生类、继承类;
- 继承的优点:1)复用现有的代码;2)自动拥有了现有类的所有功能;3)只需要编写缺少的新功能;
- 继承可以一级一级地继承下去,而任何类,最终都可以追溯到根类object,这些继承关系看上去像一颗倒着的树。
4-2 Python之继承一个类
- 继承的特点:1)总是从某个类继承,若没有合适的类,默认从object类继承;2)调用
super().__init__
来初始化父类的功能,否则父类的属性可能没有被正确调用;
# 定义一个类Person
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
# 从上述类继承一个子类Student,增加新属性score
class Student(Person):
def __init__(self, name, gender, score)
super(Student,self).__init__(name, gender)
self.score = score
-
super(Student, self)
返回当前类继承的父类,即Person类,然后调用__init__
方法,初始化Person类,否则子类Student会没有属性name和gender。注意
:self已在super()中传入,在__init__
中将隐形传递,不需要写出来,也不能写出来。
4-3 Python之判断类型
-
isinstance()
:判断一个变量的类型,如Python内置的数据类型str/int/list/dict等,也可以判断我们定义的类,它们本质上都是数据对象;
-
isinstance(实例,类)
:判断指定的实例是否属于指定的类;
- 在继承链上,一个父类的实例不能是子类类型,因为子类比父类多了一些属性和方法;一个子类的实例既可以看成本身的类型,也可以看成父类的类型。
4-4 Python之多态
- 类具有继承关系,子类类型可以向上看作是父类类型;
- 多态的几层意思:
- 父类的方法(如
run()
),只要是父类或其子类,就会自动调用该方法,运行时作用于传入的对象(该父类或其子类);
- 子类和父类拥有相同的方法(如
run()
)时,我们认为子类的方法run()覆盖了父类的方法run(),运行代码时总会调用子类的run();
- 子类调用方法(如
run()
)时,总是先查找该子类自身的定义,若没有定义方法(如run()),则顺着继承链向上查找,直到在某个父类中找到该方法。
- 开闭原则:动态语言调用实例方法,不检查类型,只要方法存在,参数正确,就可以调用
- 对扩展开放:允许增加新子类
- 对修改封闭:不需要修改父类的方法
- 有了继承,才有了多态,在调用类实例方法时,尽量把变量当作父类,以便所有子类类型都可以正常接收。
4-5 Python之多重继承
- 除了从一个父类继承外, Python允许从多个父类继承,即称为多重继承;
- 多重继承的目的:从两种继承树中分别选择并继承出子类,以便组合功能使用;
-
多重继承的继承链不再是一棵继承树了,如图,D同时继承了B和C,D便拥有了A、B、C的全部功能;
- 多重继承通过
super().__init__()
方法调用时,A虽然被继承了2次,但__init__
只调用一次。
4-6 Python之获取对象信息的函数
-
isinstance()
:判断一个变量是哪种实例类型;
-
type()
:获取变量的类型,返回一个Type对象;
-
dir()
:获取变量的所有属性,包括带下划线的特殊属性;
-
setattr()
:为变量设置新的属性,如:setattr(s, ’name’, ‘Bob’)
表示为变量s的属性name设置值为Bob;
-
getattr()
:获取变量指定属性的值,属性不存在时会报错,如:getattr(s, ’name’) = Bob
表示获取变量s的属性name的值;
-
hasattr()
:判断变量是否有指定的属性,如:hasattr(s, ’name’) = Ture
。
第5章 定制类
5-1 Python之什么是特殊方法
- 特殊方法在Python中,又称为
魔术方法
,是定义在类中的,不需要直接调用,Python的某些函数或操作符会调用对应的特殊方法;
- 使用时,我们只需要编写用到的特殊方法,若有关联性的特殊方法也都需要编写。
5-2 Python之str和repr
- 定义一个类
class Student(object):
def __init__.(self, name, grade)
self.name = name
self.grade = grade
- 创建一个实例,print(s1)或直接输入变量s1,结果返回的均是实例的地址
s1 = Student(‘Bob’,16)
print (s1)
<__main__.Student object at 0x7f7a151ff450>
- 定义类时,调用
__str__
特殊方法
class Student(object):
def __init__.(self, name, grade)
self.name = name
self.grade = grade
def __str__.(self):
return ‘(Student: %s, %s)’ % (self.name, self.grade)
- 创建一个实例,print (s2)时返回的是实例;直接输入变量s2时返回的是实例的地址。两者不同的原因:print变量,调用的是
__str__()
,返回用户看到的字符,用于显示给用户;直接输入变量调用的是__repr__()
,返回程序开发者看到的字符串,用于调试服务;
s2 = Student(‘Bob’, 17)
print (s2)
-- > (Student:Bob, 17)
s2
<__main__.Student object at 0x7f7a151ff450>
- 上述4中的问题,有一个偷懒的做法是再定义一个repr(),并赋值给str()即可。
class Student(object):
def __init__.(self, name, grade)
self.name = name
self.grade = grade
def __str__.(self):
return ‘(Student: %s, %s)’ % (self.name, self.grade)
__repr__ = __str__
5-3 Python之特殊方法cmp
- 使用特殊方法
__cmp__
对一组类的实例进行排序,__cmp__
用实例自身self和传入的实例进行比较,若self应排在前面则返回-1;若self应排在后面则返回1;若两者相等则返回0;
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __str__(self):
return ‘(Person: %s, %s)’ % (self.name, self.gender)
__repr__ = __str__
def __cmp__(self, s):
if self.name < s.name:
return -1
if self.name > s.name
return 1
else:
return 0
L = [Person(‘Bob’,’male’), Person(‘Xz’,’female’),Person(’ShiMei’,’male’)]
print (sorted(L))
-- > [(Bob:male),(Xz:female),(ShiMei:male)]
- 示例:使用特殊方法将Student类中实例,按分数从高到低排序,分数相同的按名字排序。
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def __str__(self):
return '(%s: %s)' % (self.name, self.score)
__repr__ = __str__
def __cmp__(self, s):
if self.score == s.score:
return cmp(self.name,s.name)
return -cmp(self.score,s.score)
L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]
print (sorted(L))
-- > [(Alice: 99), (Tim: 99), (Bob: 88)]
5-4 Python之特殊方法len
- 如果一个类表现得像list,要获得有多少个元素,需要用len函数,类必须提供一个特殊方法len()返回类的元素个数;
- 示例:编写一个Fib类,Fib(10)表示斐波那契数列前10个元素,求元素个数。
class Fib(object): #定义类Fib
def __init__(self, num): #为实例添加了属性:num,但在内部并没有进行self绑定
a, b, L = 0, 1, []
for n in range(num): #变量num只是起到了遍历次数的作用,是从外部传入的参数
L.append(a)
a, b = b, a + b #遍历后总是将前2项相加,作为第三项
self.numbers = L #将遍历的a值添加到列表L后,绑定到实例的属性
def __str__(self): #通过特殊方法返回实例的结果,而非对象
return str(self.numbers) #通过str函数将结果转换成字符串
_repr__ = __str__ #将特殊方法__str__赋值给__repr__,便于非 print情况下,直接能返回实例结果
def __len__(self): #通过特殊方法返回元素个数
return len(self.numbers)
f = Fib(10)
print (f)
-- > [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
print len(f)
-- > 10
5-5 Python之数学运算
- Python的四则运算不仅局限于int、float等数据类型,还可以是有理数、矩阵等;
- 定义一个Rational类表示有理数,p、q都是整数,用 p / q表示有理数
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
def __str__(self):
return ‘%s / %s’ % self.p / self.q
- 加减乘除四则运算的特殊方法:
__add__ / __sub__ / __mul__ / __div__
;
- 示范:运算结果是
6/8
,显示的时候需要归约到最简形式3/4
def gcd(a, b):
if b == 0:
return a
return gcd(b, a % b)
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
def __add__(self, r):
return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
def __sub__(self, r):
return Rational(self.p * r.q - self.q * r.p, self.q * r.q)
def __mul__(self, r):
return Rational(self.p * r.p, self.q * r.q)
def __div__(self, r):
return Rational(self.p * r.q, self.q * r.p)
def __str__(self):
g = gcd(self.p, self.q)
return '%s/%s' % (self.p / g, self.q / g)
__repr__ = __str__
r1 = Rational(1, 2)
r2 = Rational(1, 4)
print (r1 + r2)
-- > 3/4
print (r1 - r2)
-- > 1/4
print (r1 * r2)
-- > 1/8
print (r1 / r2)
-- > 2/1
5-6 Python之数据类型转换
- 在类中进行数据类型的转换,需要调用对应的特殊方法,如
__int__ / __float__
等。
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
def __int__(self):
return self.p // self.q
def __float__(self):
return float(self.p) / self.q
print (int(Rational(7, 2)))
-- > 3
print (float(Rational(10, 3)))
-- > 3.33333333333
5-7 Python之@property
- 在绑定属性时,直接给属性赋值无法检查所赋值的有效性,Python内置的@property装饰器可以把一个方法变为属性调用;
- 示例:属性score进行了set和get定义,是可读写属性;属性grade进行了set定义,是只读属性。
class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
@property #将定义的方法fenshu装饰成属性调用
def fenshu(self): #定义方法fenshu,返回属性score结果,相当于get()函数
return self.__score
@fenshu.setter #是@property装饰后的副产品,将定义的方法score装饰成属性调用
def score(self, score): #定义方法score进行属性参数判断,相当于set()函数
if score < 0 or score > 100:
raise ValueError('invalid score')
self.__score = score
@property #将定义的方法grade装饰成属性调用
def grade(self): #定义方法grade进行属性参数判断,相当于set()函数
if self.score < 60:
return 'C'
if self.score < 80:
return 'B'
return 'A'
s = Student('Bob', 59)
print (s.grade)
-- > C
s.score = 60
print (s.grade)
-- > B
s.score = 99
print (s.grade)
-- > A
5-8 Python之特殊方法slots
- Python是动态语言,任何实例在运行期间都可以动态地增加属性,通过特殊方法
__slots__
可以指定实例的属性,限制增加新的属性,本质上就是一个类允许的属性列表;
- 使用特殊方法
__slots__
时,可以节省内存,但该方法仅对当前类起作用,对继承的子类不起作用,除非子类也使用了__slots__
,这样,子类允许定义的属性就是自身的__slots__
加上父类的__slots__
。
5-9 Python之特殊方法call
- 在Python中,所有的函数都是可调用对象,通过特殊方法
__call__
可以将一个类实例变成可调用对象;
-
示例一:在特殊方法
__call__
中,friend
是传参
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __call__(self, friend):
print 'My name is %s...' % self.name
print 'My friend is %s...' % friend
p = Person('Bob','male')
p('Tim’)
-- > My name is Bob...
-- > My friend is Tim...
-
示例二:改进6-4中斐波那契数列
class Fib(object):
def __call__(self, num):
a, b, L = 0, 1, []
for n in range(num):
L.append(a)
a, b = b, a + b
return L
f = Fib()
print (f(10))
-- > [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
5-10 Python之特殊方法iter
- 如果需要对一个类进行for循环遍历,类似list/tuple,需要使用特殊方法
__iter__
,该方法返回一个迭代对象,Python的for循环会不断调用该迭代对象的next()方法,返回循环的下一个值;
- 示例:以斐波那契数列为例,写一个Fib类,可以作用于for循环。
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 > 200: #退出循环的条件
raise StopIteration();
return self.a #返回下一个值
for n in Fib(): #进行for循环遍历
print (n)
1
1
2
3
5
8
13
21
34
55
89
144
5-11 Python之类切片
- 通过特殊方法
__iter__
可以对类进行遍历,类似list/tuple,但要想进行切片操作获取数据,需要使用特殊方法__getitem__
实现,通常含2个参数,第一个是实例本身self,第二个传入的是一个int值表示指定的索引位置或者一个切片对象slice表示索引列表,因此,需要对第二参数进行if判断;
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int):
a, b = 0, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice):
start = n.start
stop = n.stop
a, b = 0, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
f = Fib()[5]
print (f)
-- > 5
f = Fib()[0:5]
print (f)
-- > [0, 1, 1, 2, 3]
- 上述没有对切片slice为负数情况和步长step进行处理,要实现一个正确的
__getitem__
还需要做很多处理。类似的,还有特殊方法__setitem__
把对象视为list或dict来对集合赋值、特殊方法__delitem__
用于删除某个元素。总之,我们定义的类,表现得和Python自带的list/tuple/dict等没什么区别,这完全归功于动态语言的鸭子类型
,不需要强制继承某个接口。