本文是近期学习中零散的一些知识点,主要是面向对象,记于此。
下面是快捷方式(点击直达)
导入模块
、导入模块
、面向对象文法
、属性优先级
、类方法
、类的继承
、面相对象比较
、不定参数的传递
、关于Python版本和函数使用
、
导入模块
1 | # 尝试导入较老版本模块 |
面向对象文法
1 | class Person(object): |
这会导致创建失败或运行不正常,因为第一个参数name被Python解释器传入了实例的引用,从而导致整个方法的调用参数位置全部没有对上。
正确写法:1
2
3
4class Person(object):
# 第一个参数一定要是self!
def __init__(self, name, gender, birth):
pass
第一个参数一定要是
self
!
属性优先级
当实例属性和类属性重名时,实例属性优先级高,它将屏蔽掉对类属性的访问。
例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Person(object):
address = 'Earth'
def __init__(self, name):
self.name = name
p1 = Person('Bob')
p2 = Person('Alice')
print('Person.address = ' + Person.address)
p1.address = 'China'
print('p1.address = ' + p1.address)
print('Person.address = ' + Person.address)
print('p2.address = ' + p2.address)
Person.address = Earth
p1.address = China
Person.address = Earth
p2.address = Earth
我们发现,在设置了 p1.address = ‘China’ 后,p1访问 address 确实变成了 ‘China’,但是,Person.address和p2.address仍然是’Earch’,怎么回事?
原因是 p1.address = ‘China’并 没有改变 Person 的 address ,而是给 p1这个实例绑定了
实例属性
address ,对p1来说,它有一个实例属性address(值是’China’),而它所属的类Person也有一个类属性
address,所以:
- 访问 p1.address 时,优先查找实例属性,返回’China’。
- 访问 p2.address 时,p2没有实例属性address,但是有类属性address,因此返回’Earth’。
类方法
@classmethod
装饰器
1 | class Person(object): |
类的继承
函数 super(子类, self)
将返回当前类继承的父类,然后调用init()方法,注意self参数已在super()中传入,在init()中将隐式传递,不需要写出(也不能写)。
例如:
1 | class Person(object): |
一定要用 super(Student, self).init(name, gender) 去初始化父类,否则,继承自 Person 的 Student 将没有 name 和 gender。
面相对象比较
使用魔术方法,这里有一种巧妙的表达:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class 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 False == isinstance(s, Student):
return -1
return -cmp(self.score, s.score) or cmp(self.name, s.name)
L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]
print(sorted(L))
需要特别注意的是,python 3.x 中不再支持 cmp() 函数,取而代之使用:
lt() for sorting, eq() with hash(), and other rich comparisons as needed. (If you really need the cmp() functionality, you could use the expression (a > b) - (a < b) as the equivalent for cmp(a, b).
不定参数的传递
对于Person类的定义:1
2
3
4class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
希望除了 name和gender 外,可以提供任意额外的关键字参数,并绑定到实例,请修改 Person 的 init() 定 义,完成该功能。
例程:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Person(object):
def __init__(self, name, gender, **kw):
self.name = name
self.gender = gender
# Python 2.7 中 iteritems 独立封装,返回一个迭代器;但是3.*版本整合进了字典内建函数 items。
for k, v in kw.items():
setattr(self, k, v)
p = Person('Bob', 'Male', age=18, course='Python')
print(p.age)
print(p.course)
# Python 2.7 中 filter 对象可以直接输出,但是在3.*版本需要强制转化成 list 对象,否则输出 <filter object at ...>
print(list(filter(lambda x: x[0] != '_', dir(p))))
**kw
以字典的形式接受参数表*args
以元组的方式接受参数表- 关于Python版本和函数使用(
链接
)
魔术方法
__repr__
:调试时输出的字符串__str__
:用户使用时输出的字符串__cmp__
:sorted等函数调用时返回结果,可以配合 cmp()
函数等使用__len__
:(略)__add__
、__sub__
、__mul__
、__div__
__slots__
由于Python是动态语言,任何实例在运行期都可以动态地添加属性。
如果要限制添加的属性,例如,Student类只允许添加 name、gender和score 这3个属性,就可以利用Python的一个特殊的 __slots__
来实现。
顾名思义,__slots__
是指一个类允许的属性列表:1
2
3
4
5
6class Student(object):
__slots__ = ('name', 'gender', 'score')
def __init__(self, name, gender, score):
self.name = name
self.gender = gender
self.score = score
现在,对实例进行操作:1
2
3
4
5
6
7'Bob', 'male', 59) > s = Student(
'Tim' # OK > s.name =
99 # OK > s.score =
'A' > s.grade =
Traceback (most recent call last):
...
AttributeError: 'Student' object has no attribute 'grade'
__slots__
的目的是限制当前类所能拥有的属性,如果不需要添加任意动态的属性,使用slots也能节省内存。
在继承中使用slot
假设Person类通过slots定义了name和gender,请在派生类Student中通过slots继续添加score的定义,使Student类可以实现name、gender和score 3个属性。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Person(object):
__slots__ = ('name', 'gender')
def __init__(self, name, gender):
self.name = name
self.gender = gender
class Student(Person):
__slots__ = ('score',)
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
s = Student('Bob', 'male', 59)
s.name = 'Tim'
s.score = 99
print(s.score)
call
在Python中,函数其实是一个对象:1
2
3
4
5 > f = abs
_ > f.__name_
'abs'
123) > f(-
123
由于 f 可以被调用,所以,f 被称为可调用对象。
所有的函数都是可调用对象。
一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法call()。
我们把 Person 类变成一个可调用对象:1
2
3
4
5
6
7
8class 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)
现在可以对 Person 实例直接调用:1
2
3
4'Bob', 'male') > p = Person(
'Tim') > p(
My name is Bob...
My friend is Tim...
单看 p(‘Tim’) 你无法确定 p 是一个函数还是一个类实例,所以,在Python中,函数也是对象,对象和函数的区别并不显著。
例程(打印斐波那契数列):1
2
3
4
5
6
7
8
9
10
11class Fib(object):
def __call__(self,num):
L = [0,1]
i = 2
while i < num:
L.append(L[i-2]+L[i-1])
i=i+1
return L
f = Fib()
print f(10)
get()/set() 及 @property 装饰器
考察 Student 类:1
2
3
4class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
当我们想要修改一个 Student 的 scroe 属性时,可以这么写:1
2s = Student('Bob', 59)
s.score = 60
但是也可以这么写:1
s.score = 1000
显然,直接给属性赋值无法检查分数的有效性。
如果利用两个方法:1
2
3
4
5
6
7
8
9
10class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
def get_score(self):
return self.__score
def set_score(self, score):
if score < 0 or score > 100:
raise ValueError('invalid score')
self.__score = score
这样一来,s.set_score(1000) 就会报错。
这种使用 get/set 方法来封装对一个属性的访问在许多面向对象编程的语言中都很常见。
但是写 s.get_score() 和 s.set_score() 没有直接写 s.score 来得直接。
有没有两全其美的方法?——有。
因为Python支持高阶函数,在函数式编程中我们介绍了装饰器函数,可以用装饰器函数把 get/set 方法“装饰”成属性调用:1
2
3
4
5
6
7
8
9
10
11
12class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
@property
def score(self):
return self.__score
@score.setter
def score(self, score):
if score < 0 or score > 100:
raise ValueError('invalid score')
self.__score = score
注意: 第一个score(self)是get方法,用@property装饰,第二个score(self, score)是set方法,用@score.setter装饰,@score.setter是前一个@property装饰后的副产品。
现在,就可以像使用属性一样设置score了:1
2
3
4
5
6
7
8>> s = Student('Bob', 59)
>> s.score = 60
>> print s.score
60
>> s.score = 1000
Traceback (most recent call last):
...
ValueError: invalid score
说明对 score 赋值实际调用的是 set方法。
关于Python版本和函数使用
map、filter 和 reduce
这三个函数号称是函数式编程的代表。在 Python3.x 和 Python2.x 中也有了很大的差异。
首先我们先简单的在 Python2.x 的交互下输入 map 和 filter,看到它们两者的类型是 built-in function
(内置函数):
1 | >>> map |
它们输出的结果类型都是列表:
1 | >>> map(lambda x:x *2, [1,2,3]) |
但是在Python 3.x中它们却不是这个样子了:
1 | > map |
首先它们从函数变成了类,其次,它们的返回结果也从当初的列表成了一个可迭代的对象, 我们尝试用 next 函数来进行手工迭代:
1 | >>> f =filter(lambda x:x %2 ==0, range(10)) |
对于比较高端的 reduce 函数,它在 Python 3.x 中已经不属于 built-in 了,被挪到 functools 模块当中。
另外,关于更多 Python 2.x 和 Python 3.x 中的不同,可以参看
这里
,希望有时间我可以仔细翻译一下。