python语法(七)面向对象

面向对象

  • 基本概念:
    • 面向过程:
      把完成某一个需求的所有步骤 从头到尾 逐步实现,根据开发需求,将某些 功能独立 的代码封装成一个又一个函数,最后完成的代码,就是顺序地调用不同的函数

    • 面向对象:
      是更大的封装,根据 职责 在一个对象中封装多个方法

    • 类 是对一群具有 相同 特征 或者 行为的事物的一个统称,是抽象,不能直接使用

      • 特征 被称为 属性
      • 行为 被称为 方法
        类相当于制造飞机时的图纸,是一个模板,是负责创建对象
      • 对象
        对象 是 由类创建出来的一个具体存在,可以直接使用,由哪一个类创建出来的对象,就拥有在哪一个类中定义的:属性方法,先有类再有对象
        	Class Person():
        		name = "xxx"
                	def __init__(self):
                      		pass
        	Person.name
        	p1 = Person()
      

类的设计

类名: 这类事物的名字,满足大驼峰命名法:首字母大写,单词之间没有下划线
属性: 这些事物具有什么样的特征
方法: 这类事物具有什么样的行为
(1)定义简单的类
格式:

  class 类名:
	def 方法1(self,参数列表):
		pass
	def 方法2(self,参数列表):
		pass
  #切记如果函数有参数,第一个参数必须是self

(2)对象的创建
对象变量 = 类名()
第一个面向对象程序:
需求

  • 小猫爱吃鱼,小猫要喝水
  • 定义一个猫类 Cat
  • 定义两个方法 eat 和 drink
  • 按照需求–不需要定义属性
class Cat:
	def eat(self):
		pirnt("小猫爱吃鱼")
	def drink(self):
		print("小猫要喝水")

(3)构造方法
1.初始化方法
以前面的class类为例子:py中可以在类外部给类添加属性:

 tom = Cat()
 tom.name = "Tom"
 tom.eat()

不建议使用
如果name 是方法 这样赋值就会失去 该方法,推导->一个类的属性应该放在什么地方?
2.构造方法
当使用 类名()创建对象时,会自动执行以下操作:

  1. 为对象在内存中分配空间–创建对象
  2. 为对象的属性设置初始值–初始化方法(init)
  3. __init__方法是专门用来定义一个类具有哪些属性的方法!
class Cat:
     """这是一个猫类"""
    def __init__(self):
       print("初始化")
    def eat(self):
       print("小猫爱吃鱼")
    def drink(self):
        print("小猫要喝水")
tom = Cat()

就会自动输出: 初始化
3.在初始化方法内部定义属性
在__init__方法内部使用 self.属性名 = 属性的初始值,就可以定义属性,定义属性之后,在使用Cat类创建的对象都会拥有该属性
4.改造初始化方法–初始化的同时设置初始值
在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对 __init__方法进行改造

  1. 把希望设置的属性值,定义成__init__方法的参数
  2. 在方法内部使用self.属性 = 形参 接收外部传递的参数
  3. 在创建对象时,使用 类名(属性1,属性2…)调用
class Cat:
     """这是一个猫类"""
    def __init__(self,name):
       print("初始化")
       self.name = name
    def eat(self):
       print("小猫爱吃鱼")
    def drink(self):
        print("小猫要喝水")

5.析构方法
(1)del 对象被从内存中销毁的,会被自动调用
当使用 类名() 创建对象时,为对象 分配完空间后,自动调用 init 方法,当一个 对象被从内存销毁 前,会 自动 调用 __del__方法
应用场景:
__init__改造初始化方法,可以让创建对象更加灵活
__del__如果希望在对象被销毁前,再做一些事情,可以考虑一下__del__方法
生命周期:
一个对象从调用 类名() 创建,生命周期开始,一个对象的__del__方法一旦被调用,生命周期结束,在对象的生命周期内,可以访问属性,或者让对象调用方法
(2)str 返回对象的描述信息,print函数输出使用
6.方法中的self参数
在Python类中规定,函数的第一个参数是实例对象本身,并且约定俗成,把其名字写为self。其作用相当于java中的this,表示当前类的对象,可以调用当前类中的属性和方法。self 指向的是当前 调用类中方法的实例对象
7.四则运算符重载
四则运算加,减,乘,除(+,-,*,/)分别是通过调用 __ add __ , __ sub __ , __ mul __
和__truediv__方法完成重载的。当两个实例对象执行四则运算时,自动调用的是它们对应的方法

创建一个Employee()类
class Employee(object):
	def __init__(self,name,age):
		self.name = name
		self.age = age

