欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

17.python面向对象

程序员文章站 2022-06-24 23:34:52
面向对象的编程(object oriented programming),简称OOP:是一种编程的思想。OOP把对象当成一个程序的基本单元,一个对象包含了数据和操作数据的函数。面向对象的出现极大的提高了编程的效率,使其编程的重用性增高。 模拟场景理解面向对象和面向过程: 1 ''' 2 使用面向过程 ......

面向对象的编程(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               #     葵花宝典

......