Python基础(六)--类与对象

目录

 

                            Python基础(六)--类与对象

1 类与对象的基本概念

1.1 什么是对象

1.2 什么是类

1.3 类与对象的关系

2 定义与初始化

2.1 类的定义

2.2 对象的初始化

2.3 动态增加属性方法

3 类成员

3.1 类属性与实例属性

3.2 类方法与实例方法

3.3 静态方法

3.4 类与实例

4 魔法方法

5 动态属性

6 面向对象与面向过程

7 面向对象的三大特征

7.1 封装

7.2 继承

7.3 多态


                            Python基础(六)--类与对象

1 类与对象的基本概念

1.1 什么是对象

对象具有属性和行为,属性多体现为名词,而行为多体现为动词。

1.2 什么是类

类,其实就是指一个类别,具有相同属性与行为的所有对象构成的一个整体。

1.3 类与对象的关系

(1)类是对象的抽象,而对象是类的具体表现形式

(2)类是设计的模板,而对象是该模板设计出的具体产物

2 定义与初始化

2.1 类的定义

使用class关键字定义类,如下:

class 类名:

      类体

想让对象具备那些功能(属性和行为),就需要在类中指出。就需要在现实与Python程序中进行一种映射,对象的属性,通过令对象绑定一个变量来实现,而对象的行为,通过在类内定义方法来实现。所谓方法,其形式与函数非常相似,只不过是定义类的内部,关联了某个对象而已。

self:在两个方法中,都具有一个参数:self。该参数用来表示当前的对象(调用该方法的时候,所使用的对象。简单说,就是谁调用了这个方法,当前对象就是谁)。

如果仅仅定义了类,而没有为类具体化(创建对象),是不能够使用类中定义的功能的,通过对象调用方法时,该对象就会隐式的传递给方法的第一个参数(无需我们显式传递)。

# 定义一个学生类
class Student:def study(self):print("学习")
# 通过类创建对象
s = Student()
# 通过对象访问类中的成员
s.study()
# 为对象增加属性,有则修改,无则添加
s.name = "refuel"
s.age = 18
print(s.name,s.age)

方法与函数的区别:①方法就是函数,只不过函数是面向过程的称呼,而方法是面向对象的称呼;②函数不依赖类对象,而方法依赖于一个类的对象

2.2 对象的初始化

在类中定义了方法,来表示对象的行为,对象也是可以具有属性的。例如,人可以具有姓名,年龄等属性。定义属性,我们可以在__init__中进行定义

(1)__init__方法

__init__方法会在创建对象时自动得到执行。并且,每创建一个对象,该方法都会执行一次。可以在该方法中执行对象的初始化,定义属性就是一件最常用的操作。而__init__方法具有初始化的能力,我们也习惯将其称为构造器或构造方法。

(2)含有参数的__init__方法

不含参的__init__方法的一个不足,就是无论我们创建多少个对象,对象的属性都是完全一致的。为了能够更加灵活的创建对象,让对象具有不同的属性值,我们可以在__init__方法中增加参数,然后在创建对象时,动态传递实际参数来初始化对象的属性(不再使用固定值初始化),这样就能够避免创建属性值完全相同的对象了。

# 对象的初始化
# self表示当前的对象,__init__(self)中的self指我们创建的对象,对于其他方法,当前对象只调用该方法的那个对象
class Student:# __init__方法在创建对象的时候会自动执行,可以在该方法中为当前创建的对象增加属性def __init__(self):self.name = "refuel"self.age = 18def study(self):print("学习")
s = Student()class Student2:# 在定义__init__方法的时候,可以为该方法提供一些参数,来更加灵活的进行初始化,就可以避免千篇一律的创建对象def __init__(self,name,age):self.name = nameself.age = agedef study(self):print("学习")
s2 = Student2("refuel",18)

 

2.3 动态增加属性方法

除了在类中预先定义属性与方法外,我们也可以动态的为对象增加属性与方法。】

(1)动态增加的方法在调用时,需要显式的传递当前对象。

(2)动态增加的属性与方法仅对当前对象有效,对于其他对象是无效的。

# 动态增加属性与方法,仅对当前对象有效,对其他对象无效
class Student:pass
def study(self):print("动态增加的方法")
s = Student
s.name = "动态增加的属性"
s.study = study
print(s.name)
# 动态增加的方法,需要显示的传递self对象
s.study(s)

3 类成员

3.1 类属性与实例属性

(1)实例属性:定义的属性都会与类的对象相关联的。对象也就是实例,创建类的对象,也可以称为创建类的实例。

(2)类属性:定义的属性与当前类进行关联,不属于对象,直接定义在类体中。

