python学习笔记,面对对象部分
 
  OOP 
作为长期使用C++,java进行开发的程序员老说,OOP可以说是一种比较情切的程序设计思想了,所谓万物皆对象。
在python,几乎所有数据类型都可视为对象,甚至函数,python同样支持自定义对象。
  模块 
 
在了解python中实现OOP之前,先来描述一下模块的概念,如下是一个自定义模块的样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ' a test module ' __author__ = 'Ender'  import  sysdef  test ():    args = sys.argv     if  len (args)==1 :         print ('Hello, world!' )     elif  len (args)==2 :         print ('Hello, %s!'  % args[1 ])     else :         print ('Too many arguments!' ) if  __name__=='__main__' :    test() 
 
第10行中,在导入sys模块后,变量sys就指向了该模块,此后便可通过该变量访问这个模块中的功能。
例如第13行的argv变量,就是该模块中用于存储命令行中参数的list,argv中至少包含一个元素,即.py文件的名称。
例如使用如下命令调用时:
 
argv中的参数就是:
 
当我们在命令行运行hello模块文件时,python解释器把一个特殊的变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败。因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的用途就是运行测试。
例如使用命令行运行hello.py
1 2 3 4 >>> python3 hello.py Hello, world! >>> python hello.py Ender Hello, Ender! 
 
而导入hello模块时,不会打印任何东西,原因是没有调用test函数:
1 2 3 4 >>> python >>> import hello >>> hello.text() >>> Hello, world! 
 
  模块内作用域 
 
Python中没有严格的作用域限定方式,只能依靠某些特定的命名方式以及约定俗称来限定模块间对象的访问规则:
能够被外部访问的模块内对象只能以字母 开头 
特殊对象,如声明作者,调用判断,文档说明等,均以__开头以及结尾,如__author__, __name__, __doc__ 
非公开变量需要使用下划线开头,例如_xxx, __xxx,这样的对象不应该被直接引用。 
 
  类 
 
接下来我们看一些python中的相关操作。
Python中定义类通过class关键字
1 2 class  Student (object ):    pass  
 
class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
实例化则通过如下方式:
1 2 3 4 5 >>> bart = Student() >>> bart <__main__.Student object at 0 x10a67a590> >>> Student <class  '__main__ .Student '> 
 
创建实例后,可以为某一单独实例绑定属性:
1 2 3 >>> bart.name = 'Bart Simpson'  >>> bart.name 'Bart Simpson' 
 
而为类绑定属性,则需要使用一个特殊的方法:__init__:
1 2 3 4 class  Student (object ):    def  __init__ (self, name, score ):         self .name = name         self .score = score 
 
其中第一个参数self表示创建的实例本身,因此在该函数内会将各种属性绑定到self即self所指的实例。
1 2 3 4 5 >>>  bart = Student('Bart Simpson' , 59 )>>>  bart.name'Bart Simpson' >>>  bart.score59 
 
可见实例本身不用显式的传入实例。
为类创建方法:
1 2 3 4 5 6 7 8 class  Student (object ):    def  __init__ (self, name, score ):         self .name = name         self .score = score     def  print_score (self ):         print ('%s: %s'  % (self .name, self .score)) 
 
调用时直接使用:
1 2 >>> bart.print_score() Bart Simpson: 59  
 
可见self也不需要显示的传入
  访问限制 
 
有时为了更好的封装某个类,我们通常会对类中的变量进行访问权限的限制。在python中,没有特定的访问限制关键字,而是通过特殊的变量名进行限制。
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,例如:
1 2 3 4 5 6 7 8 class  Student (object ):    def  __init__ (self, name, score ):         self .__name = name         self .__score = score     def  print_score (self ):         print ('%s: %s'  % (self .__name, self .__score)) 
 
再次访问将得到如下结果:
1 2 3 4 5 >>> bart = Student('Bart Simpson' , 59 ) >>> bart.__name Traceback (most recent call last):   File "<stdin>" , line 1 , in  <module> AttributeError: 'Student'  object has no attribute '__name'  
 
此时可以通过为私有变量增加get和set方法创建接口来访问他们。
还需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。
而有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
而模块中讲到,python中作用域限定通常依靠约定俗称。其实是类中的访问权限限制也是如此。
不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:
1 2 >>>  bart._Student__name'Bart Simpson' 
 
但不同版本的Python解释器可能会把__name改成不同的变量名。
另外,在修改操作上,Python的访问限制也没有达到理想效果:
1 2 3 4 5 6 >>> bart = Student('Bart Simpson' , 59 ) >>> bart.get_name() 'Bart Simpson' >>> bart.__name = 'New Name'   >>> bart.__name 'New Name' 
 
表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是 一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量:
1 2 >>>  bart.get_name() 'Bart Simpson' 
 
  继承与多态 
 
在类的章节有讲到python中继承的方式:
1 2 3 4 5 Class Animal(object ):     def  run (self ):         print ('Animal is running...' )     def  Introduce (self ):         print ('I\'m an animal' ) 
 
