1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 深入浅出 Python 面向对象编程

深入浅出 Python 面向对象编程

时间:2023-11-25 08:10:24

相关推荐

深入浅出 Python 面向对象编程

文章目录

简介专业术语对象的属性类的定义类的实例化类成员的可见性类的属性实例属性私有属性特殊属性类的方法成员方法私有方法类方法静态方法属性方法类的特殊方法`__init__`构造方法`__del__`析构方法`__str__``__new__``__call__``__reduce___`类的继承派生类的定义派生类构造函数判断类的继承类的多继承多继承简介多继承查找顺序类的多态什么是多态多态实例反弹机制

简介

Python从设计之初就已经是一门面向对象的语言, 面向对象编程三大特性如下:

封装: 隐藏实现细节,使代码模块化

继承: 扩展已存在的类来实现代码重用,避免重复编写相同的代码

多态: 封装和继承的目的都是为了实现代码重用, 而多态是为了实现接口重用,使得类在继承和派生的时候能够保证任何一个类的实例都能正确调用约定好的属性和方法

专业术语

类(Class):用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。方法:类中定义的函数。类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖,也称为方法的重写。局部变量:定义在方法中的变量,只作用于当前实例的类。实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。实例化:创建一个类的实例,类的具体对象。对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

对象的属性

在python中, 每个对象都有三个属性:

ID: 对象的地址, ID相同则表示同一对象类型(type)数值

类的定义

类是一种抽象数据类型,是对现实世界的一类数据及其操作的封装

类实例化后,可以使用其属性,创建一个类后,可以通过类名访问其类属性

如下所示, 定义一个含有三个属性的person类, 分别是ID(身份证号)、name(姓名)、nationality(国籍)

其中所有人的身份证号码都是不一样的,且不允许直接通过类或实例来访问或随意修改

import uuidclass Person:nationality = "China"#类的初始化def __init__(self, name): self.name = nameself.__id = str(uuid.uuid1())def hello(self):print("Hello, I am %s, I come from %s, My ID is %s" %(self.name, self.nationality, self.__id))

类的实例化

import uuidclass Person:nationality = "China"def __init__(self, name):self.name = nameself.__id = str(uuid.uuid1())def hello(self):print("Hello, I am %s, I come from %s, My ID is %s" % (self.name, self.nationality, self.__id))user = Person('henry') #创建一个类实例化对象useruser.hello() #输出Hello, I am henry, I come from China, My ID is cea8b568-4f53-11ed-a7de-00e04c8c73bb

类成员的可见性

Python中默认所有的成员都是公有成员,但私有成员是以两个下划线开头的名字表示私有成员,私有成员不允许直接访问,只能通过内部方法进行访问,私有成员也不允许被继承

Python中通过在类变量、实例变量、类方法、实例方法前加__前缀,可以将其对外进行隐藏,变为类的私有变量或函数

由于Python中内置变量或函数使用__作为前后缀,因此不推荐私有的变量或函数在前后缀都用__,而是只在前缀用__

Python类维护了一个用于保存类的数据的字典,字典内部Python将私有成员改名为_ClassName__variable,因此在类外通过访问私有变量新的名称可以访问相应的私有变量, 如下代码所示:

import uuidclass Person(object):nationality = "China"def __init__(self, name):self.name = nameself.__id = str(uuid.uuid1())def hello(self):print("Hello, I am %s, I come from %s, My ID is %s" % (self.name, self.nationality, self.__id))if __name__ == "__main__":bauer = Person("Bauer")print(bauer.__dict__) #输出存放私有成员的字典print(bauer._Person__id) #输出该对象的person类的ID# 输入如下:# {'name': 'Bauer', '_Person__id': 'ed496846-94c7-11e9-80c4-5ce0c5e8bcf0'}# ed496846-94c7-11e9-80c4-5ce0c5e8bcf0

类的属性

直接定义在class下的属性是叫公有属性或类属性,类属性是类的所有实例对象共同所有的

类属性可以使用ClassName.VariableName访问,在实例方法内部也可以使用self.VariableName进行访问

