17.python面向对象

面向对象的编程(object oriented programming),简称OOP:是一种编程的思想。OOP把对象当成一个程序的基本单元,一个对象包含了数据和操作数据的函数。面向对象的出现极大的提高了编程的效率,使其编程的重用性增高。

模拟场景理解面向对象和面向过程:

 1 '''
 2 使用面向过程的思想解决吃饭的问题?
 3         步骤:
 4         1).思考今天吃什么?
 5         2).去买菜(货比三家)
 6         3).摘菜
 7         4).洗菜
 8         5).切菜
 9         6).炒菜
10         7).焖饭
11         8).吃饭
12         9).洗刷
13 使用面向对象的思想解决吃饭的问题?
14         步骤:
15         1).思考今天吃什么?
16         2).去饭店
17             ①.调用服务员的点菜功能
18             ②.将菜品告知后台大厨
19             ③.大厨调用服务员的上菜功能
20         3).开始吃饭
21         4).结账走人(多种支付方式)
22 '''

Python是解释性语言,但是它是面向对象的,能够进行对象编程。面向对象是一种编程方式,此编程方式的实现是基于对  和 对象 的使用;类 是一个模板,模板中包装了多个“函数”供使用(可以讲多函数中公用的变量封装到对象中);对象,根据模板创建的实例(即:对象),实例用于调用被包装在类中的函数。大白话理解类和对象的区别:i类:具有一些列相同特征、行为的"事物",它的表现是不具体的、不清晰的、模糊的概念;对象:从类中实例化得到,一个实实在在的个体,在内存中有体现;它的表现是具体的、清晰的、看得见摸的着的。

遇到面向对象的问题,通常可以考虑如下三个环节:

  1). 设计类,定义属性、函数、...(可能需要花费大量的时间) ;

  2). 创建(实例化)对象(简单,一行代码搞定,但是内存比较复杂);

  3). 对象调用属性或者函数完成需求。

 1 # 1.设计类
 2 class Car(object): 
 3      
 4     # 属性
 5     color = "红色"
 6     brand = "BMW"
 7     number = "沪A88888"
 8 
 9     # 函数/方法
10     def run(self):
11         print('%s的%s,车牌为%s,正在飞速的行驶...' %(self.color,self.brand,self.number))
12 
13     def stop(self):
14         print('车停了...')
15 
16 # 2.创建对象/实例化对象
17 c1 = Car()
18 print(c1,type(c1))                  # 得到<__main__.Car object at 0x000000000220D710> <class '__main__.Car'>
19 
20 # 3.对象调用属性
21 print(c1.color,c1.brand,c1.number)  # 红色 BMW 沪A88888
22 
23 # 4.对象调用函数
24 c1.run()                            # 红色的BMW,车牌为沪A88888,正在飞速的行驶...
25 c1.stop()                           # 车停了...
26 
27 # 创建第二个对象
28 c2 = Car()
29 print(c2,type(c2))                  # 得到<__main__.Car object at 0x000000000272D7B8> <class '__main__.Car'>
30 
31 print(c1 == c2)                     # 得到False ,比较的是地址,c1和c2的地址不一样
32 
33 c2.color = "白色"
34 c2.brand = "BYD"
35 c2.number = "京A66666"
36 print(c2.color,c2.brand,c2.number)   # 白色 BYD 京A66666
37 print(c1.color,c1.brand,c1.number)   # 红色 BMW 沪A88888
38 '''在一个模块中可以创建多个对象,它们彼此之间是相互独立存在(堆空间有体现),切互不干扰...'''
39 
40 c3 = c1                              # c1和c3的地址是一样的,共用,c1不调用了,但c3还指在堆上,c1记录的地址又给到c3一份
41 c1 = None
42 '''此时堆中有1个空间,不存在垃圾空间;因为c1虽然被赋值为None了,但是c3仍然记录了堆中对象空间的地址(维护这层关系)'''

 

类的成员:

1). 字段:普通字段、静态字段

普通字段需要通过对象来访问,而静态字段通过类访问,在使用上可以看出普通字段和静态字段的归属是不同的;他们的本质的区别是内存中保存的位置不同,普通字段属于对象,而静态字段属于类;静态字段在内存中只保存一份,普通字段在每个对象中都要保存一份;通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态字段。

 1 class Province:
 2 
 3     # 静态字段/类属性
 4     country = '中国'
 5 
 6     def __init__(self, name):
 7         # 普通字段/对象属性
 8         self.name = name
 9 
