Python个人笔记-面向对象编程

面向对象编程——Object Oriented Programming,简称OOP。面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接受其他对象发过来的消息,并处理这些消息,计算机程序的执行就是在各个对象之间传递。

举例来说明面向过程和面向对象在程序流程上的不同之处:

假设要处理学生的成绩表,为表示学生的成绩,面向过程的程序可以用一个dict来表示:

std1 = {'name': 'Michael', 'score': 98}
std2 = {'name': 'Bob', 'score': 81}

打印学生成绩用函数实现:

def print_score(std):
    print('%s: %s' % (std['name'], std['score']))

如果采用面向对象的程序设计思想,首先思考的不是程序的执行过程,二是student这个数据类型应该被视为一个对象,它拥有name和score这两个属性(property):

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))

面向对象的程序的使用:

bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()

上例中,Student便是一个类Class,bart和lisa便是一个实例Instance。

类与实例

类起到模板的作用,因此,可以在创建实例的时候把认为必须绑定的属性通过init方法强制填进去:

class Student(object):
    
    def __init__(self, name, score):
        self.name = name
        self.score = score

当然也可以自由地给一个实例变量绑定属性,如bart.gender = 'female'

访问限制

在之前的student类的定义来看,外部代码依然可以自由修改实例中的name和score:bart.score = 99。如果让内部属性不被外部访问,可以把属性的名称前加上两个下划线,这样它就变成一个私有变量,只有内部可以访问,外部不能访问:

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))

通过如此的访问限制保护,代码更加健壮。如果外部想要获取name或score,可以通过增加get_name或get_score的方法。

需要注意,python中变量名类似xxx的,是特殊变量,可以直接访问。_xxx外部可以访问,但按照约定俗成的规定,一般将其默认视为私有变量。

student中的__name通过python解释器把这个变量改为了_student__name或类似的名称,也就是仍然可以从外部访问该变量。可以,但没必要。

注意一种错误写法

>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 设置__name变量!
>>> bart.__name
'New Name'

这样表面上是设置了name变量,但实际上这个__name和class内部的__name根本不是同一个变量,内部的__name已经被解释器自动更改为_Student__name,而外部代码给bart新增了一个__name变量。

继承和多态

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

eg:

class Animal(object):
    def run(self):
        print('Animal is running...')

class Dog(Animal):
    pass

class Cat(Animal):
    pass

dog = Dog()
dog.run()

cat = Cat()
cat.run()

我们也可以对子类增加一些方法,当增加的方法与基类中的某方法相同时,子类的方法覆盖基类的方法,代码运行时总会调用子类的方法,这就是继承的另一个好处——多态:

class Dog(Animal):
    def run(self):
        print("Dog is running")

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

开闭原则:

  1. 对扩展开放:允许新增Animal子类;
  2. 对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。
  • 静态语言 vs 动态语言

像java这种静态语言,如果需要传入Animal类型,则传入的对象必须是Animal类型或它的子类,否则将无法调用run()方法。

对于python这种动态语言,则不一定需要传入Animal类型,我们只需要保证传入的对象有一个run()方法即可。

获取对象信息

  • 判断对象类型,用type()函数

若要判断一个对象是否是函数,需要用types模块中定义的常量:

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
  • 也可以使用isinstance()函数:
>>> isinstance(dog, Animal)
True
  • 如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list:
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

类似xxx的属性或方法在python中有特殊用途,比如len方法返回长度,如果调用len()函数去获得一个函数的长度,实际上在len()函数内部自动去调用该对象的len方法,故下面的代码是等价的:

>>> len('ABC')
3
>>> 'ABC'.__len__()
3
  • 利用getattr(),setattr()和hasattr(),可以直接操作一个对象的状态:
>>> class MyObject(object):
...     def __init__(self):
...         self.x = 9
...     def power(self):
...         return self.x * self.x
...
>>> obj = MyObject()


>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19


>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: 'MyObject' object has no attribute 'z'


>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404


>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
>
>>> fn() # 调用fn()与调用obj.power()是一样的
81

实例属性和类属性

由于python是动态语言,根据类创建的实例可以任意绑定属性:

class Student(object):
    def __init__(self, name):
        self.name = name
    
s = Student('Bob')
s.score = 90

也可以给类本身绑定一个属性,可以在class中定义属性,这种属性是类属性,归类所有:

class Student(object):
    name = 'Student'

定义类属性后,这个属性虽然归类所有,但类的所有实例都可以访问:

>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student

相同名称的实例属性将屏蔽掉类属性,故尽量不要对实例属性和类属性使用相同的名字。

你可能感兴趣的:(Python个人笔记-面向对象编程)