一、⾯向对象 和 ⾯向过程

对于⼤型程序,其逻辑和功能都很复杂,如果 按照业务逻辑 去想,我们往往⽆从下⼿。

⾯向对象编程 (OOP,Object Oriented Programming)可以理解为 将具有相互关系的数据/操作封装成对象,以对象的⻆度去处理问题,让对象来完成相应处理

按照业务逻辑 编写五⼦棋程序,过程应该是这样的:

1.先显示棋盘() 
2.执⿊棋⼿下⼦() 
3.接着刷新界⾯() 
4.然后判断⿊⼦是否胜出() 
5.执⽩棋⼿下⼦() 
6.接着刷新界⾯() 
7.然后判断⽩⼦是否胜出() 
8.重复2-7

按照业务逻辑 从上到下 设计程序 的⽅式,叫做 ⾯向过程编程(Procedure Oriented Programming,POP,⾯向过程程序设计)

⾯向过程编程 最易被初学者接受,其往往⽤⼀⻓段代码来实现指定功能,⼀步接⼀步,环环相扣

对于⼤型项⽬,如果使⽤⾯向过程编程,则很容易导致 代码结构过于紧密、耦合度⾼,易出现代码冗余,并且 不利于团队开发

以对象的视⻆ 编写五⼦棋程序,过程是这样的:

1. 根据 数据和操作 抽象出对象: 棋⼿ 棋盘 计算系统 
2. 各对象实现⾃⼰的功能: 棋⼿.下⼦ 棋盘.刷新界⾯ 计算系统.判断胜负 .. 
3. 使⽤对象执⾏操作 来完成业务逻辑: 
棋盘.刷新界⾯ 
棋⼿.下⼦ 
计算系统.判断胜负 ...

⾯向对象编程 的优点

将数据和业务抽象为 对象,有利于程序整体结构的分析和设计,使设计思路更加清晰

业务以对象为单位,对象各⾃完成⼯作,减少了代码耦合度,有助于业务升级 和 代码重构

⽅便⼯作划分,有利于提⾼团队开发的效率

二、类和对象

对象的组成

对象中包含两个组成部分:

属性: ⽤于记录与对象相关的数据,⽐如姓名,年龄,身⾼,肤⾊等

⽅法: ⽤于实现与对象相关的操作,⽐如吃饭,睡觉,⻜⾏,歌唱等

描述共同⾏为的集合,称为 类 (class)

很多事物存在 相同的操作/⾏为,⽐如⼈都进⾏吃饭、睡觉,狗都会跑会叫等等

类是总结事物特征的 抽象概念,⽽对象是 具体存在的某个实物

类和对象的关系

在编程中,类就是创建对象的模板 或者说 制造⼿册,⽤来定义对象公共的⾏为

类总结了对象的共同特征,有利于复⽤代码创建拥有相同特征的对象

每个对象必须有⼀个对应的类

三、定义类和定义方法

定义类

定义⼀个类,格式如下:

class 类名: 
    ⽅法列表

类名 的命名规则按照"⼤驼峰"

定义方法

类是定义对象的共同⾏为的,也就是说 在类中定义对象的⽅法

定义⽅法:

class 类名: 
    def ⽅法名(self): 
    ...

⽅法的格式和函数类似,也可以设置参数和返回值,但是 需要设置第⼀个参数为 self

注意定义⽅法需要在类的缩进中

demo:定义⼀个Car类

# 定义类 
class Cat: 
    # ⽅法 
    def eat(self): 
        print("猫在吃⻥....") 
    def drink(self): 
        print("猫在喝可乐...")

四、创建对象

python中,可以根据已经定义的类去创建出对象

创建对象的格式为:

引⽤对象的变量名 = 类名()

创建对象demo:

# 根据类,创建⼀个对象 
tom = Cat()

调用方法:

引⽤对象的变量名.⽅法名()

注意:虽然定义⽅法时设置第⼀个参数 self ,但是 调⽤⽅法时不要传递对应 self 的参数