如上类就是继承了object类的一个Animal类,我们可以继续编写它的子类:
1 2 3 4 5 6 7 8 9 10 11 12 13 class  Dog (Animal ):    def  run (self ):         print ('Dog is running...' )          def  eat (self ):         print ('Eating meat...' ) class  Cat (Animal ):    def  run (self ):         print ('Cat is running...' )          def  eat (self ):         print ('Eating Fish...' ) 
 
可见Dog和Cat类继承了Animal的方法,并且在其中重写了run方法
Python中创建一个类,实际上是定义了一种新的数据类型,也就意味着:
1 2 3 4 5 6 7 8 a = list () b = Animal() c = Dog() isinstance (a, list )  isinstance (b, Animal)  isinstance (c, Dog)  isinstance (c, Animal)  
 
可见c即是Dog也是Animal,那么如下操作也是合法的:
1 2 3 4 5 6 7 def  running (animal ):    animal.run() running(Animal())   running(Dog())   running(Cat())   
 
可见Dog与Cat的实例能够被接受且均能调用run()方法。
这样的特性能够让我们很方便的实现**“开闭”原则**即:
对扩展开放:允许新增Animal子类 
对修改封闭:不需要修改以来Animal类型的running等函数 
 
  静态语言与动态语言 
 
上面的例子中的running函数看起来是用了一个Animal类型的变量接受了参数,但由于Python是动态语言,实际上传入running函数前,编辑器并不知道将传入的是个怎样的类型,这就意味着,只要我们传入的参数包含run方法,那么代码就能正常运行,例如:
1 2 3 4 5 class  Timer (object ):    def  run (self ):         print ('Start...' ) running(Timer())   
 
而在Java和C++中,这样的参数传入是不被允许的。
这就是动态语言中的鸭子类型,它并不要求严格的继承体系,所谓一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。 
Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。
  获取对象信息 
 
当我们拿到一个实例对象时,有两种方法可以确定他们的类:
type() 
instance() 
 
  type 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 >>> type (123 ) <class  'int '> >>> type ('str ') <class  'str '> >>> type (None ) <type (None ) 'NoneType '> >>> type (abs ) <class  'builtin_function_or_method '> >>> type (a ) <class  '__main__ .Animal '> >>> type (123)==type (456) True >>> type (123)==int  True >>> type ('abc ')==type ('123') True >>> type ('abc ')==str  True >>> type ('abc ')==type (123) False 
 
如果需要判断一个对象是否是函数,则需要使用types模块:
1 2 3 4 5 6 7 8 9 10 11 12 >>> import types >>> def fn(): ...     pass ... >>> type (fn)==types.FunctionType True >>> type (abs)==types.BuiltinFunctionType True >>> type (lambda x: x)==types.LambdaType True >>> type ((x for  x in  range(10 )))==types.GeneratorType True 
 
  instance 
 
type获取类型较为方便,但是对于继承关系来说,就没有那么方便了,此时需要用到instance,例如对于如下继承链:
1 object -> Animal -> Dog -> Husky 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 >>> a = Animal() >>> d = Dog() >>> h  = Husky() >>> isinstance(h , Husky) True >>> isinstance(h , Dog) True >>> isinstance(h , Animal) True >>> isinstance(d, Dog) and isinstance(d, Animal) True >>> isinstance(d, Husky) False >>> isinstance('a' , str) True >>> isinstance(123 , int) True >>> isinstance(b'a' , bytes) True >>> isinstance([1 , 2 , 3 ], (list, tuple)) True >>> isinstance((1 , 2 , 3 ), (list, tuple)) True 
 
总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。
 
  获取对象属性 
 
如果想要获取一个对象的所有属性和方法,可以使用dir()函数,它将返回一个包含该对象的所有属性名的字符串list
1 2 >>> dir ('ABC' ) ['__add__' , '__class__' , '__contains__' , '__delattr__' , '__dir__' , '__doc__' , '__eq__' , '__format__' , '__ge__' , '__getattribute__' , '__getitem__' , '__getnewargs__' , '__gt__' , '__hash__' , '__init__' , '__init_subclass__' , '__iter__' , '__le__' , '__len__' , '__lt__' , '__mod__' , '__mul__' , '__ne__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__rmod__' , '__rmul__' , '__setattr__' , '__sizeof__' , '__str__' , '__subclasshook__' , 'capitalize' , 'casefold' , 'center' , 'count' , 'encode' , 'endswith' , 'expandtabs' , 'find' , 'format' , 'format_map' , 'index' , 'isalnum' , 'isalpha' , 'isascii' , 'isdecimal' , 'isdigit' , 'isidentifier' , 'islower' , 'isnumeric' , 'isprintable' , 'isspace' , 'istitle' , 'isupper' , 'join' , 'ljust' , 'lower' , 'lstrip' , 'maketrans' , 'partition' , 'removeprefix' , 'removesuffix' , 'replace' , 'rfind' , 'rindex' , 'rjust' , 'rpartition' , 'rsplit' , 'rstrip' , 'split' , 'splitlines' , 'startswith' , 'strip' , 'swapcase' , 'title' , 'translate' , 'upper' , 'zfill' ] 
 