import uuidclass Person(object):nationality = "China"def __init__(self, name):self.name = nameself.__id = str(uuid.uuid1())def hello(self):print("Hello, I am %s, I come from %s, My ID is %s" % (self.name,self.nationality,self.__id))def sayHello(self):print("Hello,I come from %s" % self.nationality)if __name__ == "__main__":bauer = Person("Bauer")bauer.sayHello() #Hello,I come from Chinajack = Person("Jack")print(Person.nationality, bauer.nationality, jack.nationality) #输出China China Chinabauer.nationality = "USA"print(Person.nationality, bauer.nationality, jack.nationality) #输出China USA ChinaPerson.nationality = "Germany"print(Person.nationality, bauer.nationality, jack.nationality) #Germany USA Germany

在上述代码中, 将类属性nationality修改为 “Germany” 时,并没有修改实例bauernationality属性。因此,实例bauernationality属性的值仍然是 “USA”。

在 Python 中,如果实例有一个与类属性同名的属性,则实例的属性会覆盖类属性。例如,当你在类外面使用bauer.nationality = "USA"时,你为实例bauer创建了一个名为nationality的属性,值为 “USA”。这个属性会覆盖类属性nationality,因此在使用bauer.nationality访问实例属性时,会返回 “USA” 而不是 “Germany”

实例属性

实例属性又称成员属性或成员变量,是类的每个实例对象单独持有的属性, 实例属性必须在类的__init__方法中进行声明

import uuidclass Person(object):nationality = "China"def __init__(self, name):self.name = name #name属于实例属性self.__id = str(uuid.uuid1())def hello(self):print("Hello, I am %s, I come from %s, My ID is %s" % (self.name,self.nationality, self.__id))if __name__ == "__main__":bauer = Person("Bauer")jack = Person("Jack")print(bauer.name, jack.name) #输出"Bauer Jack"

若要访问实例属性, 只能通过实例对象访问, 若通过类访问则会报错, 如下所示

print(Person.name)

私有属性

私有属性和实例属性必须在__init__方法中进行声明,但私有属性的属性名需要以双下划线__开头, 例如上述Person中的__id属性

私有属性是一种特殊的实例属性,只允许在实例对象的内部访问, 且不能被子类继承

私有属性可以通过成员方法或是<实例对象._类名__私有变量名>的方式来访问

import uuid class Person(object):nationality = "China"def __init__(self, name):self.name = name #实例属性self.__id = str(uuid.uuid1()) #私有属性def hello(self):print("Hello, I am %s, I come from %s, My ID is %s" %(self.name, self.nationality, self.__id))def get_id(self):return self.__idif __name__ == "__main__":bauer = Person("Bauer")bauer.hello()print(bauer._Person__id) #访问bauer成员的私有属性IDprint(bauer.get_id()) #通过成员方法get_id()访问私有属性ID

特殊属性

Python的类中有一些内置的、特殊的属性,其名称以双下划线__开头且以双下划线__结尾。特殊属性不是私有属性,可以在类的外部通过实例对象去直接访问,如下是常见的特殊属性

__doc__:类的描述信息。

__module__:对象定义所在的模块名。

__class__:当前操作的对象对应的类名。

__dict__:一个字典,保存类的所有的成员(包括属性和方法)或实例对象中的所有成员属性

实例对象.__dict__类.__dict__的值是不同的,实例对象.__dict__的值中只包含成员属性和私有属性,类.__dict__的值中包含类的类属性和所有方法

类的方法

成员方法

成员方法可以通过类的实例或类名调用,但使用类名时需要手动传入一个实例对象作为 self 参数。

import uuidclass Person(object):nationality = "China"def __init__(self, name):self.name = nameself.__id = str(uuid.uuid1())def hello(self):print("Hello, I am %s, I come from %s, My ID is %s" %(self.name, self.nationality, self.__id))if __name__ == "__main__":bauer = Person("Bauer")bauer.hello() #通过类的实例对象调用Person.hello(bauer) #通过类名调用#两次输出的内容一致:Hello, I am Bauer, I come from China, My ID is 5beff5b1-500e-11ed-b861-00e04c8c73bb

