类和对象

  • 类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用
    • 特征被称为属性,行为被称为方法
  • 对象是由类创建出来的一个具体实体,可以直接使用
    • 由哪一个类创建出来的对象,就拥有在哪一个类中定义的属性和方法
  • 类和对象的关系
    • 类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象
    • 类只有一个,而对象可以有很多个
      • 不同对象之间的属性可能会各不相同
    • 类中定义了什么属性和方法,对象中就有什么属性和方法,不能多,也不能少
  • 类的设计:通常需要满足一下三个要素

    • 类名:遵守大驼峰命名法
    • 属性:这类事物具有的特征
    • 方法:这类事物具有的行为

      基础语法

      定义方法

  • 定义一个只包含方法的类,语法格式为

    1. class 类名:
    2. def 方法名(self, 参数列表)
    3. 方法体
  • 方法的定义格式和之前学习过的函数几乎一样,区别在于第一个参数必须是self

    • 在类封装的方法内部,self 表示当前调用方法的对象自己
    • 调用方法时,不需要传递 self 参数
    • 在方法内部,可以通过 self. 访问对象的属性和调用其他的对象方法

      定义属性

  • 在Python中,可以在类的外部直接设置一个属性,例如 对象名.name = 'Tom'。但是不推荐使用,因为属性应该封装在类的内部,通常在初始化方法内部定义属性

  • 初始化方法
    • 当使用类名()创建对象时,会自动执行以下操作
      • 创建对象:为对象在内存中分配空间
      • 调用初始化方法:为对象的属性设置初始值
    • 这个初始化方法就是init方法,是对象的内置方法,专门用来定义一个类具有哪些属性
  • 在初始化方法内部定义属性
    • 语法格式:self.属性名 = 属性的初始值
    • 创建该类的对象时,对象中就会拥有该属性
  • 创建对象的同时指定初始值

    1. 把希望设置的属性值,定义成init方法的参数
    2. 在方法内部使用self.属性 = 形参接收外部传递的参数
    3. 在创建对象时,使用类名(属性1, 属性2, ...)调用

      创建对象

  • 语法格式:对象变量 = 类名(参数列表)

  • 使用类创建对象之后,对象变量保存的是对象在内存中的存放地址
    • 因此使用print输出对象变量,默认是输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的十六进制地址,形如<__main__.A object at 0x0000020E675745B0>
  • 访问对象和方法的语法格式

    • 在类的外部,通过变量名.访问对象的属性和方法
    • 在类封装的方法中,通过self.访问对象的属性和方法

      常用内置函数

      方法名“格式的方法是Python提供的内置方法 / 属性
  • new:创建对象时,会被自动调用

  • init:对象被初始化时,会被自动调用
  • del:对象被从内存中销毁前,会被自动调用
    • 如果希望在从内存销毁对象之前,再做一些事情,可以使用del方法
  • str:返回对象的描述信息,print 函数输出使用

    • 如果希望使用print输出对象变量时,能够打印自定义的内容,可以使用str方法
    • 注意:str方法必须返回一个字符串 ```python class Person: def init(self, name, age, height): self.name = name self.age = age self.height = height print(‘init‘)

      def run(self): print(self.name + ‘ is running’)

      def str(self): return self.name

      def del(self): print(‘del‘)

xiaoming = Person(‘小明’, 18, 175) xiaoming.run() print(xiaoming) del xiaoming

“”” 输出: init 小明 is running 小明 del “””

  1. <a name="quXe6"></a>
  2. ## 私有属性和私有方法
  3. - 应用场景
  4. - 对象的某些属性或方法只希望在对象的内部被访问,而不希望在外部被访问
  5. - 私有属性就是对象不希望公开的属性,私有方法就是对象不希望公开的方法
  6. - 定义方式:在定义属性或方法时,在属性名或者方法名前增加两个下划线即可
  7. <a name="Qg9mT"></a>
  8. ## 封装性
  9. - 特点:将属性和方法封装到一个抽象的类的内部
  10. - 使用类创建对象,然后通过对象调用属性和方法
  11. ```python
  12. class HouseItem:
  13. def __init__(self, name, area):
  14. self.name = name
  15. self.area = area
  16. def __str__(self):
  17. return "%s,占地%.2f" % (self.name, self.area)
  18. class House:
  19. def __init__(self, house_type, area):
  20. self.house_type = house_type
  21. self.area = area
  22. self.free_area = area # 剩余面积
  23. self.item_list = [] # 家具列表
  24. def __str__(self):
  25. return ("户型:%s\n总面积:%.2f\n剩余面积:%.2f\n家具:%s"
  26. % (self.house_type, self.area,
  27. self.free_area, self.item_list))
  28. # 在一个形参后通过冒号加注解,能够联想,提高编程效率
  29. def add_item(self, item: HouseItem):
  30. print("要添加%s" % item) # 很精妙,会调用HouseItem类的__str__方法
  31. if self.free_area < item.area:
  32. print('房间面积不够')
  33. return
  34. self.item_list.append(item.name)
  35. self.free_area -= item.area
  36. # 创建家具对象
  37. bed = HouseItem("席梦思", 4)
  38. chest = HouseItem("衣柜", 2)
  39. table = HouseItem("餐桌", 1.5)
  40. # 创建房子对象
  41. my_home = House("两室一厅", 60)
  42. my_home.add_item(bed)
  43. my_home.add_item(chest)
  44. my_home.add_item(table)
  45. print(my_home)
  46. """
  47. 输出:
  48. 要添加席梦思,占地4.00
  49. 要添加衣柜,占地2.00
  50. 要添加餐桌,占地1.50
  51. 户型:两室一厅
  52. 总面积:60.00
  53. 剩余面积:52.50
  54. 家具:['席梦思', '衣柜', '餐桌']
  55. """

