面向对象之类和对象
面向过程和面向对象
面向过程编程(Procedural Programming)和面向对象编程(OOP)是两种不同的编程范式
Python是一种混合型的语言,既支持面向过程的编程,也支持面向对象的编程。
面向过程的编程是一种以过程为中心的编程方式,主要关注解决问题的步骤,并将这些步骤写成函数或方法。
面向对象的编程是一种以对象为中心的编程方式,主要关注在解决问题的过程中涉及哪些对象以及这些对象如何交互
类和对象
类(Class)
类描述了所创建的对象共同的属性(是什么)和方法(能做什么),属性和方法统称为类的成员。
- 类是对大量对象共性的抽象
- 类是创建对象的模板
- 类是客观事物在人脑中的主观反映
对象(Object)
- 在自然界中,只要是客观存在的事物都是对象
- 类是抽象的,对象是类的实例(Instance),是具体的。
- 一个对象有自己的状态(属性)、行为(方法)和唯一的标识(本质上指内存中所创建的对象的地址)。
定义类
语法
class 类名: """类说明文档""" 类体类名一般使用大驼峰命名法。
类体中可以包含类属性(也叫类变量)、方法、实例属性(也叫实例变量)等。
案例
定义一个人的类,包含__init__()方法、eat()方法和drink()方法。
class Person: """人的类""" home = "earth" def __init__(self): self.age = 0 def eat(self): print("eating...") def drink(self): print("drinking...")类的操作
类支持两种操作,成员引用和实例化。
成员引用
语法
类名.成员名
案例
class Person: """人的类""" home = "earth" def __init__(self): self.age = 0 def eat(self): print("eating...") def drink(self): print("drinking...") home = Person.home # 获取一个字符串 eat_function = Person.eat # 获取一个函数对象 doc = Person.__doc__ # 获取类的说明文档 print(home) # earth print(eat_function) # <function Person.eat at 0x00000232C8230F40> print(doc) # 人的类实例化
语法
变量名 = 类名()
案例
class Person: """人的类""" home = "earth" def __init__(self): self.age = 0 def eat(self): print("eating...") def drink(self): print("drinking...") p = Person() # 创建一个对象 print(p.home) # earth print(p.age) # 0 p.eat() # eating... p.drink() # drinking...__init()__方法
__init__() 是一个特殊的方法,也被称作构造函数。__init__() 方法的主要作用是在创建类的对象时,对对象的属性进行初始化。当你使用类名创建一个新的对象时,Python 会自动调用 __init__() 方法,并将新创建的对象作为第一个参数(通常命名为 self)传递给它。
注意:
- self:这是一个约定俗成的参数名,它代表类的实例对象本身。在方法内部,通过 self 可以访问和修改对象的属性。
- __init__() 方法不是必需的。如果类中没有定义 __init__() 方法,Python 会使用默认的构造函数,该构造函数不执行任何操作。
- __init__() 方法只能返回 None,不能返回其他值。如果尝试返回其他值,会引发 TypeError 异常
class Person: """人的类""" home = "earth" def __init__(self, name): self.name = name p = Person("张三") # 创建一个对象 print(p.name) # 张三self
self作为实例传参
self代表类的实例自身。调用实例方法时,实例对象会作为第一个参数被传入。因此,我们调用p.eat()时就相当于调用了Person.eat(p)。
class Person: """人的类""" home = "earth" def __init__(self, name): self.name = name def eat(self): print("eating...") def drink(self): print("drinking...") p = Person("张三") # 创建一个对象 p.eat() # eating... Person.eat(p) # eating...通过self在类中调用类的实例属性和实例方法
class Person: """人的类""" home = "earth" def __init__(self, name): self.name = name def eat(self): print("eating...") def drink(self): print("drinking...") def eat_and_drink(self): print(self.name) # 在类中调用name self.eat() # 在类中调用eat()方法 self.drink() # 在类中调用drink()方法 p = Person("张三") # 创建一个对象 p.eat_and_drink()属性
类属性
也叫类变量。在类中方法外定义的属性。
通过类名.属性名或实例名.属性名访问
class Person: """人的类""" home = "earth" # 定义类属性 print(Person.home) # 通过类名访问类属性 p1 = Person() # 创建一个实例对象 print(p1.home) # 通过实例名访问类属性,(如果实例没有覆盖这个类属性的值)通过类名.属性名添加与修改类属性
class Person: """人的类""" Person.home = "earth" # 添加类属性 print(Person.home) # earth Person.home = "mars" # 修改类属性 print(Person.home) # mars若使用实例名.属性名则会创建或修改实例属性,因此不建议类属性和实例属性同名。
class Person: """人的类""" home = "earth" p1 = Person() p2 = Person() print(Person.home) # earth print(p1.home) # earth print(p2.home) # earth print("通过 类名.属性名 修改 类属性") Person.home = "mars" print(Person.home) # mars print(p1.home) # mars print(p2.home) # mars print("通过 实例名.属性名 会创建 实例属性") p1.home = "venus" print(Person.home) # mars print(p1.home) # venus print(p2.home) # mars所有该类的实例共享同一个类属性
class Person: """人的类""" home = "earth" # 定义类属性,所有实例共享 p1 = Person() # 创建一个实例对象 p2 = Person() # 创建另一个实例对象 print(p1.home) # earth print(p2.home) # earth Person.home = "mars" # 修改类属性 print(p1.home) # mars print(p2.home) # mars实例属性
也叫实例变量。在类__init__方法中定义的属性。通过self.属性名定义。
通过实例名.属性名访问
class Person: """人的类""" def __init__(self, name, age): self.name = name # 定义实例属性 self.age = age # 定义实例属性 p1 = Person("张三", 18) # 创建一个实例对象 print(p1.name, p1.age) # 张三 18 p2 = Person("李四", 81) # 创建一个实例对象 print(p2.name, p2.age) # 李四 81 print(Person.name) # 报错通过实例名.属性名添加与修改实例属性
class Person: """人的类""" pass p1 = Person() # 创建一个实例对象 p1.name = "张三" # 添加实例属性 p1.age = 18 # 添加实例属性 print(p1.name, p1.age) # 张三 18 p1.age = 25 # 修改实例属性 print(p1.name, p1.age) # 张三 25每个实例独有一份实例属性
class Person: """人的类""" def __init__(self, name): self.name = name # 定义实例属性 self.age = 0 # 定义实例属性 p1 = Person("张三") # 创建一个实例对象 print(p1.name, p1.age) # 张三 0 p1.age = 18 # 修改p1的age属性 print(p1.name, p1.age) # 张三 18 p2 = Person("李四") # 创建另一个实例对象 print(p2.name, p2.age) # 李四 0方法
Python的类中有三种方法:实例方法、静态方法、类方法。
实例方法
- 实例方法在类中定义,第一个参数为self,代表实例本身。
- 实例方法只能被实例对象调用。
- 可以访问实例属性、类属性、类方法。
class Person: """人的类""" home = "earth" def __init__(self, name): self.name = name def instance_method(self): print(self.name, self.home, Person.home) p = Person("张三") p.instance_method() # 张三 earth earth,此时p中没有home实例属性,会去查找home类属性 Person.home = "venus" # 修改类属性 p.home = "mars" # 定义实例属性 p.instance_method() # 张三 mars venus类方法
- 类方法在类中通过@classmethod定义,第一个参数为cls,代表类本身。
- 类方法可以被类和实例对象调用。
- 可以访问类属性。
- 在不创建实例的情况下调用,通过类名直接调用,非常方便,适合一些和类整体相关的操作。
class Person: """人的类""" home = "earth" # 定义类属性 @classmethod def class_method(cls): print(cls.home) Person.class_method() # 通过类调用类方法 p1 = Person() # 创建一个实例对象 p1.class_method() # 通过实例对象调用类方法静态方法
- 静态方法在类中通过@staticmethod定义
- 不访问实例属性或类属性,只依赖于传入的参数
- 可以通过类名或实例调用,但它不会访问类或实例的内部信息,更像是一个工具函数,只是为了方便组织代码,把它放在了类里面。
class Person: """人的类""" home = "earth" # 定义类属性 @staticmethod def static_method(): print("static method") Person.static_method() # 通过类调用静态方法 p1 = Person() # 创建一个实例对象 p1.static_method() # 通过实例对象调用静态方法在类外定义方法
并非必须在类定义中进行方法定义,也可以将一个函数对象赋值给一个类内局部变量。
# 在类外定义的函数 def f1(self, x, y): print(x & y) class C: f = f1 C().f(6, 13) # 4特殊方法-魔法方法
方法名中有两个前缀下划线和两个后缀下划线的方法为特殊方法,也叫魔法方法。上文提到的__init__()就是一个特殊方法。这些方法会在进行特定的操作时自动被调用。
几个常见的特殊方法:__new__():对象实例化时第一个调用的方法。
__init__():类的初始化方法。
__del__():对象的销毁器,定义了当对象被垃圾回收时的行为。使用del xxx时不会主动调用__del__(),除非此时引用计数==0。
__str__():定义了对类的实例调用str()时的行为。
__repr__():定义对类的实例调用repr()时的行为。str()和repr()最主要的差别在于目标用户。repr()的作用是产生机器可读的输出(大部分情况下,其输出可以作为有效的Python代码),而str()则产生人类可读的输出。
__getattribute__():属性访问拦截器,定义了属性被访问前的操作。
动态添加属性与方法
动态给对象添加属性
class Person: def __init__(self, name=None): self.name = name p = Person("张三") print(p.name) # 张三 p.age = 18 print(p.age) # 18动态给类添加属性
class Person: def __init__(self, name=None): self.name = name p = Person("张三") print(p.name) # 张三 Person.age = 0 print(p.age) # 0动态给实例添加方法
添加普通方法
class Person: def __init__(self, name=None): self.name = name def eat(): print("吃饭") p = Person("张三") p.eat = eat p.eat() # 吃饭添加实例方法
给对象添加的实例方法只绑定在当前对象上,不对其他对象生效,而且需要传入 self 参数。需要使用 types.MethodType(方法名,实例对象) 来添加实例方法。
import types class Person: def __init__(self, name=None): self.name = name def eat(self): print(f"{self.name}在吃饭") p = Person("张三") p.eat = types.MethodType(eat, p) p.eat() # 张三在吃饭动态给类添加方法
给类添加的方法对它的所有对象都生效,添加类方法需要传入 cls 参数,添加静态方法则不需要。
class Person: home = "earth" def __init__(self, name=None): self.name = name # 定义类方法 @classmethod def come_from(cls): print(f"来自{cls.home}") # 定义静态方法 @staticmethod def static_function(): print("static function") Person.come_from = come_from Person.come_from() # 来自earth Person.static_function = static_function Person.static_function() # static function动态删除属性与方法
- del对象.属性名
- delattr(对象,属性名)
__slots__限制实例属性与实例方法
Python允许在定义类的时候,定义一个特殊的__slots__变量,来限制该类的实例能添加的属性。使用__slots__可以限制添加实例属性和实例方法,但类属性、类方法和静态方法还可以添加。__slots__仅对当前类生效,对其子类无效。
import types class Person: __slots__ = ("name", "age", "eat") def __init__(self, name=None): self.name = name def eat(self): print(f"{self.name}在吃饭") def drink(self): print(f"{self.name}在喝水") p = Person("张三") # 添加实例属性 p.age = 10 print(p.age) # 10 # 添加实例方法 p.eat = types.MethodType(eat, p) p.eat() # 张三在吃饭 # 添加实例属性 p.weight = 100 # AttributeError: 'Person' object has no attribute 'weight' # 添加实例方法 p.drink = types.MethodType(drink, p) # AttributeError: 'Person' object has no attribute 'drink'面向对象之三大特性
封装
将变量和函数写入类中的操作即为封装,即类中封装了属性和方法。
私有化
有时为了限制属性和方法只能在类内访问,外部无法访问;或父类中某些属性和方法不希望被子类继承。可以将其私有化。
单下划线:非公开API
大多数Python代码都遵循这样一个约定:有一个前缀下划线的变量或方法应被视为非公开的API,例如_var1。这种约定不具有强制力。
双下划线:名称改写
有两个前缀下划线,并至多一个后缀下划线的标识符,例如__x,会被改写为_类名__x。只有在类内部可以通过__x访问,其他地方无法访问或只能通过_类名__x访问。
私有属性
通过双下划线定义私有属性。
class Person: def __init__(self, name): self.__name = name def get_name(self): return self.__name p = Person("张三") print(p.get_name()) # 张三 print(p._Person__name) # 张三 实际真实的改写后的名称 print(p.__name) # 报错私有方法
通过双下划线定义私有方法。
class Person: # 定义私有方法 def __private_method(self): print("private method") # 定义实例方法,调用私有方法 def do_something(self): self.__private_method() p = Person() p.do_something() # private method p._Person__private_method() # private method p.__private_method() # 报错property-方法转换为属性
可通过@property装饰器将一个方法转换为属性来调用。转换后可直接使用.方法名来使用,而无需使用.方法名() 。
class Person: def __init__(self, name): self.name = name @property def eat(self): print(f"{self.name} is eating...") p = Person("张三") p.eat # 张三 is eating...只读属性
将方法名设置为去掉双下划线的私有属性名,方法中返回私有属性。
class Person: def __init__(self, name): self.__name = name @property def name(self): return self.__name p = Person("张三") print(p.name) # 张三 p.name = "李四" # 报错setter读写属性
将方法名设置为去掉双下划线的私有属性名,使用属性名.setter装饰。
class Person: def __init__(self, name): self.__name = name @property def name(self): return self.__name @name.setter def name(self, name): self.__name = name p = Person("张三") print(p.name) # 张三 p.name = "李四" print(p.name) # 李四注意
@property装饰的方法不要和变量重名,否则可能导致无限递归。
class Person: @property def name(self): return self.name p = Person() p.name # 报错:RecursionError: maximum recursion depth exceeded继承
子类(派生类)继承父类(基类)中的属性和方法,实现代码重用。子类可以新增自己特有的方法,也可以重写父类的方法。
子类不能继承父类的私有属性和私有方法,因为存在名称改写,但是可以通过改写后的名称直接访问父类的私有成员,不过,这种做法违背了封装原则,不建议使用。
单继承
语法-在类名后括号内指定要继承的父类。
class 类名(父类): 类体案例
class Person: """人的类""" home = "earth" # 定义类属性 def __init__(self, name): self.name = name # 定义实例属性 def eat(self): print("eating...") class YellowRace(Person): """黄种人""" color = "yellow" # 定义类属性 class WhiteRace(Person): """白种人""" color = "white" # 定义类属性 class BlackRace(Person): """黑种人""" color = "black" # 定义类属性 y1 = YellowRace("张三") print(y1.home) # earth print(y1.color) # yellow print(y1.name) # 张三 y1.eat() # eating... w1 = WhiteRace("李四") print(w1.home) # earth print(w1.color) # white print(w1.name) # 李四 w1.eat() # eating... b1 = BlackRace("王五") print(b1.home) # earth print(b1.color) # black print(b1.name) # 王五 b1.eat() # eating...多继承
调用方法时先在子类中查找,若不存在则从左到右依次查找父类中是否包含方法。
语法
class 类名(父类1, 父类2, ...): 类体案例
class Person: """人的类""" home = "earth" def __init__(self, name): self.name = name def eat(self): print("eating...") class YellowRace(Person): """黄种人""" color = "yellow" def run(self): print("runing...") class Student(Person): """学生""" def __init__(self, name, grade): self.name = name self.grade = grade def study(self): print("studying...") class ChineseStudent(Student, YellowRace): # 继承了Student和YellowRace """中国学生""" country = "中国" y1 = ChineseStudent("张三", "三年级") print(y1.home, y1.color, y1.country, y1.name, y1.grade) y1.eat() y1.run() y1.study()复用父类方法
子类可以在类中使用super().方法名()或父类名.方法名()来调用父类的方法。
super().方法名()
class Person: """人的类""" home = "earth" def __init__(self, name): self.name = name def eat(self): print("eating...") class YellowRace(Person): """黄种人""" color = "yellow" def run(self): print("runing...") class Student(Person): """学生""" def __init__(self, name, grade): self.name = name self.grade = grade def study(self): print("先吃再学") super().eat() # 子类中调用父类的方法 print("studying...") class ChineseStudent(Student, YellowRace): # 继承了Student和YellowRace """中国学生""" country = "中国" y1 = ChineseStudent("张三", "三年级") print(y1.home, y1.color, y1.country, y1.name, y1.grade) y1.study()父类名.方法名()
class Person: """人的类""" home = "earth" def __init__(self, name): self.name = name def eat(self): print("eating...") class YellowRace(Person): """黄种人""" color = "yellow" def run(self): print("runing...") class Student(Person): """学生""" def __init__(self, name, grade): self.name = name self.grade = grade def study(self): print("先吃再学") Person.eat(self) # 子类中调用父类的方法 print("studying...") class ChineseStudent(Student, YellowRace): # 继承了Student和YellowRace """中国学生""" country = "中国" y1 = ChineseStudent("张三", "三年级") print(y1.home, y1.color, y1.country, y1.name, y1.grade) y1.study()方法解析顺序
方法解析顺序(mro—Method Resolution Order)。可使用类名.__mro__访问类的继承链来查看方法解析顺序。
class Person: """人的类""" home = "earth" def __init__(self, name): self.name = name def eat(self): print("eating...") class YellowRace(Person): """黄种人""" color = "yellow" def run(self): print("runing...") class Student(Person): """学生""" def __init__(self, name, grade): self.name = name self.grade = grade def study(self): print("先吃再学") Person.eat(self) print("studying...") class ChineseStudent(Student, YellowRace): """中国学生""" country = "中国" y1 = ChineseStudent("张三", "三年级") print(ChineseStudent.__mro__) # (<class '__main__.ChineseStudent'>, <class '__main__.Student'>, <class '__main__.YellowRace'>, <class '__main__.Person'>, <class 'object'>)MRO顺序说明说明
""" class A: def __init__(self, a,b,c): self.a = a self.b = b self.c = c class B: def __init__(self, d,e,f): self.d = d self.e = e self.f = f class C(A,B): def __init__(self, a,b,c,d,e,f): # super().__init__(a,b,c) # A.__init__(self,a,b,c) # B.__init__(self,d,e,f) # super(A,self).__init__(d,e,f) """ """ class Parent1: def __init__(self, value1): print("Initializing Parent1") self.value1 = value1 class Parent2: def __init__(self, value2): print("Initializing Parent2") self.value2 = value2 class Child(Parent1, Parent2): def __init__(self, value1, value2): print("Initializing Child") # 调用 Parent1 的 __init__ 方法 super().__init__(value1) # 调用 Parent2 的 __init__ 方法 Parent1 之后开始查找 MRO 链,调用下一个父类(即 Parent2)的 __init__ 方法,把 value2 传递进去。 # super(Parent1, self).__init__(value2) super().__init__(value2) child = Child("value from Parent1", "value from Parent2") print(child.value1) print(child.value2) """ """ class Parent1: def __init__(self, value1, **kwargs): print("Initializing Parent1") super().__init__(**kwargs) self.value1 = value1 class Parent2: def __init__(self, value2, **kwargs): print("Initializing Parent2") super().__init__(**kwargs) self.value2 = value2 class Child(Parent1, Parent2): def __init__(self, value1, value2): print("Initializing Child") super().__init__(value1=value1, value2=value2) child = Child("value from Parent1", "value from Parent2") print(child.value1) # 输出: value from Parent1 print(child.value2) # 输出: value from Parent2 """ """ class Parent1: def __init__(self, value1): print("Initializing Parent1") self.value1 = value1 class Parent2: def __init__(self, value2): print("Initializing Parent2") self.value2 = value2 class Child(Parent1, Parent2): def __init__(self, value1, value2): print("Initializing Child") # 直接调用 Parent1 的 __init__ 方法 Parent1.__init__(self, value1) # 直接调用 Parent2 的 __init__ 方法 Parent2.__init__(self, value2) child = Child("value from Parent1", "value from Parent2") print(child.value1) print(child.value2) """ # 使用 super():适用于遵循 MRO 顺序的场景,代码可维护性和扩展性较好,特别是在复杂的多重继承结构中。 # 直接调用父类方法:代码逻辑较为直观,但在多重继承结构发生变化时,可能需要手动调整调用顺序,可维护性较差。方法重写
在子类中定义与父类方法重名的方法,调用时会调用子类中重写的方法。
class Person: home = "earth" def __init__(self, name): self.name = name def eat(self): print("eating...") class Chinese(Person): color = "yellow" # 重写父类方法 def eat(self): print("用筷子吃") y1 = Chinese("张三") y1.eat()注意:子类重写__init__()并调用时,不会执行父类的__init__()方法。如有必要,需在子类__init__()中使用super().__init__()来调用父类的__init__()方法。
class Person: def __init__(self, name): self.name = name class Chinese(Person): def __init__(self, name, area): super().__init__(name) # 调用父类的__init__() self.area = area y1 = Chinese("张三", "北京") print(y1.name, y1.area)多态
同一事物在不同场景下呈现不同状态。
class Animal: def go(self): pass class Dog(Animal): def go(self): print("跑") class Fish(Animal): def go(self): print("游") class Bird(Animal): def go(self): print("飞") def go(animal): animal.go() # 将不同的实例传入,执行不同的方法 dog = Dog() fish = Fish() bird = Bird() go(dog) go(fish) go(bird)