10 # 实例对象obj1
11 obj1 = Province('河北省')
12 print(obj.name)   # 直接访问普通字段
13 
14 # 实例对象obj2
15 obj2 = Province('河南省')
16 print(obj.name)   # 直接访问普通字段
17 
18 # 直接访问静态字段
19 Province.country

 

2). 方法:普通方法、类方法、静态方法、属性方法

前三种方法在内存中都归属于类,区别在于调用方式不同。普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;类方法:由类调用;至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;静态方法:由类调用;无默认参数。

相同点:对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份;不同点:方法调用者不同、调用方法时自动传入的参数不同。

静态方法意思是把 @staticmethod 下面的函数和所属的类截断了,这个函数就不属于这个类了,没有类的属性了,只不是还是要通过类名的方式调用  ,把静态方法当作一个独立的函数给他传参就行了。

类方法只能访问类变量,不能访问实例变量。

属性方法把一个方法变成一个静态属性,属性就不用加小括号那样的去调用了

 1 class Foo:
 2 
 3     def __init__(self, name):
 4         self.name = name
 5 
 6     def ord_func(self):
 7         """ 定义普通方法,至少有一个self参数 """
 8         print('普通方法')
 9 
10     @classmethod
11     def class_func(cls):
12         """ 定义类方法,至少有一个cls参数 """
13         print('类方法')
14 
15     @staticmethod
16     def static_func():
17         """ 定义静态方法 ,无默认参数"""
18         print('静态方法')
19 
20 # 调用普通方法
21 f = Foo()
22 f.ord_func()
23 
24 # 调用类方法
25 Foo.class_func()
26 
27 # 调用静态方法
28 Foo.static_func()

属性方法:其实是方法里面普通方法的变种。属性方法的定义和调用要注意:定义时,在普通方法的基础上添加 @property 装饰器;定义时,属性仅有一个self参数调用时,无需括号。补充:属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象,属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能。Python的属性的功能是:属性内部进行一系列的逻辑计算,最终将计算结果返回。

 1 class Foo:
 2 
 3    # 定义普通方法
 4    def func(self):
 5        pass
 6 
 7    # 定义属性
 8    @property
 9    def prop(self):
10        pass
11 
12 # 实例化对象
13 foo_obj = Foo()
14 
15 foo_obj.func() # 调用普通方法
16 foo_obj.prop   # 调用属性方法

属性方法的两种定义方式:装饰器 即:在方法上应用装饰器;静态字段 即:在类中定义值为property对象的静态字段。

 1 # 装饰器方式:在类的普通方法上应用@property装饰器
 2 class Goods:
 3 
 4     @property
 5     def price(self):
 6         return "wupeiqi"
 7 
 8 obj = Goods()
 9 
10 obj.price  # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
11 
12 
13 # 静态字段方式,创建值为property对象的静态字段
14 class Foo:
15 
16     def get_bar(self):
17         return 'wupeiqi'
18 
19     BAR = property(get_bar)
20 
21 obj = Foo()
22 
23 obj.BAR   # 自动调用get_bar方法,并获取方法的返回值

 

类特殊成员:

魔术函数:__开头并且__结尾的函数,我们称为魔术函数;特点:调用执行都不需要程序员关注,系统自行决定;例如:__init__、__del__、__str__ 、......  构造函数,析构函数,  重写函数,...... 

构造函数(constructor):又称构造方法/构造器,在生成对象时调用,一个对象只会被执行一次,可以用来进行一些初始化操作,不需要显示去调用,系统会默认去执行。

格式:__init__(self):

执行时机:在创建对象时被执行

 1 class Person(object):
 2 
 3     def __init__(self,name,age):
 4         self.name = name     
 5         self.age = age
 6         self.address = '中国'
6 7 def details(self): 8 print('姓名为:%s,年龄为:%d,籍贯是:%s' %(self.name,self.age,self.address)) 9 10 # 创建对象 11 p1 = Person("多多",18) # 构造方法,通过类创建对象时,自动触发执行 12 # 创建第二个对象,与p1互不干扰,共同存在与堆中 13 p2 = Person("老王",28) 14 p2.details()