(3)实例属性与类属性的区别:

①属性的绑定不同:类属性与类进行绑定,与对象无关,可以共享给所有对象访问。实例属性与对象绑定,每个对象有自己的实例属性,不影响其他对象

②访问方式不同:类属性可以通过类访问,也可以通过对象访问。实例属性只能通过对象访问,而不能通过类访问。通过对象访问类属性,仅限于访问,无法修改类属性的值,尝试修改的话只不过是动态的为当前对象新增一个实例属性,这个实例属性与类属性同名。

类属性与实例属性名称相同时:不会产生错误。通过类访问的,是类属性,通过对象访问的,可能是类属性,也可能是实例属性。此时要分为读取属性的值,还是修改属性的值。当读取属性值时,首先尝试访问同名的实例属性,如果实例属性不存在,则会访问类属性(如果类属性也不存在则会产生错误)。当修改属性值时,如果实例属性存在,则修改,如果实例属性不存在,则新增该实例属性,即通过对象修改属性时,操作的属性一定是实例属性。

注意:最好使用类名访问类属性,不要通过对象访问类属性,避免造成不必要的混淆

# 实例属性:每个对象独享,彼此不受干扰;类属性:对象共享
# 类属性与实例属性的区别:①属性的绑定不同:类属性绑定的是当前类,与当前类的任何对象都无关,实例属性绑定的是当前对象
# ②访问方式不同:类属性可以通过类名访问,也可以通过对象进行访问(只读),因为类属性共享给所有对象。实例属性只能通过对象访问,不能通过类名访问
class Student:# 类属性stu = "学生"def __init__(self,name):# 实例属性self.name = name
# 类名访问类属性
print(Student.stu)
# 对象访问类属性
s = Student("refuel")
print(s.stu)
# 注意:通过类名是不能访问实例属性的,也就是不能Student.name
# 对象无法修改类属性,只能访问,所以当名称和类属性一样时,仅仅是为当前对象增加一个实例属性。修改类属性可以通过类名修改
s.stu = "学生-修改"
print(s.stu)
print(Student.stu)
# 实例属相是对象独享的,彼此不受干扰
s2 = Student("张三")
s3 = Student("李四")
print(s2.name,s3.name)

3.2 类方法与实例方法

(1)类方法:

使用@classmethod修饰的方法,这样的方法称为类方法。

按照惯例(并非语法要求),将类方法的第一个参数命名为cls(这类似于实例方法的第一个参数self),该参数是有特殊意义的,用来表示当前类,即调用该类方法的那个类。在调用类方法时,cls无需我们显式传递(就像实例方法的self无需我们显式传递一样)。

类方法的作用:类方法与当前类绑定,与类的对象无关,因此,我们通常使用类方法来作为工厂方法,用来创建并返回类的对象,这就相当于提供了另外一种构造器。

(2)实例方法:

实例方法与具体的类的某个对象想关联

类方法与实例方法,二者既可以通过类名调用,也可以通过对象来调用。不过,就像通过对象访问类属性一样,通过对象来调用类方法会造成不必要的混淆(类方法与类的对象无关,是绑定当前类的),因此,我们应该总是通过类名来调用类方法。

(3)类方法与实例烦啊对属性的访问

在类方法中,需要通过参数(cls)访问类属性,而不能直接访问类属性。实例方法没有cls参数,可以通过类名来访问类属性,但是,这是一种硬编码的方式,如果以后类名发生了改变,则我们通过类名访问类属性的语句都需要进行修改。这不利于程序的维护。为了降低耦合性,我们在实例方法中,可以通过__class__来动态获取当前对象所属的类:

self.__class__  相当于获取到了cls,也就是self对象所属的类(动态获取),如果是在类的内部,我们还可以省略对象的限定,直接使用:__class__    那么访问方式就可以改为:  类的对象.__class__.类属性(__class__.类属性   必须在类的内部)。这种访问的优势在于,以后就算类名发生了改变,访问类属性的语句也无需进行任何修改,利于程序的维护。

