目录
7. >>> print Foo('ethan') # 使用 print
1 概述
Python 是一门面向对象编程( Object Oriented Programming, OOP )的语言,这里的对象可以。看做是由数据(或者说特性)以及一系列可以存取、操作这些数据的方法所组成的集合。面向对象编程。主要有以下特点:多态( Polymorphism ):不同类( Class )的对象对同一消息会做出不同的响应。封装( Encapsulation ):对外部世界隐藏对象的工作细节。继承( Inheritance ):以已有的类(父类)为基础建立专门的类对象。在 Python 中,元组、列表和字典等数据类型是对象,函数也是对象。那么,我们能创建自己的对象吗?答案是肯定的。跟其他 OOP 语言类似,我们使用类来自定义对象。本章主要介绍以下几个方面:
2 类和实例
2.1 基本概念
类是一个抽象的概念,我们可以把它理解为具有相同属性和方法的一组对象的集合,而实例则是一个具体的对象。类和对象的关系就如同模具和用这个模具制作出的物品之间的关系。一个类为它的全部对象给出了一个统一的定义,而他的每个对象则是符合这种定义的一个实体,因此类和对象的关系就是抽象和具体的关系。
我们还是先来看看在 Python
中怎么定义一个类。
这里以动物(
Animal
)类为例,
Python
提供关键字
class
来声明一个类:
1. class Animal ( object ):2. pass
其中,
Animal
是类名,通常类名的首字母采用大写(如果有多个单词,则每个单词的首字母大
写),后面紧跟着
(object)
,表示该类是从哪个类继承而来的,所有类最终都会继承自
object
类。
类定义好了,接下来我们就可以创建实例了:
1. >>> animal = Animal () # 创建一个实例对象2. >>> animal3. < __main__ . Animal at 0x1030a44d0 >
我们在创建实例的时候,还可以传入一些参数,以初始化对象的属性,为此,我们需要添加一个
__init__
方法:
1. class Animal ( object ):2. def __init__ ( self , name ):3. self . name = name
然后,在创建实例的时候,传入参数:
1. >>> animal = Aniaml ( 'dog1' ) # 传入参数 'dog1'2. >>> animal . name # 访问对象的 name 属性3. 'dog1'
我们可以把
__init__
理解为对象的初始化方法,它的第一个参数永远是
self
,指向创建的
实例本身。定义了
__init__
方法,我们在创建实例的时候,就需要传入与
__init__
方法匹
配的参数。
接下来,我们再来添加一个方法:
1. class Animal ( object ):2. def __init__ ( self , name ):3. self . name = name4. def greet ( self ):5. print 'Hello, I am %s.' % self . name
我们添加了方法
greet
,看看下面的使用:
1. >>> dog1 = Animal ( 'dog1' )2. >>> dog1 . name3. 'dog1'4. >>> dog1 . greet ()5. Hello , I am dog1 .
现在,让我们做一下总结
。我们在
Animal
类定义了两个方法:
__init__
和
greet
。
__init__
是
Python
中的特殊方法(
special method
),它用于对对象进行初始化,类似于 C++
中的构造函数;
greet
是我们自定义的方法。
注意到
,我们在上面定义的两个方法有一个共同点,就是它们的第一个参数都是
self
,指向实例
本身,也就是说它们是和实例绑定的函数,
这也是我们称它们为方法而不是函数的原因
。
2.2 访问限制
在某些情况下,我们希望限制用户访问对象的属性或方法,也就是希望它是私有的,对外隐蔽。比如,对于上面的例子,我们希望
name
属性在外部不能被访问,我们可以在属性或方法的名称前面加上两个下划线,即
__
,对上面的例子做一点改动:
1. class Animal ( object ):2. def __init__ ( self , name ):3. self . __name = name4. def greet ( self ):5.print 'Hello, I am %s.' % self . __name1. >>> dog1 = Animal ( 'dog1' )2. >>> dog1 . __name # 访问不了3. ---------------------------------------------------------------------------4. AttributeError Traceback ( most recent call last )5. < ipython - input - 206 - 7f6730db631e > in < module >()6. ----> 1 dog1 . __name7.8. AttributeError : 'Animal' object has no attribute '__name'9. >>> dog1 . greet () # 可以访问10. Hello , I am dog1 .
可以看到,加了
__
的
__name
是不能访问的,而原来的
greet
仍可以正常访问。
需要注意的是,在
Python
中,以双下划线开头,并且以双下划线结尾(即
__xxx__
)的变量是
特殊变量,特殊变量是可以直接访问的。所以,不要用
__name__
这样的变量名。
另外,如果变量名前面只有一个下划线
_
,表示不要随意访问这个变量,虽然它可以直接被访问。
2.3 获取对象信息
当我们拿到一个对象时,我们往往会考察它的类型和方法等,比如:
1. >>> a = 1232. >>> type ( a )3. int4. >>> b = '123'5. >>> type ( b )6. str
当我们拿到一个类的对象时,我们用什么去考察它呢?回到前面的例子:
1. class Animal ( object ):2. def __init__ ( self , name ):3. self . name = name4. def greet ( self ):5. print 'Hello, I am %s.' % self . name
第 1 招
:使用
type
使用
type(obj)
来获取对象的相应类型:
1. >>> dog1 = Animal ( 'dog1' )2. >>> type ( dog1 )3. __main__ . Animal
第 2 招
:使用
isinstance
使用
isinstance(obj, type)
判断对象是否为指定的
type
类型的实例:
1. >>> isinstance ( dog1 , Animal )2. True
第 3 招
:使用
hasattr/getattr/setattr
使用
hasattr(obj, attr)
判断对象是否具有指定属性
/
方法;
使用
getattr(obj, attr[, default])
获取属性
/
方法的值
,
要是没有对应的属性则返
回
default
值(前提是设置了
default
),否则会抛出
AttributeError
异常;
使用
setattr(obj, attr, value)
设定该属性
/
方法的值,类似于
obj.attr=value
;
看下面例子:
1. >>> hasattr ( dog1 , 'name' )2. True3. >>> hasattr ( dog1 , 'x' )4. False5. >>> hasattr ( dog1 , 'greet' )6. True7. >>> getattr ( dog1 , 'name' )8. 'dog1'9. >>> getattr ( dog1 , 'greet' )10. < bound method Animal . greet of < __main__ . Animal object at 0x10c3564d0 >>11. >>> getattr ( dog1 , 'x' )12. ---------------------------------------------------------------------------13. AttributeError Traceback ( most recent call last )14. < ipython - input - 241 - 42f5b7da1012 > in < module >()15. ----> 1 getattr ( dog1 , 'x' )16.17. AttributeError : 'Animal' object has no attribute 'x'18. >>> getattr ( dog1 , 'x' , 'xvalue' )19. 'xvalue'20. >>> setattr ( dog1 , 'age' , 12 )21. >>> dog1 . age22. 12
第 4 招
:使用
dir
使用
dir(obj)
可以获取相应对象的所有属性和方法名的列表:
1. >>> dir ( dog1 )2. [ '__class__' ,3. '__delattr__' ,4. '__dict__' ,5. '__doc__' ,6. '__format__' ,7. '__getattribute__' ,8. '__hash__' ,9. '__init__' ,10. '__module__' ,11. '__new__' ,12. '__reduce__' ,13. '__reduce_ex__' ,14. '__repr__' ,15. '__setattr__' ,16. '__sizeof__' ,17. '__str__' ,18. '__subclasshook__' ,19. '__weakref__' ,20. 'age' ,21. 'greet' ,22. 'name' ]
2.4 小结
(1)类是具有相同属性和方法的一组对象的集合,实例是一个具体的对象。
(2)方法是与实例绑定的函数。
(3)获取对象信息可使用下面方法:
type(obj)
:来获取对象的相应类型;
isinstance(obj, type)
:判断对象是否为指定的
type
类型的实例;
hasattr(obj, attr)
:判断对象是否具有指定属性
/
方法;
getattr(obj, attr[, default])
获取属性
/
方法的值
,
要是没有对应的属性则返回
default
值(前提是设置了
default
),否则会抛出
AttributeError
异常;
setattr(obj, attr, value)
:设定该属性
/
方法的值,类似于
obj.attr=value
;
dir(obj)
:可以获取相应对象的所有属性和方法名的列表;
3 继承和多态
3.1 继承
在面向对象编程中,当我们已经创建了一个类,而又想再创建一个与之相似的类,比如添加几个方法,或者修改原来的方法,这时我们不必从头开始,可以从原来的类派生出一个新的类,我们把原来的类称为父类或基类,而派生出的类称为子类,子类继承了父类的所有数据和方法。
让我们看一个简单的例子,首先我们定义一个
Animal
类:
1. class Animal ( object ):2. def __init__ ( self , name ):3. self . name = name4. def greet ( self ):5. print 'Hello, I am %s.' % self . name
现在,我们想创建一个
Dog
类,比如:
1. class Dog ( object ):2. def __init__ ( self , name ):3. self . name = name4. def greet ( self ):5. print 'WangWang.., I am %s. ' % self . name
可以看到,
Dog
类和
Animal
类几乎是一样的,只是
greet
方法不一样,我们完全没必要创建
一个新的类,而是从
Animal
类派生出一个新的类:
1. class Dog ( Animal ):2. def greet ( self ):3. print 'WangWang.., I am %s. ' % self . name
Dog
类是从
Animal
类继承而来的,
Dog
类自动获得了
Animal
类的所有数据和方法,而且还可以
对父类的方法进行修改,我们看看使用:
1. >>> animal = Animal ( 'animal' ) # 创建 animal 实例2. >>> animal . greet ()3. Hello , I am animal .4. >>>5. >>> dog = Dog ( 'dog' ) # 创建 dog 实例6. >>> dog . greet ()7. WangWang .., I am dog .
我们还可以对
Dog
类添加新的方法:
1. class Dog ( Animal ):2. def greet ( self ):3. print 'WangWang.., I am %s. ' % self . name4. def run ( self ):5. print 'I am running.I am running'
使用:
1. >>> dog = Dog ( 'dog' )2. >>> dog . greet ()3. WangWang .., I am dog .4. >>> dog . run ()5. I am running
3.2 多态
多态的概念其实不难理解,它是指对不同类型的变量进行相同的操作,它会根据对象(或类)类型的不同而表现出不同的行为。
事实上,我们经常用到多态的性质,比如:
1. >>> 1 + 22. 33. >>> 'a' + 'b'4. 'ab'
可以看到,我们对两个整数进行
+
操作,会返回它们的和,对两个字符进行相同的
+
操作,
会返回拼接后的字符串。也就是说,不同类型的对象对同一消息会作出不同的响应。
再看看类的例子:
1. class Animal ( object ):2. def __init__ ( self , name ):3. self . name = name4. def greet ( self ):5. print 'Hello, I am %s.' % self . name6.7. class Dog ( Animal ):8. def greet ( self ):9. print 'WangWang.., I am %s.' % self . name10.11. class Cat ( Animal ):12. def greet ( self ):13. print 'MiaoMiao.., I am %s' % self . name14.15. def hello ( animal ):16. animal . greet ()
看看多态的使用:
1. >>> dog = Dog ( 'dog' )2. >>> hello ( dog )3. WangWang .., I am dog .4. >>>5. >>> cat = Cat ( 'cat' )6. >>> hello ( cat )7. MiaoMiao .., I am cat
可以看到,
cat
和
dog
是两个不同的对象,对它们调用
greet
方法,它们会自动调用实
际类型的
greet
方法,作出不同的响应。这就是多态的魅力。
4 类方法和静态方法
在讲类方法和静态方法之前,先来看一个简单的例子:
1. class A ( object ):2. def foo ( self ):3. print 'Hello ' , self4.5. >>> a = A ()6. >>> a . foo ()7. Hello , < __main__ . A object at 0x10c37a450 >
在上面,我们定义了一个类
A
,它有一个方法
foo
,然后我们创建了一个对象
a
,并调用方法
foo
。
4.1 类方法
如果我们想通过类来调用方法,而不是通过实例,那应该怎么办呢?
Python
提供了
classmethod
装饰器让我们实现上述功能,看下面的例子:
1. class A ( object ):2. bar = 13. @classmethod4. def class_foo ( cls ):5. print 'Hello, ' , cls6. print cls . bar7.8. >>> A . class_foo () # 直接通过类来调用方法9. Hello , < class '__main__.A' >10. 1
在上面,我们使用了
classmethod
装饰方法
class_foo
,它就变成了一个类方
法,
class_foo
的参数是
cls
,代表类本身,当我们使用
A.class_foo()
时,
cls
就会接
收
A
作为参数。另外,被
classmethod
装饰的方法由于持有
cls
参数,因此我们可以在方法
里面调用类的属性、方法,比如
cls.bar
。
4.2 静态方法
在类中往往有一些方法跟类有关系,但是又不会改变类和实例状态的方法,这种方法是静态方法,我们
使用
staticmethod
来装饰,比如下面的例子:
1. class A ( object ):2. bar = 13. @staticmethod4. def static_foo ():5. print 'Hello, ' , A . bar6.7. >>> a = A ()8. >>> a . static_foo ()9. Hello , 110. >>> A . static_foo ()11. Hello , 1
可以看到,静态方法没有
self
和
cls
参数,可以把它看成是一个普通的函数,我们当然可以把它
写到类外面,但这是不推荐的,因为这不利于代码的组织和命名空间的整洁。
5 定制类和魔法方法
在
Python
中,我们可以经常看到以双下划线
__
包裹起来的方法,比如最常见的
__init__
,这些方法被称为魔法方法(
magic method
)或特殊方法(
special method
)。简
单地说,这些方法可以给
Python
的类提供特殊功能,方便我们定制一个类,比如
__init__
方
法可以对实例属性进行初始化。
完整的特殊方法列表可在
这里
查看,本文介绍部分常用的特殊方法:
__new____str__ , __repr____iter____getitem__ , __setitem__ , __delitem____getattr__ , __setattr__ , __delattr____call__
5.1 new
在
Python
中,当我们创建一个类的实例时,类会先调用
__new__(cls[, ...])
来创建实例,
然后
__init__
方法再对该实例(
self
)进行初始化。
关于
__new__
和
__init__
有几点需要注意:
__new__
是在
__init__
之前被调用的;
__new__
是类方法,
__init__
是实例方法;
重载
__new__
方法,需要返回类的实例;
一般情况下,我们不需要重载
__new__
方法。但在某些情况下,我们想控制实例的创建过程,这
时可以通过重载
__new_
方法来实现。
让我们看一个例子:
1. class A ( object ):2. _dict = dict ()3.4. def __new__ ( cls ):5. if 'key' in A . _dict :6. print "EXISTS"7. return A . _dict [ 'key' ]8. else :9. print "NEW"10. return object . __new__ ( cls )11.12. def __init__ ( self ):13. print "INIT"14. A . _dict [ 'key' ] = self
在上面,我们定义了一个类
A
,并重载了
__new__
方法:当
key
在
A._dict
中
时,直接返回
A._dict['key']
,否则创建实例。
执行情况:
1. >>> a1 = A ()2. NEW3. INIT4. >>> a2 = A ()5. EXISTS6. INIT7. >>> a3 = A ()8. EXISTS9. INIT
5.2 str & repr
先看一个简单的例子:
1. class Foo ( object ):2. def __init__ ( self , name ):3. self . name = name4.5. >>> print Foo ( 'ethan' )6. < __main__ . Foo object at 0x10c37aa50 >
在上面,我们使用
print
打印一个实例对象,但如果我们想打印更多信息呢,比如把
name
也打印
出来,这时,我们可以在类中加入
__str__
方法,如下:
1. class Foo ( object ):2. def __init__ ( self , name ):3. self . name = name4. def __str__ ( self ):5. return 'Foo object (name: %s)' % self . name6.7. >>> print Foo('ethan') # 使用 print
8. Foo object ( name : ethan )9. >>>10. >>> str ( Foo ( 'ethan' )) # 使用 str11. 'Foo object (name: ethan)'12. >>>13. >>> Foo ( 'ethan' ) # 直接显示14. < __main__ . Foo at 0x10c37a490 >
可以看到,使用
print
和
str
输出的是
__str__
方法返回的内容,但如果直接显示则不是,
那能不能修改它的输出呢?当然可以,我们只需在类中加入
__repr__
方法,比如:
1. class Foo ( object ):2. def __init__ ( self , name ):3. self . name = name4. def __str__ ( self ):5. return 'Foo object (name: %s)' % self . name6. def __repr__ ( self ):7. return 'Foo object (name: %s)' % self . name8.9. >>> Foo ( 'ethan' )10. 'Foo object (name: ethan)'
可以看到,现在直接使用
Foo('ethan')
也可以显示我们想要的结果了,然而,我们发现上面的代
码中,
__str__
和
__repr__
方法的代码是一样的,能不能精简一点呢,当然可以,如下:
1. class Foo ( object ):2. def __init__ ( self , name ):3. self . name = name4. def __str__ ( self ):5. return 'Foo object (name: %s)' % self . name6. __repr__ = __str__
5.3 iter
在某些情况下,我们希望实例对象可被用于
for...in
循环,这时我们需要在类中定义
__iter__
和
next
(在
Python3
中是
__next__
)方法,其中,
__iter__
返回一
个迭代对象,
next
返回容器的下一个元素,在没有后续元素时抛出
StopIteration
异常。
看一个斐波那契数列的例子:
1. class Fib ( object ):2. def __init__ ( self ):3. self . a , self . b = 0 , 14.5. def __iter__ ( self ): # 返回迭代器对象本身6. return self7.8. def next ( self ): # 返回容器下一个元素9. self . a , self . b = self . b , self . a + self . b10. return self . a11.12. >>> fib = Fib ()13. >>> for i in fib :14. ... if i > 10 :15. ... break16. ... print i17. ...18. 119. 120. 221. 322. 523. 8
5.4 getitem
有时,我们希望可以使用
obj[n]
这种方式对实例对象进行取值,比如对斐波那契数列,我们希望
可以取出其中的某一项,这时我们需要在类中实现
__getitem__
方法,比如下面的例子:
1. class Fib ( object ):2. def __getitem__ ( self , n ):3. a , b = 1 , 14. for x in xrange ( n ):5. a , b = b , a + b6. return a7.8. >>> fib = Fib ()9. >>> fib [ 0 ], fib [ 1 ], fib [ 2 ], fib [ 3 ], fib [ 4 ], fib [ 5 ]10. ( 1 , 1 , 2 , 3 , 5 , 8 )
我们还想更进一步,希望支持
obj[1:3]
这种切片方法来取值,这时
__getitem__
方法传入的参数可能是一个整数,也可能是一个切片对象 slice
,因此,我们需要对传入的参数进行判断,可
以使用
isinstance
进行判断,改后的代码如下:
1. class Fib ( object ):2. def __getitem__ ( self , n ):3. if isinstance ( n , slice ): # 如果 n 是 slice 对象4. a , b = 1 , 15. start , stop = n . start , n . stop6. L = []7. for i in xrange ( stop ):8. if i >= start :9. L . append ( a )10. a , b = b , a + b11. return L12. if isinstance ( n , int ): # 如果 n 是 int 型13. a , b = 1 , 114. for i in xrange ( n ):15. a , b = b , a + b16. return a
现在,我们试试用切片方法:
1. >>> fib = Fib ()2. >>> fib [ 0 : 3 ]3. [ 1 , 1 , 2 ]4. >>> fib [ 2 : 6 ]5. [ 2 , 3 , 5 , 8 ]
上面,我们只是简单地演示了
getitem
的操作,但是它还很不完善,比如没有对负数处理,不支持带
step
参数的切片操作
obj[1:2:5]
等等,读者有兴趣的话可以自己实现看看。
__geitem__
用于获取值,类似地,
__setitem__
用于设置值,
__delitem__
用于删除
值,让我们看下面一个例子:
1. class Point ( object ):2. def __init__ ( self ):3. self . coordinate = {}4.5. def __str__ ( self ):6. return "point(%s)" % self . coordinate7.8. def __getitem__ ( self , key ):9. return self . coordinate . get ( key )10.11. def __setitem__ ( self , key , value ):12. self . coordinate [ key ] = value13.14. def __delitem__ ( self , key ):15. del self . coordinate [ key ]16. print 'delete %s' % key17.18. def __len__ ( self ):19. return len ( self . coordinate )20.21. __repr__ = __str__
在上面,我们定义了一个
Point
类,它有一个属性
coordinate
(坐标),是一个字典,让我们看
看使用:
1. >>> p = Point ()2. >>> p [ 'x' ] = 2 # 对应于 p.__setitem__('x', 2)3. >>> p [ 'y' ] = 5 # 对应于 p.__setitem__('y', 5)4. >>> p # 对应于 __repr__5. point ({ 'y' : 5 , 'x' : 2 })6. >>> len ( p ) # 对应于 p.__len__7. 28. >>> p [ 'x' ] # 对应于 p.__getitem__('x')9. 210. >>> p [ 'y' ] # 对应于 p.__getitem__('y')11. 512. >>> del p [ 'x' ] # 对应于 p.__delitem__('x')13. delete x14. >>> p15. point ({ 'y' : 5 })16. >>> len ( p )17. 1
5.5 getattr
当我们获取对象的某个属性,如果该属性不存在,会抛出
AttributeError
异常,比如:
1. class Point ( object ):2. def __init__ ( self , x = 0 , y = 0 ):3. self . x = x4. self . y = y5.6. >>> p = Point ( 3 , 4 )7. >>> p . x , p . y8. ( 3 , 4 )9. >>> p . z10. ---------------------------------------------------------------------------11. AttributeError Traceback ( most recent call last )12. < ipython - input - 547 - 6dce4e43e15c > in < module >()13. ----> 1 p . z14.15. AttributeError : 'Point' object has no attribute 'z'
那有没有办法不让它抛出异常呢?当然有,只需在类的定义中加入
__getattr__
方法,比如:
1.
class
Point
(
object
):
2.
def
__init__
(
self
,
x
=
0
,
y
=
0
):
3.
self
.
x
=
x
4.
self
.
y
=
y
5.
def
__getattr__
(
self
,
attr
):
6.
if
attr
==
'z'
:
7.
return
0
8.
9.
>>>
p
=
Point
(
3
,
4
)
10.
>>>
p
.
z
11.
0
现在,当我们调用不存在的属性(比如
z
)时,解释器就会试图调用
__getattr__(self, 'z')
来获取值,但是,上面的实现还有一个问题,当我们调用其他属性,比如
w
,会返回
None
,因为
__getattr__
默认返回就是
None
,只有当
attr
等于
‘z’
时才返回
0
,如果我们想让
__getattr__
只响应几个特定的属性,可以加入异常处理,修改
__getattr__
方法,如下:
1. def __getattr__ ( self , attr ):2. if attr == 'z' :3. return 04. raise AttributeError ( "Point object has no attribute %s" % attr )
这里再强调一点,
__getattr__
只有在属性不存在的情况下才会被调用,对已存在的属性不会调用
__getattr__
。
与
__getattr__
一起使用的还有
__setattr__
,
__delattr__
,类似
obj.attr =value
,
del obj.attr
,看下面一个例子:
1. class Point ( object ):2. def __init__ ( self , x = 0 , y = 0 ):3. self . x = x4. self . y = y5.6. def __getattr__ ( self , attr ):7. if attr == 'z' :8. return 09. raise AttributeError ( "Point object has no attribute %s" % attr )10.11. def __setattr__ ( self , * args , ** kwargs ):12. print 'call func set attr (%s, %s)' % ( args , kwargs )13. return object . __setattr__ ( self , * args , ** kwargs )14.15. def __delattr__ ( self , * args , ** kwargs ):16. print 'call func del attr (%s, %s)' % ( args , kwargs )17. return object . __delattr__ ( self , * args , ** kwargs )18.19. >>> p = Point ( 3 , 4 )20. call func set attr (( 'x' , 3 ), {})21. call func set attr (( 'y' , 4 ), {})22. >>> p . z23. 024. >>> p . z = 725. call func set attr (( 'z' , 7 ), {})26. >>> p . z27. 728. >>> p . w29. Traceback ( most recent call last ):30. File "<stdin>" , line 1 , in < module >31. File "<stdin>" , line 8 , in __getattr__32. AttributeError : Point object has no attribute w33. >>> p . w = 834. call func set attr (( 'w' , 8 ), {})35. >>> p . w36. 837. >>> del p . w38. call func del attr (( 'w' ,), {})39. >>> p . __dict__40. { 'y' : 4 , 'x' : 3 , 'z' : 7 }
5.6 call
我们一般使用
obj.method()
来调用对象的方法,那能不能直接在实例本身上调用呢?在
Python
中,只要我们在类中定义
__call__
方法,就可以对实例进行调用,比如下面的例子:
1. class Point ( object ):2. def __init__ ( self , x , y ):3. self . x , self . y = x , y4. def __call__ ( self , z ):5. return self . x + self . y + z
使用如下:
1. >>> p = Point ( 3 , 4 )2. >>> callable ( p ) # 使用 callable 判断对象是否能被调用3. True4. >>> p ( 6 ) # 传入参数,对实例进行调用,对应 p.__call__(6)5. 13 # 3+4+6
可以看到,对实例进行调用就好像对函数调用一样。
__new__
在
__init__
之前被调用,用来创建实例。
__str__
是用
print
和
str
显示的结果,
__repr__
是直接显示的结果。
__getitem__
用类似
obj[key]
的方式对对象进行取值
__getattr__
用于获取不存在的属性
obj.attr
__call__
使得可以对实例进行调用
5.7 小结
__new__
在
__init__
之前被调用,用来创建实例。
__str__
是用
print
和
str
显示的结果,
__repr__
是直接显示的结果。
__getitem__
用类似
obj[key]
的方式对对象进行取值
__getattr__
用于获取不存在的属性
obj.attr
__call__
使得可以对实例进行调用
6 slots 魔法
在
Python
中,我们在定义类的时候可以定义属性和方法。当我们创建了一个类的实例后,我们还可以给该实例绑定任意新的属性和方法。
看下面一个简单的例子:
1. class Point ( object ):2. def __init__ ( self , x = 0 , y = 0 ):3. self . x = x4. self . y = y5.6. >>> p = Point ( 3 , 4 )7. >>> p . z = 5 # 绑定了一个新的属性8. >>> p . z9. 510. >>> p . __dict__11. { 'x' : 3 , 'y' : 4 , 'z' : 5 }
在上面,我们创建了实例
p
之后,给它绑定了一个新的属性
z
,这种动态绑定的功能虽然很有用,但它的代价是消耗了更多的内存。
因此,为了不浪费内存,可以使用
__slots__
来告诉
Python
只给一个固定集合的属性分配空
间,对上面的代码做一点改进,如下:
1. class Point ( object ):2. __slots__ = ( 'x' , 'y' ) # 只允许使用 x 和 y3.4. def __init__ ( self , x = 0 , y = 0 ):5. self . x = x6. self . y = y
上面,我们给
__slots__
设置了一个元组,来限制类能添加的属性。现在,如果我们想绑定一个
新的属性,比如
z
,就会出错了,如下:
1. >>> p = Point ( 3 , 4 )2. >>> p . z = 53. ---------------------------------------------------------------------------4. AttributeError Traceback ( most recent call last )5. < ipython - input - 648 - 625ed954d865 > in < module >()6. ----> 1 p . z = 57.8. AttributeError : 'Point' object has no attribute 'z'
使用
__slots__
有一点需要注意的是,
__slots__
设置的属性仅对当前类有效,对继承的子
类不起效,除非子类也定义了
__slots__
,这样,子类允许定义的属性就是自身的
slots
加上父
类的
slots
。
7 使用 @property
在使用
@property
之前,让我们先来看一个简单的例子:
1. class Exam ( object ):2. def __init__ ( self , score ):3. self . _score = score4.5. def get_score ( self ):6. return self . _score7.8. def set_score ( self , val ):9. if val < 0 :10. self . _score = 011. elif val > 100 :12. self . _score = 10013. else :14. self . _score = val15.16. >>> e = Exam ( 60 )17. >>> e . get_score ()18. 6019. >>> e . set_score ( 70 )20. >>> e . get_score ()21. 70
在上面,我们定义了一个
Exam
类,为了避免直接对
_score
属性操作,我们提供了
get_score
和
set_score
方法,这样起到了封装的作用,把一些不想对外公开的属性隐蔽起来,
而只是提供方法给用户操作,在方法里面,我们可以检查参数的合理性等。
这样做没什么问题,但是我们有更简单的方式来做这件事,
Python
提供了
property
装饰器,被
装饰的方法,我们可以将其『当作』属性来用,看下面的例子:
1. class Exam ( object ):2. def __init__ ( self , score ):3. self . _score = score4.5. @property6. def score ( self ):7. return self . _score8.9. @score . setter10. def score ( self , val ):11. if val < 0 :12. self . _score = 013. elif val > 100 :14. self . _score = 10015. else :16. self . _score = val17.18. >>> e = Exam ( 60 )19. >>> e . score20. 6021. >>> e . score = 9022. >>> e . score23. 9024. >>> e . score = 20025. >>> e . score26. 100
在上面,我们给方法
score
加上了
@property
,于是我们可以把
score
当成一个属性来用,
此时,又会创建一个新的装饰器
score.setter
,它可以把被装饰的方法变成属性来赋值。
另外,我们也不一定要使用
score.setter
这个装饰器,这时
score
就变成一个只读属性了:
1. class Exam ( object ):2. def __init__ ( self , score ):3. self . _score = score4.5. @property6. def score ( self ):7. return self . _score8.9. >>> e = Exam ( 60 )10. >>> e . score11. 6012. >>> e . score = 200 # score 是只读属性,不能设置值13. ---------------------------------------------------------------------------14. AttributeError Traceback ( most recent call last )15. < ipython - input - 676 - b0515304f6e0 > in < module >()16. ----> 1 e . score = 20017.18. AttributeError : can 't set attribute
@property
把方法『变成』了属性
。
8 你不知道的 super
在类的继承中,如果重定义某个方法,该方法会覆盖父类的同名方法,但有时,我们希望能同时实现父类的功能,这时,我们就需要调用父类的方法了,可通过使用
super
来实现,比如:
1. class Animal ( object ):2. def __init__ ( self , name ):3. self . name = name4. def greet ( self ):5. print 'Hello, I am %s.' % self . name6.7. class Dog ( Animal ):8. def greet ( self ):9. super ( Dog , self ). greet () # Python3 可使用 super().greet()10. print 'WangWang...'
在上面,
Animal
是父类,
Dog
是子类,我们在
Dog
类重定义了
greet
方法,为了能同时实现
父类的功能,我们又调用了父类的方法,看下面的使用:
1. >>> dog = Dog ( 'dog' )2. >>> dog . greet ()3. Hello , I am dog .4. WangWang ..
super
的一个最常见用法可以说是在子类中调用父类的初始化方法了,比如:
1. class Base ( object ):2. def __init__ ( self , a , b ):3. self . a = a4. self . b = b5.6. class A ( Base ):7. def __init__ ( self , a , b , c ):8.super ( A , self ). __init__ ( a , b ) # Python3 可使用 super().__init__(a, b)9. self . c = c
8.1 深入 super()
看了上面的使用,你可能会觉得
super
的使用很简单,无非就是获取了父类,并调用父类的方
法。其实,在上面的情况下,
super
获得的类刚好是父类,但在其他情况就不一定了,
super
其实和
父类没有实质性的关联。
让我们看一个稍微复杂的例子,涉及到多重继承,代码如下:
1. class Base ( object ):2. def __init__ ( self ):3. print "enter Base"4. print "leave Base"5.6. class A ( Base ):7. def __init__ ( self ):8. print "enter A"9. super ( A , self ). __init__ ()10. print "leave A"11.12. class B ( Base ):13. def __init__ ( self ):14. print "enter B"15. super ( B , self ). __init__ ()16. print "leave B"17.18. class C ( A , B ):19. def __init__ ( self ):20. print "enter C"21. super ( C , self ). __init__ ()22. print "leave C"
其中,
Base
是父类,
A, B
继承自
Base, C
继承自
A, B
,它们的继承关系是一个典型的『菱形继
承』,如下:
1. Base2. / \3. / \4. A B5. \ /6. \ /7. C
现在,让我们看一下使用:
1. >>> c = C ()2. enter C3. enter A4. enter B5. enter Base6. leave Base7. leave B8. leave A9. leave C
如果你认为
super
代表『调用父类的方法』,那你很可能会疑惑为什么
enter A
的下一句不是
enter Base
而是
enter B
。原因是,
super
和父类没有实质性的关联,现在让我们搞清
super
是怎么运作的。
8.2 MRO 列表
事实上,对于你定义的每一个类,
Python
会计算出一个方法解析顺序(
Method Resolution
Order, MRO
)列表,它代表了类继承的顺序,我们可以使用下面的方式获得某个类的
MRO
列表:
1. >>> C . mro () # or C.__mro__ or C().__class__.mro()2. [ __main__ . C , __main__ . A , __main__ . B , __main__ . Base , object ]
那这个
MRO
列表的顺序是怎么定的呢,它是通过一个
C3
线性化算法
来实现的,这里我们就不去深究
这个算法了,感兴趣的读者可以自己去了解一下,总的来说,一个类的
MRO
列表就是合并所有父类的
MRO
列表,并遵循以下三条原则:
子类永远在父类前面
如果有多个父类,会根据它们在列表中的顺序被检查
如果对下一个类存在两个合法的选择,选择第一个父类
8.3 super 原理
super
的工作原理如下:
1. def super ( cls , inst ):2. mro = inst . __class__ . mro ()3. return mro [ mro . index ( cls ) + 1 ]
其中,
cls
代表类,
inst
代表实例,上面的代码做了两件事:
获取
inst
的
MRO
列表
查找
cls
在当前
MRO
列表中的
index,
并返回它的下一个类,即
mro[index + 1]
当你使用
super(cls, inst)
时,
Python
会在
inst
的
MRO
列表上搜索
cls
的下一个类。
现在,让我们回到前面的例子。
首先看类
C
的
__init__
方法:
1. super ( C , self ). __init__ ()
这里的
self
是当前
C
的实例,
self.
class
.mro()
结果是:
1. [ __main__ . C , __main__ . A , __main__ . B , __main__ . Base , object ]
可以看到,
C
的下一个类是
A
,于是,跳到了
A
的
__init__
,这时会打印出
enter A
,并执
行下面一行代码:
1. super ( A , self ). __init__ ()
注意,这里的
self
也是当前
C
的实例,
MRO
列表跟上面是一样的,搜索
A
在
MRO
中的下一个
类,发现是
B
,于是,跳到了
B
的
__init__
,这时会打印出
enter B
,而不是
enter
Base
。
整个过程还是比较清晰的,关键是要理解
super
的工作方式,而不是想当然地认为
super
调用了父
类的方法。
事实上,
super
和父类没有实质性的关联。
super(cls, inst)
获得的是
cls
在
inst
的
MRO
列表中的下一个类
8.4 陌生的 metaclass
Python
中的元类(
metaclass
)是一个深度魔法,平时我们可能比较少接触到元类,本文将通过一些简单的例子来理解这个魔法。
在
Python
中,一切皆对象。字符串,列表,字典,函数是对象,类也是一个对象,因此你可以:
把类赋值给一个变量
把类作为函数参数进行传递
把类作为函数的返回值
在运行时动态地创建类
看一个简单的例子:
1. class Foo ( object ):2. foo = True3.4. class Bar ( object ):5. bar = True6.7. def echo ( cls ):8. print cls9.10. def select ( name ):11. if name == 'foo' :12. return Foo # 返回值是一个类13. if name == 'bar' :14. return Bar15.16. >>> echo ( Foo ) # 把类作为参数传递给函数 echo17. < class '__main__.Foo' >18. >>> cls = select ( 'foo' ) # 函数 select 的返回值是一个类,把它赋给变量 cls19. >>> cls20. __main__ . Foo
8.5 熟悉又陌生的 type
在日常使用中,我们经常使用
object
来派生一个类,事实上,在这种情况下,
Python
解释器会
调用
type
来创建类。
这里,出现了
type
,没错,是你知道的
type
,我们经常使用它来判断一个对象的类型,比
如:
1. class Foo ( object ):2. Foo = True3.4. >>> type ( 10 )5. < type 'int' >6. >>> type ( 'hello' )7. < type 'str' >8. >>> type ( Foo ())9. < class '__main__.Foo' >10. >>> type ( Foo )11. < type 'type' >
事实上,
type
除了可以返回对象的类型,它还可以被用来动态地创建类(对象)。下面,我们看
几个例子,来消化一下这句话。
使用
type
来创建类(对象)的方式如下:
type( 类名 , 父类的元组(针对继承的情况,可以为空),包含属性和方法的字典(名称和值) )
8.6 最简单的情况
假设有下面的类:
1. class Foo ( object ):2. pass
现在,我们不使用
class
关键字来定义,而使用
type
,如下:
1. Foo = type ( 'Foo' , ( object , ), {}) # 使用 type 创建了一个类对象
上面两种方式是等价的。我们看到,
type
接收三个参数:
第
1
个参数是字符串
‘Foo’
,表示类名
第
2
个参数是元组
(object, )
,表示所有的父类
第
3
个参数是字典,这里是一个空字典,表示没有定义属性和方法。
在上面,我们使用
type()
创建了一个名为
Foo
的类,然后把它赋给了变量
Foo
,我们当然可
以把它赋给其他变量,但是,此刻没必要给自己找麻烦。
接着,我们看看使用:
1. >>> print Foo2. < class '__main__.Foo' >3. >>> print Foo ()4. < __main__ . Foo object at 0x10c34f250 >
假设有下面的类:
1. class Foo ( object ):2. foo = True3. def greet ( self ):4. print 'hello world'5. print self . foo
用
type
来创建这个类,如下:
1. def greet ( self ):2. print 'hello world'3. print self . foo4.5. Foo = type ( 'Foo' , ( object , ), { 'foo' : True , 'greet' : greet })
上面两种方式的效果是一样的,看下使用:
1.
>>>
f
=
Foo
()
2. >>> f . foo3. True4. >>> f . greet5. < bound method Foo . greet of < __main__ . Foo object at 0x10c34f890 >>6. >>> f . greet ()7. hello world8. True
8.7 继承的情况
再来看看继承的情况,假设有如下的父类:
1. class Base ( object ):2. pass
我们用
Base
派生一个
Foo
类,如下:
1. class Foo ( Base ):2. foo = True
改用
type
来创建,如下:
1. Foo = type ( 'Foo' , ( Base , ), { 'foo' : True })
9 元类
9.1 概念
元类(
metaclass
)是用来创建类(对象)的可调用对象。这里的可调用对象可以是函数或者类等。但一般情况下,我们使用类作为元类。对于实例对象、类和元类,我们可以用下面的图来描述:
我们在前面使用了
type
来创建类(对象),事实上,
type
就是一个元类。
那么,元类到底有什么用呢?要你何用
…
元类的主要目的是为了控制类的创建行为。我们还是先来看看一些例子,以消化这句话。
9.2 元类的使用
先从一个简单的例子开始,假设有下面的类:
1. class Foo ( object ):2. name = 'foo'3. def bar ( self ):4. print 'bar'
现在我们想给这个类的方法和属性名称前面加上
my_
前缀,即
name
变成
my_name
,
bar
变成
my_bar
,另外,我们还想加一个
echo
方法。当然,有很多种做法,这里展示用元类的做法。
1.
首先,定义一个元类,按照默认习惯,类名以
Metaclass
结尾,代码如下:
1. class PrefixMetaclass ( type ):2. def __new__ ( cls , name , bases , attrs ):3. # 给所有属性和方法前面加上前缀 my_4. _attrs = (( 'my_' + name , value ) for name , value in attrs . items ())5.6. _attrs = dict (( name , value ) for name , value in _attrs ) # 转化为字典7. _attrs [ 'echo' ] = lambda self , phrase : phrase # 增加了一个 echo 方法8.9. return type . __new__ ( cls , name , bases , _attrs ) # 返回创建后的类
上面的代码有几个需要注意的点:
PrefixMetaClass
从
type
继承,这是因为
PrefixMetaclass
是用来创建类的
__new__
是在
__init__
之前被调用的特殊方法,它用来创建对象并返回创建后的对象,
对它的参数解释如下:
cls
:当前准备创建的类
name
:类的名字
bases
:类的父类集合
attrs
:类的属性和方法,是一个字典
2.
接着,我们需要指示
Foo
使用
PrefixMetaclass
来定制类。
在
Python2
中,我们只需在
Foo
中加一个
__metaclass__
的属性,如下:
1. class Foo ( object ):2. __metaclass__ = PrefixMetaclass3. name = 'foo'4. def bar ( self ):5. print 'bar'
在
Python3
中,这样做:
1. class Foo ( metaclass = PrefixMetaclass ):2. name = 'foo'3. def bar ( self ):4. print 'bar'
现在,让我们看看使用:
1. >>> f = Foo ()2. >>> f . name # name 属性已经被改变3. ---------------------------------------------------------------------------4. AttributeError Traceback ( most recent call last )5. < ipython - input - 774 - 4511c8475833 > in < module >()6. ----> 1 f . name7.8. AttributeError : 'Foo' object has no attribute 'name'9. >>>10. >>> f . my_name11. 'foo'12. >>> f . my_bar ()13. bar14. >>> f . echo ( 'hello' )15. 'hello'
可以看到,
Foo
原来的属性
name
已经变成了
my_name
,而方法
bar
也变成了
my_bar
,这就是
元类的魔法。
再来看一个继承的例子,下面是完整的代码:
1. class PrefixMetaclass ( type ):2. def __new__ ( cls , name , bases , attrs ):3. # 给所有属性和方法前面加上前缀 my_4. _attrs = (( 'my_' + name , value ) for name , value in attrs . items ())5.6. _attrs = dict (( name , value ) for name , value in _attrs ) # 转化为字典7. _attrs [ 'echo' ] = lambda self , phrase : phrase # 增加了一个 echo 方法8.9. return type . __new__ ( cls , name , bases , _attrs )10.11. class Foo ( object ):12. __metaclass__ = PrefixMetaclass # 注意跟 Python3 的写法有所区别13. name = 'foo'14. def bar ( self ):15. print 'bar'16.17. class Bar ( Foo ):18. prop = 'bar'
其中,
PrefixMetaclass
和
Foo
跟前面的定义是一样的,只是新增了
Bar
,它继承自
Foo
。先让
我们看看使用:
1. >>> b = Bar ()2. >>> b . prop # 发现没这个属性3. ---------------------------------------------------------------------------4. AttributeError Traceback ( most recent call last )5. < ipython - input - 778 - 825e0b6563ea > in < module >()6. ----> 1 b . prop7.8. AttributeError : 'Bar' object has no attribute 'prop'9. >>> b . my_prop10. 'bar'11. >>> b . my_name12. 'foo'13. >>> b . my_bar ()14. bar15. >>> b . echo ( 'hello' )16. 'hello'
我们发现,
Bar
没有
prop
这个属性,但是有
my_prop
这个属性,这是为什么呢?
原来,当我们定义
class Bar(Foo)
时,
Python
会首先在当前类,即
Bar
中寻找
__metaclass__
,如果没有找到,就会在父类
Foo
中寻找
__metaclass__
,如果找不到,就
继续在
Foo
的父类寻找,如此继续下去,如果在任何父类都找不到
__metaclass__
,就会到模块
层次中寻找,如果还是找不到,就会用
type
来创建这个类。
这里,我们在
Foo
找到了
__metaclass__
,
Python
会使用
PrefixMetaclass
来创建
Bar
,也就是说,元类会隐式地继承到子类,虽然没有显示地在子类使用
__metaclass__
,这也解
释了为什么
Bar
的
prop
属性被动态修改成了
my_prop
。
写到这里,不知道你理解元类了没?希望理解了,如果没理解,就多看几遍吧
~
9.3 小结
(1)在
Python
中,类也是一个对象。
(2)类创建实例,元类创建类。
(3)元类主要做了三件事:
拦截类的创建
修改类的定义
返回修改后的类
当你创建类时,解释器会调用元类来生成它,定义一个继承自
object
的普通类意味着调用
type
来创建它。