一,概述
- 面向对象编程(oop)和面向程序编程的不同
- 数据封装、继承和多态是面向对象的三大特点
- 关于类和实例
类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响
二,类和实例
1.步骤
- 定义类
- 创建实例(+属性) ~属性的调用
- 数据封装--也叫定义类的方法 ~方法的调用
~2,3本质上都是函数,特殊之处在于1)有self这个实例变量,但是调动时不需要写;2)语法为self.***(属性)
【1】定义类
class Student(object):
class+
类名,即Student
(类名通常是大写开头的单词)+(object)
表示该类是从哪个类继承下来的,通常用object
类,这是所有类最终都会继承的类。
【2】定义:实例属性的函数---‘创建实例’
类可以起到模板的作用,通过定义一个特殊的__init__
方法,在创建实例的时候,就把name
,score
等属性绑上去:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
注意:
1.__init__
方法的第一个参数永远是self(
创建的实例本身),因此,可以把各种属性绑定到self
,因为self
就指向创建的实例本身。
2.__init__
方法,在创建实例的时候,不能传入空的参数,必须传入与__init__
方法匹配的参数,但self
不需要传,Python解释器自己会把实例变量传进去:
3.特殊方法“__init__”前后分别有两个下划线!!!
~属性的调用
>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59
4.自由绑定属性
>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'
【4】定义:调用实例数据的函数 --‘数据封装’or‘类的方法’
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))
定义一个方法,除了第一个参数是self
外,其他和普通函数一样
~方法的调用
要调用一个方法,只需要在实例变量上直接调用,除了self
不用传递,其他参数正常传入,这里只有self一个参数,所以直接()即可。
>>> bart.print_score()
Bart Simpson: 59
1.我们从外部看Student
类,就只需要知道,创建实例需要给出name
和score
,而如何打印,都是在Student
类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。
2.给类增加新的方法:get_grade:正常调用即可
class Student(object):
...
def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'
2.小结
~类 创建实例的模板
~实例 一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响
~方法 方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同
bart = Student('Bart Simpson', 59)
>>> lisa = Student('Lisa Simpson', 87)
>>> bart.age = 8
>>> bart.age
8
>>> lisa.age
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'
三,访问限制
1.让内部属性不被外部访问
【1】可以访问的情况
>>> bart = Student('Bart Simpson', 59)
>>> bart.score
59
>>> bart.score = 99
>>> bart.score
99
【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))
结果:无法从外部访问实例变量.__name
和实例变量.__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'
2.外部代码要获取name和score
给Student类增加get_name
和get_score(单_下划线)
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
bart=Student('Bart Simpson',59)
print(bart.get_name())
3.允许外部代码修改score
给Student类增加set_score
class Student(object):
...
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
bart=Student('Bart Simpson',59)
print(bart.get_score()) #用get_score可以查看实例的数据
print(bart.set_score(100)) #用set_score可以更改实例的数据
print(bart.get_score()) #用get_score验证实例的数据确实已经被更改
结果:59 100 100
注意点:
1.变量名类似__xxx__(前后,双下划线):特殊变量,可以直接访问,不是private变量
_name,单下划线开头的实例变量名:虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
__name
、__score(前,双下划綫):私有变量,按理说不可以访问,but见2.
2.私有变量不可以访问的本质在于:Python解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
变量
>>> bart._Student__name
'Bart Simpson'
but, 不同版本的Python解释器可能会把__name
改成不同的变量名
3.错误写法注意
>>> 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'
四,继承和多态
1.子类
~类是可以继承的
编写一个animal类
class Animal(object):
def run(self):
print('Animal is running...')
编写dog和cat类,可以直接继承animal
class Dog(Animal):
pass
class Cat(Animal):
pass
【1】对于Dog
来说,Animal
就是它的父类
【2】子类获得了父类的全部功能,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
dog = Dog()
dog.run()
结果:Animal is running...
~对子类增加方法
class Dog(Animal):
def run(self):
print('Dog is running...')
结果: Dog is running...
当子类和父类都存在相同的run()
方法时,我们说,子类的run()
覆盖了父类的run()
,在代码运行的时候,总是会调用子类的run()
。-多态
2.多态
~特点:对扩展开放:允许新增Animal
子类;对修改封闭:不需要修改依赖Animal
类型的run_twice()
等函数
def run_twice(animal):
animal.run()
animal.run()
>>> run_twice(Animal())
Animal is running...
Animal is running...
>>> run_twice(Dog())
Dog is running...
Dog is running...
再定义一个Tortoise
类型,也从Animal
派生
3.静态语言VS动态语言
~定义的类本质上就是一种数据类型
对于静态语言(例如Java)来说,如果需要传入Animal
类型,则传入的对象必须是Animal
类型或者它的子类,否则,将无法调用run()
方法。
对于Python这样的动态语言来说,则不一定需要传入Animal
类型。我们只需要保证传入的对象有一个run()
方法就可以了:
class Timer(object):
def run(self):
print('Start...')
Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()
方法,返回其内容。但是,许多对象,只要有read()
方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()
方法的对象。
五,获取对象信息
确定对象的‘类型’以及‘方法’--(方法☞数据处理方式的意思)
1.type()函数
【1】return 对应的class类型
【2】可以判断基本数据类型:int str也可以判断函数:abs
>>> 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'>
【3】可以判断两个变量的类型是否相同,判断是否为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
2.types模块中定义的常量--实现判断是否为某一个函数
>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
3.isinstance()--- 总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。
【1】继承类常用
继承关系为
object -> Animal -> Dog -> Husky
可以判断一个对象是否是某种类型
创建
>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
判断
>>> isinstance(h, Husky)
True
>>> isinstance(h, Dog)
True
【2】type可以判断的,isinstance也可以
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
【3】 还可以判断一个变量是否是某些类型中的一种
判断是否是list或者tuple:
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
4.dir() -获得一个对象的所有属性和方法
【1】返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
5.其他属性和方法
【1】长度
__len__
方法返回长度。在Python中,如果你调用len()
函数试图获取一个对象的长度,实际上,在len()
函数内部,它自动去调用该对象的__len__()
方法,所以,下面的代码是等价的:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
【2】返回小写
lower()
返回小写的字符串:
>>> 'ABC'.lower()
'abc'
【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
如果试图获取不存在的属性,会抛出AttributeError的错误:
>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'
可以传入一个default参数,如果属性不存在,就返回默认值:
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
~获得对象的方法(因为power是对象的方法)
>>> 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()是一样的