zs =Employee("张三",20)
ls = Employee("李四",26)
#想要实现求得二者的平均年龄
sum_age = (zs.age + ls.age)/2
print(sum_age)
//现在想优化加法 让其可以实现 zs + ls
class Employee(object):
	def __init__(self,name,age):
		self.name = name
		self.age = age
	def __add__(self,other):
		return self.age + other.age


sum_age = (zs + ls)/2
print(sum_age)
#这样即可
减法,除法,乘法依次类推  
小测试:实现 a + b 返回的结果是 a - b,a - b 返回的结果是a + b 
就是,重写__add__() 和 __sub__()

class New_int(int):
       def __add__(self,other):
             return  int.__sub__(self,other)
       def __sub__(self,other):
              return int.__add__(self,other)


a = New_int(3)
b = New_int(1)
a + b = 2
a- b = 4
错误演示
class New_int(int):
       def __add__(self,other):
             return  self+other
       def __sub__(self,other):
              return self-other

a = New_int(3)
b = New_int(1)
a + b
这样会陷入无限递归
原因是:
self+other这两个相加等于调用def __add__(self,other)
class New_int(int):
       def __add__(self,other):
             return  int(self)+int(other)
       def __sub__(self,other):
              return int(self)-int(other)
这样就可以了

反运算
a + b 当a 的__add__()函数不能正常用时
就会调用b的__radd__()函数
举个例子

class New_int(int):
       def __radd__(self,other):
             return __sub__(self,other)

a = New_int(3)
b = New_int(1)
a+ b = 4
1 +  b = 0

__ rsub __()也是如此

索引和分片重载
以下是跟索引和分片相关的三个可重载方法:
__ getitem __ :索引,分片取值
__ setitem __:索引,分片赋值
__ delitem __:索引和分片删除

这三个方法都可以进行重载,具体方法如下
__getitem__方法
在对实例对象执行索引,分片或者for迭代操作取值时,会自动调用__getitem__方法。
例如:

class Company(object):
	def __init__(self,boss,ls):
		self.boss = boss
		self.ls = ls
company = Company("刘备",["关羽","张飞","赵子龙","黄忠"])

想要输出列表中的某一个可用 company.ls[i]
这样显得不怎么方便

class Company(object):
	def  __init__(self,boss,ls):
		self.boss = boss
		self.ls = ls
	def  __getitem__(self,item): #item 是一个切片对象
		return self.ls[item]
company[i] 即可输出第i个数据  #如果这里是下标,item就是下标
company[i:]#这里是切片的用法,所以 item 也是一个切片对象 

其他的几个方法:

class Company(object):
	def  __init__(self,boss,ls):
		self.boss = boss
		self.ls = ls
	def  __getitem__(self,item): #item 是一个切片对象
		return self.ls[item]
	def  __setitem__(self,key,value):
		self.ls[key] = value
	def  __delitem__(self,key):
		del self.ls[key]
company[i] = "内容"
del company[1]

删除和修改都可以使用这种简介方法来使用了
重写 __ str__ 方法
在 python 中,使用print输出对象变量,默认情况下,会输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示)
如果在开发中希望使用 print 输出对象变量时,能够打印自定义的内容,就可以利用__str__这个内置方法了
注意:__ str__方法必须返回一个字符串

class Cat:
	def __init__(self,new_name):
		self.name = new_name
		print("%s 来了" % self.name)
	def __str__(self):
		return "我是小猫:%s" % self.name
tom = Cat("Tom")
print(tom)
如果想要输出默认情况则可
print(repr(tom))

8.面向对象的封装
封装:
1.封装是面向对象编程的一大特点
2.面向对象编程的第一步 - - 将属性 和 方法 封装到一个抽象的类中
3.外界使用类创建对象,然后让对象调用方法
4.对象方法的细节 都被 封装 在类的内部
5.一个对象的属性可以是另一个类创建的对象
需求:士兵突击

士兵 许三多 有一把 AK47
士兵 可以 开火
枪 能够 发射 子弹
 枪 装填 装填子弹 --增加子弹数
Soldier 类,属性:name ,gun
方法:
__init__(self):
fire(self): 
#1.判断 士兵是否有枪  2. 高喊口号  3.让枪装填子弹  4.让枪发射子弹
Gun 类
属性:
model
bullet_count
方法:
__init__(self,model):
add_bullet(self,count):
shoot(self):

身份运算符:
身份运算符用于比较两个对象的内存地址 是否一致 --是否是对同一个对象的引用
在 python 中针对 None 比较时,建议使用 is

