08.类和对象

主要内容

  • 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!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值