# 创建了⼀个对象 
tom = Cat() 
tom.eat() # 调⽤对象的eat⽅法
tom.drink()

定义/使⽤属性

定义/设置属性 格式:

引⽤对象的变量名.属性名 = 数据

属性和变量类似,⾸次赋值时会定义属性

# 创建对象 
tom = Cat() 
# ⾸次赋值属性,会定义属性 
tom.age = 3 
# 定义属性后,再赋值属性,会修改属性保存的数据 
tom.age = 1 
# 获取属性值并打印 
print(tom.age)

 第一次给对象的属性赋值就是定义一个属性,再次赋值只是修改属性记录的值

五、self关键字

关键字 self 主要⽤于对象⽅法中,表示调⽤该⽅法的对象。

在⽅法中使⽤ self ,可以获取到调⽤当前⽅法的对象,进⽽获取到该对象的属性和⽅法

某个对象调⽤其⽅法时,python解释器会把这个对象作为第⼀个参数传递给⽅法,所以开发者只需要在定义⽅法时 “预留” 第⼀个参数为 self即可

class Dog:
    def eat(self):
        print("%s在吃东西" % self.name)
dog1 = Dog()
dog1.name = "旺财"
dog1.eat()
dog2= Dog()
dog2.name = "来福"
dog2.eat()

结果:

旺财在吃东西
来福在吃东西

 六、__init__()⽅法定义属性

在Python中,__xx__() 双下划线的方法一般叫魔法方法(运算符重载方法),都具有特殊含义,并且在特定的情况下自动调用。

__init__() ⽅法叫做 对象的初始化⽅法,主要用于对象的初始化操作,如定义属性。在 创建⼀个对象后默认会被调⽤,不需要⼿动调⽤

class Dog:
    def __init__(self):
        self.type = "小型动物"
    def eat(self):
        print("%s在吃东西" % self.name)
dog1 = Dog() # 给对象开辟内存空间
print(dog1.type)

结果:小型动物

__init__() ⾃定义参数

__init__(self) 除了默认参数 self ,还可以设置任意个数的⾃定义参数,例如 __init__(self,x,y,z)

init⽅法 设置的⾃定义参数必须和创建对象时传递的参数保持⼀致,例如 tom = Cat(x,y,z)

开发者可以 设置⾃定义参数,为对象的默认属性提供 不同的初始值

开发中,⼀般会在 __init__() 中定义对象的属性

开发者可以 实现这个⽅法,并在该⽅法中定义属性并设置初始值

想要自定义属性的初始值,可以设置init的自定义参数。

init自定义参数对应的实参在创建对象的圆括号里边传递。

class Dog:
    def __init__(self,name): # 给自定义方法添加相应的形参,叫自定义构造方法
        self.type = "小型动物"
        self.name = name # 自定义对象属性的初始值
    def eat(self):
        print("%s在吃东西" % self.name)
dog1 = Dog("旺财")
print(dog1.name)
dog2 = Dog("来福")
print(dog2.name)

 七、__str__()⽅法

打印dog1对象时,输出的是dog1的地址

class Dog:
    def __init__(self,name): # 给自定义方法添加相应的形参,叫自定义构造方法
        self.type = "小型动物"
        self.name = name # 自定义对象属性的初始值
    def eat(self):
        print("%s在吃东西" % self.name)
dog1 = Dog("旺财")
print(dog1.name)
print(dog1)
dog2 = Dog("来福")
print(dog2.name)
print(dog2)

结果

旺财
<__main__.Dog object at 0x000002A89F753FD0>
来福
<__main__.Dog object at 0x000002A89F753F10>

如果我们想输出某些特定的内容,则需要使用 __str__方法

class Dog:
    def __init__(self,name): # 给自定义方法添加相应的形参,叫自定义构造方法
        self.type = "小型动物"
        self.name = name # 自定义对象属性的初始值
    def eat(self):
        print("%s在吃东西" % self.name)
    def __str__(self):
        return "我是%s"%(self.name)