私有方法

私有方法是以双下划线__开头的成员方法

私有方法只能在实例方法内部访问,且不能被子类继承;私有方法的第一个参数也必须是当前实例对象本身,通常写为self

前后加双下划线的命名方式用于Python内置的方法,不推荐自定义方法使用。

如果开发者以前后加双下划线的方式命名成员方法,则相应成员方法是公有的

import uuidclass Person(object):nationality = "China"def __init__(self, name):self.name = nameself.__id = str(uuid.uuid1())def __hello(self): # 私有方法print("Hello, I am %s, I come from %s, My ID is %s" % (self.name,self.nationality, self.__id))def say_hello(self): # 成员方法/实例方法self.__hello()if __name__ == "__main__":bauer = Person("Bauer")bauer.say_hello() #输出Hello, I am Bauer, I come from China, My ID is 5beff5b1-500e-11ed-b861-00e04c8c73bb

类方法

类方法是以@classmethod来装饰的成员方法,要求第一个参数必须是当前类, 通常写为cls

类方法可通过实例对象进行访问,还可以直接通过类名去访问

类方法只能访问类属性,不能访问实例属性,因此第一个参数传递的是代表当前类的cls,而不是表示实例对象的self

import uuidclass Person(object):nationality = "China"def __init__(self, name):self.name = nameself.__id = str(uuid.uuid1())def __hello(self): # 私有方法print("Hello, I am %s, I come from %s, My ID is %s" % (self.name,self.nationality, self.__id))def say_hello(self): # 成员方法/实例方法self.__hello()@classmethoddef get_nationality(cls): # 类方法return cls.nationalityif __name__ == "__main__":bauer = Person("Bauer")print(bauer.get_nationality()) #通过实例对象调用类方法print(Person.get_nationality()) #通过类名调用类方法

静态方法

静态方法是以@staticmethod来装饰的成员方法

静态方法通常通过类名进行访问,也可以通过类的实例对象进行访问

静态方法已经与类没有任何关联,因此定义静态方法不要求必须传递实例对象或类参数

静态方法对参数没有要求,因此可以任意给静态方法定义参数,如果给静态方法定义表示当前类的参数(cls),那么就可以访问类属性;如果给静态方法定义了表示当前类的实例对象的参数(self),那么就可以访问实例属性;如果没有给静态方法定义当前类参数或当前实例参数,那么就不能访问类或实例对象的任何属性

import uuidclass Person(object):sum = 0nationality = "China"def __init__(self, name):self.name = nameself.__id = str(uuid.uuid1())Person.sum += 1@staticmethoddef add(a, b): # 静态方法return a + b@staticmethod # 静态方法,内部使用类变量def counter():return Person.sum@staticmethoddef get_counter(cls): # 静态方法,传递当前类return cls.sumif __name__ == "__main__":bauer = Person("Bauer")print(bauer.add(1, 2)) #通过实例对象调用静态方法print(Person.add(1, 2)) #通过类名调用静态方法print(Person.counter())print(Person.get_counter(Person)) #调用需传递类参数

属性方法

属性方法是以@property装饰的成员方法,用来访问实例属性。属性方法的第一个参数必须是当前实例,且必须有返回值

import uuidclass Person(object):nationality = "China"def __init__(self, name):self.name = nameself.__id = str(uuid.uuid1())def __hello(self): # 私有方法print("Hello, I am %s, I come from %s, My ID is %s" %(self.name,self.nationality, self.__id))def say_hello(self): # 成员方法/实例方法self.__hello()@classmethoddef get_nationality(cls): # 类方法return cls.nationality@staticmethoddef add(a, b): # 静态方法return a + b@propertydef id(self):return self.__idif __name__ == "__main__":bauer = Person("Bauer")print(bauer.id)

类的特殊方法

Python的类中有一些内置的、特殊的方法,其名称是以双下划线__开头且以双下划线__结尾。特殊方法不是私有方法,可以在类的外部通过实例对象去直接访问,且都有着各自特殊的意义

