天天看点

Python——类

目录

​​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.                

print

'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.                 

print

'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.                 

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.

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.                

print

'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.                 

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

.

name

4.         

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

+

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.                 

print

'Hello, I am %s.'

%

self

.

name

6.

7.

class

Dog

(

Animal

):

8.         

def

greet

(

self

):

9.                 

print

'WangWang.., I am %s.'

%

self

.

name

10.

11.

class

Cat

(

Animal

):

12.         

def

greet

(

self

):

13.                 

print

'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.                 

print

'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.                 

print

'Hello, '

,

cls

6.                 

print

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.                 

print

'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.                         

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.

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.

>>>

print

Foo

(

'ethan'

)

6.

<

__main__

.

Foo

object at

0x10c37aa50

>

在上面,我们使用

print

打印一个实例对象,但如果我们想打印更多信息呢,比如把

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

>

可以看到,使用

print

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.

...

print

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.                         

print

'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.               

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

.

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__

是用

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

=

,

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.                 

print

'Hello, I am %s.'

%

self

.

name

6.

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

=

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.                 

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.

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.         

print

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.

>>>

print

Foo

2.

<

class

'__main__.Foo'

>

3.

>>>

print

Foo

()

4.

<

__main__

.

Foo

object at

0x10c34f250

>

假设有下面的类:

1.

class

Foo

(

object

):

2.        

foo

=

True

3.         

def

greet

(

self

):

4.                 

print

'hello world'

5.                 

print

self

.

foo

type

来创建这个类,如下:

1.

def

greet

(

self

):

2.         

print

'hello world'

3.         

print

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

)是用来创建类(对象)的可调用对象。这里的可调用对象可以是函数或者类等。但一般情况下,我们使用类作为元类。对于实例对象、类和元类,我们可以用下面的图来描述:

Python——类

我们在前面使用了 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

的普通类意味着调用