dog1 = Dog("旺财")
print(dog1.name)
print(dog1)
dog2 = Dog("来福")
print(dog2.name)
print(dog2)

结果

旺财
我是旺财
来福
我是来福

print输出对象时,会自动调用 __str__,return的内容会被输出。

如果没有定义__str__方法,直接print打印对象,会看到创建出来的对象在内存中的地址,只要对象定义了 __str__(self) ⽅法,就会 打印该⽅法return的信息描述

 八、私有属性

定义set方法用来赋值,定义get方法用来取值。

class Dog:
    def __init__(self): # 给自定义方法添加相应的形参,叫自定义构造方法
        self.age = None
    def setAge(self,age):
        if age > 0:
            self.age = age
    def getAge(self):
        return self.age
dog1 = Dog()
dog1.setAge(-10) # set方法赋值
print(dog1.getAge()) # get方法取值
dog1.setAge(10)
print(dog1.getAge())
dog1.age = -9
print(dog1.getAge())

 以上代码虽然避免了脏数据,但是外部仍然可以访问age属性。此时我们需要将age属性私有化

class Dog:
    def __init__(self): # 给自定义方法添加相应的形参,叫自定义构造方法
        self.__age = None
    def setAge(self,age):
        if age > 0:
            self.__age = age
    def getAge(self):
        return self.__age
dog1 = Dog()
dog1.setAge(-10)
print(dog1.getAge())
dog1.setAge(10)
print(dog1.getAge())
dog1.age = -9
print(dog1.getAge())

结果:

None
10
10 #外部设置值未成功

私有属性只能在类的内部访问,格式:

属性名前加双下划线,如__age

属性私有化之前,需要提供get和set方法来让外部通过调用方法的形式间接的访问私有属性。定义一对get和set方法,在set方法中对数据进行安全判断后再使用。

为了避免属性被设置为 脏数据,更好的保护属性安全,⼀般的处理⽅式为

  1)、添加⼀个set⽅法,在⽅法内部先判断数据的有效性,再赋值属性

  2)、将属性定义为 私有属性,避免对象在外部直接操作属性

如果在属性名前⾯加了2个下划线'__',则表明该属性是私有属性,否则为公有属性

私有属性只能在类的内部访问

九、私有方法

私有方法和私有属性类似,也是只能在类的内部使用,在方法名字前面加双下划线。

class Dog:
    def __init__(self): # 给自定义方法添加相应的形参,叫自定义构造方法
        self.__age = None
    def setAge(self,age):
        if age > 0:
            self.__age = age
        else:
            self.__info("age")
    def getAge(self):
        return self.__age
    def __info(self,property_name):
        print("%s属性赋值不成功" % property_name)
dog1 = Dog()
dog1.setAge(-10)
print(dog1.getAge())
dog1.setAge(10)
print(dog1.getAge())

私有⽅法和私有属性的 设计⽬的主要有两个:

  1)、保护数据或操作的安全性

  2)、向使⽤者隐藏核⼼开发细节

十、__del__() ⽅法

作用:设置对象被删除前的“临终遗言”,也可以检查当前某个对象是否被删除了。当对象被删除时会自动调用。

创建对象后,python解释器默认调⽤ __init__() ⽅法;

当删除⼀个对象时,python解释器也会默认调⽤⼀个⽅法,这个⽅法为 __del__() ⽅法
class Dog:
    def __init__(self,name):
        self.name = name
    def __del__(self):
        print("%s 要被删除了"% self.name)
dog1 = Dog("旺财")
del dog1
print("*****************")

结果:

旺财 要被删除了
*****************

当有1个变量保存了某个对象的引⽤时,此对象的引⽤计数就会加1,即此时dog1计数会加1.

当使⽤del删除变量dog1时,只有当变量指向的对象的所有引⽤都被删除后,由于没有变量指向该对象,也就是引⽤计数为0时,才会真删除该对象(释放内存空间),这时才会触发 __del__() ⽅法

