了解python之面向对象
面向对象概念:
面向对象编程(Object Oriented Programming,简称OOP)是一种程序涉及思想。OOP把对象作为程序的基本单元,一个对象包含数据和操作数据的函数。面向对象程序设计把计算机程序视为一组对象的集合,每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间的传递。
在python中,所有数据类型都被视为对象,也可以自定义对象。自定义对象数据类型就是面向对象中的类(Class)的概念。
面向对象术语介绍:
- 类:用来描述具有相同属性和方法的对象的集合。类定义了集合中每个对象共有的属性和方法。对象是类的实例。
- 类变量(属性):类变量在整个实例化的对象中是公用的,类变量定义在类中,且在方法之外,类变量通常不作为实例变量使用。类变量也称为属性。
- 数据成员:类变量或者实例变量用于处理类及其实例对象的相关数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,就可以对其进行改写,这个过程称为方法的覆盖,也称为方法的重写。
- 实例变量:定义在方法中的变量只作用于当前实例的类。
- 多态:对不同类的对象使用同样的操作。
- 封装:对外部世界隐藏对象的工作细节。
- 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承允许把一个派生类的对象作为一个基类对象对待,以普通类为基础建立专门的类对象。
- 实例化:创建一个类的实例,类的具体对象。
- 方法:类中定义的函数。
- 对象:通过类定义的数据结构实例,对象包括两个数据成员(类变量和实例变量)和方法。
python中的类提供了面向对象编程的所有基本功能;类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法、方法中可以调用基类中的同名方法。对象可以包含任意数量和类型的数据。
类的定义与使用
1)类的定义:
class MyCLass(object):
i=123
def f(self):
return 'hello world'
从面可以看到类的语法如下:
class ClassName(object):
<statement-1>
...
<statement-N>
python中定义类使用class关键词,class后面接类的名字,如上面例子中的MyClass,类的名字通常是大写开头的单词;紧接着是(object),表示MyClass这个类是从哪个类继承下来的。通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。类包含属性(相当于函数中的语句)和方法(类中的函数)。
2)类的使用
class MyClass(object):
i=123
def f(self):
return 'hello world'
use_class=MyClass() #类的实例化。即创建一个类的实例,use_class变量称为类的具体对象
print('调用类的属性:',use_class.i) #调用类的属性,就是前面我们说的类变量
print('调用类的方法:',use_class.f()) #调用类的方法
##输出结果:
调用类的属性: 123
调用类的方法: hello world
上面定义的f()函数可以不用self参数吗?
在类定义方法的要求:在类中定义方法时,第一个参数必须是self。除了第一个参数外,类的方法和普通函数没有区别,可以使用默认参数,可变参数,关键字参数等。
类中调用方法的要求:要调用方法,在实例变量上直接调用即可。除了self不用传递参数,其他参数正常传入。
类对象支持两种操作,即属性引用和实例化。
属性引用的标准语法:
obj.name # obj代表类对象,name代表属性
深入类
1)类的构造方法
class MyClass(object):
i=123
def __init__(self,name):
self.name=name
def f(self):
return 'hello world'
use_class=MyClass('lucky') #类的实例化。即创建一个类的实例,use_class变量称为类的具体对象
print('调用类的属性:',use_class.i) #调用类的属性,就是前面我们说的类变量
print('调用类的方法:',use_class.f()) #调用类的方法
##输出结果:
调用类的属性: 123
调用类的方法: hello world
在python中,__init__()方法是一个特殊的方法,在对象实例化时会被调用。__init__()意思是初始化,是initialization的简写。这个方法的书写方式:先输入两个下划线,后面接init,再接着两个下划线。这个方法叫做构造函数。在定义类的时候,若不显示的定义一个__init__()方法,则程序默认会调用一个无参的__init__()方法。
比方说下面的代码:
class DefaultInit(object):
def __init__(self):
print('在类实例化时执行我,我是__init__方法')
def show(self):
print('我是类中定义的方法,需要通过实例化对象调用')
test=DefaultInit()
print('类实例化结束')
test.show()
##输出结果:
在类实例化时执行我,我是__init__方法
类实例化结束
我是类中定义的方法,需要通过实例化对象调用
/
class DefaultInit(object):
def show(self):
print('我是类中定义的方法,需要通过实例化对象调用')
test=DefaultInit()
print('类实例化结束')
test.show()
##输出结果:
类实例化结束
我是类中定义的方法,需要通过实例化对象调用
通过上面两段代码可以得出:
在代码中定义了init()方法时,实例化类时会调用该方法;若没有定义init()方法,实例化类时也不会报错,此时调用默认的__init__()方法。在python中定义类时,若没有定义构造方法(__init__()),则在类实例化时系统调用默认的构造方法。另外,__init__()方法可以有参数,参数通过__init__()传递到类的实例化操作上。
2)可以在一个类中定义多个构造方法吗?
class DefaultInit(object):
def __init__(self):
print('我是不带参数的init方法')
test=DefaultInit()
print('类实例化结束')
##输出结果:
我是不带参数的init方法
类实例化结束
//
class DefaultInit(object):
def __init__(self):
print('我是不带参数的init方法')
def __init__(self,name):
print('我是带参数的init方法,参数值是:',name)
test=DefaultInit('hello')
print('类实例化结束')
##输出结果:
我是带参数的init方法,参数值是: hello
类实例化结束
/
class DefaultInit(object):
def __init__(self, name):
print('我是带参数的init方法,参数值是:', name)
def __init__(self):
print('我是不带参数的init方法')
test=DefaultInit()
print('类实例化结束')
##输出结果:
我是不带参数的init方法
类实例化结束
在一个类中可以定义多个构造方法,但是实例化类时只实例化最后的构造方法,即后面的构造方法会覆盖前面的构造方法,并需要根据最后一个构造方法的形式进行实例化。建议一个类中只定义一个构造方法。
类的访问权限
在类的内部有属性和方法,外部代码可以直接通过调用实例变量的方法操作数据,这样就隐藏了内部的复杂逻辑。
例如:
class Student(object):
def __init__(self,name,score):
self.name=name
self.score=score
def info(self):
print('学生: %s; 分数: %s' % (self.name,self.score))
stu=Student('lucky',96)
print('修改前的分数:',stu.score)
stu.info()
stu.score=0
print('修改后的分数:',stu.score)
stu.info()
##输出结果:
修改前的分数: 96
学生: lucky; 分数: 96
修改后的分数: 0
学生: lucky; 分数: 0
类中定义的非构造方法可以调用类中的构造方法实例变量的属性,调用的方式为self.实例变量属性名,如代码中self.name和self.score。可以在类的外部修改类的内部属性。
如果要让类的内部属性不被外部访问,怎么办?
要让内部的属性不被外部访问,可以在属性名称前加两个下划线__。在python,如果实例的变量名以__开头,就会变成私有变量,只有内部可以访问,外部不能访问。对上面的代码修改:
class Student(object):
def __init__(self,name,score):
self.__name=name
self.__score=score
def info(self):
print('学生: %s; 分数: %s' % (self.__name,self.__score))
stu=Student('lucky',96)
print('修改前的分数:',stu.__score)
stu.info()
stu.__score=0
print('修改后的分数:',stu.__score)
stu.info()
##输出结果:
AttributeError: 'Student' object has no attribute '__score'
把类的中的变量变成私有变量有什么用呢?
可以确保外部的代码不能随意修改对象内部的状态,通过访问限制的保护,代码更加安全。比如上面分数是一个比较重要的对象,是不允许在程序外随意的修改的。
如果外部的代码想要获取类中的name和score怎么办?
在python中可以增加get_attrs()方法,获取类中私有变量,可以在上面的例子条件一个get_score方法,如下:
class Student(object):
def __init__(self,name,score):
self.__name=name
self.__score=score
def info(self):
print('学生: %s; 分数: %s' % (self.__name,self.__score))
def get_score(self): #在类外部可以得到类内部的私有变量
return self.__score
def set_score(self,score):
self.__score=score
stu=Student('lucky',96)
print('修改前的分数:',stu.get_score())
stu.info()
stu.set_score(0) #在类的外部修改私有变量
print('修改后的分数:',stu.get_score())
stu.info()
##输出结果:
修改前的分数: 96
学生: lucky; 分数: 96
修改后的分数: 0
学生: lucky; 分数: 0
类的私有方法
私有方法调用的方式:
self.__private_methods
class PrivatePublicMethod(object):
def __init__(self):
pass
def __foo(self): #定义私有方法
print('这是私有方法')
def foo(self): #公共方法
print('这是公共方法')
print('公共方法中调用私有方法')
self.__foo()
print('公共方法调用私有方法结束')
pri_pub=PrivatePublicMethod()
print('开始调用公共方法:')
pri_pub.foo()
print('开始调用私有方法:')
pri_pub.__foo()
##输出结果:
AttributeError: 'PrivatePublicMethod' object has no attribute '__foo'
开始调用公共方法:
这是公共方法
公共方法中调用私有方法
这是私有方法
公共方法调用私有方法结束
开始调用私有方法:
上面代码在调用私有方法时候, 报错。 注意:私有方法和私有变量类似,不能在类外部被调用。
类的继承
面向对象编程带来的好吃之一就是代码的重用,实现重用的方法之一是通过继承机制。继承可以理解成类之间类型和子类型的关系。
在面向对象程序设计中,当我们定义一个class时,可以从某个现有的class继承,定义的新的class称为子类(Subclass),而被继承的class称为基类、父类或者超类(Base Class、Super Class)
继承的定义如下:
class DerivedClassName(BaseClassName):
<statement-1>
...
<statement-2>
需要注意的是,继承语法 class 子类名(基类名) 时, #基类名写在括号里,基本类是在定义类的时候,在元组中指明的。
在python中,继承有以下特点:
1)在继承中,基类的构造方法(__init__)不会被自动调用,需要在子类的构造方法中专门调用。
2)在调用基类的方法时,需要加上基类的类名前缀,并带上self参数变量。区别在于类中调用普通函数时不需要带self参数。
3)在python中,首先查找对应类型的方法,如果在子类中找不到对应的方法,才到基类中逐个查找。
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()
##输出结果:
Animal is running...
Animal is running...
继承有什么好处?
继承最大的好处是子类获得了父类全部非私有的功能。由于在Animal中定义了run()非私有的方法,作为Animal的子类,Dog和Cat什么方法都没有定义,自动拥有Animal这个基类中的run()方法。
子类中可以拥有自己的方法:
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
def eat(self):
print('Eating...')
class Cat(Animal):
pass
dog=Dog()
dog.run()
dog.eat()
##输出结果:
Animal is running...
Eating...
从结果可以看到,既执行了父类的方法,又执行了自己定义的方法。
子类不能继承父类的私有方法,也不能调用父类的私有方法,父类的定义如下:
class Animal(object):
def run(self):
print('Animal is running...')
def __run(self):
print('I am a private method..')
class Dog(Animal):
def eat(self):
print('Eating...')
dog=Dog()
dog.run()
dog.eat()
dog.__run()
##输出结果:
Animal is running...
Eating...
AttributeError: 'Dog' object has no attribute '__run'
从结果可以看到,子类不能调用父类的私有方法,子类虽然继承了父类,但是调用父类的私有方法相当于从外部调用类中的方法,因此调用不成功。
对于父类中扩展的非私有方法,子类可以拿来即用,如在父类Animal增加一个jump方法:
class Animal(object):
def run(self):
print('Animal is running...')
def jump(self):
print('Animal is jumping..')
class Dog(Animal):
def eat(self):
print('Eating...')
dog=Dog()
dog.run()
dog.jump()
dog.eat()
##输出结果:
Animal is running...
Animal is jumping..
Eating...
继承可以一级一级继承下来,所有类最终都可以追朔到根类object。
类的多态
当子类和基类存在相同的run方法的时候,子类的run方法会覆盖基类的run方法。在代码运行时总会调用子类的run()方法,称之为多态。
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
def eat(self):
print('Eating...')
class Cat(Animal):
pass
dog=Dog()
dog.run()
cat=Cat()
cat.run()
##输出结果:
Animal is running...
Animal is running...
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
dog=Dog()
dog.run()
cat=Cat()
cat.run()
##输出内容:
Dog is running...
Cat is running...
当我们定义一个类的时候,实际上就定义了一种数据类型,自定义的数据类型和python自带的数据类型(字符串、列表、元组、字典等)没什么区别。
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
dog=Dog()
dog.run()
cat=Cat()
cat.run()
a=list() #变量a是list类型
b=Animal() #变量b是Animal
c=Dog() #c是Dog类型
print('a是否是list类型:',isinstance(a,list)) #isinstance()方法判断一个变量是否是某个类型
print('b是否是Animal类型:',isinstance(b,Animal))
print('c是否是Dog类型:',isinstance(c,Dog))
print('c是否是Dog类型:',isinstance(c,Dog))
print('c是否是Animal类型:',isinstance(c,Animal))
print('b是否是Dog数据类型:',isinstance(b,Dog)) #b是否是Dog数据类型: False
##输出内容:
Dog is running...
Cat is running...
a是否是list类型: True
b是否是Animal类型: True
c是否是Dog类型: True
c是否是Dog类型: True
c是否是Animal类型: True
b是否是Dog数据类型: False
可以看到c既是Dog类型又是Animal类型。该怎么理解?
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以看作是父类。但是反过来不行,如下:
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
class Bird(Animal):
def run(self):
print('Bird is flying the sky...')
def run_two_times(animal):
animal.run()
animal.run()
run_two_times(Animal())
run_two_times(Dog())
run_two_times(Cat())
run_two_times(Bird())
##输出内容:
Animal is running...
Animal is running...
Dog is running...
Dog is running...
Cat is running...
Cat is running...
Bird is flying the sky...
Bird is flying the sky...
新增的Animal子类Bird不必对run_two_tomes()方法做任何修改。实际上,任何依赖Animal作为参数的函数或方法都可以不加修改的正常运行,原因就在于多态。
多态的好处:
当我们需要传入Dog、Cat、Bird等对象时,只需要接收Animal类型就可以了,因为Dog、Cat、Bird都是Animal类型,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此传入的类型只要是Animal类或者继承自Animal类,都会调用实际类型的run()方法。
多态:对于一个变量,我们只需要知道它是Animal类型,无需确切知道它的子类型,就可以方法调用run()方法,具体调用的run方法作用于Animal、Cat、Dog、或Bird对象,由运行时该对象的确切类型决定。
封装:封装是全局作用域中其他地区隐藏多余信息的原则。听起来有些像多态,使用对象而不用知道其内部细节。它们都是抽象原则,都会帮忙处理程序组件而不用关心过多细节,就像函数一样。封装并不等同于多态,多态可以让用户对不知道类的对象进行方法调用,而封装可以不用关心对象是如何创建的,直接使用即可。
class Student(object):
def __init__(self,name,score):
self.name=name
self.score=score
std=Student('lucky',96)
def info(std):
print('学生: %s;分数: %s' % (std.name,std.score))
info(std)
##输出结果:
学生: lucky;分数: 96
///
class Student(object):
def __init__(self,name,score):
self.name=name
self.score=score
def info(self):
print('学生: %s;分数: %s' % (self.name,self.score))
stu=Student('lucky',96)
stu.info()
##输出结果:
学生: lucky;分数: 96
这样一来,我们从外部看Student类,只需要知道创建实例需要给出的name和socre,如何输出是在Student类内部定义的,这些数据和逻辑被封装起来,调用很容易,但却不知道内部实现的细节。
多重继承
语法:
class DerivedClassName(Base1,Base2,Base3):
<statement-1>
...
<statement-2>
多重继承就是有多个基类。
需要注意圆括号中基类的顺序,若基类中有相同的方法名,在子类使用时未指定,python会从左到右搜索。若方法在子类中未找到,则从左到右查找基类中是否包含方法。
假设要实现4种动物:Dog(狗)、Bat(蝙蝠)、Parrot(鹦鹉)、Ostrich(鸵鸟)
如果按照哺乳动物和鸟类分类:
- 哺乳类:能跑的哺乳类和能飞的哺乳类
- 鸟类:能跑的鸟类和能飞的鸟类
哺乳类:Dog和Bat
- 能跑的哺乳类:Dog
- 能飞的哺乳类:Bat
鸟类:Parrot和Ostrich
- 能跑的鸟类:Ostrich
- 能飞的鸟类:Parrot
class Animal(object):
pass
#大的分类
class Mammal(Animal):
pass
class Bird(Animal):
pass
#定于能跑和能飞的类
class Runnable(object):
def run(self):
print('Running...')
class Flable(object):
def fly(self):
print('Flying...')
#各种动物分类
class Dog(Mammal,Runnable):
pass
class Bat(Mammal,Flyable):
pass
class Parrot(Bird,Flyable):
pass
class Ostrich(Bird,Runnable):
pass
通过上面的多重继承,一个子类就可以继承多个基类,同时获得多个基类所有非私有功能。