其中类似__len__的方法再python中是有特殊用途的,如果调用len()函数试图获取一个对象的长度,实际上,在len()函数试图获取一个对象的长度,实际上,在len()函数内部是去调用该对象的__len__()方法,所以以下两行代码是等价的:
1 2 3 4 >>>  len ('ABC' )3 >>>  'ABC' .__len__()3 
 
这就意味着自定义类如果也想通过len函数获取长度,则只需要在我们自定义的类中实现__len__()方法即可:
1 2 3 4 5 6 class  MyDog (object ):    def  __len__ (self ):         retrun 100  dog = MyDog() len (dog)  
 
此外配合getattr(),setattr(),hasattr(),可以直接操作一个对象的状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 class  MyObject (object ):     def  __init__ (self ):          self .x = 9       def  power (self ):          return  self .x * self .x          obj = MyObject() hasattr (obj,'x' )  hasattr (obj,'y' )  setattr (obj,'y' ,19 )  hasattr (obj,'y' )  getattr (obj,'y' )  
 
如果获取一个没有的属性,则会报如下错误:
1 2 3 4 >>> getattr(obj, 'z' )  Traceback (most recent call last):   File "<stdin>" , line 1 , in  <module> AttributeError: 'MyObject'  object has no attribute 'z'  
 
getattr()还支持自定义错误返回值:
1 2 >>> getattr(obj, 'z' , 404 )  404 
 
此外,对象的方法也是可以操作的:
1 2 3 4 5 6 7 8 >>> hasattr(obj, 'power' )  True >>> getattr(obj, 'power' )  <bound method MyObject.power of <__main__.MyObject object at 0 x10077a6a0>> >>> fn = getattr(obj, 'power' )  >>> fn  <bound method MyObject.power of <__main__.MyObject object at 0 x10077a6a0>> >>> fn()  
 
这些操作在不确定自己获得的是一个怎样的对象时会派上用场:
1 2 3 4 def  readImage (fp ):    if  hasattr (fp, 'read' ):         return  readData(fp)     return  None  
 
  实例属性和类属性 
 
python中类的属性有实例属性与类属性的区别
实例属性是,在创建实例时,为每个实例都增加上的属性,操作如下:
1 2 3 4 5 6 class  Student (object ):    def  __init__ (self, name ):         self .name = name s = Student('Bob' ) s.score = 90  
 
而类属性则是为类绑定的属性:
1 2 class  Student (object ):    name = 'Student'  
 
这样的属性我们不实例化也可以访问到:
1 2 3 4 5 6 7 8 9 10 11 12 13 >>> Student.name Student >>> s = Student() >>> s.name Student >>> s.name = "Michael"  >>> s.name Michael >>> Student.name Student >>> del  s.name >>> s.name Student 
 
可以看到,在类和对象具有同名属性时,我们访问对象的该属性,优先访问到的是实例属性,因此不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性。
  属性绑定 
 
由于Python动态语言的特性,Python可以轻松实现在允许过程中对类进行操作,比如可以很轻松的为类绑定方法与属性:
1 2 class  Student (object ):    pass  
 
1 2 3 4 >>> s = Student() >>> s.name = 'Michael'   >>> print(s.name) Michael 
 
绑定方法需要用到types库:
1 2 3 4 5 6 7 8 >>> def set_age(self, age):  ...     self.age = age ... >>> from types import MethodType >>> s.set_age = MethodType(set_age, s)  >>> s.set_age(25 )  >>> s.age  25 
 
与属性相同,为特定对象绑定的方法无法在另一对象或类中使用,需要为类绑定方法才能让所有对象均可使用
  __slots__ 
python还提供了一种可以限制运行时绑定的属性的操作,比如只允许动态的为Student实例添加name和age属性,此时就可以用到__slots__变量:
1 2 class  Student (object ):    __slots__ = ('name' , 'age' )  
 
然后尝试为其绑定属性:
1 2 3 4 5 6 7 >>> s = Student()  >>> s.name = 'Michael'   >>> s.age = 25   >>> s.score = 99   Traceback (most recent call last):   File "<stdin>" , line 1 , in  <module> AttributeError: 'Student'  object has no attribute 'score'  
 
可见由于score没有被__slots__指明,因此绑定此属性时报错。
但__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
1 2 3 4 5 >>> class  GraduateStudent (Student ): ...     pass  ... >>> g  = GraduateStudent () >>> g .score  = 9999 
 
除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。
  @property 
 