class Dog:
    def __init__(self,name):
        self.name = name
    def __del__(self):
        print("%s 要被删除了"% self.name)
dog1 = Dog("旺财")
a = dog1
del dog1
print("*****************")

虽然删除了dog1变量,但是仍然有a变量指向对象,所以先执行打印操作,等代码执行完,a变量才会被回收,所以对象才会被释放,从而触发__del__方法。

class Dog:
    def __init__(self,name):
        self.name = name
    def __del__(self):
        print("%s 要被删除了"% self.name)
def main():
    dog1 = Dog("旺财")
main()
print("*****************")

由于函数内部定义的变量是局部变量,函数执行完成就被销毁了。

在开发中,如果项目比较复杂时,可以用此方法来验证对象是否能正常销毁

 十一、继承

⾯向对象三⼤特性: 封装 继承 多态

继承:某个类直接具备另⼀个类的能⼒(属性和⽅法)

格式:

class ⼦类名(⽗类名):

作用:减少代码冗余,提升代码可读性。

class Animal:
    def eat(self):
        print("-----吃-----")
    def drink(self):
        print("-----喝-----")
class Dog(Animal):
    pass
dog1 = Dog()
dog1.eat()
dog1.drink()

结果:

-----吃-----
-----喝-----

 子类添加新功能

class Animal:
    def eat(self):
        print("-----吃-----")
    def drink(self):
        print("-----喝-----")
class Dog(Animal):
    def bark(self): print("-----汪汪叫------")
dog1 = Dog()
dog1.eat()
dog1.drink()
dog1.bark()

多层继承

class Animal:
    def eat(self):
        print("-----吃-----")
    def drink(self):
        print("-----喝-----")
class Dog(Animal):
    def bark(self): print("-----汪汪叫------")
class XTQ(Dog):
    """定义了⼀个哮天⽝ 类"""
    pass
dog1 = XTQ()
dog1.eat()
dog1.drink()
dog1.bark()

结果:

-----吃-----
-----喝-----
-----汪汪叫------

注意:

1)、⽗类中的 私有⽅法、私有属性,不会被⼦类继承

2)、可以通过调⽤继承的⽗类的共有⽅法,间接的访问⽗类的私有⽅法、属性

class Animal:
    def __init__(self):
        self.__num2 = 2
    def __run(self):
        print("-----跑-----")
    def eat(self):
        print("-----吃-----")
    def drink(self):
        print("-----喝-----")
class Dog(Animal):
    def bark(self):
        print("-----汪汪叫------")
        self.__run()
     print(self.__num2) dog1
= Dog() dog1.bark()

结果:

Traceback (most recent call last):
  File "F:\work\pythonProject\SecondDemo\ClassAndObject.py", line 99, in <module>
    dog1.bark()
  File "F:\work\pythonProject\SecondDemo\ClassAndObject.py", line 96, in bark
    self.__run()
AttributeError: 'Dog' object has no attribute '_Dog__run'
-----汪汪叫------

私有方法也是一样的.

class Animal:
    def __init__(self):
        self.__num2 = 2
    def __run(self):
        print("-----跑-----")
    def eat(self):
        print("-----吃-----")
    def drink(self):
        print("-----喝-----")
    def test(self):
        print(self.__num2)
        self.__run()
class Dog(Animal):
    def bark(self):
        print("-----汪汪叫------")
        # self.__run()
        # print(self.__num2)
dog1 = Dog()
dog1.test()

结果:

2
-----跑-----

十二、重写⽗类⽅法

当⼦类实现⼀个和⽗类同名的⽅法时,叫做 重写⽗类⽅法

⼦类重写了⽗类⽅法,⼦类再调⽤该⽅法将不会执⾏⽗类的处理。

使用场景:当父类的某个方法不能满足我们的需求,重写父类方法,做自己需要的功能。