class Student:# 实例方法def study(self):print("实例方法")# 类方法,使用@classmethod,类方法的第一个参数固定的,按照惯例命名为cls@classmethoddef clsmethod(cls):print("类方法")
# 类方法的第一个参数会隐式传递
Student.clsmethod()
# 通过对象可以调用实例方法,也可以调用类方法。虽然对象可以访问类方法,但是不建议这么做
s = Student()
s.clsmethod()
s.study()
# 通过类名可以访问类方法,也可以访问实例方法(需要显示传递对象)
Student.study(s)
print("--------------------------------")
# 类方法与实例方法访问属性
class Person:# 类属性intro = "类属性"def __init__(self,name,age):# 实例属性self.name = nameself.age = age# 类方法@classmethoddef eat(cls):# 类方法访问类属性cls.intro = "类方法修改"# 类方法不能访问实例属性,如果一定要访问那就要传入一个对象进行访问,但是如果这样,那就应该定义成实例方法# 实例方法def jump(self):# 实例方法访问实例属性print(self.name)# 实例方法访问类属性print(self.intro)# 获取对象所属的类 self.__class__print(self.__class__.intro)# Python不支持方法的重载,可以用类方法来返回一个对象,相当于提供了一种不同的构造器来创建对象@classmethoddef copy(cls,p):return cls(p.name,p.age)
Person.eat()
print(Person.intro)
p = Person("refuel",18)
p.jump()
p2 = Person.copy(p)
print(p2.name,p2.age)

3.3 静态方法

静态方法使用@staticmethod修饰

静态方法没有cls参数。对于静态方法而言,其既与当前类的对象无关,也与当前类无关,可以说,静态方法只是定义在类的内部,从逻辑结构上,与当前类划分到了一起,从功能的角度讲,我们完全可以将静态方法迁移到类的外部,作为函数来实现同样的功能。

如果某个函数,其实现的功能与某个类关系紧密,但是又不依赖于当前类(cls)与当前对象(self)时,我们就可以将该函数声明在类的内部,作为静态方法。静态方法既可以通过类名调用,也可以通过对象调用,建议通过类名调用,因为静态方法与对象无关。

class Student:def __init__(self,name):self.name = name# 静态方法使用@staticmethod修饰,既没有self,也没有cls,所以# 静态方法的设计原则为:既不依赖当前的类属性,也不访问当前对象的实例属性# 从结构上看,可以将静态方法移除到类外,就像函数一样,但是# 静态方法定义在类的内部的意义为:有些方法完全为一个类服务,具有逻辑上的关联性@staticmethoddef statmethod():print("静态方法")@staticmethoddef hand_in_homework(s):print(f"{s.name}交作业")
Student.statmethod()
s = Student("refuel")
# 虽然对象可以访问静态方法,但是静态方法与当前对象没有关系,我们应该总是通过类名来访问静态方法
s.statmethod()
# 上交作业的函数,既没有用到类属性,也没有使用实例属性(虽然用到name,是对象传过来的,不是用self访问的),
# 但是该函数的功能是完全为Student类服务的,因此可以定义在Student方法中,成为静态方法
def hand_in_homework(s):print(f"{s.name}交作业")Student.hand_in_homework(s)

3.4 类与实例

我们在类中可以定义类属性,类方法,静态方法,也可以定义实例属性与实例方法。在设计一个类的时候,该如何进行选择呢?

(1)对于类属性和实例属性

看属性是所有对象共享的(类属性,如人有几根手指),还是单独一个对象独享的(实例属性,如人的名字年龄)

(2)实例方法,类方法和静态方法

定义方法的目的往往就是操作类中的属性(因为如果不操作类属性就和类没关系就可以定义成函数就可以了)。方法里面对实例属相进行操作定义成实例方法,因为有self,self才能访问到实例属性。当与当前对象没有关系,不需要访问实例属性,但是需要访问到类中定义的类属性,定义成类方法。静态方法既没有self,也没有cls,为了提供逻辑上的紧密关联,完全是为了类服务的,既没有用到self,也没有用到cls,就用静态方法

4 魔法方法

魔法方法:定义了一些特殊的方法,这些方法通常不会显式去调用,而是在特定的场合下隐式调用执行的。这些特殊的方法名称都是以双下划线开头,并且以双下划线结尾。

(1)__new__(cls, ……)

创建cls类的对象。通过参数的cls可知,该方法是一个类方法,但是不需要使用装饰器来修饰(@classmethod),该方法的其他参数由构造器传入。该方法中,通常会调用父类的__new__方法来创建对象,并返回。如果该方法返回值为当前类的对象,则会继续调用初始化方法(__init__),传入当前创建的对象,否则,初始化方法不会执行。

该方法通常不需要自行实现,当我们实现该方法时,主要用来完成自定义的对象创建。

(2)__init__(self)

初始化方法,在创建对象时,如果__new__方法创建并返回了当前类型的对象,则会调用该方法,对new创建的对象执行初始化。

(3)__del__(self)

当销毁对象时,调用该方法。

(4)__str__(self)

当调用内建函数str,format或print时,就会调用对象的该方法。该方法必须返回字符串(str)类型,用来描述对象的字符串表示。

(5)__repr__(self)