继承性

  • 概念:子类继承父类,从而拥有父类的所有属性和方法
  • 作用:实现代码的重用,不需要重复编写相同的代码

    单继承

  • 语法格式:class 子类名(父类名):

  • 子类继承父类,可以直接享受父类中已经封装好的方法,不需要再次开发
  • 继承的传递性:子类拥有父类以及父类的父类中封装的所有属性和方法
  • 说明:在Python3中定义类时,如果没有指定父类,会默认使用object类作为该类的基类
  • 一些术语
    • 父类 / 基类
    • 子类 / 派生类
    • 继承 / 派生
  • 方法的重写:当父类的方法实现不能满足子类的需求时,可以对所继承的方法进行重写(override),重写的两种情况:
    • 覆盖父类的方法:在子类中重新编写父类的方法实现,适用于子类的方法实现和父类的方法实现完全不同的情况
      • 重写之后,在运行时只会调用子类中重写的方法,而不会调用父类中封装的方法
    • 对父类方法进行扩展:父类原本封装的方法实现是子类方法实现的一部分,适用于子类的方法实现中包含父类的方法实现
      • 在需要的位置使用super().父类方法来调用父类方法的执行
      • 而在代码的其他位置,针对子类的需求,编写子类特有的实现代码
  • 关于super
    • 在Python中super是一个特殊的类,super()就是使用super类创建出来的对象,即父类的匿名对象
    • 最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现
  • 关于父类的私有属性和私有方法

    • 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法
    • 子类对象需要通过父类的公有方法,间接访问父类的私有属性或私有方法

      多继承

  • 子类可以继承多个父类,并且具有所有父类的属性和方法

  • 语法格式:class 子类名(父类名1, 父类名2, …):
  • 问题:如果不同的父类中存在同名的方法或属性,子类对象在调用时,调用的是哪一个
    • Python中针对类提供了一个内置属性mro可以查看方法和属性的调用顺序
      • MRO是method resolution order,用于多继承时判断方法或属性的调用顺序
      • 使用方式:类名.mro
      • 遵循的准则:子类会先于父类被检查,多个父类会根据它们在子类继承列表中的顺序被检查
      • 举例