在进行面向对象编程时,我们为了代码的健壮性,通常会对某个类的属性操作进行封装,让这些属性变成私有属性,然后通过对外暴露getter和setter方法来操作这些属性,这样我们在修改和访问这些属性时,就能增加一些类似类型检测,安全检测等诸如此类的操作。
通常我们可以这样写:
1 2 3 4 5 6 7 8 9 10 11 class  Student (object ):    def  get_score (self ):          return  self ._score     def  set_score (self, value ):         if  not  isinstance (value, int ):             raise  ValueError('score must be an integer!' )         if  value < 0  or  value > 100 :             raise  ValueError('score must between 0 ~ 100!' )         self ._score = value 
 
修改和查询score属性时,我们需要这样做:
1 2 3 4 5 6 7 8 >>> s = Student() >>> s.set_score(60 )  >>> s.get_score() 60 >>> s.set_score(9999 ) Traceback (most recent call last):   ... ValueError: score must between 0  ~ 100 ! 
 
访问score属性时,我们需要通过get_score函数进行访问。
python为我们提供了更为直观的访问方法,可以直接通过s.score进行访问和修改,并且还能实现如上的安全检测功能,这就是@property语法糖:
1 2 3 4 5 6 7 8 9 10 11 12 13 class  Student (object ):    @property     def  score (self ):         return  self ._score     @score.setter     def  score (self, value ):         if  not  isinstance (value, int ):             raise  ValueError('score must be an integer!' )         if  value < 0  or  value > 100 :             raise  ValueError('score must between 0 ~ 100!' )         self ._score = value 
 
把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
1 2 3 4 5 6 7 8 >>> s = Student() >>> s.score = 60   >>> s.score  60 >>> s.score = 9999  Traceback (most recent call last):   ... ValueError: score must between 0  ~ 100 ! 
 
还可以实现对属性的只读访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 class  Student (object ):    @property     def  birth (self ):         return  self ._birth     @birth.setter     def  birth (self, value ):         self ._birth = value     @property     def  age (self ):         return  2021  - self ._birth 
 
上面的age属性并没有设置setter方法,因此感官上来说是不能对其进行修改操作。
需要特别注意的是:
看如下例子:
1 2 3 4 5 6 class  Student (object ):         @property     def  birth (self ):         return  self .birth 
 
这时如果我们调用s.birth,首先执行上方定义的方法,在执行到return self.birth,又视为调用了对象s的score属性,于是又转到birth方法,形成无限层递归,由于没有对尾递归进行优化,最终将报栈溢出错误RecursionError
  MixIn 
 
Python中的类是支持多继承的,而MixIn设计思路是Python中为了更好的实现Python多继承的设计思路。
现在我们需要为如下几种动物创建类:
Dog - 狗勾🐕 
Bat - 蝙蝠🦇 
Parrot - 鹦鹉🦜 
Ostrich - 鸵鸟🦩 
 
如果我们想要将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23                 ┌───────────────┐                 │    Animal     │                 └───────────────┘                         │            ┌────────────┴────────────┐            │                         │            ▼                         ▼     ┌─────────────┐           ┌─────────────┐     │   Mammal    │           │    Bird     │     └─────────────┘           └─────────────┘            │                         │      ┌─────┴──────┐            ┌─────┴──────┐      │            │            │            │      ▼            ▼            ▼            ▼ ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐ │  MRun   │  │  MFly   │  │  BRun   │  │  BFly   │ └─────────┘  └─────────┘  └─────────┘  └─────────┘      │            │            │            │      │            │            │            │      ▼            ▼            ▼            ▼ ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐ │   Dog   │  │   Bat   │  │ Ostrich │  │ Parrot  │ └─────────┘  └─────────┘  └─────────┘  └─────────┘ 
 
在Java中采用单继承的方式,如果必须为每个种类定义一个类的话,想必定义这些类就需要一长串的代码。但是通常Java中会使用接口来解决这一问题。Python中则使用多继承:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class  Animal (object ):    pass  class  Mammal (Animal ):    pass  class  Bird (Animal ):    pass  class  RunnableMixIn (object ):    def  run (self ):         print ('Running...' ) class  FlyableMixIn (object ):    def  fly (self ):         print ('Flying...' )          class  Dog (Mammal, RunnableMixIn):    pass  class  Bat (Mammal, FlyableMixIn):    pass  class  Parrot (Bird, FlyableMixIn):    pass  class  Ostrich (Bird, RunnablebleMixIn):    pass  
 
其中以MixIn结尾的类的目的就是给一个类增加多个功能,这一在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次复杂的继承关系。
可见MixIn在思想上有些类似java中的接口,但是具体操作和实现上有很大的区别。
那么为什么java没有多继承机制呢?
因为采用多继承时,如果继承的两个类中有同名方法,那么调用该方法时编译器将不知道调用的是哪个父类中的方法。
那么Python中又是通过怎样的方式解决的呢?
实验过程参考如下博客:
该博客通过介绍拓扑排序以及C3算法,最后经过举例验证,得出结论:python中多继承的方法访问顺序遵从拓扑序。
该博客还补充了一个概念:MRO即method resolution order 
对于如下继承关系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class  A (object ):    def  foo (self ):         print ('A foo' )     def  bar (self ):         print ('A bar' ) class  B (object ):    def  foo (self ):         print ('B foo' )     def  bar (self ):         print ('B bar' ) class  C1 (A,B):    pass  class  C2 (A,B):    def  bar (self ):         print ('C2-bar' ) class  D (C1,C2):    pass  if  __name__ == '__main__' :    print (D.__mro__)     d=D()     d.foo()     d.bar() 
 