当调用内建函数repr时,就会调用对象的该方法。该方法必须返回字符串(str)类型,用来描述对象的字符串表示。如果类中定义了该方法,但是没有定义__str__方法,则当需要调用__str__方法时,也会调用该方法代替。

(6)__bytes__(self)

当调用内建函数bytes时,会调用该方法,该方法必须返回字节(bytes)类型,用来以字节的形式描述对象。

(7)__call__(self)

当把类的对象作为方法调用时,就会调用该方法。

# 魔法方法就是使用__方法名__的命名规则,通常情况下,不会主动调用,在满足一定条件下自动调用
class Student:# __new__是一个不使用@classmethod修饰的类方法,在创建对象时,首先会调用这个方法,返回当前类的对象# 如果该方法返回当前类的对象,那么接下来会调用__init__方法,对对象进行初始化,否则__init__方法不会得到执行def __new__(cls):print("new执行")# return cls()不能这么做会无限递归# 通过super调用__new__就不会出现无限递归return super().__new__(cls)# 初始化方法,在创建对象时,如果__new__方法创建并返回了当前类型的对象,则会调用该方法,对new创建的对象执行初始化。def __init__(self):print("init方法")# 当前对象销毁时调用这个方法,可以执行一些清理的工作def __del__(self):print("对象销毁")# 使用内建函数str,format,print时调用,需要返回一个str类型的对象def __str__(self):return "Student类型对象"# 在使用内建函数repr时调用,需要返回一个str类型的对象# __str__与__repr__的区别:都返回字符串,__str__返回的是让人容易阅读的格式,__repr__通常返回# 的是面向Python解释器的。当需要调用__str__的场合,没有定义__str__,就是要__repr__替代def __repr__(self):return "<Student class>"# 当使用内建函数bytes时调用,返回字节类型def __bytes__(self):return b"byte student class"# 当将对象当成函数调用会执行该方法def __call__(self):print("对象当函数调用")
s = Student()
print(s)
print(str(s))
print(repr(s))
print(bytes(s))
s()
del s

5 动态属性

Python提供了如下的内建函数,用来操作对象的属性。

(1)hasattr(obj,name):判断obj对象中是否存在name指定的属性名,存在返回True,否则返回False。

(2)setattr(obj,name,value):将obj对象的name属性设置为value值。相当于执行如下的操作:obj.name = value。如果name属性存在,则覆盖,如果不存在,则为对象obj新增name属性。

(3)getattr(obj,name):返回obj对象的name属性值,相当于执行如下的操作:obj.name。如果name属性值存在,则返回属性值,如果属性值不存在,返回default参数值,如果属性值不存在,default参数也不存在,则产生AttributeError。

(4)delattr(obj,name,value):删除obj对象的name属性。相当于执行如下的操作:del obj.name.如果指定的属性不存在,则会产生AttributeError。

如果使用obj.name的方式访问属性的话,name必须是编写代码时已知的内容,而不能通过字符串来指定。以上函数的优势在于,我们可以通过字符串的形式来操作属性,而无需在编写代码的时候就知道具体的属性名称。因此,这就给我们操作属性带来了一定的灵活性。我们完全可以在运行时以字符串的形式传递属性名,进行操作。

# 魔法方法就是使用__方法名__的命名规则,通常情况下,不会主动调用,在满足一定条件下自动调用
class Student:pass
s = Student()
# 参数1:对象,参数2:属性名   判断对象中是否存在第二个参数指定的属性名,存在返回True,否则False
print(hasattr(s,"name"))
# 参数1:对象,参数2:属性名   返回对象对应的第二个参数指定的属性,不存在产生异常,可以指定第三参数,不存在时返回的默认值
print(getattr(s,"name","没属性的默认值"))
# 参数1:对象,参数2:属性名   参数三:属性值
print(setattr(s,"age",18))
delattr(s,"age")

6 面向对象与面向过程

编程方式可分为面向对象和面向过程。

面向过程:体现的是一种流程设计,即按部就班的完成每一个环节。为了实现每个环节的可复用性,环节都是通过调用一个个函数来实现的。因此,在面向过程的编程中,通常都是定义大量的函数,按照需求顺序依次进行调用。

面向对象:程序不再是以函数为单位,而是以对象为单位,通过调用对象提供的方法处理。

7 面向对象的三大特征

7.1 封装

(1)什么是封装?

封装:隐藏具体的实现细节,只提供给外界调用的接口。只要提供给外界的接口不变即可,底层细节改变的时候,不会对外界造成影响。

(2)私有成员

在程序中可以通过变量私有化做封装,使得在类中定义的变量,仅能在当前类(定义变量的类)中访问,而不能在类的外部访问。如果一个属性名(或方法名)使用两个下划线(__)开头,并且少于两个下划线结尾,则这样的属性(方法)就称为私有属性(方法)。私有属性(方法)只能在类的内部访问。

