如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有name和score这两个属性(Property)。
如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。

  1. class Student(object):
  2. def __init__(self, name, score)
  3. self.name = name
  4. self.score = score
  5. def print_scope(self):
  6. print('%s: %s' % (self.name, self.score))

给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。面向对象的程序写出来就像这样:

  1. bart = Student('Bart Simpson', 59)
  2. lisa = Student('Lisa Simpson', 87)
  3. bart.print_score()
  4. lisa.print_score()

Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student。
所以,面向对象的设计思想是抽象出Class,根据Class创建Instance

类和实例

在Python中,定义类是通过class关键字。

  1. # class后面紧跟着类名Student,类名通常是大写开头
  2. # 紧接着是(object),表示该类是从哪个类继承下来的
  3. # 通常如果没有合适的继承类就使用object类,这是所有类最终都会继承的类。
  4. class Student(object):
  5. pass
  6. # 创建实例是通过类名+()
  7. bart = Student()

可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性:

  1. >>> bart.name = 'Bart Simpson'
  2. >>> bart.name
  3. 'Bart Simpson'

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把namescore等属性绑上去:

  1. class Student(object):
  2. # __init__方法的第一个参数永远是self,表示创建的实例本身
  3. def __init__(self, name, score):
  4. self.name = name
  5. self.score = score
  6. bart = Student('Bart Simpson', 59)

数据分装

内部定义访问数据的函数,就把“数据”给封装起来了。
这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法:

  1. class Student(object):
  2. def __init__(self, name, score):
  3. self.name = name
  4. self.score = score
  5. # 除了第一个参数是self外,其他和普通函数一样。调用时self不用传
  6. def print_score(self):
  7. print('%s: %s' % (self.name, self.score))