析构函数:当对象在内存中被释放时,自动触发执行,此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。这个方法默认是不需要写的,不写的时候,默认是不做任何操作的。因为不知道对象是在什么时候被垃圾回收掉,所以,除非确实要在这里面做某些操作,不然不要自定义这个方法。

格式:__del__(self):

执行时机:在程序结束前,将对象回收,清出内存

 1 class Dog:
 2 
 3     def __init__(self,name,age,color):
 4         print('我是构造函数...')
 5         self.name = name      
 6         self.age = age
 7         self.color = color
 8 
 9     def __del__(self):
10         print('我是析构函数...')
11     
12     def func(self):
13         print('我是func函数...')

__str__(self)函数:如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值

对象实例化之后将数据给到对象名,此时如果打印对象名,在控制台上我们看到的是整个对象的类型以及在内存中的地址(十六进制),但是我们在开发过程中,对于类型和地址并不关注;我们更希望看到的是对象的各个属性内容,此时我们可以自己重新定义__str__(self)函数的函数体(就是函数重写),此函数有返回值,return后面的内容必须是str类型

执行时机:在打印对象名/引用名时被触发

 1 # 没有定义__str__方法时,打印创建的对象
 2 class Person(object):
 3 
 4     def __init__(self,name,age,address):
 5         self.name = name
 6         self.age = age
 7         self.address = address
 8 
 9 p = Person('韩梅梅',20,'上海')
10 print(p)          # <__main__.Person object at 0x0000000004C3D978>
11 
12 
13 # 有定义__str__方法时,打印创建的对象
14 class Person(object):
15 
16     def __init__(self,name,age,address):
17         self.name = name
18         self.age = age
19         self.address = address
20 
21     def __str__(self):
22         return '姓名为:%s,年龄为:%d,籍贯是:%s人' %(self.name,self.age,self.address)   # return后面必须是字符串数据
23 
24 p = Person('韩梅梅',20,'上海')
25 print(p)          # 姓名为:韩梅梅,年龄为:20,籍贯是:上海人

 __dict__方法:这个方法是以字典的形式列出类或对象中的所有成员,在类里面有,在对象里面也有。

 1 class abc:
 2     def __init__(self,age):
 3         self.age=age
 4     def __add__(self,obj):
 5         return self.age+obj.age
 6 
 7 a1=abc(18)
 8 
 9 print(abc.__dict__) # 类里面的所有成员{'__add__': <function abc.__add__ at 0x0000020666C9E2F0>, '__module__': '__main__', '__weakref__': 
10                     # <attribute '__weakref__' of 'abc' objects>, '__init__': <function abc.__init__ at 0x0000020666C9E268>, '__doc__': None, 
11                     # '__dict__': <attribute '__dict__' of 'abc' objects>}
12 
13 print(a1.__dict__)  # 对象里的成员{'age': 18}

 

面向对象的三大特性:封装性、继承性、多态性

1). 封装(Encapsulation):将内容封装到某个地方,以后再去调用被封装在某处的内容。 对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。

计算机层面:①.模块、类、函数...②.属性数据的封装与隐藏 (数据私有化);封装的好处:安全性提高了。

场景演示:

 1 class Person:
 2     def __init__(self,name,age,money):
 3         self.name = name
 4         self.age = age
 5         self.money = money
 6     def __str__(self):
 7         return "name:%s,age:%s,money:%s"%(self.name,self.age,self.money)
 8 
 9 # 实例化Person对象
10 p = Person('tom',30,10000)
11 print(p)      # name:tom,age:30,money:10000
12 
13 # age可以设置值,但是不符逻辑了
14 p.age=-40      
15 print(p)      # name:tom,age:-40,money:10000

以上情况不会出现编译和运行异常,但是出现了数据不符合逻辑的情况;关系到对象直接在外部去操作数据(属性),导致"脏数据"的出现;要解决此问题,需要将上面的年龄age私有化,一旦私有化age之后,那么age使用的方位只有在class中,出了class外界无法使用他,使用__属性名的方式。
1).首先第一步是在外界不允许对象直接操作/访问属性(将此权利没收) --> 将属性私有化:__属性名
2).需要在类的内部提供给外界额外的访问方式(函数:getter/setter)

 1 class Person:
 2     def __init__(self,name,age,money):
 3         self.name = name
 4         self.__age = age
 5         self.money = money
 6     def __str__(self):
 7         return "name:%s,age:%s,money:%s"%(self.name,self.__age,self.money)
 8 
 9 # 实例化Person对象