注意:其他语言定义成私有了是真正访问不到,但是python的私有只不过是进行了伪装而已。当在类中定义私有成员时,在程序内部会将其处理成_类名 + 原有成员名称的形式。也就是会将私有成员的名字进行一下伪装而已,如果使用处理之后的名字,还是能够进行访问的。

# 私有变量以__开头,但是不能以两个或更多的_进行结尾
class Student:def __init__(self):self.desc = "学生"# 私有成员(私有实例属性)self.__books = 5# 在定义成员类的内部可以访问私有成员print(self.__books)# 提供公有方法访问私有的属性def set_books(self,books):self.__books = books# 提供公有反复噶获取私有的属性def get_books(self):return self.__books
s = Student()
print(s.desc)
# print(s.__books) AttributeError: 'Student' object has no attribute '__books' 私有成员不能在类外进行访问
# 通过类提供的公有方法访问私有成员
s.set_books(10)
print(s.get_books())

(3)property

在客户端访问时,公有的方法总不如变量访问那样简便,为了既可以直接访问变量,又能够实现很好的封装,做到信息隐藏,可以使用property的两种方式来实现封装。

property有两种方式来实现封装:①使用property函数;②使用@property装饰器

# 使用property定义一个属性(伪装成一个属性)
# 使用property既可以提供访问的便利性,也可以提供很好的封装性
# 1使用property函数
class Student:def __init__(self):self.__books = 2# property的4个参数# get方法:在读取property值的时候调用# set方法:在修改property值的时候调用# del方法:在删除property的时候调用# doc:给出该property的说明文档def getBooks(self):print("调用get方法")return self.__booksdef setBooks(self,books):print("调用set方法")self.__books = booksdef delBooks(self):print("调用del方法")del self.__booksbooks = property(getBooks,setBooks,delBooks,"书本的数量")
s = Student()
print(s.books)
s.books = 10
print(s.books)
del s.books
print("----------------------")
# 2 使用property装饰器
class Student2:def __init__(self):self.__books = 2# 将方法名伪装成一个属性,获取属性会调用,说明文档也写在该方法中@propertydef books(self):"""books的说明文档"""return self.__books# 设置属性会调用该方法@books.setterdef books(self,books):self.__books = books# 删除属性调用该方法@books.deleterdef books(self):del self.__books
s2 = Student()
print(s2.books)
s2.books = 10
print(s2.books)
del s2.books

 

7.2 继承

(1)什么是继承

继承体现的是一种一般与特殊的关系。如果两个类A与B,A(苹果)是一种特殊的B(水果),我们就称,特殊的类A继承了一般的类B(苹果继承了水果)。对于一般的类型(水果),我们称为父类,而对于特殊的类型(苹果),我们称为子类。当子类继承了父类,子类就可以继承父类中定义的成员(变量,方法等),就好像在子类中自己定义的一样。继承的语法为:

class B(A):

      类体

这样,B类就继承了A类,B就成为一种特殊的A,B类就会继承A类的成员。

如果没有显式指定继承的类型,则类隐式继承object类,object是Python中最根层次的类,所有类都是object的直接或间接子类。

# 通过继承可以将公共的功能提取出来,放入父类中,然后每一个子类去继承父类
# 那就无需将公共的功能在子类中分别实现,从而实现代码的重用
class Animal:def desc(self):print("动物")def eat(self):print("动物需要进食")
# 继承:定义类是指定继承的父类
class cat(Animal):# 子类重写父类的功能def desc(self):print("我是一只猫")# 子类增加自己特有的方法def catchMouse(self):print("抓老鼠")
a = Animal()
a.desc()
c = cat()
c.desc()
c.catchMouse()

(2)为什么要使用继承

我们有时候可能还需要对现有类进行调整,这体现在:①现有类的提供的功能不充分,我们需要增加新的功能。②现有类的提供的功能不完善(或对我们来说不适合),我们需要对现有类的功能进行改造(就是重写)。

(3)成员的继承

子类可以继承父类的成员,父类中声明的类属性、实例属性、类方法、实例方法与静态方法,子类都是可以继承的。但是,对于实例属性有些特别。因为实例属性是定义__init__方法中,实现与对象(self)的绑定。如果子类没有定义init方法,就会继承父类的init方法,从而在创建对象时,调用父类的init方法,会将子类对象传递到父类的init方法中,从而实现子类对象与父类init方法中实例属性的绑定。但是,如果子类也定义了自己的init方法(重写),则父类init方法就不会得到调用,这样,父类init方法中定义的实例属性就不会绑定到子类对象中。