运算符描述实例
isis是判断两个标识符是不是引用同一个对象x is y. 类似 id(x) == id(y)
is notis not 是判断两个标识符是不是引用不同对象x is not y, 类似 id(x) != id(y)

is 与 = = 区别:
is 用于判断两个变量 引用对象是否为同一个,== 用于判断引用变量的值 是否相等
意思就是:两个事物值相等但是其地址可以不一样
私有属性和私有方法
应用场景及定义方式
应用场景:

  • 在实际开发中,对象 的 某些属性或方法 可能只希望 在对象的内部被使用,而 不希望在外部被访问到
  • 私有属性 就是 对象 不希望公开的 属性 在外界不能被直接被访问
  • 私有方法 就是 对象 不希望公开的 方法 在外界不能被直接被访问

定义方式:
在 定义属性或方法时,在属性名或者方法名前 增加 两个下划线,定义的就是 私有 属性或方法
但是在python中是伪私有属性和私有属性(科普)
提示:在日常开发中,不要使用这种方式,访问对象的 私有属性 或 私有方法 python 中,并没有真正意义 的 私有
在给属性,方法命名时,实际上是对 名称 做了一些特殊处理,使得外界无法访问到
处理方式:在名称前面加上 _类名__名称

8.单继承
面向对象三大特征

  1. 封装 根据 职责 将 属性 和 方法 封装 到一个抽象的类中
  2. 继承实现代码的重用,相同的代码不需要重复的编写
  3. 多态 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度

(1)继承的概念,语法和特点:
继承的概率:子类拥有父类的所有方法 和 属性
e.g:
不使用继承:
编写 1.Animal 类 2.Dog 类 3.Cat 类 4.Chinese_garden_dog
Animal 的特性 吃,喝,睡…
Dog 的特性 吃,喝,睡…
Cat 的特性 吃,喝,睡…

可以发现这些类都具有一些相同的属性或者行为(方法),不使用继承就会造成这部分代码的累赘。

e.g:
class Animal:
     def eat(self):
         print("吃")
     def drink(self):
         print("喝")
     def sleep(self):
         print("睡觉")


class Dog(Animal):
     def bark(self):
         print("汪汪")


class Cat(Animal):
     def bark(self):
         print("喵喵")

(2)继承的语法

         class 类名(父类名):
			pass

子类继承自 父类,可以直接 享受 父类中已经封装好的方法,不需要再次开发
子类中应该根据 职责,封装 子类特有的属性和方法
专业术语:

       Dog 类是 Animal 类的子类,Animal 类是 Dog 类的父类,Dog 类从 Animal 类继承
       Dog 类是 Animal 类的派生类,Animal 类是Dog 类的基类,Dog类从Animal 类派生

继承的传递性:
C类从B类继承,B类又从A类继承
那么C类就具有B类和A类的所有属性和方法
子类 拥有 父类以及 父类的父类 中封装的所有属性 和 方法
9.多继承
子类 可以拥有 多个父类,并且所有具有所有父类的属性和方法
语法格式:

	class 子类名(父类名1,父类名2.....):
		pass

注意事项:
如果 不同的父类 中存在同名的方法,子类对象 在调用方法时,会调用哪一个父类方法?

e.g:
class A:
	def fun1():
		print("A----fun1")
	def fun2():
		print("A----fun2")
class B:
	def fun1():
		print("B----fun1")
	def fun2():
		print("B----fun2")

class C(A,B):
	pass

c = C()
c.fun1()
c.fun2()

class c(B,A):
	pass

c = C()
c.fun1()
c.fun2()

在开发时,应该尽量避免这种容易产生混淆的情况!–如果父类之间存在同名的属性或者方法,应该 尽量避免 使用多继承
python 中的 MRO – 方法搜索顺序(知道)
python 中针对 类提供了一个内置属性 __mro__可以查看方法 搜索顺序
MRO 是 method resolution order,主要用于 在多继承时判断 方法,属性的调用路径
print(C. __ mro __)

在搜索方法时,是按照 __mro__的输出结果从左到右 的顺序查找的
如果在当前类中 找到方法,就直接执行,不再搜索
如果没有找到,就查找下一个类 中是否有对应的方法,如果找到,就直接执行,不再搜索
如果找到最后一个类,还没有找到方法,程序报错

新式类 和 旧式类
object 是 python 为所有对象提供的基类,提供有一些内置的属性和方法,额可以使用 dir 函数查看
新式类 : 以object 为基类的类,推荐使用 py3都是这样
旧式类: 不以object为基类的类,不推荐使用

