08.类和对象

    技术2024-11-15  20

    主要内容

    1.了解什么是对象和类2.了解类的三个主要特征:继承、封装和多态3.掌握创建类的方法4.掌握如何为类添加私有方法5.掌握如何继承一个或多个类(多继承)6.掌握如何检测类之间的继承关系

    因为Python被称为面向对象语言(与Java、C#、C++以及其他语言一样),所以面向对象技术支持非常完整,例如类、接口、继承、封装、多态等。在面向对象程序设计中,对象(object)可以看作数据以及可以操作这些数据的一系列方法的集合。这里所说的方法其实就是函数,只是这些函数都写在了类中,为了区分全局函数,将这些写在类中的函数称为方法。要想访问这些类中的函数,必须要对类实例化,实例化后的产物被称为对象。实例化后,调用方法时需要先指定对象名称,然后才可以调用这些方法。

    类:拥有共同特征的同一类事物的总称或抽象。如Bird

    对象:将抽象的事物具体化,从类创建对象也称为类的实例化。如Sparrow ->Bird,Pigeon ->Bird

    继承(inheritance):当前类从其他类获得资源(数据和方法),以便更好地代码重用,并且可以描述类与类之间的关系。

    封装(encapsulation):对外部世界隐藏对象的工作细节。

    多态(polymorphism):多态是面向对象中最有意思的部分,多态意味着同一个对象的同样的操作(方法)在不同的场景会有不同的行为,好像施了魔法一样,非常神奇。

    一、类

    1.类是面向对象的基石,Python类和其他编程语言(Java、C#等)的类差不多,也需要使用class关键字定义,类名直接跟在class关键字的后面。2.类也是一个代码块,所以类名后面要跟着一个冒号(:)。3.如果类是空的,必须加pass语句。4.类中的方法其实就是函数,定义的方法也完全一样,只是由于函数定义在类的内部,所以为了区分,将定义在类内部的函数称为方法。5.每一个方法的第1个参数都是self,这是必需的。这个参数名不一定叫self(可以叫abc或任何其他名字,这里之所以用self,就是一种习惯),但任意一个方法必须至少指定一个self参数,如果方法中包含多个参数,第1个参数将作为self参数使用。在调用方法时,这个参数的值不需要自己传递,系统会将方法所属的对象传入这个参数。在方法内部可以利用这个参数调用对象本身的资源,如属性、方法等。6.使用类创建对象的方式与调用函数的方式相同。在Python语言中,不需要像Java一样使用new关键字创建对象,只需要用类名加上构造方法参数值即可。7.调用对象的方法有两种方式,一种是直接通过对象变量调用方法,另一种是通过类调用方法,并且将相应的对象传入方法的第一个参数。 # 本例会创建一个类,以及利用这个类创建两个对象,并调用其中的方法。 # 创建一个Person类 class Person: # 定义setName方法 def setName(self,name): self.name = name # 定义getName方法 def getName(self): return self.name # 定义greet方法 def greet(self): print("Hello, I'm {name}.".format(name = self.name)) # 创建person1对象 person1 = Person() # 创建person2对象 person2 = Person() # 调用person1对象的setName方法 person1.setName("Bruce Lee") # 调用person2对象的name属性 person2.name = "Jackson Huang" # 调用person1对象的getName方法 print(person1.getName()) # 调用person1对象的greet方法 person1.greet() # 调用person2对象的属性 print(person2.name) # 调用person2对象的greet方法(另外一种调用方法的方式,即通过类调用方法,将person2传入greet方法的第一个参数) Person.greet(person2) Bruce Lee Hello, I'm Bruce Lee. Jackson Huang Hello, I'm Jackson Huang.

    二、方法和私有化

    1.很多其他编程,如Java、C#等,都提供了private关键字将方法私有化,也就是说只有类的内部方法才能访问私有化的方法,通过正常的方式是无法访问对象的私有化方法的(除非使用反射技术)。不过Python类中并没有提供private或类似的关键字将方法私有化,但可以迂回解决。 # 在Python类的方法名前面加双下划线(__)可以让该方法在外部不可访问。 class Person: # method1方法在类的外部可以访问 def method1(self): print("method1") # __method2方法在类的外部不可访问 def __method2(self): print("method2") p = Person() p.method1() p.__method2() # 抛出异常 method1 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-9-b4b829538594> in <module> 10 p = Person() 11 p.method1() ---> 12 p.__method2() # 抛出异常 AttributeError: 'Person' object has no attribute '__method2'

    其实“__method2”方法也不是绝对不可访问。Python编译器在编译Python源代码时并没有将“method2”方法真正私有化,而是一旦遇到方法名以双下划线()开头的方法,就会将方法名改成“_ClassName__methodName”的形式。对于上面的代码抛出异常的原因并不是“__method2”方法被私有化,而是Python编译器把“__method2”的名称改为“_Person__method2”了,可以通过调用“_Person__method2”方法来执行“__method2”方法。

    p = Person() p._Person__method2() method2

    例: 本例会创建一个MyClass类,并定义两个公共的方法(getName和setName)和一个私有的方法(__outName)。然后创建MyClass类的实例,并调用这些方法。为证明Python编译器在编译MyClass类时做了手脚,本例还使用了inspect模块中的getmembers函数获取MyClass类中所有的成员方法,并输出方法名。很显然,“__outName”方法被改成了“_MyClass__outName”。

    class MyClass: # 公共方法 def getName(self): return self.name # 公共方法 def setName(self, name): self.name = name # 在类的内部可以直接调用私有方法 self.__outName() # 私有方法 def __outName(self): print("Name = {}".format(self.name)) myClass = MyClass() # 导入inspect模块 import inspect # 获取MyClass类中所有的方法 methods = inspect.getmembers(myClass, predicate=inspect.ismethod) print(methods) # 输出类方法的名称 for method in methods: print(method[0]) print("------------") # 调用setName方法 myClass.setName("Bill") # 调用getName方法 print(myClass.getName()) # 调用“__outName”方法,这里调用了改完名后的方法,所以可以正常执行 myClass._MyClass__outName() # 抛出异常,因为“__outName”方法在MyClass类中并不存在 print(myClass.__outName()) [('_MyClass__outName', <bound method MyClass.__outName of <__main__.MyClass object at 0x0000000004F99948>>), ('getName', <bound method MyClass.getName of <__main__.MyClass object at 0x0000000004F99948>>), ('setName', <bound method MyClass.setName of <__main__.MyClass object at 0x0000000004F99948>>)] _MyClass__outName getName setName ------------ Name = Bill Bill Name = Bill --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-13-837a3a8e3eb6> in <module> 28 myClass._MyClass__outName() 29 # 抛出异常,因为“__outName”方法在MyClass类中并不存在 ---> 30 print(myClass.__outName()) AttributeError: 'MyClass' object has no attribute '__outName'

    三、类代码块

    1.class语句与for、while语句一样,都是代码块,这就意味着,定义类其实就是执行代码块。2.class代码块中可以包含任何语句。如果这些语句是立即可以执行的(如print函数),那么会立即执行它们。除此之外,还可以动态向class代码块中添加新的成员。、例: 本例创建了一个MyClass类,并在这个类代码块中添加了一些语句。MyClass类中有一个count变量,通过counter方法可以让该变量值加1。在创建MyClass类的实例后,可以动态向MyClass对象添加新的变量。 # 创建MyClass类 class MyClass: # class块中的语句,会立刻执行 print("MyClass") count = 0 def counter(self): self.count += 1 my = MyClass() my.counter() # 调用counter方法 print(my.count) my.counter() # 调用counter方法 print(my.count) my.count = "abc" # 将count变量改成字符串类型 print(my.count) my.name = "Hello" # 向my对象动态添加name变量 print(my.name) MyClass 1 2 abc Hello

    四、类的继承

    与其他面向对象编程语言(Java、C#等)一样,Python也支持类的继承。所谓类的继承,就是指一个类(子类)从另外一个类(父类)中获得了所有的成员。父类的成员可以在子类中使用,就像子类本身的成员一样。Python类的父类需要放在类名后的圆括号中。 # 父类 class Filter: def filter1(self): return 20 # 子类 class MyFilter(Filter): def filter2(self): return 30 # 在上面的代码中,MyFilter是Filter的子类,拥有Filter类的所有成员,包括filter1方法。 # 所以在创建MyFilter类的实例后,可以直接调用filter1方法。 filter = MyFilter() filter.filter1() 20

    例:

    本例创建了一个父类(ParentClass)和一个子类(ChildClass),并通过创建子类的实例调用父类的method1方法。 # 父类 class ParentClass: def method1(self): print("method1") # 子类 class ChildClass(ParentClass): def method2(self): print("method2") child = ChildClass() child.method1() # 调用父类的method1方法 child.method2() method1 method2

    五、检测继承关系

    1.判断类与类之间的关系可以使用issubclass函数,该函数接收两个参数,第1个参数是子类、第2个参数是父类。如果第1个参数指定的类与第2个参数指定的类确实是继承关系,那么该函数返回True,否则返回False。2.可以使用isinstance函数检测一个对象是否是某一个类的实例。isinstance函数有两个参数,第1个参数是要检测的对象,第2个参数是一个类。如果第1个参数指定的对象是第2个参数指定的类的实例,那么该函数返回True,否则返回False。 # ParentClass是ChildClass的父类,返回True,否则返回False issubclass(ChildClass,ParentClass) True # 如果要想获得已知类的父类(们),可以直接使用“__bases__”,这是类的一个特殊属性,bases两侧是双下划线。 print(ChildClass.__bases__) (<class '__main__.ParentClass'>,) child1 = ChildClass() print(isinstance(child1,ChildClass)) True

    例:

    本例创建了4个类,其中ChildClass、ParentClass和MyParentClass三个类有继承关系,也就是说,后一个类是前一个的父类。另外一个MyClass类是独立的类。接下来利用这4个类来演示issubclass、__bases__和isinstance的用法。 class MyParentClass: def method(self): return 50 class ParentClass(MyParentClass) : def method1(self): print("method1") class MyClass: def method(self): return 40 class ChildClass(ParentClass): def method2(self): print("method2") print(issubclass(ChildClass,ParentClass)) print(issubclass(ChildClass,MyClass)) print(issubclass(ChildClass,MyParentClass)) print(ChildClass.__bases__) print(ParentClass.__bases__) True False True (<class '__main__.ParentClass'>,) (<class '__main__.MyParentClass'>,) child = ChildClass() print(isinstance(child,ChildClass)) print(isinstance(child,ParentClass)) print(isinstance(child,MyParentClass)) print(isinstance(child,MyClass)) True True True False

    在上面的程序中,使用issubclass函数检测类的继承关系时,不只是直接的继承关系返回True,间接的继承关系也会返回True。

    六、多继承

    1.Python类支持多继承,这一点与C++相同。要想为某一个类指定多个父类,需要在类名后面的圆括号中设置。多个父类名之间用逗号(,)分隔。2.如果多个父类中有相同的成员,例如,在两个或两个以上父类中有同名的方法,那么会按着父类书写的顺序继承。也就是说,写在前面的父类会覆盖写在后面的父类同名的方法。在Python中,不会根据方法参数个数和数据类型进行重载。3.在Java、C#等面向对象语言中,如果方法名相同,但参数个数和数据类型不同,也会认为是不同的方法,这叫作方法的重载,也就是拥有方法名相同,但参数不同的多个方法。不过由于Python是动态语言,无法像静态类型语言一样根据参数的不同实现重载,所以Python类只判断方法名是否相同,如果相同就认为是同一个方法,先继承的父类同名方法会覆盖后继承的父类同名方法。 # class MyClass(MyParent1,MyParent2,MyParent3): # pass #如果类中没有任何代码,必须加一条pass,否则会编译出错

    例:本例创建了4个类,其中Calculator类和MyPrint类是NewCalculator类和NewCalculator1类的父类,只是继承的顺序不同。如果Calculator放在MyPrint前面,那么Calculator类中的printResult方法将覆盖MyPrint类中的printResult方法,如果把顺序调过来,那么方法覆盖的结果也会调过来。

    class Calculator: def calculate(self,expression): self.value = eval(expression) def printResult(self): print("result:{}".format(self.value)) class MyPrint: def printResult(self): print("计算结果:{}".format(self.value)) # Calculator在MyPrint的前面,所以Calculator类中的printResult方法会覆盖MyPrint类中的同名方法 class NewCalculator(Calculator,MyPrint): pass # MyPrint在Calculator的前面,所以MyPrint类中的printResult方法会覆盖Calculator类中的同名方法 class NewCalculator1(MyPrint,Calculator): pass calc = NewCalculator() calc.calculate("1 + 3 * 5") calc.printResult() result:16 print(NewCalculator.__bases__) print(NewCalculator1.__bases__) (<class '__main__.Calculator'>, <class '__main__.MyPrint'>) (<class '__main__.MyPrint'>, <class '__main__.Calculator'>) calc1 = NewCalculator1() calc1.calculate("1+3*5") calc1.printResult() 计算结果:16

    七、接口

    1.在很多面向对象语言(如Java、C#等)中都有接口的概念。接口其实就是一个规范,指定了一个类中都有哪些成员。接口也被经常用在多态中,一个类可以有多个接口,也就是有多个规范。不过Python语言中并没有这些东西,在调用一个对象的方法时,就假设这个方法在对象中存在吧。当然更稳妥的方法是在调用方法之前,先使用hasattr函数检测一下,如果方法在对象中存在,该函数返回True,否则,返回False。例如,c是一个对象,如果c中存在名为process的方法,hasattr函数返回True,否则返回False,print(hasattr(c,“process”))2.除了可以使用hasattr函数判断对象中是否存在某个成员外,还可以使用getattr函数实现同样的功能。该函数有三个参数,其中前面两个与hasattr函数完全一样,第三个参数用于设置默认值。当第二个参数指定的成员不存在时,getattr函数会返回第三个参数指定的默认值。3.与getattr函数对应的是setattr函数,该函数用于设置对象中成员的值。setattr函数有三个参数,前两个与getattr函数完全相同,第三个参数用于指定对象成员的值。例如,如果c对象中有name属性,则更新该属性的值,如果没有name属性,会添加一个新的name属性。setattr(c,“name”,“new value”)

    例:本例创建了一个MyClass类,该类中定义了两个方法:method1和default。在调用MyClass对象中的方法时,会首先判断调用的方法是否存在。使用getattr函数判断方法是否在对象中存在时,将default方法作为默认值返回。

    class MyClass: def method1(self): print("method1") def default(self): print("default") my = MyClass() # 判断method1是否在my中存在 if hasattr(my,"method1"): my.method1() else: print("method1方法不存在") print("------------------------") # 判断method2是否在my中存在 if hasattr(my,"method2"): my.method2() else: print("method2方法不存在") print("---------------------------") # 从my对象中获取method2方法,如果method2方法不存在,返回default方法作为默认值 method = getattr(my,'method2',my.default) # 如果method2方法不存在,那么method方法实际上就是my.default方法 method() print("----------------------") def method2(): print("动态添加的method2") # 通过setattr函数将method2函数作为method2方法的值添加到my对象中 # 如果method2方法在my中不存在,那么会添加一个新的method2方法,相当于动态添加method2方法 setattr(my,'method2',method2) # 调用my对象中的method2方法 my.method2() method1 ------------------------ method2方法不存在 --------------------------- default ---------------------- 动态添加的method2

    实战与练习

    1.编写一个Python程序,创建三个类:Person、Teacher和Student。这三个类中,Person是Teacher和Student的父类。类中的方法可以自己任意指定。用这三个类演示Python类的继承关系。 class Person: def sing(self): print("only you...") def greet(self): print("Hello girl") class Teacher(Person): def teach(self): print("how to learning") class Student(Person): def learn(self): print("how to study") Jackson = Student() Jackson.sing() Jackson.learn() Jackson.teach() only you... how to study --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-4-9e9dee54f165> in <module> 13 Jackson.sing() 14 Jackson.learn() ---> 15 Jackson.teach() 16 AttributeError: 'Student' object has no attribute 'teach' 2.接上一题,在调用Student类中不存在的方法时,使用setattr函数添加一个新的方法,然后再调用Student类的这个方法。 def slogan(): print("Good good study, day day up!") setattr(Jackson,"slogan",slogan) Jackson.slogan() Good good study, day day up!
    Processed: 0.011, SQL: 9