如果子类与父类的初始化方式完全相同,子类只要继承父类__inir__方法就可以的。但有的时候,子类可能会增加自己的属性,此时,就不能完全套用父类的初始化方式。

虽然父类的__init__不完全适合子类,但是也并非完全不适合子类。因为两个类还是存在相同的属性的,因此,我们应该充分利用现有的功能,不要重复的实现。实现方式就是,我们在子类的构造器中,去调用父类的构造器,完成公共属性的初始化,然后在子类构造器中,再对子类新增属性进行初始化。我们可以这样来调用父类的构造器:

          super().__init__(父类构造器参数)

# 成员的继承
class Animal:classAttr = 6def __init__(self):self.instanceAttr = 2def instanceMethod(self):print("实例方法")@classmethoddef classMethod(cls):print("类方法")@staticmethoddef staticMethod():print("静态方法")# 私有成员是可以被子类继承的,不过继承的不是原来的名字,而是伪装之后的名字def __pri(self):print("私有方法")
class cat(Animal):# 父类的初始化不完全适用于子类,子类定义自己的初始化方法def __init__(self,name,age):#调用父类的__init__方法super().__init__()self.name = nameself.age = agedef private(self):# 继承私有成员self._Animal__pri()
c = cat("aa",18)
print(cat.classAttr)
print(c.instanceAttr,c.name,c.age)
cat.classMethod()
c.instanceMethod()
cat.staticMethod()
c.private()

(4)继承的两个内建函数isinstance与issubclass

class Animal:pass
class Cat(Animal):pass
a = Animal()
c = Cat()
# instance(o,t)参数一:对象,参数二:类型。判断第一个指定的参数是否是第二个参数类型(包括父类型)
print(isinstance(a,Animal))
print(isinstance(c,Cat))
print(isinstance(c,Animal))
print(isinstance(c,object))
print(isinstance(a,Cat))
print("------------")
# issubclass()判断第一个参数(类型)是否是第二个参数的类型(或者是其子类型)
print(issubclass(Cat,Cat))
print(issubclass(Cat,Animal))
print(issubclass(Animal,Cat))
print(issubclass(Animal,Animal))
print(issubclass(Cat,object))

(5)重写

当子类继承了父类,子类就可以继承父类的成员。然而,父类的成员未必完全适合于子类(例如鸟会飞,但是鸵鸟不会飞),此时,子类就将父类中的成员进行调整,以实现适合子类的特征与功能。我们将父类中的成员在子类中重新定义的现象,称为重写。当通过子类对象访问成员时,如果子类重写了父类的成员,将会访问子类自己的成员。否则(没有重写)访问父类的成员。

如果子类需要访问父类的成员,可以通过:super().父类成员     进行访问。

class Animal:def eat(self):print("进食")
class Cat(Animal):def eat(self):super().eat()print("吃鱼")
c = Cat()
c.eat()

(6)多重继承

在Python中,类是支持多重继承的,即一个子类可以继承多个父类。java只能单继承。

当子类继承多个父类时,子类会继承所有父类的成员。当多个父类含有相同名称的成员时,我们可以通过具体的父类名,来指定要调用哪一个父类的成员(如果是实例方法,需要显式传递一个类对象),这样就能够避免混淆。

同名的访问:由Python中的方法解析顺序(MRO)来决定,访问某个类的成员时,成员的搜索顺序。该顺序大致如下:①如果是单继承(继承一个父类),则比较简单,搜索顺序从子类到父类,一直到object为止。②如果是多继承(继承多个父类),则子类到每个父类为一条分支,按照继承的顺序,沿着每一条分支,从子类到父类进行搜索,一直到object类为止(深度优先)。③在搜索的过程中,子类一定会在父类之前进行搜索。

成员的搜索顺序为:F -> D -> B -> E -> C -> A -> object

# 在python中一个类可以继承多个父类
class Animal:def desc(self):print("动物")class Felid:def desc(self):print("猫科动物")
# 猫同时继承两个类型
class Cat(Animal,Felid):def meth(self):self.desc()
c = Cat()
c.meth()

成员搜索顺序:之前使用的super类,就是根据方法解析顺序来查找指定类中成员,但是有一点例外,就是super对象不会在当前类中搜索,即从方法解析顺序的第二个类开始。super的构造器会返回一个代理对象,该代理对象会将成员访问委派给相关的类型(父类或兄弟类)。

(7)方法解析顺序MRO

每个类都提供了mro方法与__mro__属性,可以获得该类的方法解析顺序。mro方法与__mro__属性获取的元素内容是相同的,只不过前者返回的是列表(list)类型,后者返回的是元组(tuple)类型。