10.重写父类方法与调用父类方法和属性
子类 拥有 父类的所有方法 和 属性
子类 继承自父类,可以直接享用父类中已经安排好的方法,不需要再次开发

当父类的方法实现不能满足子类需求时,可以对方法进行重写
重写父类方法两种情况
1.覆盖父类的方法
2.对父类方法进行 扩展
(1)覆盖:
具体实现方法,就相当于在子类中 定义了一个和父类同名的方法并且实现
(重写后,在运行时只会调用子类中重写的方法,而不会调用 父类封装的方法)
(2)扩展:
如果开发中,子类的方法实现 中 包含 父类方法的实现
父类原本封装的方法实现 是 子类方法的一部分
这里就可以使用 扩展 的方法
- 在子类中 重写 父类的方法
- 在需要的位置使用 super().父类方法,来调用父类方法的执行
- 代码其他的位置针对子类的需求,编写子类特有的代码实现
- 也可以 父类名.方法(self)
父类名.方式(self)#调用未绑定的父类的方法,可以通过父类.方法名调用,并且此时的self是子类的属性
super().父类方法#也可以通过super()函数调用,推荐使用super()函数,可以自动找到父类的属性,我们可以不用手动传参

11.多态
不同的 子类对象 调用相同的 父类方法,产生不同的执行结果
-多态 可以 增加代码的灵活度
-以 继承 和 重写 父类方法为前提
-是调用方法的技巧,不会影响到类的内部设计
12.
类对象:就是类本身,支持两种操作:属性引用和实例化 ,上述的例子中 Person 就是类对象
实例对象:由类实例化出来的对象 唯一操作是属性引用,p1就是实例对象
类属性:就是类对象所拥有的属性,它被所有类对象的实例对象所共有,在内存中只存在一个副本;对于公有的类属性,在类外可以通过类对象和实例对象访问
e.g:

class Tool:
	count = 0 # 类属性
	def __init__(self,name):
		self.name = name
		Tool.count += 1

-类属性 就是给 类对象 中定义的属性
-通常用来记录 与这个类相关的特征
-类属性不会用于记录 具体对象的特征
访问类属性有两种方式:
(1)类名.类属性:
(2)对象.类属性(不推荐)

  • 向上查找机制:首先在对象内部查找对象属性,没有找到就会向上寻址类属性
    如果使用 对象.类属性 = 值,只会给对象添加一个属性,而不会影响到类属性的值
  • 实例属性:类的实例化对象的属性,属于实例对象独有
    类对象和实例对象都可以访问类属性
    类对象无法访问实例对象
    类属性只有通过类对象进行修改,实例对象无法修改类属性,通过实例对象给类属性进行赋值,
    实际上是给实例对象临时添加了一条新的属性
  • 类方法:
    类方法:是类对象所拥有的方法,需要用修饰器"@classmethod"来标识其为类方法,
    对于类方法,第一个参数必须是类对象,一般以"cls"作为第一个参数(只是一个参数命名习惯,也可以换成其他的,不过不推荐)
    能够通过实例对象和类对象去访问。
    类方法的一个用途就是可以对类属性进行修改, 这样的话,实例对象通过调用类方法也可以对类属性进行修改
e.g:
class Tool:
	count = 0 # 类属性
	@classmethod#类方法 , cls.属性, cls.类方法名
	def show(cls):
		print("数量"%cls.count)
	def __init__(self,name):
		self.name = name
		Tool.count += 1
  • 实例方法:在类中最常定义的成员方法,它至少有一个参数并且必须以实例对象作为其第一个参数,一般以名为"self"的变量作为第一个参数。 在类外实例方法只能通过实例对象去调用,不能通过其他方式去调用(其实类对象可以调用实例方法,只不过需要传一个实例对象作为第一个参数)。
  • 静态方法:
    需要通过修饰器 @staticmethod 来进⾏修饰,静态⽅法不需要多定义参数
    跟普通函数没什么区别,与类和实例都没有所谓的绑定关系,不论是通过类还是实例都可以引用该方法。
    静态方法使用场景:如果在方法中不需要访问任何实例方法和属性,纯粹地通过传入参数并返回数据的功能性方法,那么它就适合用静态方法来定, 它节省了实例化对象的开销成本,往往这种方法放在类外面的模块层作为一个函数存在也是没问题的,而放在类中,仅为这个类服务。
  • 语法方式:
@staticmethod
def  静态方法名():
	pass
通过 类名.调用静态方法
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dearzcs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值