上述继承关系的图如下:
当我们调用d.foo()时,按照拓扑排序规则,解释器会先去寻找D类中是否拥有foo()方法,即在图中去掉D对应的点以及D的出边。
于是存在的点只剩下:[C1,C2,A,B,object],其中没有入度的点为C1和C2,根据左优先原则,寻找C1中是否包含foo()方法,去掉C1点以及C1的出边。
此时存在的点只剩下:[C2,A,B,object],其中没有入度的点为C2,寻找C2中是否包含foo()方法,去掉C2点以及C2的出边。
此时存在的点只剩下:[A,B,object],其中没有入度的点为A,B,根据左优先原则,寻找A中是否包含foo()方法,发现包含,于是调用该方法输出A foo
调用d.bar()时,同样也是按照拓扑序进行查找,最终输出C2-bar
而第26行代码,调用了D类的__mro__方法,将直接输出解释器寻找方法的先后顺序。因此最终的测试结果为:
1 2 3 (<class  '__main__.D' >, <class  '__main__.C1' >, <class  '__main__.C2' >, <class  '__main__.A' >, <class  '__main__.B' >, <class  'object' >) A foo C2-bar 
 
  定制类 
 
与__slots__类似,Python还具有很多类似命名的,有特殊作用的函数以及变量。下面来积累几个
  __str__ 
 
类似Java中的toString()方法,当对某个实例进行打印时,实际上就是调用该实例的__str__方法:
1 2 3 4 5 6 7 8 9 class  Student (object ):	def  __init__ (self, name ):         self .name = name     def  __str__ (self ):         return  'Student object (name: %s)'  % self .name print (Student('Michael' ))
 
  __repr__ 
 
用于显示对该对象的解释,即我们直接在命令行中输入对象名时显示的内容,就是通过调用对象的__repr__方法得到,通常情况下,我们会将__repr__方法与__str__方法设置为同一个:
未定义__repr__时:
1 2 3 >>> s = Student('Michael' ) >>> s <__main__.Student object at 0 x109afb310> 
 
1 2 3 4 5 6 class  Student (object ):    def  __init__ (self, name ):         self .name = name     def  __str__ (self ):         return  'Student object (name=%s)'  % self .name     __repr__ = __str__ 
 
定义后:
1 2 3 >>> s = Student('Michael' ) >>> s Student object (name: Michael) 
 
  __iter__&__next__ 
 
如果我们需要使用for...in来遍历某个对象,我们就需要实现__iter__与__next__方法。
__iter__用于返回一个迭代对象。即for n in object中的n 
__next__用于在循环中反复调用1中返回的迭代对象的该函数以得到下一个对象,知道遇到StopIteration错误时退出循环。 
 
例如我们写一个可遍历的斐波那契类:
1 2 3 4 5 6 7 8 9 10 class  Fib (object ):    def  __init__ ():         self .a, self .b = 0 , 1      def  __iter__ (self ):         return  self      def  __next__ (self ):         self .a, self .b = self .b, self .a + self .b         if  self .a > 100000 :             raise  StopIteration()         return  self .a 
 
1 2 3 4 5 6 7 8 9 10 11 >>> for  n in  Fib(): ...    print(n) ... 1 1 2 3 5 ... 46368 75025 
 
  __getitem__ 
 
用于对对象进行索引访问,如:
1 2 3 4 >>> Fib()[5 ]  Traceback (most recent call last):   File "<stdin>" , line 1 , in  <module> TypeError: 'Fib'  object does not support indexing 
 
该操作实际上时调用对象的__getitem__方法,并将索引当作参数传入。当我们实现该方法后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class  Fib (object ):    def  __init__ ():         self .a, self .b = 0 , 1      def  __iter__ (self ):         return  self      def  __next__ (self ):         self .a, self .b = self .b, self .a + self .b         if  self .a > 100000 :             raise  StopIteration()         return  self .a     def  __getitem__ (self, n ):         a, b = 1 , 1          for  x in  range (n):             a,b = b, a + b         return  a 
 
1 2 3 4 5 6 7 8 9 10 11 >>> f = Fib() >>> f[0 ] 1 >>> f[1 ] 1 >>> f[5 ] 8 >>> f[10 ] 89 >>> f[100 ] 573147844013817084101 
 