10 p = Person('tom',30,10000)
11 print(p)      # name:tom,age:30,money:10000
12 
13 p.age=-40     # 现在此行代码相当于动态为对象p添加一个属性age  
14 print(p)      # name:tom,age:30,money:10000,年龄任然是30
15 
16 # 查看对象p中所有的成员变量(属性)
17 print(p.__dict__)   # 得到{'name': 'tom', '_Person__age': 30, 'money': 10000} 
18 '''所以age私有化之后,在计算机底层真正的名字已经变成了_Person__age,一个属性一旦被私有化,在底层真正的名字是:_类名__属性名 19 其实python的私有化我们可以理解为伪私有(只是换了个名)''' 20 21 # 以下的操作仅仅是为对象p动态添加一个属性为__age 22 p.__age=-50 23 print(p) # 仍然得到name:tom,age:30,money:10000 24 print(p.__dict__) # 再看属性{'name': 'tom', '_Person__age': 30, 'money': 10000, '__age': -50} ,只是多了一个名为__age的参数 25 26 '''但是动态数据还是可以改的(但是不要去改,这样私有化就没意义了)''' 27 p._Person__age = -100 28 print(p) # 得到name:tom,age:-100,money:10000

私有化之后可以不会出现逻辑不符的现象,但是对于age,需要在类的内部提供给外界额外的访问方式(函数:getter/setter);

格式:get属性名(self)-->有返回值;set属性名(self,变量参数)-->有返回值;

以上两个函数的属性名都满足首字母大写其余字母小写的规范。

 1 class Person:
 2     def __init__(self,name,age,money):
 3         self.name = name
 4         self.__age = age
 5         self.money = money
 6 
 7     # 设置__age
 8     def setAge(self,age):
 9         # 对age值进行合法性的校验
10         if age < 0 or age > 130:
11             raise Exception('年龄不合法...')
12         else:
13             self.__age = age
14 
15     # 获取__age
16     def getAge(self):
17         return self.__age
18 def __str__(self): 19 return "name:%s,age:%s,money:%s"%(self.name,self.__age,self.money) 20 21 p = Person('tom',30,10000) 22 print(p) # name:tom,age:30,money:10000 23 24 # 调用函数完成设置和获取属性值的操作 25 print(p.getAge()) # 30 26 p.setAge(40) 27 print(p) # name:tom,age:40,money:10000 28 29 p.setAge(-40) 30 print(p) # Exception: 年龄不合法...

总结:python的类中只有私有成员和公有成员两种,不像c++中的类有公有成员(public),私有成员(private)和保护成员(protected).并且python中没有关键字去修饰成员,默认python中所有的成员都是公有成员,但是私有成员是以两个下划线开头的名字标示私有成员,私有成员不允许直接访问,只能通过内部方法去访问,私有成员也不允许被继承。在类的内部提供外界额外的访问方式(定义setter和getter方法),并且在需要的时候,可以在函数的内部加入数据合法性的校验;模板:对于setter函数,命名:set属性名(首字母大写);对于getter函数,命名:get属性名(首字母大写)

 

2). 继承性(Inheritance):面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。计算机层面:两部分组成,一部分我们称为父类(基类、超类、superclass);另一部分我们称为子类(派生类、subclass);子类可以使用父类中的成员(使用权)。

继承性的好处:1).代码复用性变强;2).代码扩展性变强;3).代码维护性变好;4).代码阅读性变好;继承性弊端:类和类之间是一种强耦合关系,继承的好处要远远多于弊端,所以我们还是要经常使用继承的(合理),但不能为了继承而继承。 

继承体系可以很庞大(呈现树状结构图),越往上层的类,感觉越模糊,越不清晰越往下层的类,感觉越清晰,越具体,所以得出结论,开发过程中创建父类的可能性变低,子类实例化的可能性极高。注意事项:1).由于继承的特点,子类对象被实例化,但是可能需要为父类属性赋值,那么可以在子类的构造函数中显示的调用父类构造来实现;2) .记住:虽然父类构造被执行,但是它仅仅做的就是赋值这件事,内存中的对象只有子类对象一个。