__init__构造方法

__init__方法是类构造函数,是类的特殊的方法,在创建类对象时自动调用,不能有返回值

__init__方法的第一个参数必须是创建的实例本身,通常推荐使用self。类的实例属性、私有属性必须在__init__方法进行声明

import uuidclass Person(object):nationality = "China"def __init__(self, name):self.name = nameself.__id = str(uuid.uuid1())print(self.name, "__init__")if __name__ == "__main__":bauer = Person("Bauer") #输出"Bauer __init__"

__del__析构方法

__del__是类的析构方法,当对象在内存中被释放,会自动触发执行__del__方法,如实例对象的作用域退出时,或者执行del实例对象操作

import uuidclass Person(object):nationality = "China"def __init__(self, name):self.name = nameself.__id = str(uuid.uuid1())print(self.name, "__init__")#定义析构方法def __del__(self):print(self.name, "__del__")if __name__ == "__main__":bauer = Person("Bauer") #输出"Bauer __init__"del bauer #删除实例对象,触发析构函数,输出"Bauer __del__"

__str__

若类中定义了__str__方法,那么在打印对象时默认输出__str__方法的返回值,否则会打印出实例对象的内存地址

import uuidclass Person(object):nationality = "China"def __init__(self, name):self.name = nameself.__id = str(uuid.uuid1())print(self.name, "__init__")def __del__(self):print(self.name, "__del__")def __str__(self):return "name: %s, nationality: %s, id: %s" % (self.name,self.nationality, self.__id)if __name__ == "__main__":bauer = Person("Bauer") print(bauer) #输出内容如下:# Bauer __init__# 输出name: Bauer, nationality: China, id: 27c26af1-508a-11ed-899f-00e04c8c73bb# Bauer __del__

__new__

其实在python中,__init__并不是真正的构造函数,__new____init__才是真正的构造函数

当我们在对类进行实例化的时候,__new__方法会在__init__方法前被执行, 会创建并返回一个新的实例对象传给__init__

在下面的例子中, 我们把类demo进行实例化对象为a, 那么在这个过程中, 类demo是如何确定它的实例对象会是a的呢?

这是因为__new__方法返回了这个值, 而这个值就是a, 最后python解析器将这个返回值告诉给__init__方法, 在__init__方法中的参数self其实就是这个a, 因为self代表的是类的对象

class demo():def __init__(self,arg,kwarg): #定义属性并初始化self.arg = argself.kwarg = kwargdef Output(self):print(self.arg)print(self.kwarg)a = demo("NMSL","WSND") #实例化a.Output() #调用类中的Output方法

__new__方法常用于单例设计模式, 它是由object基类提供的内置静态方法

让类创建的对象, 在内存中只有唯一的一个实例每一次实例化生成的对象, 其内存地址是相同的

class demo(object):ins = Nonedef __new__(cls):if cls.ins == None:cls.ins = super().__new__(cls)return cls.insa = demo()b = demo()print(a) #0x000001CE34C96FA0>print(b) #0x000001CE34C96FA0>

__call__

类中定义__call__方法时,类对象实例可以作为一个函数去调用

import uuidclass Person(object):nationality = "China"def __init__(self, name):self.name = nameself.__id = str(uuid.uuid1())def __call__(self, *args, **kwargs):print("name: ", self.name, "args: ", *args)def hello(self):print("Hello, I am %s, I come from %s, My ID is %s" %(self.name, self.nationality, self.__id))if __name__ == "__main__":bauer = Person("Bauer")bauer(26) #将实例对象看成函数来传参进行调用,输出:"name: Bauer args: 26"

__reduce___

在 Python 中,类的__reduce__方法是一个特殊方法,用于描述对象的可序列化表示。当你使用内置的序列化函数(如pickle.dumps)将对象序列化时,会调用对象的__reduce__方法

__reduce__方法必须返回一个元组,元组中包含两个元素