但是单纯的这样写,无法支持切片操作,切片操作实际上也是调用了对象的__getitem__方法,但是传入的是slice切片对象。由于动态语言python并不具备重载的能力,我们需要函数内部通过if进行判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class  Fib (object ):    def  __init__ ():         self .a, self .b = 0 , 1      def  __iter__ (self ):         return  self      def  __next__ (self ):         self .a, self .b = self .b, self .a + self .b         if  self .a > 100000 :             raise  StopIteration()         return  self .a     def  __getitem__ (self, n ):         if  isinstance (n, int ):   	        a, b = 1 , 1      	    for  x in  range (n):         	    a,b = b, a + b 	        return  a         if  isinstance (n, slice ):               start = n.start             stop = n.stop             if  start is  None :                 start = 0              a, b = 1 , 1              L = []             for  x in  range (stop):                 if  x >= start:                     L.append(a)                 a, b = b, a + b             return  L 
 
1 2 3 4 5 >>> f = Fib() >>> f[0 :5 ] [1 , 1 , 2 , 3 , 5 ] >>> f[:10 ] [1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , 55 , 89 ] 
 
如上并没有对step参数和负数进行处理,实际上这些都是可以处理的。
另外,如果将对象看作是dict,那么在该方法内还需要实现对object类型的参数第处理。
此外,除了__getitem__,还有___setitem__和__delitem__方法,用于删除和修改某个元素。
因此我们可以让自己的类创建的对象与list、tuple、dict没有什么区别。这都要归功于动态语言的鸭子类型
  __getattr__ 
 
该方法当我们访问某个对象中的未定义的属性时调用:
1 2 3 4 5 6 7 8 class  Student (object ):    def  __init__ (self ):         self .name = 'Michael'      def  __getattr__ (self, attr ):         if  attr=='score' :             return  99  
 
当调用不存在的属性时,比如score,Python解释器会试图调用__getattr__(self, 'score')来尝试获得属性,这样,我们就有机会返回score的值:
1 2 3 4 5 6 7 >>> s = Student() >>> s.name 'Michael' >>> s.score 99 >>> s.age None 
 
当访问没有定义的属性score和age时,就会调用__getattr__函数,由于该函数中未包含对age的处理,于是返回了默认返回值None。
要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:
1 2 3 4 5 6 class  Student (object ):    def  __getattr__ (self, attr ):         if  attr=='age' :             return  lambda : 25          raise  AttributeError('\'Student\' object has no attribute \'%s\''  % attr) 
 
返回函数也是完全可以的:
1 2 3 4 5 class  Student (object ):    def  __getattr__ (self, attr ):         if  attr=='age' :             return  lambda : 25  
 
只是调用方式要变为:
 
该方法在写SDK时运用广泛。
有时我们可能需要给每个URL对应的API都写一个方法,API一旦改动,SDK也要改,利用动态的__getattr__,我们可以写一个链式调用:
1 2 3 4 5 6 7 8 9 10 11 class  Chain (object ):    def  __init__ (self, path=''  ):         self ._path = path              def  __getattr__ (self, path ):         return  Chain('%s/%s'  % (self ._path, path))          def  __str__ (self ):         return  self ._path          __repr__ = __str__ 
 
这样我你们进行如下调用时,就能自由的获取各式各样的链接:
1 2 >>> Chain().status.user.timeline.list '/status/user/timeline/list' 
 
每次访问未定义参数时,都会调用对象的__getattr__方法,在此方法内,我们使用传入对象的_path以及传入的参数创建一个Chain对象,并返回,这样就能与后面属性调用组合,形成链式调用。
  __call__ 
 
当我们需要直接对实例进行函数调用时,就会调用__call__函数:
1 2 3 4 5 6 class  Student (object ):    def  __init__ (self, name ):         self .name = name     def  __call__ (self ):         print ('My name is %s.'  % self .name) 
 
1 2 3 >>> s = Student('Michael' ) >>> s()  My name is Michael. 
 
有了这个参数,那么完全可以把对象看成函数,把函数看成对象。
此处补充一个区分对象与函数的方法:Callable
1 2 3 4 5 6 7 8 9 10 >>> callable(Student()) True >>> callable(max) True >>> callable([1 , 2 , 3 ]) False >>> callable(None) False >>> callable('str' ) False 
 
可见,函数即“可调用”对象。
有时我们会看到一些将参数放入URL中的REST API,比如GitHub的API:
 
调用时,我们需要把:user替代为实际用户名,我们需要写出如下调用式:
1 Chain().users('michael' ).repos 
 
此时在之前的链式调用的基础上,我们看到中间多出来一个类似函数调用的形式,因此我们需要将其中一环变成可调用对象,就需要用到我们的__call__,我们可以这样实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class  Chain (object ):    def  __init__ (self, path=''  ):         self ._path = path              def  __getattr__ (self, path ):         return  Chain('%s/%s'  % (self ._path, path))          def  __str__ (self ):         return  self ._path          def  __call__ (self, users ):         return  Chain('%s/%s'  % (self .path, users))          __repr__ = __str__ 
 