class A:x = "A"
class B(A):x = "B"
class C(A):x = "C"
class D(B):x = "D"
class E(C):x = "E"
class F(D,E):x = "F"
# 获取方法的解析顺序
print(F.mro())
print(F.__mro__)
# super()返回一个委派对象,通过委派对象访问类中的成员,委派的顺序
# 与mro的顺序是一致的,除了排除自身

7.3 多态

多态:指运行时表现的多种形态,运行时会根据对象的真正类型来决定调动哪一个类型的成员。

因为Python是鸭子类型,因此多态在Python中体现的不明显,不像一些定义变量时需要指定明确类型的语言(例如Java,C++等)中那么明显。

class Father:def talk(self):print("father")
class Son:def talk(self):print("son")
def talk(m):m.talk()
f = Father()
s = Son()
talk(f)
talk(s)
"""
其他指定类型的语言
Father f = new Father()
Son s = new Son()
talk(f)
talk(s)
def talk(Father f)f.talk
"""

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/473708.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTTP和HTTPS的请求和响应

HTTP协议&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;&#xff1a;是一种发布和接收 HTML页面的方法。 HTTPS&#xff08;Hypertext Transfer Protocol over Secure Socket Layer&#xff09;简单讲是HTTP的安全版&#xff0c;在HTTP下加入…

LeetCode 794. 有效的井字游戏(分类讨论)

文章目录1. 题目2. 解题1. 题目 用字符串数组作为井字游戏的游戏板 board。当且仅当在井字游戏过程中&#xff0c;玩家有可能将字符放置成游戏板所显示的状态时&#xff0c;才返回 true。 该游戏板是一个 3 x 3 数组&#xff0c;由字符 " "&#xff0c;"X&quo…

Requests: 让 HTTP 服务人类

Requests支持HTTP连接保持和连接池&#xff0c;支持使用cookie保持会话&#xff0c;支持文件上传&#xff0c;支持自动确定响应内容的编码&#xff0c;支持国际化的 URL 和 POST 数据自动编码。 Requests的文档非常完备&#xff0c;中文文档也相当不错。Requests能完全满足当前…

Python基础(七)--模块和包

目录 Python基础&#xff08;七&#xff09;--模块和包 1 模块 1.1 什么是模块 1.2 模块的使用 1.3 模块的搜索路径 1.4 模块的缓存 2 包 2.1 什么是包 2.2 包的使用 3 常用模块 3.1 math 3.2 random 3.3 time 3.4 datetime 3.5 sys Python基础&#xff08;七&am…

LeetCode 909. 蛇梯棋(BFS)

文章目录1. 题目2. 解题1. 题目 N x N 的棋盘 board 上&#xff0c;按从 1 到 N*N 的数字给方格编号&#xff0c;编号 从左下角开始&#xff0c;每一行交替方向。 例如&#xff0c;一块 6 x 6 大小的棋盘&#xff0c;编号如下&#xff1a; r 行 c 列的棋盘&#xff0c;按前…

爬虫必须学会的正则表达式

为什么要学正则表达式 实际上爬虫一共就四个主要步骤&#xff1a; 明确目标 (要知道你准备在哪个范围或者网站去搜索)爬 (将所有的网站的内容全部爬下来)取 (去掉对我们没用处的数据)处理数据&#xff08;按照我们想要的方式存储和使用&#xff09; 我们在昨天的案例里实际上…

Python基础(八)--迭代,生成器,装饰器与元类

目录 Python基础&#xff08;八&#xff09;--迭代&#xff0c;生成器&#xff0c;装饰器与元类 1 迭代 1.1 可迭代对象与迭代器 1.2 自定义迭代类型 1.3 迭代合体 2 生成器 2.1 什么是生成器 2.2 生成器表达式 2.3 生成器函数 3 装饰器 3.1 闭包 3.2 什么是装饰器 …

XPath和lxml类库

XPath&#xff0c;我们可以先将 HTML文件 转换成 XML文档&#xff0c;然后用 XPath 查找 HTML 节点或元素。 什么是XML XML 指可扩展标记语言&#xff08;EXtensible Markup Language&#xff09;XML 是一种标记语言&#xff0c;很类似 HTMLXML 的设计宗旨是传输数据&#xf…

Python基础(九)--异常

Python基础&#xff08;九&#xff09;--异常 1 异常相关概念 1.1 什么是异常 异常是程序运行过程中产生的一种事件&#xff0c;该事件会打乱程序的正常流程。可以说&#xff0c;异常就是一种意外&#xff0c;指程序没有按照正常或期望的方式执行。 当异常产生时&#xff0…

