面向对象编程
-
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
-
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念
-
例如:
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()
-
面向对象的三大特征:封装,继承,多态
1.1类和实例
class
后面紧接着是类名,即Student
,类名通常是大写开头的单词,紧接着是(object)
,表示该类是从哪个类继承下来的,通常,如果没有合适的继承类,就使用object
类,这是所有类最终都会继承的类。
class Student(object):
pass
-
创建实例是通过类名+()实现的:
>>> bart = Student() >>> bart <__main__.Student object at 0x10a67a590> >>> Student <class '__main__.Student'>
可以看到,变量
bart
指向的就是一个Student
的实例,后面的0x10a67a590
是内存地址,每个object的地址都不一样,而Student
本身则是一个类。 -
把一些我们认为必须绑定的属性强制填写进类中。通过定义一个特殊的
__init__
方法,在创建实例的时候,就等属性绑上去:class Student(object): def __init__(self, name, score): self.name = name self.score = score
注意到
__init__
方法的第一个参数永远是self
,表示创建的实例本身 -
有了
__init__
方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__
方法匹配的参数,但self
不需要传,Python解释器自己会把实例变量传进去
1.2访问限制
-
实例的变量名如果以
__
开头,就变成了一个私有变量(private) -
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮
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) >>> bart.__name Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute '__name'
-
双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问
__name
是因为Python解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
变量:>>> bart._Student__name 'Bart Simpson' 但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。
-
总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。
-
最后注意下面的这种错误写法:
>>> bart = Student('Bart Simpson', 59) >>> bart.get_name() 'Bart Simpson' >>> bart.__name = 'New Name' # 设置__name变量! >>> bart.__name 'New Name'
表面上看,外部代码“成功”地设置了
__name
变量,但实际上这个__name
变量和class内部的__name
变量不是一个变量!内部的__name
变量已经被Python解释器自动改成了_Student__name
,而外部代码给bart
新增了一个__name
变量。不信试试:>>> bart.get_name() # get_name()内部返回self.__name 'Bart Simpson'
-
而是通过get。set方法
def get_score(self): return self.__score def set_score(self, score): self.__score = score
1.3继承和多态
-
例如
class Animal(object): def run(self): print('Animal is running...') class Dog(Animal): #括号内是继承的对象 pass class Cat(Animal): pass
对于
Dog
来说,Animal
就是它的父类,对于Animal
来说,Dog
就是它的子类。Cat
和Dog
类似。 -
继承有什么好处?最大的好处是子类获得了父类的全部功能。由于
Animial
实现了run()
方法,因此,Dog
和Cat
作为它的子类,什么事也没干,就自动拥有了run()
方法: -
当子类和父类都存在相同的
run()
方法时,我们说,子类的run()
覆盖了父类的run()
,在代码运行的时候,总是会调用子类的run()
。这样,我们就获得了继承的另一个好处:多态。
静态语言和动态语言:
- 静态语言:java就是静态语言,如果参数要传入animal类型,则传入的对象必须是animal或者它的子类对象
- 动态语言:python,就不一定传入animal类型了,我们只需要传入与animal有相同方法的对象即可
1.4获取对象信息
当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?
判断对象类型 type()
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>
-
判断是否是基本数据类型,直接写int,str等
>>> type(123)==type(456) True >>> type(123)==int True >>> type('abc')==type('123') True >>> type('abc')==str True >>> type('abc')==type(123) False
-
判断一个对象是否为函数,可以用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(d, Dog) and isinstance(d, Animal)
True
并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”
使用dir()
-
如果要获得一个对象的所有属性和方法,可以使用
dir()
函数,它返回一个包含字符串的list>>> dir('ABC') ['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill','upper']
-
此方法可以查看出类中包含的方法。例如在Python中,如果你调用
len()
函数试图获取一个对象的长度,实际上,在len()
函数内部,它自动去调用该对象的__len__()
方法,所以,下面的代码是等价的:>>> len('ABC') 3 >>> 'ABC'.__len__() 3
我们自己写的类,如果也想用
len(myObj)
的话,就自己写一个__len__()
方法:>>> class MyDog(object): ... def __len__(self): ... return 100 ... >>> dog = MyDog() >>> len(dog) 100
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
也可以获得对象的方法:
>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81
1.5实例属性和类属性
类属性可以理解为静态属性?
-
创建实例属性:
-
实例绑定属性的方法是通过实例变量,或者通过
self
变量class Student(object): def __init__(self, name): self.name = name s = Student('Bob') s.score = 90
-
创建类属性:
-
class Student(object): name = 'Student'
-
>>> 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
-
例子:
为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:
class Student(object):
count = 0
def __init__(self, name):
self.name = name
Student.count+=1