这样最外层的Chain()创建了一个对象,访问users属性,由于不存在该属性,进入__getattr__方法,并返回一个新的对象,该对象与之后的('michael')构成函数调用,于是调用该对象的__call__方法,再次返回一个新对象,最后与repos构成访问属性。最终完成链式调用。
  枚举类型 
 
与java一样,python也提供枚举类型。
引入enum类后即可创建:
1 2 3 from  enum import  EnumMonth = Enum('Month' , ('Jan' , 'Feb' , 'Mar' , 'Apr' , 'May' , 'Jun' , 'Jul' , 'Aug' , 'Sep' , 'Oct' , 'Nov' , 'Dec' )) 
 
这样我们就可以这样访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 for  name,member in  Month.__members__.items():    print (name, '=>' , member, ',' , member.value) 
 
其中__members__是Month中的一个特殊属性,该属性的类型是mappingproxy,具有如下特性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27           from  types import  MappingProxyTypeindex_a = {'a'  : 'b' } a_proxy = MappingProxyType(index_a) print (a_proxy)a_proxy['a' ] index_a['b' ] = 'bb'  print (a_proxy)  
 
如果需要更精确的控制枚举类型,可以用一个派生自Enum的自定义类创建:
1 2 3 4 5 6 7 8 9 10 11 for  enum import  Enum@unique class  Weekday (Enum ):    sun = 0      Mon = 1      Tue = 2      Wed = 3      Thu = 4      Fri = 5      Sat = 6  
 
其中装饰器@unique用以检查并保证没有重复值
这样创建的枚举类型可以用如下方式访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 >>> day1 = Weekday.Mon >>> print(day1) Weekday.Mon >>> print(Weekday.Tue) Weekday.Tue >>> print(Weekday['Tue' ]) Weekday.Tue >>> print(Weekday.Tue.value) 2 >>> print(day1 == Weekday.Mon) True >>> print(day1 == Weekday.Tue) False >>> print(Weekday(1 )) Weekday.Mon >>> print(day1 == Weekday(1 )) True >>> Weekday(7 ) Traceback (most recent call last): ... ValueError: 7  is not a valid Weekday >>> for  name, member in  Weekday.__members__.items(): ...     print(name, '=>' , member) ... Sun => Weekday.Sun Mon => Weekday.Mon Tue => Weekday.Tue Wed => Weekday.Wed Thu => Weekday.Thu Fri => Weekday.Fri Sat => Weekday.Sat 
 
  元类 
 
动态语言与静态语言的差别在于函数于类的定义,动态语言并不是在编译时创建类,而是在运行时创建类。比如如下类的创建:
1 2 3 class  Hello (object ):    def  hello (self, name='world'  ):         print ('Hello. %s.'  % name) 
 
我们可以将该类写到一个模块hello.py,再通过另一个模块来引入该模块,查看类创建的效果:
1 2 3 4 5 6 from  hello import  Helloh = Hellp() h.hello() print (type (Hello))print (type (h))
 
引入模块时,python解释器就会依次执行模块中的所有语句,执行hello.py的结果就是创建了一个Hello对象。为什么说是对象呢。Hello明明是类啊。Python中,万物接对象,包括我们创建的类。我们创建的类,实际上也是type类的一个对象。
从上例的输出结果可以看出:
1 2 3 Hello, world. <class  'type' > <class  'hello.Hello' > 
 
Hello是一个class,它的类型就是type,而h是一个实例,它的类型就是class Hello。
type()函数出了可以看到对象的类型外,还可以用于动态的创建类:
1 2 3 4 5 6 7 8 def  fn (self, name = 'world'  ):    print ('Hello, %s.'  % name) Hello = type ('Hello' , (object ,), dict (hello = fn)) h = Hello() h.hello() print (type (Hello))print (type (h))
 
输出结果:
1 2 3 Hello, world. <class  'type' > <class  'hello.Hello' > 
 
利用type()动态的创建类时,需要提供三个参数:
class的名称。 
继承的父类集合,需要传入tuple,注意tuple的单元素写法。 
class的方法名称于函数进行绑定,此处将方法fn绑定到hello上 
 
通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
可见,在动态操作类这件事上,动态语言比如python比静态语言,比如java方便很多。
除了使用type外,还能使用metaclass对类的创建进行控制
 
metaclass直译为原类。
实例的创建可由类控制,那么类的创建则是由元类进行控制。
下面引用一个例子:
定义一个metaclass可以给我们自定义的MyList增加一个add方法:
定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass:
1 2 3 4 5 class  ListMetaclass (type ):    def  __new__ (cls, name, bases, attrs ):         attrs['add' ] = lambda  self , value: self .append(value)         return  type .__new__(cls, name, bases, attrs) 
 
接下来我们利用这个原类,来创建我们自己的list类:
1 2 class  MyList (list , metaclass = ListMetaclass):    pass  
 
