0
点赞
收藏
分享

微信扫一扫

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 语言类似,我们使用类来自定义对象。

本章主要介绍以下几个方面:


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 = 0 , 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 [ 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 , 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 [ 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 . 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 = 0 , y = 0 ):

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 = 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 0

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 = 0 , y = 0 ):

3.                  self . x = x

4.                  self . y = y

5.

6.          def __getattr__ ( self , attr ):

7.                  if attr == 'z' :

8.                          return 0

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

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 = 0 , y = 0 ):

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 = 0 , y = 0 ):

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 < 0 :

10.                        self . _score = 0

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 < 0 :

12.                         self . _score = 0

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——类_python_02


 


我们在前面使用了 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 的普通类意味着调用


type 来创建它。


 


举报

相关推荐

python 类命名

python测试类

【python】类方法

python基础_类

Python-类

0 条评论