面向对象编程(Object Oriented Programming,OOP),是一种程序设计思想,把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。面向过程的程序设计把计算机程序视为一系列命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。而面向对象的程序设计把计算机程序视为一组对象集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
那面向对象编程与面向过程编程到底有什么区别?通过一个例子进行比较:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25#面向过程 stu1 = {"name":"aaa","score":90} stu2 = {"name":"bbb","score":85} def print_score(stu): print("%s: %s" %(stu["name"],stu["score"])) print_score(stu1) #aaa: 90 print_score(stu2) #bbb: 85 ------------这是分界线----------------- #面向对象,面向对象的设计思想是抽象出class,根据class创建instance 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)) stu1 = Student("aaa",90) #创建实例stu1 stu2 = Student("bbb",85) #创建实例stu2 stu1.print_score() #调用方法,输出aaa: 90 stu2.print_score() #调用方法,输出bbb: 85
1.类和实例
面向对象最重要的概念就是类(class)和实例(instance),类是抽象的模板,如上述Student类,而实例是根据类创建出来的一个个具体“对象”,如上述stu1对象。
1
2
3
4
5#Python类定义格式 class Classname(object): psss #说明:class关键字定义类,后面紧接类名(类名通常首字母大写),紧接着为(object),表该类是从哪个类继承下来的。如果没有合适的继承类,就用object类,这是所有类最终都会继承的类。
1
2#Python实例 instancename = Classname() #创建了实例instancename,创建实例通过类名+()实现
__init__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Student(object): #实例属性 #__init__()叫做初始化方法(构造方法),在类被调用时,这个方法会自动执行,进行一些初始化动作。 def __init__(self,name,score): self.name = name self.score = score #实例方法 def print_score(self): print("%s: %s" %(self.name,self.score)) #析构方法,在实例释放、销毁的时候自动执行,通常用于做一些收尾工作。这个方法可选,不是必须写 def __del__(self): print("这里是析构函数,结束啦!") stu1 = Student("张三",90)#生成一个角色,会自动把参数传给Student下面的__init__()方法,此时相当于Student(stu1,"张三",90) stu1.print_score()#此时相当于Student.print_score(stu1)
注意到__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。有了__init__
方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__
方法匹配的参数,但self
不需要传,Python解释器自己会把实例变量传进去:
1
2
3stu1 = Student("aaa",90) #实例stu1 print(stu1.name) #aaa print(stu1.score) #90
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self
,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别!
2.访问限制
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Student(object): def __init__(self,name,score): self.name = name self.score = score def print_score(self): print('%s: %s' %(self.name,self.score)) s1 = Student('zhangsan',90) print(s1.name)#打印实例名称,这里输出为zhangsan print(s1.score)#打印实例成绩,这里输出为90 s1.score = 100 #在类外部,我们修改实例属性 print(s1.score)#打印输出为修改后的100
通过上述代码可以发现,类虽然隐藏了内部的复杂逻辑,但是其内部属性可以被外部代码任意修改其属性,有没有什么方法可以不让外部代码修改呢?在Python中,实例变量名如果以 __ 开头,就变成了一个私有变量,只有内部可以访问,外部不能访问,所以,上述代码可以这么修改:
1
2
3
4
5
6
7
8
9
10
11class Student(object): def __init__(self,name,score): self.__name = name #原self.name修改为self.__name self.__score = score #原self.score修改为self.__score def print_score(self): print('%s: %s' %(self.__name,self.__score)) #修改 s1 = Student('zhangsan',90) print(s1.__name)#报错,AttributeError: 'Student' object has no attribute '__name' print(s1.__score)#报错,AttributeError: 'Student' object has no attribute '__score' #说明:这里已经无法从外部访问 实例变量.__name和实例变量.__score
上述修改确保了外部代码不能随意修改对象内部状态,但是改的太过于彻底了,我们都无法访问其属性了~~~~。如果我们还想获取属性name与score应该怎么办?可以给Student类增加get_name和get_score方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class Student(object): def __init__(self,name,score): self.__name = name self.__score = score def print_score(self): print('%s: %s' %(self.__name,self.__score)) def get_name(self): return self.__name #获取name属性 def get_score(self): return self.__score #获取score属性 def set_name(self,name): self.__name = name #修改name属性 return self.__name def set_score(self,score): self.__score = score #修改score属性 s1 = Student('zhangsan',90) print(s1.get_name())#zhangsan print(s1.get_score())#90 print(s1.set_name('lisi')) #lisi
说了半天,这样一番周折(看起来有点化简为繁,代码量增加)有什么优势?其实这样相比于直接s1.score = 100,在set_score方法中,可以对参数做检查,更符合实际要求。
1
2
3
4
5
6
7
8class Student(object): ... def set_score(self,score): if 0<=score<=100: self.__score = score else: raise ValueError('成绩不规范')
在Python中,变量名类似 __xxx__ ,即双下划线开头,并且双下划线结尾的,是特殊变量,可以直接访问;
变量名类似_xxx,即一个下划线开头,这种实例变量外部是可以访问的,按照约定成俗的规定,这样的变量意思是“虽然可以被访问,但是,请将其视为私有变量,不要随意访问”
变量名类似__xxx,即双下划线开头,这种变量不可以被外部访问,需要在类内部写相应方法。是不是外部绝对不能访问呢?实际上也不是,实际上通过 _类名__xxx仍可以访问到 __xxx变量,但是强烈不建议这么干!!!
3.封装、继承和多态
(1)继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#继承 class Person(object): def speak(self): print('People is speaking...') class Teacher(Person): pass class Student(Person): pass teacher1 = Teacher() teacher1.speak() #People is speaking... student1 = Student() student1.speak() #People is speaking... #注:Person为父类,Teacher、Student为其子类,子类自动继承父类方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33#继承--新市类super用法 class Persion(object): def __init__(self,name,age): self.name = name self.age = age def speak(self): print("People is speaking...") class Teacher(Persion): def __init__(self,name,age,course,salary): #Persion.__init__(self,name,age) #父类实现,另一种方法见super,这种写法多继承不太方便 super(Teacher, self).__init__(name,age) self.course = course self.salary = salary def teaching(self): print("Teacher %s is teaching %s"%(self.name,self.course)) class Student(Persion): def __init__(self,name,age,grade,sid): #Persion.__init__(self,name,age) #父类实现,另一种方法见super,这种写法多继承不太方便 super(Student, self).__init__(name,age) self.grade = grade self.sid = sid def study(self): print("The student %s ID is %s"%(self.name,self.sid)) teacher1 = Teacher("Jack",30,"C++",3000) teacher1.teaching() #Teacher Jack is teaching C++ student1 = Student("Tom",20,"大一","201911") student1.study() #The student Tom ID is 201911
(2)多态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#多态 class Person(object): def speak(self): print('People is speaking...') class Teacher(Person): def speak(self): print('Teacher is speaking...') def work(self): print('Teacher is working...') class Student(Person): def speak(self): print('Student is speaking...') def learn(self): print('Student is learning...') teacher1 = Teacher() teacher1.speak() #Teacher is speaking... student1 = Student() student1.speak() #Student is speaking... #注:当子类和父类都存在相同的speak()方法时,子类的speak()覆盖了父类的speak(),在代码运行的时候总是调用子类的speak()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30#开闭原则 class Person(object): def speak(self): print('People is speaking...') class Teacher(Person): def speak(self): print('Teacher is speaking...') def work(self): print('Teacher is working...') class Student(Person): def speak(self): print('Student is speaking...') def learn(self): print('Student is learning...') #新增一个Person子类,不必对run_twice()做任何修改,任何依赖Person作为参数的函数或者方法都可以不加修改正常运行,原因就在于多态 class Test(Person): def speak(self): print('Testing...') def run_twice(people): people.speak() people.speak() run_twice(Person()) #People is speaking... People is speaking... run_twice(Teacher()) #Teacher is speaking... Teacher is speaking... run_twice(Student()) #Student is speaking... Student is speaking... run_twice(Test()) #Testing... Testing...
多态的好处就是,当我们需要传入 Teacher、Student、Test...时,我们只需要接收Person类型就可以了,因为 Teacher、Student、Test...都是Person类型,然后按照Person类型进行操作即可。由于Person类型有speak()方法,因此,传入任意类型,只要是Person类或者子类就会自动调用实际类型的speak()方法,这就是多态的意思:
对于一个变量,我们只需要知道它是Person类型,无需确切地知道它的子类型,就可以放心地调用speak()方法,而具体调用的speak()方法是作用在Person、Teacher、Student、还是Test对象上,由运行该对象的确切类型决定,这就是多态的真正威力:调用方只管调用,不管细节,而我们新增一种Person的子类时,只需确保speak()方法编写正确,不用管原来代码是如何调用的,这就是著名的“开闭”原则:
对扩展开放:允许新增Person子类
对修改封闭:不需要修改依赖Person类型的run_twice()等函数
静态语言VS动态语言
静态语言(如Java):如果需要传入Person类型,则传入的对象必须是Person类型或者它的子类,否则,将无法调用speak()方法。
动态语言(如Python):不一定需要传入Person类型,我们只需要保证传入的对象有一个speak()方法就可以了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36#鸭子类型:并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做鸭子 class Person(object): def speak(self): print('People is speaking...') class Teacher(Person): def speak(self): print('Teacher is speaking...') def work(self): print('Teacher is working...') class Student(Person): def speak(self): print('Student is speaking...') def learn(self): print('Student is learning...') #新增一个Person子类,不必对run_twice()做任何修改,任何依赖Person作为参数的函数或者方法都可以不加修改正常运行,原因就在于多态 class Test(Person): def speak(self): print('Testing...') #鸭子类型,不需要判断参数是Person类型,只要对象有speak()方法,就能调用成功! class Timer(object): def speak(self): print("鸭子类型...") def run_twice(people): people.speak() people.speak() run_twice(Person()) #People is speaking... People is speaking... run_twice(Teacher()) #Teacher is speaking... Teacher is speaking... run_twice(Student()) #Student is speaking... Student is speaking... run_twice(Test()) #Testing... Testing... run_twice(Timer()) #鸭子类型... 鸭子类型...
总结:a、继承可以把父类的所有功能直接拿过来,这样不必重零做起,子类只需新增自己特有方法,也可以把父类不适合的方法覆盖重写。
b、动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。
4.type()和isinstance()
type()主要用于获取未知变量的类型
isinstance()只要用于判断子类是否继承父类
isinstance()可以判断子类对象是否继承与父类,而type()不可以
dir()
获取一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:
1
2
3
4#dir()函数 >>> dir('ABC') ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] >>>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23#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 >>> hasattr(obj,'y') #有属性"y"吗? False >>> setattr(obj,'y',2) #设置一个属性"y" >>> hasattr(obj,'y') True >>> getattr(obj,'y') #获取属性"y" 2 >>> getattr(obj,'x') #获取属性"x" 9 >>> getattr(obj,'z') #获取属性"z",属性不存在抛出AttributeError Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'MyObject' object has no attribute 'z'
5.实例属性和类属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#实例属性和类属性 class Person(object): city = 'XIAN' #类属性,可以直接在类中定义,归Person类所有 def __init__(self,name): self.name = name person1 = Person('Tom') #创建实例 person1.age = 24 #给实例绑定age属性 person1.city print(person1.name) #Tom print(person1.age) #24 print(person1.city) #XIAN person1.city = 'Shanghai' #给实例绑定city属性,由于实例属性优先级比类属性高,因此会屏蔽掉类的city属性 print(person1.city) #Shanghai del person1.city #删除实例city属性 print(person1.city) #XIAN,再次调用person1.city,由于实例city属性没有找到,类的city属性显示 #注:在编写程序时,千万不要对实例属性和类属性使用相同的名字,因为相同的名称实例属性将屏蔽类属性,但是当删除实例属性后,再使用相同的名称,访问到的是类属性
1
2
3
4
5
6
7
8
9
10
11#练习:统计学生人数——每创建一个实例,该属性自动增加 class Student(object): count = 0 #实例属性,属于所有类所有,所有实例共享一个属性 def __init__(self,name): self.name = name Student.count += 1 s1 = Student('AAA') s2 = Student('BBB') s3 = Student('CCC') print(Student.count) #3
6.经典类vs新式类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24#多继承新式类、经典类区别 #经典类:深度优先 新式类:广度优先 class A: def __init__(self): self.n = 'A' class B(A): # def __init__(self): # self.n = 'B' pass class C(A): # def __init__(self): # self.n = 'C' pass class D(B, C): # def __init__(self): # self.n = 'D' pass obj = D() print(obj.n)
7.静态方法、类方法、属性方法
(1)静态方法
通过@staticmethod装饰器即可以把其装饰的方法变为一个静态方法,什么是静态方法呢?其实不难理解,普通的方法,可以在实例化后直接调用,并且在方法里可以通过self. 调用实例变量或类变量,但静态方法是不可以访问实例变量或类变量的,一个不能访问实例变量和类变量的方法,其实相当于跟类本身已经没什么关系了,它与类唯一的关联就是需要通过类名来调用这个方法。
1
2
3
4
5
6
7
8
9
10
11
12#静态方法 # -*- coding:utf-8 -*- class Dog(object): def __init__(self,name): self.name = name @staticmethod #把eat方法变为静态方法 def eat(self): print("%s is eating"%self.name) d = Dog("大黄") #d.eat() #这样调用会报错,当eat变为静态方法以后,再通过实例调用时就不会自动把实例本身当作一个参数传给self了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#方法一:调用时主动传递实例本身给eat方法,即d.eat(d) class Dog(object): def __init__(self,name): self.name = name @staticmethod #把eat方法变为静态方法 def eat(self): print("%s is eating"%self.name) d = Dog("大黄") d.eat(d) #方法二:在eat方法中去掉self参数,但这也意味着在eat中不能通过self.调用实例中的其他变量了 class Dog(object): def __init__(self,name): self.name = name @staticmethod #把eat方法变为静态方法 def eat(): print("%s is eating"%("二黄")) d = Dog("大黄") d.eat()
(2)类方法
类方法通过@classmethod装饰器实现,类方法和普通方法的区别是,类方法只能访问类变量,不能访问实例变量。
1
2
3
4
5
6
7
8
9
10
11#类方法 class Dog(object): def __init__(self,name): self.name = name @classmethod def eat(self): print("%s is eating"%self.name) d = Dog("大黄") d.eat() #报错,因为name是个实例变量,类方法不能访问实例变量
1
2
3
4
5
6
7
8
9
10
11
12#可以定义一个类变量,叫name1 class Dog(object): name1 = "这里是类变量" def __init__(self,name): self.name = name @classmethod def eat(self): print("%s is eating"%self.name1) d = Dog("大黄") d.eat() #这里是类变量 is eating
(3)属性方法
属性方法的作用就是通过@property把一个方法变为一个静态属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23#属性方法 class Dog(object): def __init__(self,name): self.name = name @property def eat(self): print("%s is eating"%self.name) d = Dog("大黄") d.eat() #报错,因为eat此时已经变成一个静态属性了,不是方法了,想调用已经不需要加()了,直接d.eat就可以了 #直接d.eat class Dog(object): def __init__(self,name): self.name = name @property def eat(self): print("%s is eating"%self.name) d = Dog("大黄") d.eat
把一个方法变成静态属性有什么用呢?既然想要静态变量,那直接定义成一个静态变量不就得啦?以后你会遇到很多场景是不能 简单通过定义静态属性来实现的,比如,你想知道一个航班当前的状态,是到达了、延迟了、取消了、还是已经飞走了,想知道这种状态必须经历以下几步:
①连接航空公司API查询
②对查询结果进行解析
③返回结果给你的用户
因此这个status属性的值是一系列动作后才得到的结果,所以每次调用时,其实它都要经过一系列的动作才返回结果,但这些动作过程不需要用户关心,用户只需要调用这个属性就可以。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Flight(object): def __init__(self,name): self.flight_name = name def checking_status(self): print("checking flight %s status " % self.flight_name) return 1 @property def flight_status(self): status = self.checking_status() if status == 0 : print("flight got canceled...") elif status == 1 : print("flight is arrived...") elif status == 2: print("flight has departured already...") else: print("cannot confirm the flight status...,please check later") f = Flight("东方航空") f.flight_status
现在我只能查询航班状态, 既然这个flight_status已经是个属性了, 那我能否给它赋值呢?试试吧
1
2
3f = Flight("东方航空") f.flight_status f.flight_status = 2
输出, 说不能更改这个属性,怎么办怎么办???当然可以改, 不过需要通过@proerty.setter装饰器再装饰一下,此时你需要写一个新方法, 对这个flight_status进行更改。当然@flight_status.deleter允许可以将这个属性删除 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39# -*- coding:utf-8 -*- class Flight(object): def __init__(self,name): self.flight_name = name def checking_status(self): print("checking flight %s status"%self.flight_name) return 1 @property def flight_status(self): status = self.checking_status() if status == 0: print("flight got canceled...") elif status == 1: print("flight is arrived...") elif status == 2: print("flight has departured already...") else: print("cannot confirm the flight status...,please check later") @flight_status.setter def flight_status(self,status): status_dic = { 0:"canceled", 1:"arrived", 2:"departured" } print("has changed the flight status to ",status_dic.get(status)) @flight_status.deleter def flight_status(self): print("status got removed...") f = Flight("东方航空") f.flight_status f.flight_status = 2 del f.flight_status
最后
以上就是生动魔镜最近收集整理的关于Python面向对象的全部内容,更多相关Python面向对象内容请搜索靠谱客的其他文章。
发表评论 取消回复