8. 类与对象

术语简介

  • 类:用来描述具有相同属性和方法的对象的集合。类定义了集合中每个对象共有的属性和方法。对象是累的实例。
  • 类变量(属性):类变量在整个实例化的对象中是公用的。类变量定义在类中,且在方法之外。类变量通常不作为实例变量使用。类变量也称作属性。
  • 方法:类中定义的函数。
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
  • 实例化:创建一个类的实例、类的具体对象。
  • 数据成员:类变量或实例变量用于处理类及其实例对象的相关数据。

类的定义和使用

类的定义语法格式如下:

class MyClass(object):
    i=123
    def f(self):
        return 'Hello World'
class ClassName(object):    #类名,驼峰命名
    属性1                   #类的属性
    属性2
    ...
    方法1                   #类的方法
    方法2
    ...

python中定义类使用class关键字,class后面紧接着类名,如示例中的MyClass,类名通常是大写开头的单词(一般使用驼峰命名,要求见名知意);紧接着是(object),表示该类是从哪个类继承下来的。通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。类包含属性(相当于函数中的语句)和方法(方法大体可以理解成函数)。

  • 注意:在类中定义方法的形式和函数差不多,但不称为函数,而称为方法。方法的调用需要绑定到特定的对象上,而函数不需要。\

类的使用:

class MyClass(object):
    i=123
    def f(self):
        return 'Hello World'

use_class=MyClass()
print('调用类的属性:',use_class.i)
print('调用类的方法:',use_class.f())
D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
调用类的属性: 123
调用类的方法: Hello World

由输入代码中的调用方式可知,类的使用比函数调用多了几个操作,调用类时需要执行如下操作:

use_class=MyClass()

这步叫做类的实例化,即创建一个类的实例。此处得到的 use_class 变量称为类的具体对象。再看后面两行的调用:

print('调用类的属性:',use_class.i)
print('调用类的方法:',use_class.f())

这里第一行后面的 use_class.i 用于调用类的属性,也就是我们前面说的类变量。第二行后的 use_class.f() 用于调用类的方法。
在上面的例子中,在类中定义 f()方法时带了一个self参数,该参数在方法中并没有被调用,是否可以不要呢?调用f()方法时没有传递参数,是否表示参数可以传递也可以不传递?
对于在类中定义方法的要求:在类中定义方法时,第一个参数必须是self。 第一个参数外,类的方法和普通函数并没有什么区别。
对于在类中调用方法的要求:要调用一个方法,在实例变量上直接调用即可。除了self不用传递,其他参数正常传入。
类对象直尺两种操作,即属性引用和实例化。属性引用的标准语法如下:

obj.name

语法中boj代表类对象,name代表属性。

构造函数(初始化函数)

class MyClass(object):
    i=123
    def __init__(self,name):
        self.name=name

    def f(self):
        return 'hello,'+self.name

use_class=MyClass('xiaomeng')
print('调用类的属性:',use_class.i)
print('调用类的方法:',use_class.f())
D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
调用类的属性: 123
调用类的方法: hello,xiaomeng

若类的实例化语句写法和之前一样,即:

use_class=MyClass()

程序执行结果如下:

D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
Traceback (most recent call last):
  File "D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py", line 11, in <module>
    use_class=MyClass()
TypeError: __init__() missing 1 required positional argument: 'name'

从代码和输出结果可以看到,实例化MyClass类时调用了__init__()方法。这里就奇怪了,我们在代码中并没有指定调用__init__()方法,怎么会报__init__()方法错误呢?
在python中,init()方法是一个特殊方法,在对象实例化时会被调用。init()的意思是初始化,是initialization的简写。这个方法的书写方式是:先输入两个下划线,后面接着init,再接着两个下划线。这个方法也叫构造方法。在定义类时,若不显式地定义一个__init__()方法,则程序默认调用一个无参的__init__()方法。
比如下面两段代码的使用效果是一样的:

class DefualtInit(object):
    def __init__(self):
        print('类实例化时执行我,我是__init__方法。')

    def show(self):
        print('我是类中定义的方法,需要通过实例化对象调用。')

test=DefualtInit()
print('类实例化结束。')
test.show()