第一个元素是一个调用对象的构造函数,它用于在反序列化时创建新的对象。第二个元素是一个包含对象参数的元组,它用于在反序列化时传递给对象的构造函数

import pickleshellcode = "list1 = [1,2,3,4]"class A(object):def __reduce__(self):return (exec,(shellcode,))#当实例对象被序列化后,则会调用特殊方法__reduce__,所以下列代码相当于pickle.dumps((exec,(shellcode,)))ret = pickle.dumps(A())print(ret)#输出:b'\x80\x04\x95-\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x04exec\x94\x93\x94\x8c\x11list1 = [1,2,3,4]\x94\x85\x94R\x94.'pickle.loads(ret)print(list1) #输出[1,2,3,4]

类的继承

派生类的定义

Python中类的继承按照父类中的方法是否已实现可分为两种:

实现继承: 直接继承父类的属性和已定义并实现的方法接口继承: 仅继承父类类的属性和方法名称, 子类必须自行实现方法的功能

若根据继承父类的个数来分类, 又可分为两种:

单继承: 只继承一个父类多继承: 继承多个父类

派生类构造函数

派生类的构造函数需要显式调用父类的构造函数,对父类的属性成员进行初始化,调用父类的构造函数时需要显式传递实例对象self

什么是显示调用和隐式调用? —— 在python使用3+23*2时,您似乎没有显式地调用任何方法,但实际上您调用了它,因为它们的实现方式是调用(3).__add__(2)(3).__mul__(2)。因此,您隐式地调用这些方法

子类需要在自己的__init__方法中的第一行位置调用父类的构造方法, 以下有两种方法, 在上述代码的注释中也进行了具体描述

super.(子类名, self).__init__(父类构造参数)父类名.__init__(self,父类构造参数)

如下代码所示, Teacher类和Student类都继承了Person类, 也就是说Teacher类和Student类是Person类的子类或派生类, 而Person类是Teacher类和Student类的父类、基类或超类

Teacher和Student对Person的继承属于实现继承,且是单继承

Teacher类和Student类都继承了Person类的name和age属性, 以及talk()walk()方法, 并扩展了自身的属性和方法

Teacher类和Student类可以在自己的类定义中重新定义父类Person的方法, 这种我们称为方法重写

class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef walk(self):print('%s is walking...' % self.name)def talk(self):print('%s is talking...' % self.name)class Teacher(Person):def __init__(self, name, age, level, salary):super(Teacher, self).__init__(name, age) #派生类调用父类构造函数的第一种方法:super.(子类名, self).__init__(父类构造参数)self.level = levelself.salary = salarydef teach(self):print('%s is teaching...' % self.name)class Student(Person):def __init__(self, name, age):Person.__init__(self, name, age) #派生类调用父类构造函数的第二种方法:父类名.__init__(self,父类构造参数)def study(self):print('%s is studying...' % self.name)

判断类的继承

isinstance: 判断一个类对象是否是类的对象或者是类的子类对象issubclass: 判断一个类是否是某个类的子类

class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef walk(self):print('%s is walking...' % self.name)def talk(self):print('%s is talking...' % self.name)class Student(Person):def __init__(self, name, age):Person.__init__(self, name, age) #派生类调用父类构造函数的第二种方法:父类名.__init__(self,父类构造参数)def study(self):print('%s is studying...' % self.name)if __name__ == '__main__':Tom = Student('tom',10)print(isinstance(Tom,Person)) #判断类对象Tom是否是Person类的子类对象或类对象,返回Trueprint(isinstance(Tom,Student)) #判断类对象Tom是否是Student类的子类对象或类对象,返回Trueprint(issubclass(Student,Person)) #判断Student类是否是Person类的子类,返回True

类的多继承

多继承简介

Python支持多层父类继承, 子类会继承父类以及父类的父类所有的属性和方法

在多继承时, 使用的super()函数只会调用第一个父类的属性方法, 若想调用特定父类的构造函数, 只能使用父类名.__init__这种方式调用

若在多个继承的父类中有相同的方法名, 而在子类未使用显示调用父类的方法, Python会根据继承顺序从左至右搜索查找父类中是否包含方法。