爬虫工具:虚拟机Selenium和PhantomJS,ChromeDriver 镜像安装教程

虚拟机Ubuntu 16.04中安装&#xff1a; 1.安装Selenium命令&#xff1a;pip install Selenium 2.安装PhantomJS命令&#xff1a;sudo apt install PhantomJS 3.找到Ubuntu中谷歌所对应的的版本号&#xff1a; 看到网上基本没有最新的chromedriver与chrome的对应关系表&#xff…

Python基础(十)--文件相关

目录 Python基础&#xff08;十&#xff09;--文件相关 1 读写文件 1.1 获取文件对象 1.2 文件读取 1.3 文件写入 1.4 文件定位 2 文件与路径的操作 2.1 os模块 2.2 os.path模块 2.3 shutil模块 2.4 glob模块 3 序列化 3.1 csv 3.2 json 3.3 pickle 4 上下文管理…

memcache 原理 监测 查看状态 stats 结构

Mencache内存存储方式&#xff1a;slab/LRU&#xff0c;采用预先申请固定大小的内存页&#xff08;slab/page&#xff09;&#xff0c;然后再把内存分成多个块&#xff08;chunk) 先放一张从网上找到的memcache内存结构图&#xff0c;觉得非常的赞&#xff1a; 再来一张memcach…

爬虫——多线程糗事百科案例

Queue&#xff08;队列对象&#xff09; Queue是python中的标准库&#xff0c;可以直接import Queue引用;队列是线程间最常用的交换数据的形式 python下多线程的思考 对于资源&#xff0c;加锁是个重要的环节。因为python原生的list,dict等&#xff0c;都是not thread safe的…

LeetCode 1625. 执行操作后字典序最小的字符串(BFS)

文章目录1. 题目2. 解题1. 题目 给你一个字符串 s 以及两个整数 a 和 b 。其中&#xff0c;字符串 s 的长度为偶数&#xff0c;且仅由数字 0 到 9 组成。 你可以在 s 上按任意顺序多次执行下面两个操作之一&#xff1a; 累加&#xff1a;将 a 加到 s 中所有下标为奇数的元素…

C++ 0x 使用可变参数模板类 实现 C# 的委托机制

1 #ifndef _ZTC_DELEGATE_H_2 #define _ZTC_DELEGATE_H_3 4 #include <vector>5 #include <functional>6 7 ///8 // C 使用 可变参数模板类, 来实现9 // C#中的 委托 10 // Anchor: ztc 11 // Date : 2014-01-10 12 /// 13 14 template<typename R, typename …

爬虫技巧:在pycharm 下 调试 scrapy项目

&#xff08;1&#xff09; 用pycharm导入scrapy项目 &#xff08;2&#xff09;选择自己编写的scrapy&#xff0c;run一下 &#xff08;3&#xff09;点击菜单栏的run &#xff0c;选择Edit Configurations。 &#xff08;4&#xff09;选择运行的spider文件 &#xff08;5&am…

LeetCode 1626. 无矛盾的最佳球队(最大上升子序DP)

文章目录1. 题目2. 解题1. 题目 假设你是球队的经理。对于即将到来的锦标赛&#xff0c;你想组合一支总体得分最高的球队。球队的得分是球队中所有球员的分数 总和 。 然而&#xff0c;球队中的矛盾会限制球员的发挥&#xff0c;所以必须选出一支 没有矛盾 的球队。 如果一名…

爬虫最基本的工作流程:内涵社区网站为例

网络爬虫&#xff08;又被称为网页蜘蛛&#xff0c;网络机器人&#xff09;就是模拟客户端发送网络请求&#xff0c;接收请求响应&#xff0c;一种按照一定的规则&#xff0c;自动地抓取互联网信息的程序。 只要是浏览器能做的事情&#xff0c;原则上&#xff0c;爬虫都能够做…

LeetCode 网易-2. 古老的游戏机

文章目录1. 题目2. 解题1. 题目 小易有一个古老的游戏机&#xff0c;上面有着经典的游戏俄罗斯方块。因为它比较古老&#xff0c;所以规则和一般的俄罗斯方块不同。 首先&#xff0c;荧幕上一共有 n 列&#xff0c;每次都会有一个 1 x 1 的方块随机落下&#xff0c;在同一列中…

RDD(弹性分布式数据集)

1、什么是RDD RDD&#xff08;分布式弹性数据集&#xff09;是对分布式计算的抽象&#xff0c;代表要处理的数据&#xff0c;一个数据集,RDD是只读分区的集合。数据被分片&#xff0c;分成若干个数据分片&#xff0c;存储到不同的节点中&#xff0c;可以被并行的操作&#xff…