image.png

  • 在搜索方法或属性时,是按照mro的输出结果从左到右的顺序查找的,如果找到最后一个类还没有找到,程序就报错

    多态性

  • 多态:不同的子类对象调用相同的父类方法,产生不同的执行结果
  • 多态以继承和重写父类方法为前提,可以增加代码的灵活度,且不会影响到类的内部设计 ```python class Dog: def init(self, name):
      self.name = name
    
    def game(self):
      print("%s蹦蹦跳跳的玩耍" % self.name)
    

class XiaoTianDog(Dog): def game(self): print(“%s飞到天上玩耍” % self.name)

class Person: def init(self, name): self.name = name def game_with_dog(self, dog): print(“%s和%s快乐的玩耍” % (self.name, dog.name)) dog.game()

wangcai = XiaoTianDog(“飞天旺财”) xiaoming = Person(“小明”) xiaoming.game_with_dog(wangcai)

“”” 输出: 小明和飞天旺财快乐的玩耍 飞天旺财飞到天上去玩耍 “””

<a name="BN4jV"></a>
## 类属性和类方法

- 一些术语
   - 类的实例:创建出来的对象
   - 实例化:创建对象的动作
   - 实例属性:对象的属性
   - 实例方法:对象的方法
- 每一个对象都有自己独立的内存空间,保存各自不同的属性
- 而多个对象的方法,在内存中只有一份。在调用方法时,需要把对象的引用传递到方法内部,因此定义方法时有self参数
- 在Python中,类是一个特殊的对象 ----> 类对象。在程序运行时,类对象在内存中只有一份,使用一个类可以创建出很多个实例对象
   - 类对象拥有自己的属性和方法,即类属性和类方法,可以通过`类名.`的方式访问类的属性或者调用类的方法

![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654620136817-843afabe-eda0-4a0e-bba6-87982b5d5a59.png#clientId=u82e40517-ede0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=188&id=uc08eb5d1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=282&originWidth=811&originalType=binary&ratio=1&rotation=0&showTitle=false&size=133178&status=done&style=none&taskId=u7fe09ba4-a67f-4ca2-ad9d-d3cf218f67f&title=&width=540.6666666666666)
<a name="Ai2xu"></a>
### 类属性

- 类属性就是给类对象定义的属性,通常用来记录与这个类相关的特征,类属性不会用于记录具体对象的特征
- 定义类属性的语法格式:使用赋值语句在class关键字下方可以定义类属性
- 属性的获取机制:通过`对象名.属性名`首先是在对象内部查找对象属性,如果没有找到再向上查找类属性,因此访问类属性有两种方式
   - 类名.类属性
   - 对象名.类属性(不推荐)
- 注意:如果使用`对象名.类属性 = 值`赋值语句,只会给对象添加一个与类属性同名的对象属性,而不会影响到类属性的值
```python
class Tool:
    # 类属性
    count = 0

    def __init__(self, name):
        self.name = name
        Tool.count += 1

tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("铁锹")
print(Tool.count) # 3

tool1.count = 99  # 给对象tool1新增了一个count的对象属性
print(tool3.count)  # 3
print(Tool.count)  # 3

类方法

  • 类方法就是针对类对象定义的方法,在类方法内部可以直接访问类属性或者调用其他的类方法
  • 定义类方法的语法格式

image.png

  • 类方法需要使用修饰器 @classmethod 来标识,告诉解释器这是一个类方法
  • 类方法的第一个参数应该是cls
    • 由哪一个类调用的方法,方法内的cls就是哪一个类的引用
    • 与实例方法的第一个参数是self的意义类似
    • 也可以使用其他名称,不过习惯使用cls
  • 通过类名.类方法名调用类方法时,不需要传递cls参数
  • 在类方法内部,可以使用cls.访问类的属性和调用其他的类方法 ```python class Tool:

    类属性

    count = 0

    def init(self, name): self.name = name Tool.count += 1

    @classmethod def show_tool_count(cls): print(cls.count)

tool1 = Tool(“斧头”) tool2 = Tool(“榔头”) tool3 = Tool(“铁锹”)

Tool.show_tool_count() # 输出3

<a name="zVNCR"></a>
### 静态方法

- 如果需要在类中封装一个方法,这个方法既不需要访问实例属性或者调用实例方法,也不需要访问类属性或者调用类方法,那么可以将这个方法封装成一个静态方法,例如打印一些提示信息
- 定义静态方法的语法格式

![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654652542595-05c819a7-da45-4d68-98d5-816fbba04cf4.png#clientId=ub089f1ef-e9ea-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=60&id=u0bfb415d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=90&originWidth=190&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4678&status=done&style=none&taskId=ua6d32ea3-171f-4d01-8f50-7d8bde5b3cd&title=&width=126.66666666666667)

- 通过`类名.静态方法名`调用静态方法
- 比较实例方法、类方法、静态方法
   - 实例方法:方法内部需要访问实例属性
      - 实例方法内部可以使用`类名.类属性`访问类属性
      - 如果方法内部既需要访问实例属性,又需要访问类属性,应该定义成实例方法
   - 类方法:方法内部只需要访问类属性
   - 静态方法:方法内部不需要访问实例属性和类属性
      - 注意:静态方法内部可以访问类属性,但是由于参数列表为空,所以不能访问实例属性
      - 静态方法也可以调用类方法,但是在Pycharm中可能会有点小bug
```python
class Game(object):
    top_score = 0

    def __init__(self, player_name):
        self.player_name = player_name

    @staticmethod
    def show_help():
        print("帮助信息:让僵尸进入大门")

    @classmethod
    def show_top_score(cls):
        print("历史记录:%d" % cls.top_score)

    def start_game(self):
        print("%s开始游戏啦" % self.player_name)


Game.show_help()
Game.show_top_score()

game = Game("小明")
game.start_game()