class B():def __init__(self):print("class B")def hello(self):print('hello, class B')class C():def __init__(self):print("class C")def hello(self):print('hello, class C')class D(B, C):def __init__(self):super(D,self).__init__() #调用第一个父类(B)的构造函数print("class D")if __name__ == "__main__":d = D()d.hello() #调用父类B的hello(),而没有调用父类C的hello()#输出内容:# class B# class D# hello, class B

多继承查找顺序

类的属性__mro__或者方法mro()都能打印出类的继承顺序,super()在执行时查找MRO列表,到列表当前位置的类中去查找其下一个类,也就是说为了实现继承, python会在MRO列表从左到右开始查找父类, 直到找到第一个匹配属性的类为止

super是MRO中的一个类。MRO全称Method Resolution Order,代表类的继承顺序。对于定义的每一个类,Python会计算出一个方法解析顺序(MRO)列表,MRO列表是一个简单的所有基类的线性顺序列表

子类会先于父类被检查多个父类会根据它们在列表的顺序被检查如果对下一个类存在两个合法的选择,选择第一个父类

class B():def __init__(self):print("class B")def hello(self):print('hello, class B')class C():def __init__(self):print("class C")def hello(self):print('hello, class C')class D(B, C):def __init__(self):super(D,self).__init__() #调用第一个父类(B)的构造函数print("class D")if __name__ == "__main__":print(D.mro()) #输出[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]

类的多态

什么是多态

接口的所有子类必须实现接口中定义的所有方法;接口的各个子类在实现接口中同一个方法时,具体的代码实现各不相同,即为多态

多态实例

多态通常是通过继承接口的方式实现的,虽然Python中没有接口这个概念,但Python中可以通过在一个成员方法体中抛出一NotImplementedError异常来强制继承接口的子类在调用接口方法前必须先实现接口方法

举个例子,我们定义了一个名为Shape的类,用于表示形状。这个类中有一个方法area用于计算形状的面积,但是并没有为这个方法实现具体的代码逻辑。这样,当我们尝试调用Shape类的area方法时,就会抛出NotImplementedError异常,告诉调用者这个方法尚未实现

class Shape:def area(self):raise NotImplementedErrorclass Rectangle(Shape):def __init__(self, width, height):self.width = widthself.height = heightdef area(self):return self.width * self.heightclass Circle(Shape):def __init__(self, radius):self.radius = radiusdef area(self):return 3.14 * self.radius **

反弹机制

Python中反射机制是通过hasattrgetattrsetattrdelattr四个内置函数实现的,四个内置函数不仅可以用在类和对象中,也可以用在模块等

hasattr(obj,key): 返回bool值, 判断某个成员或者属性在不在类或对象中getattr(obj,key,default=xxx): 获取类或者对象的成员或属性, 若不存在, 则会抛出AttirbuteError异常; 若定义了default, 那么当没有属性的时候会返回默认值setattr(obj,key,value): 用于修改对象的属性值, 若有key属性, 那么更新key属性, 若没有则添加key属性并赋值valuedelattr(obj,key): 删除key属性

import uuidclass Person(object):nationality = "China"def __init__(self, name):self.name = nameself.__id = str(uuid.uuid1())def hello(self):print("Hello, I am %s, I come from %s, My ID is %s" % (self.name,self.nationality, self.__id))if __name__ == "__main__":bauer = Person("Bauer")setattr(bauer, "sex", "Man") #设置bauer的成员属性print(getattr(bauer, "name")) #获取bauer的name属性值,输出:Bauerprint(getattr(bauer, "sex")) #获取bauer的sex属性值,输出:Manprint(hasattr(bauer,'sex')) #判断bauer是否有名为'sex'的属性或成员,输出:Trueprint(hasattr(bauer,'hello')) #判断bauer是否有名为'hello'的属性或成员,输出:Truedelattr(bauer,'sex') #删除bauer的sex属性print(getattr(bauer, "sex")) #抛出异常:AttributeError: 'Person' object has no attribute 'sex'

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。