分类:1).单继承(单一继承);2).多重继承;3).多继承(很多语言是不合法的)

1). 单继承的使用:

 1 # 父类
 2 class Person:
 3     def __init__(self,name,age):
 4         print('我是Person类的构造函数。。。')
 5         self.name = name
 6         self.age = age
 7     #
 8     def eat(self):
 9         print('吃一个...')
10     #
11     def sleep(self):
12         print('睡一会...')
13 
14 # 子类
15 class Teacher(Person):
16     def __init__(self,name,age,salary):
17         print('我是teacher类的构造函数。。。')
18         self.salary = salary
19         # 在子类构造函数中显示的调用其父类构造;调用父类构造函数的目的:父类的属性由父类自己赋值
20         # 方法1:super(Teacher,self).__init__(name,age)
21         # 方法2:super().__init__(name,age)
22         # 方法3:调用父类,这种方式最好
23         Person.__init__(self,name,age)    
24 
25     # 教学
26     def teach(self):
27         print('教书育人...')
28 
29 # 实例化子类对象
30 t = Teacher('老郭',30,6000.0)
31 # 调用属性
32 print(t.name,t.age,t.salary)
33 # 调用函数
34 t.eat()
35 t.sleep()
36 t.teach()

2). 多重继承的使用:

 1 # 定义生物类:
 2 class Creature:
 3     def __init__(self,age):
 4         self.age = age
 5 
 6     def breath(self):
 7         print('呼吸...')
 8 
 9 # 定义动物类:
10 class Animal(Creature):
11     def __init__(self,age,name):
12         self.name = name
13         super().__init__(age)
14 
15     def eat(self):
16         print('吃饭...')
17 
18 # 定义狗类
19 class Dog(Animal):
20     def __init__(self,age,name,color):
21         self.color = color  
22         Animal.__init__(self,age,name)
23 
24     def wangwang(self):
25         print('犬吠...')
26 
27     def __str__(self):
28         return "name:%s,age:%s,color:%s" %(self.name,self.age,self.color)
29 
30 d = Dog(3,'旺财','black')
31 print(d)     # 因为定义了__str__,即重写,所以可以直接看到属性,不然 print(d)得到的是打印d的地址而已,只有print(t.name,t.age,t.color),才能看到结果
33 d.wangwang()
34 d.eat()
35 d.breath()

3). 多继承的使用:大白话就是一个子类可以调用多个父类 

 1 # 定义Father类
 2 class Father:
 3     def __init__(self,money):
 4         self.money = money
 5 
 6     def drinking(self):
 7         print('喝喝喝...')
 8 
 9 # 定义Mother类
10 class Mother:
11     def __init__(self,faceValue):
12         self.faceValue = faceValue
13     def shopping(self):
14         print('买买买...')
15 
16 # 定义Child类,同时继承Father和Mother类
17 class Child(Father,Mother):
18     def __init__(self,money,faceValue,work):
19         self.work = work
20         Father.__init__(self,money)
21         Mother.__init__(self,faceValue)
22 
23     def playing(self):
24         print('玩玩玩...')
25 
26 # 实例化子类对象
27 child = Child(1000000,True,"语数外")
28 
29 # 调用函数
30 child.playing()      # 玩玩玩...
31 child.drinking()     # 喝喝喝...
32 child.shopping()     # 买买买...

 

函数重写(复写,覆盖,override) :

前提:必须有继承性;原因:父类中的功能(函数),子类需要用,但是父类中函数的函数体内容和我现在要执行的逻辑还不相符,那么可以将函数名保留(功能还是此功能),但是将函数体重构;注意:子类重写父类的函数,除了函数体以外的部分,直接复制父类的即可

 1 class Fu:
 2     def func(self):
 3         print('辟邪剑法...')
 4 
 5 class Zi(Fu):
 6     def func(self):
 7         super().func()
 8         print('葵花宝典...')
 9 
10 # 实例化子类对象
11 zi = Zi()    
12 
13 zi.func()     # 得到 辟邪剑法
14               #     葵花宝典

......

 

17.python面向对象

全文结束