目录
1 概述
2 类和实例
2.1 基本概念
2.2 访问限制
2.3 获取对象信息
2.4 小结
3 继承和多态
3.1 继承
3.2 多态
4 类方法和静态方法
4.1 类方法
4.2 静态方法
5 定制类和魔法方法
5.1 new
5.2 str & repr
7. >>> print Foo('ethan') # 使用 print
5.3 iter
5.4 getitem
5.5 getattr
5.6 call
5.7 小结
6 slots 魔法
7 使用 @property
8 你不知道的 super
8.1 深入 super()
8.2 MRO 列表
8.3 super 原理
8.4 陌生的 metaclass
8.5 熟悉又陌生的 type
8.6 最简单的情况
8.7 继承的情况
9 元类
9.1 概念
9.2 元类的使用
9.3 小结
1 概述
Python
是一门面向对象编程(
Object Oriented Programming, OOP
)的语言,这里的对象可以。
看做是由数据(或者说特性)以及一系列可以存取、操作这些数据的方法所组成的集合。面向对象编程。
主要有以下特点:
多态(
Polymorphism
):不同类(
Class
)的对象对同一消息会做出不同的响应。
封装(
Encapsulation
):对外部世界隐藏对象的工作细节。
继承(
Inheritance
):以已有的类(父类)为基础建立专门的类对象。
在
Python
中,元组、列表和字典等数据类型是对象,函数也是对象。那么,我们能创建自己的对象吗?答案是肯定的。跟其他 OOP
语言类似,我们使用类来自定义对象。
本章主要介绍以下几个方面:
![]()
Python——类
2 类和实例
2.1 基本概念
类是一个抽象的概念,我们可以把它理解为具有相同属性和方法的一组对象的集合,而实例则是一个具体的对象。类和对象的关系就如同模具和用这个模具制作出的物品之间的关系。一个类为它的全部对象给出了一个统一的定义,而他的每个对象则是符合这种定义的一个实体,因此类和对象的关系就是抽象和具体的关系。
我们还是先来看看在 Python
中怎么定义一个类。
这里以动物(
Animal
)类为例,
Python
提供关键字
class
来声明一个类:
1.
class
Animal
(
object
):
2.
pass
其中,
Animal
是类名,通常类名的首字母采用大写(如果有多个单词,则每个单词的首字母大
写),后面紧跟着
(object)
,表示该类是从哪个类继承而来的,所有类最终都会继承自
object
类。
类定义好了,接下来我们就可以创建实例了:
1.
>>>
animal
=
Animal
()
#
创建一个实例对象
2.
>>>
animal
3.
<
__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
=
name
4.
def
greet
(
self
):
5.
'Hello, I am %s.'
%
self
.
name
我们添加了方法
greet
,看看下面的使用:
1.
>>>
dog1
=
Animal
(
'dog1'
)
2.
>>>
dog1
.
name
3.
'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
=
name
4.
def
greet
(
self
):
5.
'Hello, I am %s.'
%
self
.
__name
1.
>>>
dog1
=
Animal
(
'dog1'
)
2.
>>>
dog1
.
__name
#
访问不了
3.
---------------------------------------------------------------------------
4.
AttributeError Traceback
(
most recent call last
)
5.
<
ipython
-
input
-
206
-
7f6730db631e
>
in
<
module
>()
6.
---->
1
dog1
.
__name
7.
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
=
123
2.
>>>
type
(
a
)
3.
int
4.
>>>
b
=
'123'
5.
>>>
type
(
b
)
6.
str
当我们拿到一个类的对象时,我们用什么去考察它呢?回到前面的例子:
1.
class
Animal
(
object
):
2.
def
__init__
(
self
,
name
):
3.
self
.
name
=
name
4.
def
greet
(
self
):
5.
'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.
True
3.
>>>
hasattr
(
dog1
,
'x'
)
4.
False
5.
>>>
hasattr
(
dog1
,
'greet'
)
6.
True
7.
>>>
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
.
age
22.
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
=
name
4.
def
greet
(
self
):
5.
'Hello, I am %s.'
%
self
.
name
现在,我们想创建一个
Dog
类,比如:
1.
class
Dog
(
object
):
2.
def
__init__
(
self
,
name
):
3.
self
.
name
=
name
4.
def
greet
(
self
):
5.
'WangWang.., I am %s. '
%
self
.
name
可以看到,
Dog
类和
Animal
类几乎是一样的,只是
greet
方法不一样,我们完全没必要创建
一个新的类,而是从
Animal
类派生出一个新的类:
1.
class
Dog
(
Animal
):
2.
def
greet
(
self
):
3.
'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.
'WangWang.., I am %s. '
%
self
.
name
4.
def
run
(
self
):
5.
'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
+
2
2.
3
3.
>>>
'a'
+
'b'
4.
'ab'
可以看到,我们对两个整数进行
+
操作,会返回它们的和,对两个字符进行相同的
+
操作,
会返回拼接后的字符串。也就是说,不同类型的对象对同一消息会作出不同的响应。
再看看类的例子:
1.
class
Animal
(
object
):
2.
def
__init__
(
self
,
name
):
3.
self
.
name
=
name
4.
def
greet
(
self
):
5.
'Hello, I am %s.'
%
self
.
name
6.
7.
class
Dog
(
Animal
):
8.
def
greet
(
self
):
9.
'WangWang.., I am %s.'
%
self
.
name
10.
11.
class
Cat
(
Animal
):
12.
def
greet
(
self
):
13.
'MiaoMiao.., I am %s'
%
self
.
name
14.
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.
'Hello '
,
self
4.
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
=
1
3.
@classmethod
4.
def
class_foo
(
cls
):
5.
'Hello, '
,
cls
6.
cls
.
bar
7.
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
=
1
3.
@staticmethod
4.
def
static_foo
():
5.
'Hello, '
,
A
.
bar
6.
7.
>>>
a
=
A
()
8.
>>>
a
.
static_foo
()
9.
Hello
,
1
10.
>>>
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.
"EXISTS"
7.
return
A
.
_dict
[
'key'
]
8.
else
:
9.
"NEW"
10.
return
object
.
__new__
(
cls
)
11.
12.
def
__init__
(
self
):
13.
"INIT"
14.
A
.
_dict
[
'key'
] =
self
在上面,我们定义了一个类
A
,并重载了
__new__
方法:当
key
在
A._dict
中
时,直接返回
A._dict['key']
,否则创建实例。
执行情况:
1.
>>>
a1
=
A
()
2.
NEW
3.
INIT
4.
>>>
a2
=
A
()
5.
EXISTS
6.
INIT
7.
>>>
a3
=
A
()
8.
EXISTS
9.
INIT
5.2 str & repr
先看一个简单的例子:
1.
class
Foo
(
object
):
2.
def
__init__
(
self
,
name
):
3.
self
.
name
=
name
4.
5.
>>>
Foo
(
'ethan'
)
6.
<
__main__
.
Foo
object at
0x10c37aa50
>
在上面,我们使用
打印一个实例对象,但如果我们想打印更多信息呢,比如把
name
也打印
出来,这时,我们可以在类中加入
__str__
方法,如下:
1.
class
Foo
(
object
):
2.
def
__init__
(
self
,
name
):
3.
self
.
name
=
name
4.
def
__str__
(
self
):
5.
return
'Foo object (name: %s)'
%
self
.
name
6.
7. >>> print Foo('ethan') # 使用 print
8.
Foo
object
(
name
:
ethan
)
9.
>>>
10.
>>>
str
(
Foo
(
'ethan'
))
#
使用
str
11.
'Foo object (name: ethan)'
12.
>>>
13.
>>>
Foo
(
'ethan'
)
#
直接显示
14.
<
__main__
.
Foo
at
0x10c37a490
>
可以看到,使用
和
str
输出的是
__str__
方法返回的内容,但如果直接显示则不是,
那能不能修改它的输出呢?当然可以,我们只需在类中加入
__repr__
方法,比如:
1.
class
Foo
(
object
):
2.
def
__init__
(
self
,
name
):
3.
self
.
name
=
name
4.
def
__str__
(
self
):
5.
return
'Foo object (name: %s)'
%
self
.
name
6.
def
__repr__
(
self
):
7.
return
'Foo object (name: %s)'
%
self
.
name
8.
9.
>>>
Foo
(
'ethan'
)
10.
'Foo object (name: ethan)'
可以看到,现在直接使用
Foo('ethan')
也可以显示我们想要的结果了,然而,我们发现上面的代
码中,
__str__
和
__repr__
方法的代码是一样的,能不能精简一点呢,当然可以,如下:
1.
class
Foo
(
object
):
2.
def
__init__
(
self
,
name
):
3.
self
.
name
=
name
4.
def
__str__
(
self
):
5.
return
'Foo object (name: %s)'
%
self
.
name
6.
__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
=
,
1
4.
5.
def
__iter__
(
self
):
#
返回迭代器对象本身
6.
return
self
7.
8.
def
next
(
self
):
#
返回容器下一个元素
9.
self
.
a
,
self
.
b
=
self
.
b
,
self
.
a
+
self
.
b
10.
return
self
.
a
11.
12.
>>>
fib
=
Fib
()
13.
>>>
for
i
in
fib
:
14.
...
if
i
>
10
:
15.
...
break
16.
...
i
17.
...
18.
1
19.
1
20.
2
21.
3
22.
5
23.
8
5.4 getitem
有时,我们希望可以使用
obj[n]
这种方式对实例对象进行取值,比如对斐波那契数列,我们希望
可以取出其中的某一项,这时我们需要在类中实现
__getitem__
方法,比如下面的例子:
1.
class
Fib
(
object
):
2.
def
__getitem__
(
self
,
n
):
3.
a
,
b
=
1
,
1
4.
for
x
in
xrange
(
n
):
5.
a
,
b
=
b
,
a
+
b
6.
return
a
7.
8.
>>>
fib
=
Fib
()
9.
>>>
fib
[
],
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
,
1
5.
start
,
stop
=
n
.
start
,
n
.
stop
6.
L
= []
7.
for
i
in
xrange
(
stop
):
8.
if
i
>=
start
:
9.
L
.
append
(
a
)
10.
a
,
b
=
b
,
a
+
b
11.
return
L
12.
if
isinstance
(
n
,
int
):
#
如果
n
是
int
型
13.
a
,
b
=
1
,
1
14.
for
i
in
xrange
(
n
):
15.
a
,
b
=
b
,
a
+
b
16.
return
a
现在,我们试试用切片方法:
1.
>>>
fib
=
Fib
()
2.
>>>
fib
[
:
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
.
coordinate
7.
8.
def
__getitem__
(
self
,
key
):
9.
return
self
.
coordinate
.
get
(
key
)
10.
11.
def
__setitem__
(
self
,
key
,
value
):
12.
self
.
coordinate
[
key
] =
value
13.
14.
def
__delitem__
(
self
,
key
):
15.
del
self
.
coordinate
[
key
]
16.
'delete %s'
%
key
17.
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.
2
8.
>>>
p
[
'x'
]
#
对应于
p.__getitem__('x')
9.
2
10.
>>>
p
[
'y'
]
#
对应于
p.__getitem__('y')
11.
5
12.
>>>
del
p
[
'x'
]
#
对应于
p.__delitem__('x')
13.
delete x
14.
>>>
p
15.
point
({
'y'
:
5
})
16.
>>>
len
(
p
)
17.
1
5.5 getattr
当我们获取对象的某个属性,如果该属性不存在,会抛出
AttributeError
异常,比如:
1.
class
Point
(
object
):
2.
def
__init__
(
self
,
x
=
,
y
=
):
3.
self
.
x
=
x
4.
self
.
y
=
y
5.
6.
>>>
p
=
Point
(
3
,
4
)
7.
>>>
p
.
x
,
p
.
y
8.
(
3
,
4
)
9.
>>>
p
.
z
10.
---------------------------------------------------------------------------
11.
AttributeError Traceback
(
most recent call last
)
12.
<
ipython
-
input
-
547
-
6dce4e43e15c
>
in
<
module
>()
13.
---->
1
p
.
z
14.
15.
AttributeError
:
'Point'
object has no attribute
'z'
那有没有办法不让它抛出异常呢?当然有,只需在类的定义中加入
__getattr__
方法,比如:
1.
class
Point
(
object
):
2.
def
__init__
(
self
,
x
=
,
y
=
):
3.
self
.
x
=
x
4.
self
.
y
=
y
5.
def
__getattr__
(
self
,
attr
):
6.
if
attr
==
'z'
:
7.
return
8.
9.
>>>
p
=
Point
(
3
,
4
)
10.
>>>
p
.
z
11.
现在,当我们调用不存在的属性(比如
z
)时,解释器就会试图调用
__getattr__(self, 'z')
来获取值,但是,上面的实现还有一个问题,当我们调用其他属性,比如
w
,会返回
None
,因为
__getattr__
默认返回就是
None
,只有当
attr
等于
‘z’
时才返回
,如果我们想让
__getattr__
只响应几个特定的属性,可以加入异常处理,修改
__getattr__
方法,如下:
1.
def
__getattr__
(
self
,
attr
):
2.
if
attr
==
'z'
:
3.
return
4.
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
=
,
y
=
):
3.
self
.
x
=
x
4.
self
.
y
=
y
5.
6.
def
__getattr__
(
self
,
attr
):
7.
if
attr
==
'z'
:
8.
return
9.
raise
AttributeError
(
"Point object has no attribute %s"
%
attr
)
10.
11.
def
__setattr__
(
self
, *
args
, **
kwargs
):
12.
'call func set attr (%s, %s)'
% (
args
,
kwargs
)
13.
return
object
.
__setattr__
(
self
, *
args
, **
kwargs
)
14.
15.
def
__delattr__
(
self
, *
args
, **
kwargs
):
16.
'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
.
z
23.
24.
>>>
p
.
z
=
7
25.
call func set attr
((
'z'
,
7
), {})
26.
>>>
p
.
z
27.
7
28.
>>>
p
.
w
29.
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 w
33.
>>>
p
.
w
=
8
34.
call func set attr
((
'w'
,
8
), {})
35.
>>>
p
.
w
36.
8
37.
>>>
del
p
.
w
38.
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
,
y
4.
def
__call__
(
self
,
z
):
5.
return
self
.
x
+
self
.
y
+
z
使用如下:
1.
>>>
p
=
Point
(
3
,
4
)
2.
>>>
callable
(
p
)
#
使用
callable
判断对象是否能被调用
3.
True
4.
>>>
p
(
6
)
#
传入参数,对实例进行调用,对应
p.__call__(6)
5.
13
# 3+4+6
可以看到,对实例进行调用就好像对函数调用一样。
__new__
在
__init__
之前被调用,用来创建实例。
__str__
是用
和
str
显示的结果,
__repr__
是直接显示的结果。
__getitem__
用类似
obj[key]
的方式对对象进行取值
__getattr__
用于获取不存在的属性
obj.attr
__call__
使得可以对实例进行调用
5.7 小结
__new__
在
__init__
之前被调用,用来创建实例。
__str__
是用
和
str
显示的结果,
__repr__
是直接显示的结果。
__getitem__
用类似
obj[key]
的方式对对象进行取值
__getattr__
用于获取不存在的属性
obj.attr
__call__
使得可以对实例进行调用
6 slots 魔法
在
Python
中,我们在定义类的时候可以定义属性和方法。当我们创建了一个类的实例后,我们还可以给该实例绑定任意新的属性和方法。
看下面一个简单的例子:
1.
class
Point
(
object
):
2.
def
__init__
(
self
,
x
=
,
y
=
):
3.
self
.
x
=
x
4.
self
.
y
=
y
5.
6.
>>>
p
=
Point
(
3
,
4
)
7.
>>>
p
.
z
=
5
#
绑定了一个新的属性
8.
>>>
p
.
z
9.
5
10.
>>>
p
.
__dict__
11.
{
'x'
:
3
,
'y'
:
4
,
'z'
:
5
}
在上面,我们创建了实例
p
之后,给它绑定了一个新的属性
z
,这种动态绑定的功能虽然很有用,但它的代价是消耗了更多的内存。
因此,为了不浪费内存,可以使用
__slots__
来告诉
Python
只给一个固定集合的属性分配空
间,对上面的代码做一点改进,如下:
1.
class
Point
(
object
):
2.
__slots__
= (
'x'
,
'y'
)
#
只允许使用
x
和
y
3.
4.
def
__init__
(
self
,
x
=
,
y
=
):
5.
self
.
x
=
x
6.
self
.
y
=
y
上面,我们给
__slots__
设置了一个元组,来限制类能添加的属性。现在,如果我们想绑定一个
新的属性,比如
z
,就会出错了,如下:
1.
>>>
p
=
Point
(
3
,
4
)
2.
>>>
p
.
z
=
5
3.
---------------------------------------------------------------------------
4.
AttributeError Traceback
(
most recent call last
)
5.
<
ipython
-
input
-
648
-
625ed954d865
>
in
<
module
>()
6.
---->
1
p
.
z
=
5
7.
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
=
score
4.
5.
def
get_score
(
self
):
6.
return
self
.
_score
7.
8.
def
set_score
(
self
,
val
):
9.
if
val
<
:
10.
self
.
_score
=
11.
elif
val
>
100
:
12.
self
.
_score
=
100
13.
else
:
14.
self
.
_score
=
val
15.
16.
>>>
e
=
Exam
(
60
)
17.
>>>
e
.
get_score
()
18.
60
19.
>>>
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
=
score
4.
5.
@property
6.
def
score
(
self
):
7.
return
self
.
_score
8.
9.
@score
.
setter
10.
def
score
(
self
,
val
):
11.
if
val
<
:
12.
self
.
_score
=
13.
elif
val
>
100
:
14.
self
.
_score
=
100
15.
else
:
16.
self
.
_score
=
val
17.
18.
>>>
e
=
Exam
(
60
)
19.
>>>
e
.
score
20.
60
21.
>>>
e
.
score
=
90
22.
>>>
e
.
score
23.
90
24.
>>>
e
.
score
=
200
25.
>>>
e
.
score
26.
100
在上面,我们给方法
score
加上了
@property
,于是我们可以把
score
当成一个属性来用,
此时,又会创建一个新的装饰器
score.setter
,它可以把被装饰的方法变成属性来赋值。
另外,我们也不一定要使用
score.setter
这个装饰器,这时
score
就变成一个只读属性了:
1.
class
Exam
(
object
):
2.
def
__init__
(
self
,
score
):
3.
self
.
_score
=
score
4.
5.
@property
6.
def
score
(
self
):
7.
return
self
.
_score
8.
9.
>>>
e
=
Exam
(
60
)
10.
>>>
e
.
score
11.
60
12.
>>>
e
.
score
=
200
# score
是只读属性,不能设置值
13.
---------------------------------------------------------------------------
14.
AttributeError Traceback
(
most recent call last
)
15.
<
ipython
-
input
-
676
-
b0515304f6e0
>
in
<
module
>()
16.
---->
1
e
.
score
=
200
17.
18.
AttributeError
:
can
't set attribute
@property
把方法『变成』了属性
。
8 你不知道的 super
在类的继承中,如果重定义某个方法,该方法会覆盖父类的同名方法,但有时,我们希望能同时实现父类的功能,这时,我们就需要调用父类的方法了,可通过使用
super
来实现,比如:
1.
class
Animal
(
object
):
2.
def
__init__
(
self
,
name
):
3.
self
.
name
=
name
4.
def
greet
(
self
):
5.
'Hello, I am %s.'
%
self
.
name
6.
7.
class
Dog
(
Animal
):
8.
def
greet
(
self
):
9.
super
(
Dog
,
self
).
greet
()
# Python3
可使用
super().greet()
10.
'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
=
a
4.
self
.
b
=
b
5.
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.
"enter Base"
4.
"leave Base"
5.
6.
class
A
(
Base
):
7.
def
__init__
(
self
):
8.
"enter A"
9.
super
(
A
,
self
).
__init__
()
10.
"leave A"
11.
12.
class
B
(
Base
):
13.
def
__init__
(
self
):
14.
"enter B"
15.
super
(
B
,
self
).
__init__
()
16.
"leave B"
17.
18.
class
C
(
A
,
B
):
19.
def
__init__
(
self
):
20.
"enter C"
21.
super
(
C
,
self
).
__init__
()
22.
"leave C"
其中,
Base
是父类,
A, B
继承自
Base, C
继承自
A, B
,它们的继承关系是一个典型的『菱形继
承』,如下:
1.
Base
2.
/
\
3.
/
\
4.
A B
5.
\
/
6.
\
/
7.
C
现在,让我们看一下使用:
1.
>>>
c
=
C
()
2.
enter C
3.
enter A
4.
enter B
5.
enter
Base
6.
leave
Base
7.
leave B
8.
leave A
9.
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
=
True
3.
4.
class
Bar
(
object
):
5.
bar
=
True
6.
7.
def
echo
(
cls
):
8.
cls
9.
10.
def
select
(
name
):
11.
if
name
==
'foo'
:
12.
return
Foo
#
返回值是一个类
13.
if
name
==
'bar'
:
14.
return
Bar
15.
16.
>>>
echo
(
Foo
)
#
把类作为参数传递给函数
echo
17.
<
class
'__main__.Foo'
>
18.
>>>
cls
=
select
(
'foo'
)
#
函数
select
的返回值是一个类,把它赋给变量
cls
19.
>>>
cls
20.
__main__
.
Foo
8.5 熟悉又陌生的 type
在日常使用中,我们经常使用
object
来派生一个类,事实上,在这种情况下,
Python
解释器会
调用
type
来创建类。
这里,出现了
type
,没错,是你知道的
type
,我们经常使用它来判断一个对象的类型,比
如:
1.
class
Foo
(
object
):
2.
Foo
=
True
3.
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.
>>>
Foo
2.
<
class
'__main__.Foo'
>
3.
>>>
Foo
()
4.
<
__main__
.
Foo
object at
0x10c34f250
>
假设有下面的类:
1.
class
Foo
(
object
):
2.
foo
=
True
3.
def
greet
(
self
):
4.
'hello world'
5.
self
.
foo
用
type
来创建这个类,如下:
1.
def
greet
(
self
):
2.
'hello world'
3.
self
.
foo
4.
5.
Foo
=
type
(
'Foo'
, (
object
, ), {
'foo'
:
True
,
'greet'
:
greet
})
上面两种方式的效果是一样的,看下使用:
1.
>>>
f
=
Foo
()
2.
>>>
f
.
foo
3.
True
4.
>>>
f
.
greet
5.
<
bound method
Foo
.
greet of
<
__main__
.
Foo
object at
0x10c34f890
>>
6.
>>>
f
.
greet
()
7.
hello world
8.
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__
=
PrefixMetaclass
3. 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
.
name
7.
8. AttributeError
:
'Foo'
object has no attribute
'name'
9. >>>
10. >>>
f
.
my_name
11. 'foo'
12. >>>
f
.
my_bar
()
13. bar
14. >>>
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
.
prop
7.
8. AttributeError
:
'Bar'
object has no attribute
'prop'
9. >>>
b
.
my_prop
10. 'bar'
11. >>>
b
.
my_name
12. 'foo'
13. >>>
b
.
my_bar
()
14. bar
15. >>>
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
的普通类意味着调用