一、对象基本概念
1.1 什么是对象
- 对象是Python中所有数据的抽象表示
- 一切皆为对象:数字、字符串、函数、类、模块等
- 对象包含:标识(identity)、类型(type)、值(value)
1.2 对象三要素
- 标识:对象的唯一标识符(内存地址),通过id(对象)获取,Python 对 -5 ~ 256 的整数做了缓存,id 永远一样
- 类型:对象的类型,通过type()获取
- 值:对象存储的数据
二、类(Class)定义
class 类名(继承对象): # 类属性(所有实例共享) 类属性名 = 值 def __init__(self,参数):#这里self相当于this,每个方法都要写这个, #这个self是命名可以换成别的名字 """构造方法""" self.实例属性1 = 参数1 self.实例属性2 = 参数2 # 初始化代码 def 方法名(self, 参数1, 参数2, ...): """实例方法""" # 方法体 return 结果 @classmethod #注解 def 类方法名(cls, 参数1, 参数2, ...): """类方法""" # 方法体 return 结果 @staticmethod #注解 def 静态方法名(参数1, 参数2, ...): """静态方法""" # 方法体 return 结果这里我好好说下,那 self.实例属性1 = 参数1 self.实例属性2 = 参数2,这两个是调用了这个构造函数的实例才具有,但是构造函数不能重载,会进行覆盖,所以都用默认参数,如果你调用的不是这个构造函数,那么你对象没有这个实例属性
动态添加实例方法
# 先定义基础类 class Person: def __init__(self, name, age): self.name = name self.age = age # 1. 定义一个待添加的实例方法(必须带self参数) def introduce(self): return f"我是{self.name},今年{self.age}岁" # 2. 给Person类动态添加实例方法 Person.introduce = introduce # 3. 测试:新创建的实例、已创建的实例都能使用该方法 # 已创建的实例(添加方法前创建) p1 = Person("李四", 18) # 新创建的实例(添加方法后创建) p2 = Person("张三", 20) print(p1.introduce()) # 输出:我是李四,今年18岁 print(p2.introduce()) # 输出:我是张三,今年20岁属性默认是public,外部可以对象.属性,private的属性是以__开头,两个下划线
class Person: # 类属性 species = "人类" def __init__(self, name, age): """构造方法,创建实例时自动调用""" # 实例属性 self.name = name self.age = age # 私有属性(约定,非强制) self._private_attr = "约定的私有,可以访问" self.__real_private = "真正私有" # 名称重整 def introduce(self): """实例方法""" return self._private_attr,self.__real_private if __name__=='__main__': p=Person('lisi',18) a,b=p.introduce()#解包 print(f'我是{p.species},我是{p.name},今年{p.age}') print(p.__real_private ) #报错,非法访问私密属性 print(a,b)类方法 vs 实例方法 vs 静态方法
类方法没有实例self,用的是(cls,参数),只能访问类里定义的属性方法,不能访问,构造函数,还有外部添加的实例属性和方法,这里的静态方法和其他语言,不一样,不要静态方法访问静态变量和方法,这里的静态方法就相当于普通函数 静态方法的本质是 “挂在类名下的工具函数”,和实例无关,所以一般用类名直接调用
被类方法改变的属性和方法就是改了,不管多少个实例都是改了的属性
私有属性和方法
class BankAccount: def __init__(self, owner, balance=0): self.owner = owner # 公共属性 self._balance = balance # 受保护的属性(约定) self.__password = "123456" # 私有属性(名称重整) def get_balance(self): """公共方法访问私有属性""" return self._balance def deposit(self, amount): """存款""" if amount > 0: self._balance += amount return True return False def __validate_password(self, password): """私有方法""" return self.__password == password def withdraw(self, amount, password): """取款需要密码验证""" if self.__validate_password(password) and amount <= self._balance: self._balance -= amount return True return False # 使用 account = BankAccount("张三", 1000) print(account.owner) # 张三 print(account.get_balance()) # 1000 # print(account.__password) # 错误:无法直接访问 # print(account._balance) # 可以访问但不推荐继承和多态
单继承
class Animal: def __init__(self, name): self.name = name def speak(self): """父类方法""" return "动物叫声" def move(self): """父类方法""" return f"{self.name}在移动" class Dog(Animal): # 继承Animal def __init__(self, name, breed): super().__init__(name) # 调用父类构造方法 self.breed = breed def speak(self): """重写父类方法""" return "汪汪汪" def wag_tail(self): """子类特有方法""" return f"{self.name}在摇尾巴" # 使用 dog = Dog("旺财", "金毛") print(dog.speak()) # 汪汪汪(多态) print(dog.move()) # 旺财在移动(继承) print(dog.wag_tail()) # 旺财在摇尾巴(子类特有)多重继承
class Flyable: def fly(self): return "能飞" class Swimmable: def swim(self): return "能游" class Duck(Flyable, Swimmable): # 多重继承 def __init__(self, name): self.name = name #为什么这里没有super,因为它会自动调用,在父类并没有显示写出来 def quack(self): return "嘎嘎嘎" # 使用 duck = Duck("唐老鸭") print(duck.fly()) # 能飞 print(duck.swim()) # 能游 print(duck.quack()) # 嘎嘎嘎super:调用父类的方法
- 父类
__init__没显式写→ Python 会隐式调用默认的空__init__,子类不写super()也没问题; - 父类
__init__显式写了(哪怕是空的)→ 子类如果重写__init__,必须手动调用super().__init__(),否则父类的__init__不会执行(哪怕里面是空代码)。
重写(python里没有重载概念,你写了两个参数不同的实例在c++,java里算重载,python直接覆盖,取最下面的实例)
重写就是子类方法和父类的一摸一样,然后重写自己的逻辑和父类不同
class Parent: def __init__(self, value): self.value = value def display(self): return f"父类值:{self.value}" def common_method(self): return "父类通用方法" class Child(Parent): def __init__(self, value, extra): super().__init__(value) # 调用父类构造方法 self.extra = extra def display(self): """重写并扩展父类方法""" parent_info = super().display() # 调用父类方法 return f"{parent_info},额外值:{self.extra}" def common_method(self): """完全重写""" return "子类重写的方法"多态
因为参数类型是python自己判断没有自己规定的
# 定义不同类,都有同名的 `make_sound` 方法 class Cat: def make_sound(self): return "喵呜~" class Dog: def make_sound(self): return "汪汪!" class Duck: def make_sound(self): return "嘎嘎~" # 统一的调用函数(不关心对象类型,只关心是否有 make_sound 方法) def animal_cry(animal): print(animal.make_sound()) # 调用:传入不同对象,表现出不同行为(多态) animal_cry(Cat()) # 输出:喵呜~ animal_cry(Dog()) # 输出:汪汪! animal_cry(Duck()) # 输出:嘎嘎~抽象基类
使用abc模块
from abc import ABC, abstractmethod import math class Shape(ABC): # 抽象基类 @abstractmethod def area(self): """抽象方法,子类必须实现""" pass @abstractmethod def perimeter(self): """抽象方法,子类必须实现""" pass def describe(self): """具体方法,子类可继承""" return f"这是一个形状" class Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): """实现抽象方法""" return math.pi * self.radius ** 2 def perimeter(self): """实现抽象方法""" return 2 * math.pi * self.radius class Rectangle(Shape): def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height def perimeter(self): return 2 * (self.width + self.height) # 使用 circle = Circle(5) rect = Rectangle(4, 6) print(circle.area()) # 78.53981633974483 print(rect.perimeter()) # 20 print(circle.describe()) # 这是一个形状属性装饰器
@property:把方法变成属性,所以一般不要用到参数的实例可以用这个
比如我调用一个没有参数的p.isshow(),要多(),如果用了@property修饰那个方法,就可以p.isshow,更访问属性一样
class Circle: def __init__(self, radius): self._radius = radius @property def radius(self): """获取半径""" return self._radius # 2. 属性设置器(@radius.setter):定义“赋值 radius 属性”的逻辑 @radius.setter def radius(self, value): """设置半径""" if value < 0: raise ValueError("半径不能为负数") self._radius = value @property def diameter(self): """只读属性:直径""" return 2 * self._radius @property def area(self): """只读属性:面积""" import math return math.pi * self._radius ** 2 # 使用 circle = Circle(5) print(circle.radius) # 5 print(circle.diameter) # 10 print(circle.area) # 78.53981633974483 circle.radius = 10 # 调用setter print(circle.area) # 314.1592653589793 # circle.diameter = 20 # 错误:只读属性