访问限制

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。

  1. class Student(object):
  2. def __init__(self, name, score):
  3. self.__name = name
  4. self.__score = score
  5. def print_score(self):
  6. print('%s: %s' % (self.__name, self.__score)

如果外部代码需要获取namescore,可以给Student增加get_nameget_score方法:

  1. class Student(object):
  2. ...
  3. def get_name(self):
  4. return self.__name
  5. def get_score(self):
  6. return self.__score

如果外部代码需要修改score,可以给Student增加set_score方法:

  1. class Student(object):
  2. ...
  3. def set_score(self, score):
  4. if 0 <= score <= 100:
  5. self.__score = score
  6. else:
  7. raise ValueError('bad scord')

在 Python 中,变量名类似__xxx__的,也就是双下划线开头且双下划线结尾的是特殊变量,特殊变量是可以直接访问的,不是私有变量,所以不能用__name____score__这样的变量名。

可能会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

双下划线开头的实例变量其实外部也可以访问。不能直接访问__name是因为Python解释器对外把__name 变量改成了_Student__name,所以仍然可以通过_Student__name来访问__name变量。
但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。

继承和多态

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

  1. # 编写一个名为`Animal`的class,有一个run()方法可以直接打印
  2. class Animal(object):
  3. def run(self):
  4. print('Animal is running...')
  5. # 需要编写DOg和Cat类的时候,可以直接从 Animal类继承
  6. class Dog(Animal):
  7. pass
  8. class Cat(Animal):
  9. pass

对于DogCat来说,Animal就是它的父类。对Animal来说,DogCat就是他的子类。

继承

子类获得了父类的全部功能

  1. >>> dog = Dog()
  2. >>> dog.run()
  3. Animal is running...

也可以对子类增加方法:

  1. class Dog(Animal):
  2. def run(self):
  3. print('Dog is running...')
  4. def eat(self):
  5. print('Eating meat...')
  • 要确定一个类是否是另一个类的子类,可使用内置方法issubclass ```python class Filter: pass class SPAMFilter(Filter): pass

issubclass(SPAMFilter, Filter) True issubclass(Filter, SPAMFilter) False ```

  • 如果你有一个类,并想知道它的基类,可访问其特殊属性__bases__

    1. >>> SPAMFilter.__bases__
    2. (<class __main__.Filter at 0x171e40>,)
    3. >>> Filter.__bases__
    4. (<class 'object'>,)
  • 同样,要确定对象是否是特定类的实例,可使用isinstance

    1. >>> s = SPAMFilter()
    2. >>> isinstance(s, SPAMFilter)
    3. True
    4. >>> isinstance(s, Filter)
    5. True
    6. >>> isinstance(s, str)
    7. False
  • 如果你要获悉对象属于哪个类,可使用属性__class__

    1. >>> s.__class__
    2. <class __main__.SPAMFilter at 0x1707c0>

多态

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态

在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。

  1. a = Animal() # a是Animal的类型
  2. d = Dog() # d是Dog的类型
  3. isinstance(a, Animal) # True
  4. isinstance(d, Dog) # True
  5. # !!!注意:
  6. isinstance(d, Animal) # True

上面代码中,d不仅是Dog的类型,还是Animal的类型。

对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在AnimalDog、还是Cat对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

  • 对扩展开放:允许新增Animal子类;
  • 对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。
  1. >>> def run_twice(animal):
  2. ... animal.run()
  3. >>>run_twice(Animal()):
  4. Animal is running...
  5. >>> run_twice(Dog())
  6. Dog is running...

静态语言 vs 动态语言

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:

  1. class Timer(object):
  2. def run(self):
  3. print('Start...')

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。

获取对象信息

type()

判断对象类型。

  1. # 基本类型都可以用type()判断:
  2. >>> type(123)
  3. <class 'int'>
  4. >>> type('str')
  5. <class 'str'>
  6. >>> type(None)
  7. <type(None) 'NoneType'>
  8. # 如果一个变量指向函数或者类,也可以用type()判断:
  9. >>> type(abs)
  10. <class 'builtin_function_or_method'>
  11. >>> type(a)
  12. <class '__main__.Animal'>
  1. # 判断一个对象是否是函数,可以使用types模块中定义的常量:
  2. >>> import types
  3. >>> def fn():
  4. ... pass
  5. ...
  6. >>> type(fn)==types.FunctionType
  7. True
  8. >>> type(abs)==types.BuiltinFunctionType
  9. True
  10. >>> type(lambda x: x)==types.LambdaType
  11. True
  12. >>> type((x for x in range(10)))==types.GeneratorType
  13. True

isinstance()

isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。

  1. >>> a = Animal()
  2. >>> d = Dog()
  3. >>> h = Husky()
  4. >>> isinstance(h, Dog)
  5. True
  6. >>> isinstance(h, Animal)
  7. True
  8. >>> isinstance(d, Dog) and isinstance(d, Animal)
  9. True
  10. >>> isinstance(d, Husky)
  11. False

能用type()判断的基本类型也可以用isinstance()判断:

  1. >>> isinstance('a', str)
  2. True
  3. >>> isinstance(123, int)
  4. True
  5. >>> isinstance(b'a', bytes)
  6. True

并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:

  1. >>> isinstance([1, 2, 3], (list, tuple))
  2. True
  3. >>> isinstance((1, 2, 3), (list, tuple))
  4. True

isinstance() 与 type() 区别:

  • type() 不会认为子类是一种父类类型,不考虑继承关系。
  • isinstance() 会认为子类是一种父类类型,考虑继承关系。

如果要判断两个类型是否相同推荐使用 isinstance()。

  1. class A(object):
  2. pass
  3. class B(A):
  4. pass
  5. b = B()
  6. >>> print(type(b) == B)
  7. True
  8. >>>print(type(b) == A)
  9. False
  10. >>> print(isinstance(b, B))
  11. True
  12. >>> print(isinstance(b, A))
  13. True

dir()

获得一个对象的所有属性和方法,返回一个包含字符串的list。

  1. >>> dir('ABC')
  2. ['__add__', '__class__',..., 'capitalize',...]

类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法:

  1. >>> len('ABC')
  2. 3
  3. >>> 'ABC'.__len__()
  4. 3

自己写的类也想用len(myObj)的话,就自己写一个__len__()方法:

  1. class MyDog(object):
  2. def __len__(self):
  3. return 100
  4. dog = MyDog()
  5. >>> len(dog)
  6. 100

配合getattr()setattr()hasattr(),可以操作一个对象的状态:

  1. class MyObject(object):
  2. def __init__(self):
  3. self.x = 9
  4. def power(self):
  5. return self.x * self.x
  6. obj = MyObject()
  7. hasattr(obj, 'x') # 有属性x吗? True
  8. hasattr(obj, 'y') # False
  9. setattr(ibj, 'y', 19) # 设置一个属性'y'
  10. getattr(obj, 'y') # 获取属性'y', 19
  11. obj.y # 获取属性'y', 19
  12. # 可以传入一个default参数,如果属性不存在,就返回默认值(不设置默认值,查询不存在的属性会报错):
  13. getattr(obj, 'z', 404) # 获取属性'z',不存在的话返回默认值404

也可以获得对象的方法:

  1. >>> hasattr(obj, 'power') # 有属性'power'吗?
  2. True
  3. >>> getattr(obj, 'power') # 获取属性'power'
  4. <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
  5. >>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
  6. >>> fn # fn指向obj.power
  7. <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
  8. >>> fn() # 调用fn()与调用obj.power()是一样的
  9. 81

小结

通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。
如果可以直接写:sum = obj.x + obj.y就不要写sum = getattr(obj, 'x') + getattr(obj, 'y')

实例属性和类属性

根据类创建的实例可以绑定任意属性。
给实例绑定属性的方法是通过实例变量,或者通过self变量:

  1. class Student(object):
  2. def __init__(self, name):
  3. self.name = name
  4. s = Student('Bob')
  5. s.score = 90

如果Student类本身需要绑定一个属性,可以直接在class中定义属性,这种属性是类属性,归Student类所有,但是类的所有实例都可以访问到。

  1. class Student(object):
  2. name = 'Student'
  3. s = Student()
  4. print(s.name) # Student
  5. s.name = 'Michael' # 给实例绑定name属性
  6. print(s.name) # Michael
  7. print(Student.name) # Student
  8. del s.name # 如果删除实例的name属性
  9. print(s.name) # Student
  1. # 为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:
  2. class Student(object):
  3. count = 0
  4. def __init__(self, name):
  5. self.name = name
  6. Student.count += 1