执行结果:
D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
类实例化时执行我,我是__init__方法。
类实例化结束。
我是类中定义的方法,需要通过实例化对象调用。
class DefualtInit(object):
    def show(self):
        print('我是类中定义的方法,需要通过实例化对象调用。')

test=DefualtInit()
print('类实例化结束。')
test.show()

执行结果:
D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
类实例化结束。
我是类中定义的方法,需要通过实例化对象调用。

由上面两段代码的输出结果可以看到,当代码中定义了__init__()方法时,实例化类时会调用该方法;若没有定义__init__()方法,实例化类时也不会报错,此时调用默认的__init__()方法。
在python中定义类时若没有定义构造方法(init()方法),则在类的实例化时系统调用默认的构造方法。此外,init()方法可以有参数,参数通过__init__()传递到类的实例化操作上。
既然__init__()方法是python中的构造方法,那么是否可以在一个类中定义多个构造方法呢?先看如下3段代码:
代码一

class DefualtInit(object):
    def __init__(self):
        print('我是不带参数的__init__方法。')

DefualtInit()
print('类实例化结束。')

执行结果:
D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
我是不带参数的__init__方法。
类实例化结束。

只有一个__init__()方法时,实例化类没有什么顾虑。



代码二

class DefualtInit(object):
    def __init__(self):
        print('我是不带参数的__init__方法。')

    def __init__(self,param):
        print('我是一个带参数的__init__方法,参数值为:',param)

DefualtInit('hello')
print('类实例化结束。')

执行结果如下:
D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
我是一个带参数的__init__方法,参数值为: hello
类实例化结束。

由执行结果可以看到,调用的时带了一个param参数的构造方法,若把类的实例化语句改为:

DefualtInit()

执行结果为:

D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
Traceback (most recent call last):
  File "D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py", line 10, in <module>
    DefualtInit()
TypeError: __init__() missing 1 required positional argument: 'param'

或更改为:

DefualtInit('hello','world')

执行结果:

D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
Traceback (most recent call last):
  File "D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py", line 10, in <module>
    DefualtInit('hello','world')
TypeError: __init__() takes 2 positional arguments but 3 were given

由执行结果可以看到,实例化类时只能调用带两个占位参数的构造方法,调用其他构造方法都会报错。



代码三

class DefualtInit(object):
    def __init__(self,param):
        print('我是一个带参数的__init__方法,参数值为:',param)

    def __init__(self):
        print('我是不带参数的__init__方法。')

DefualtInit()
print('类实例化结束。')

执行结果如下:
D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
我是不带参数的__init__方法。
类实例化结束。

由执行结果看到,调用的构造方法除了self外,没有其他参数。若把类的实例化语句更改为:

DefualtInit('hello')

执行结果如下:

D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
Traceback (most recent call last):
  File "D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py", line 10, in <module>
    DefualtInit('hello')
TypeError: __init__() takes 1 positional argument but 2 were given

或更改为:

DefualtInit('hello','world')

执行结果如下:

D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
Traceback (most recent call last):
  File "D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py", line 10, in <module>
    DefualtInit('hello','world')
TypeError: __init__() takes 1 positional argument but 3 were given

由执行结果可以看到,实例化类时只能调用带一个占位参数的构造方法,调用其他构造方法都会报错。
由以上几个示例我们得知:一个类中可以定义多个构造方法,但实例化类时只实例化最后的构造方法,即后面的构造方法会覆盖前面的构造方法,并且需要根据最后一个构造方法的形式进行实例化。建议一个类中只定义一个构造方法(初始化函数)。

PS
我们可以根据情况选择是否将参数放在构造函数中:

class Math:
    def __init__(self,a,b):
        self.a=a
        self.b=b
    def add(self):
        print(self.a+self.b)

#依次求1+2,3+4,5+6的和
test=Math(1,2)
test.add()
test=Math(3,4)
test.add()
test=Math(5,6)
test.add()
class Math:
    def add(self,a,b):
        print(a+b)

#依次求1+2,3+4,5+6的和
test=Math()
test.add(1,2)
test.add(3,4)
test.add(5,6)

显然,以上情况第二种更为方便。
再看如下情况:

class Math:
    def __init__(self,a,b):
        self.a=a
        self.b=b
    def add(self):
        print(self.a+self.b)
    def sub(self):
        print(self.a-self.b)
    def mul(self):
        print(self.a*self.b)
    def div(self):
        print(self.a/self.b)

#依次求1+2,1-2,1*2,1/2的结果
test=Math(1,2)
test.add()
test.sub()
test.mul()
test.div()
class Math:
    def add(self,a,b):
        print(a+b)
    def sub(self,a,b):
        print(a-b)
    def mul(self,a,b):
        print(a*b)
    def div(self,a,b):
        print(a/b)

#依次求1+2,1-2,1*2,1/2的结果
test=Math()
test.add(1,2)
test.sub(1,2)
test.mul(1,2)
test.div(1,2)

以上情况,第二种方法就比较方便。

类的继承

面向对象变成带来的好处之一是代码的重用,实现重用的方法之一是通过继承机制,继承完全可以理解成类之间类型和子类型的关系。

在面向对象程序设计中,当我们定义一个class时,可以从某个现有的class继承,定义的新class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)

继承的定义如下:

class DerivedClassName(BaseClassName)
    <statement-1>
    ...
    <statement-N>

需要注意:继承语法class子类名(基类名),其中基类名(被继承的类名)写在括号里,基本类是在定义类时,在元组中指明的。

python中,继承有以下特点:

  1. 在继承中,基类的构造方法(init()方法)不会被自动调用,需要在子类的构造方法中专门调用。
  2. 在调用基类的方法时需要加上基类的类名前缀,并带上self参数变量。区别于在类中调用普通函数时不需要带self参数。
  3. 在python中,首先查找对应类型的方法,如果在子类中找不到对应的方法,才到基类中逐个查找。
class Animal(boject):
    def run(self):
        print('Animal is running...')

上面定义了一个名为Animal的类,类中定义了一个 run()方法直接输出(没有显式定义__init__()方法,会调用默认的构造方法)。在编写Dog和Cat类时,可以直接从Animal类继承:

class Dog(Animal):
    pass

class Cat(Animal):
    pass

在这段代码中,对于Dog来说,Animal就是它的父类;对于Animal来说,Dog就是它的子类。Cat和Dog类似。
继承有什么好处?
继承最大的好处就是子类获得了父类全部非私有的功能。由于在Animal中定义了非私有的run()方法,因此作为Animal的子类,Dog和Cat什么方法都没有定义,自动拥有父类中的run()方法。

dog=Dog()
dog.run()
cat=Cat()
cat.run()
D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
Animal is running...
Animal is running...

由执行结果可以看到,子类中没有定义任何方法,但都成功执行了run()方法。

当然,子类可以拥有自己的一些方法,比如在Dog类中增加一个eat方法:

class Dog(Animal):
    def eat(self):
        print('Eating...')

dog=Dog()
dog.run()
dog.eat()
D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
Animal is running...
Eating...

由执行结果可以看到,既执行了父类的方法,也执行了自己定义的方法。
子类不能继承父类中的私有方法,也不能调用父类的私有方法。

class Animal():
    def run(self):
        print('Animal is running...')

    def __run(self):            #私有方法
        print('I am a private method')      

class Dog(Animal):
    def eat(self):
        print('Eating...')

dog=Dog()
dog.__run()

D:\Python\python.exe D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py
Traceback (most recent call last):
  File "D:/Work/Tools/python_workspace/python_2017/class_basic/temp3.py", line 14, in <module>
    dog.__run()
AttributeError: 'Dog' object has no attribute '__run'

由执行结果可以看到,子类不能调用父类的私有方法,子类虽然继承了父类,但是调用父类的私有方法相当于从外部调用类中的方法,因而调用不成功。
对于父类中扩展的非私有方法,子类可以拿来即用。子类可以乐基获得父类增加的非私有方法。
集成可以一级一级继承下来,就好比爷爷到爸爸再到儿子的关系。所有类最终都可以追溯到根类object,这些继承关系看上去就像是一颗倒着的树。如下所示:

object
Animal
Plant
Dog
Cat
Shepherd
Husky
Tree
Flower
posted @ 2020-02-04 15:19  土匪哥的歌儿  阅读(94)  评论(0编辑  收藏  举报