此时,在定义类时,我们指定使用ListMetaclass来定制类,传入关键字参数metaclass
此后,Python解释器在创建MyList时,要通过ListMetaclass.__new__()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。
__new__()方法接收到的参数依次是:
当前准备创建的类的对象 
类的名字 
类继承的父类集合 
类方法集合 
 
下面我们试着创建一个MyList对象:
1 2 3 4 >>> L = MyList() >>> L.add(1 ) >> L [1 ] 
 
普通的list并没有add()方法。
动态的修改类的定义将在编写ORM中起到非常大的作用。
ORM——‘Object Relational Mapping’,即对象——关系映射。也即是将数据库中的一个表于一个类对应,一行与一个对象对应。
我们尝试利用metaclass来实现ORM中的保存功能:
编写底层模块的第一步,就是先把调用接口写出来。比如,使用者如果使用这个ORM框架,想定义一个User类来操作对应的数据库表User,我们期待他写出这样的代码: 
 
1 2 3 4 5 6 7 8 9 10 11 class  User (Model ):         id  = IntegerField('id' )     name = StringField('username' )     email = StringField('email' )     password = StringField('password' ) u = User(id =12345 , name='Michael' , email='test@orm.org' , password='my-pwd' ) u.save() 
 
其中,父类Model和属性类型StringField、IntegerField是由ORM框架提供的,剩下的方法比如save()全部由父类Model自动完成。虽然metaclass的编写会比较复杂,但ORM的使用者用起来却异常简单。
接下来我们实现Field类,负责保存数据库表的字段名和字段类型: 
 
1 2 3 4 5 6 7 8 class  Field (object ):    def  __init__ (self, name, column_type ):         self .name = name         self .column_type = column_type     def  __str__ (self ):         return  '<%s:%s>'  % (self .__class__.__name__, self .name) 
 
在此基础上,定义各种类型的子类:
1 2 3 4 5 6 7 8 9 class  StringField (Field ):    def  __init__ (self, name ):         super (StringField, self ).__init__(name, 'varchar(100)' )   class  IntegerField (Field ):    def  __init__ (self, name ):         super (IntegerField, self ).__init__(name, 'bigint' ) 
 
接下来编写ModelMetaclass: 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class  ModelMetaclass (type ):    def  __new__ (cls, name, bases, attrs ):         if  name=='Model' :             return  type .__new__(cls, name, bases, attrs)         print ('Found model: %s'  % name)         mappings = dict ()         for  k, v in  attrs.items():             if  isinstance (v, Field):                 print ('Found mapping: %s ==> %s'  % (k, v))                 mappings[k] = v         for  k in  mappings.keys():             attrs.pop(k)         attrs['__mappings__' ] = mappings          attrs['__table__' ] = name          return  type .__new__(cls, name, bases, attrs) 
 
使用元类ModelMetaclass创建基类Model: 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class  Model (dict , metaclass=ModelMetaclass):    def  __init__ (self, **kw ):         super (Model, self ).__init__(**kw)     def  __getattr__ (self, key ):         try :             return  self [key]         except  KeyError:             raise  AttributeError(r"'Model' object has no attribute '%s'"  % key)     def  __setattr__ (self, key, value ):         self [key] = value     def  save (self ):         fields = []         params = []         args = []         for  k, v in  self .__mappings__.items():             fields.append(v.name)             params.append('?' )             args.append(getattr (self , k, None ))         sql = 'insert into %s (%s) values (%s)'  % (self .__table__, ',' .join(fields), ',' .join(params))         print ('SQL: %s'  % sql)         print ('ARGS: %s'  % str (args)) 
 
当用户定义一个class User(Model)时,Python解释器首先在当前类User的定义中查找metaclass,如果没有找到,就继续在父类Model中查找metaclass,找到了,就使用Model中定义的metaclass的ModelMetaclass来创建User类,也就是说,metaclass可以隐式地继承到子类,但子类自己却感觉不到。
在ModelMetaclass中,一共做了几件事情:
排除掉对Model类的修改; 
在当前类(比如User)中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到一个__mappings__的dict中,同时从类属性中删除该Field属性,否则,容易造成运行时错误(实例的属性会遮盖类的同名属性); 
把表名保存到__table__中,这里简化为表名默认为类名。 
 
在Model类中,就可以定义各种操作数据库的方法,比如save(),delete(),find(),update等等。
使用如下代码进行测试:
1 2 u = User(id =12345 , name='Michael' , email='test@orm.org' , password='my-pwd' ) u.save() 
 
输出如下:
1 2 3 4 5 6 7 Found model: User Found mapping: email ==> <StringField:email> Found mapping: password ==> <StringField:password> Found mapping: id ==> <IntegerField:uid> Found mapping: name ==> <StringField:username> SQL: insert into User (password,email,username,id) values (?,?,?,?) ARGS: ['my-pwd', 'test@orm.org', 'Michael', 12345] 
 
这样一来我们就能直接得到对应操作的SQL语句,允许即可。