class Animal:
    def eat(self):
        print("-----吃-----")
    def drink(self):
        print("-----喝-----")
class Dog(Animal):
    def bark(self): print("-----汪汪叫------")
class XTQ(Dog):
    def bark(self): print("----嗷嗷叫-----")
dog1 = XTQ()
dog1.bark()

结果:

----嗷嗷叫-----

调⽤被重写的⽗类⽅法

⼦类重写了⽗类⽅法,仍然想执⾏⽗类中的⽅法,两种方式:

方式一:父类名.方法名(当前对象),注意点:需要手动传递对象

class Animal:
    def eat(self):
        print("-----吃-----")
    def drink(self):
        print("-----喝-----")
class Dog(Animal):
    def bark(self): print("-----汪汪叫------")
class XTQ(Dog):
    def bark(self):
        Dog.bark(self)
dog1 = XTQ()
dog1.bark()

方式二:super(类名,对象).方法名()

class Animal:
    def eat(self):
        print("-----吃-----")
    def drink(self):
        print("-----喝-----")
class Dog(Animal):
    def bark(self): print("-----汪汪叫------")
class XTQ(Dog):
    def bark(self):
        super(XTQ,self).bark()
dog1 = XTQ()
dog1.bark()

方式二的简写:super().方法名()

class Animal:
    def eat(self):
        print("-----吃-----")
    def drink(self):
        print("-----喝-----")
class Dog(Animal):
    def bark(self): print("-----汪汪叫------")
class XTQ(Dog):
    def bark(self):
        super().bark()
dog1 = XTQ()
dog1.bark()

 十三、多继承

所谓多继承,即⼦类有多个⽗类,并且具有它们的特征

类名.__mro__查看类的继承链

多继承中,多个父类有同名方法时,想要调用指定父类的方法,先重写此方法,再指定 父类名.方法(self)

class A:
    def printA(self):
        print('----A----')
# 定义⼀个⽗类
class B:
    def printB(self):
        print('----B----')
# 定义⼀个⼦类,继承⾃A、B
class C(A,B):
    def printC(self):
        print('----C----')
obj_C = C()
obj_C.printA()
obj_C.printB()

结果:

----A----
----B----

如果在上⾯的多继承例⼦中,如果⽗类A和⽗类B中,有⼀个同名的⽅法,那么通过⼦类去调⽤的时候,调⽤哪个?

class A:
    def print(self):
        print('----A----')
# 定义⼀个⽗类
class B:
    def print(self):
        print('----B----')
# 定义⼀个⼦类,继承⾃A、B
class C(A,B):
    def print(self):
        # A.print(self)
        B.print(self)
obj_C = C()
print(C.__mro__)
obj_C.print()

结果:

(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
----B----

 十四、多态

多态的概念是应⽤于Java和C#这⼀类强类型语⾔中,⽽Python崇尚“鸭⼦类型”。

所谓多态:定义时的类型和运⾏时的类型不⼀样,此时就成为多态。即需要使用父类对象的地方,也可以使用子类对象。

Python伪代码实现Java或C#的多态

Python “鸭⼦类型”:可以不是鸭子,长得像就行。并不强制数据的类型,只要数据完成了需要的处理,就可以执行,Python动态语言独有的特性。几乎可以理解为没有多态。

鸭⼦类型语⾔中,函数/⽅法可以接受⼀个任意类型的对象作为参数/返回值,只要该对象实现了代码后续⽤到的属性和⽅法就不会报错

鸭子类型是多态的升级版,

class F1(object):
    def show(self):
        print('F1.show')
class S1(F1):
    def show(self):
        print('S1.show')
class S2(F1):
    def show(self):
        print('S2.show')
def Func(obj):
    obj.show()
s1_obj = S1()
Func(s1_obj)
s2_obj = S2()
Func(s2_obj)

结果

S1.show
S2.show

 十五、实例属性、类属性

属性记录的数据不同时,定义实例属性。属性记录的数据始终相同时,定义类属性(节省内存)

1、类属性定义格式:在类的内部,方法的外部,直接向定义变量一样。

2、类属性也可以私有,私有后也只能在类的内部使用,前面加双下划线。

3、修改类属性时,只能通过类对象来修改。

4、实例对象不能修改类属性,而是创建了实例对象自己的属性

5、类对象和实例对象都可以访问类属性

注意:尽量不要类属性和实例属性同名,如果同名时,实例对象会访问自己实例属性,类对象访问类属性。如果有同名对象属性,实例对象会优先访问对象属性

实例属性:

通过类创建的对象 ⼜称为 实例对象,对象属性 ⼜称为 实例属性,记录对象各⾃的数据,不同对象的同名实例属性,记录的数据可能各不相同

类属性:

类属性就是 类对象 所拥有的属性,它被 该类的所有实例对象 所共有。

类属性可以使⽤ 类对象 或 实例对象 访问

class Dog:
    type = "" # 类属性
dog1 = Dog()
dog1.name = "旺财"
dog2 = Dog()
dog2.name = "来福" # 类属性 取值
print(Dog.type) # 结果:狗
print(dog1.type) # 结果:狗
print(dog2.type) # 结果:狗

使⽤场景:

类的实例 记录的某项数据 始终保持⼀致时,则定义类属性

实例属性 要求 每个对象 为其 单独开辟⼀份内存空间 来记录数据,⽽ 类属性 为全类所共有 ,仅占⽤⼀份内存,更加节省内存空间。

 十六、类⽅法、静态⽅法

类⽅法:

类对象所拥有的⽅法

需要⽤装饰器 @classmethod 来标识其为类⽅法,对于类⽅法,第⼀个参数必须是类对象,⼀般以 cls 作为第⼀个参数。

class Dog(object):
    __type = ""
    # 类⽅法,⽤classmethod来进⾏修饰
    @classmethod
    def get_type(cls):
        return cls.__type
print(Dog.get_type())

使⽤场景:

当⽅法中 需要使⽤类对象 (如访问私有类属性等)时,定义类⽅法

类⽅法⼀般和类属性配合使⽤

静态⽅法

需要通过装饰器 @staticmethod 来进⾏修饰,静态⽅法既不需要传递类对象也不需要传递实例对象(形参没有self/cls)。

静态⽅法 也能够通过 实例对象 和 类对象 去访问。

class Dog(object):
    type = ""
    def __init__(self):
        name = None
        # 静态⽅法
    @staticmethod
    def introduce(): # 静态⽅法不会⾃动传递实例对象和类对象
        print("⽝科哺乳动物,属于⻝⾁⽬..")
dog1 = Dog() 
Dog.introduce() # 可以⽤ 实例对象 来调⽤ 静态⽅法
dog1.introduce() # 可以⽤ 类对象 来调⽤ 静态⽅法

结果:

⽝科哺乳动物,属于⻝⾁⽬..
⽝科哺乳动物,属于⻝⾁⽬..

使⽤场景:

1、当⽅法中 既不需要使⽤实例对象(如实例对象,实例属性),也不需要使⽤类对象 (如类属性、类⽅法、创建实例等)时,定义静态⽅法

2、取消不需要的参数传递,有利于 减少不必要的内存占⽤和性能消耗

注意点:

类中定义了同名的对象⽅法、类⽅法、静态⽅法时,调⽤⽅法会优先执⾏最后定义的⽅法

class Dog:
    def demo_method(self):
        print("对象⽅法")
    @classmethod
    def demo_method(cls):
        print("类⽅法")
    @staticmethod
    def demo_method(): # 被最后定义,调⽤时优先执⾏
        print("静态⽅法")
dog1 = Dog()
Dog.demo_method() # 结果: 静态⽅法
dog1.demo_method() # 结果: 静态⽅法

结果

静态⽅法
静态⽅法

 

posted on 2021-09-11 23:44  周文豪  阅读(76)  评论(0编辑  收藏  举报