9. Python的类与对象

Python是一门面向对象的编程语言。面向对象是软件开发的一种方法,有别于面向过程

面向对象的核心就是:对象。面向对象编程就是基于对类和对象的使用,所有的代码都是通过类和对象来实现的编程就是面向对象编程。


类与对象

类是现实世界或思维世界中的实体在计算机中的反映,它将数据以及这些数据上的操作封装在一起。

在类中,数据成员(变量)用来定义类的属性方法(函数)用来定义类的行为

类是一系列事务的总称,如学生可以是一个类,学生类的特征是名字、年龄、学校、性别等,学生类的行为就是上学、写作业等。

对于学生类来说,对象就是具体的某一个学生,这个学生有他自己的名字、年龄,这些是通过实例化来完成的。类就像是一个模板,通过这个模板可以复制出无数个对象。


类的定义

类就是用来描述具有相同的属性和行为的对象的集合。它定义了该集合中每个对象所共有的属性和方法,类最基本的作用就是封装。

之前讲过,在类中的函数称为方法,有关函数的一切都适用于方法。方法(实例方法)的参数列表中第一个参数必须是self(约定俗成是self,不怕被看代码的人打的话也可以自定义),且必须要有。

对象是类的实例。根据类来创建对象被称为实例化。基于类创建对象时,每个对象都自动具备类的属性与方法。

Python中通过class关键字来定义一个类。类名的定义采用“驼峰命名法”,比如StudentName,不建议使用下划线_

在类的内部可以定义变量、方法。类只负责定义,不负责执行。如果在类中调用方法,那类和模块又有什么区别呢?建议不要在同一模块中既定义类又执行实例化与方法的调用。

  • 示例:
class Student():
    name = 'Jack'
    age = 18

    def print_file(self):
        print('name: ' + self.name)
        print('age: ' + str(self.age))

student = Student()             #实例化
student.print_file()

name: Jack              #结果
age: 18

通过对类的实例化来调用类的方法。

  • 示例:
class Student():                #假如该模块名为c1.py
    name = 'Jack'
    age = 18

    def print_file(self):
        print('name: ' + self.name)
        print('age: ' + str(self.age))
from c1 import Student

student = Student()
student.print_file()

name: Jack              #结果
age: 18

在另外一个模块中,通过from module_name import ClassName这样的格式来引用该模块中的类。


构造函数

类的内部有一个特殊的函数——构造函数__init__(),这个函数用来初始化类的属性。

构造函数__init__()是一个特殊的方法,每当你根据类创建新实例时,Python都会自动运行它。在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。

  • 示例:
class Student():
    
    def __init__(self):
        print('student')
    
    def do_homework(self):
        print('homework')
        
student = Student()
a = student.__init__()
print(a)
print(type(a))

student             #结果
student
None
<class 'NoneType'>

可以看到,Python在运行时会自动先调用构造函数__init__()。Python的函数如果没有定义return时默认会return None,而构造函数__init__()只能return None


类变量与实例变量

前面讲过模块的全局变量与局部变量,但是在类中,模块变量与类的变量是有区别的。

类变量,就是与类相关联的变量;实例变量,就是与对象(实例)相关联的变量。

为什么同一个类实例化出来的对象不一样呢?原因就是每个对象的实例变量不同。

Python调用这个__init__()方法来创建对象时,将自动传入实参self。每个与类相关联的方法调用都自动传递实参self,它是一个指向对象本身的引用,让对象能够访问类中的属性和方法。

  • 示例:
class Student():
    name = 'Jack'               #类变量
    age = 18
    def __init__(self, name, age):
        self.name = name                #实例属性   #初始化对象属性
        self.age = age
    
    def do_homework(self):
        print('homework')
        
student1 = Student('张小喜', 18)
student2 = Student('李小乐', 17)
print(Student.name)
print(student1.name)
print(student2.name)

Jack                #结果
张小喜
李小乐

可以看到,类变量与实例变量是有区别的。当然,上面对于Student类定义属性name和age是不合适的,因为学生不都是同一个name和age。因此,在定义类属性时,需要考虑适不适合定义为类变量。

在类中定义方法时,如果该方法是实例方法,则该方法参数列表中的第一个方法就是self,且必须要有。但当调用该方法时,参数列表中不需要加上self

self和对象相关,与类无关,self就是当前调用方法的对象,如对于student1.do_homework()do_homework(self)中的self就表示student1。这样对于self.name = name就更好理解了,等同于student1.name = name


实例方法中访问实例变量与类变量

既然有实例变量,那么也有实例方法。与对象(实例)相关联的方法称为实例方法,或者说对象可以调用的方法称为实例方法。实例方法的第一个参数必须是self,构造函数也是一个特殊的实例方法。

  • 示例:
class Student():

    def __init__(self, name, age):
        self.name = name
        self.age = age
        print(self.name)
        print(name)
        
student1 = Student('张小喜', 19)

张小喜              #结果
张小喜
class Student():

    def __init__(self, name1, age):
        self.name = name1
        self.age = age
        print(self.name)
        print(name)
        
student1 = Student('张小喜', 19)

张小喜              #结果
Traceback (most recent call last):
  File "h:\Python Practice\nine\c6.py", line 13, in <module>
    student1 = Student('张小喜', 19)
  File "h:\Python Practice\nine\c6.py", line 8, in __init__
    print(name)
NameError: name 'name' is not defined

上面两个例子,之所以结果不一样是因为self.name读取的对象的实例变量,而name读取的是形参的name

因此,在实例方法内访问实例变量需要通过self.var_name这样的形式来访问,否则容易出现错误。

在类的外部,是通过ClassName.var_name来访问类变量的。那么在实例方法内,如何访问类变量呢?

  • 示例:
class Student():
    sum = 0
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print(Student.sum)
        print(self.name)
        
student1 = Student('张小喜', 19)
print(Student.sum)

0               #结果
张小喜
0
class Student():
    sum = 0
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print(self.__class__.sum)
        print(self.name)

student1 = Student('张小喜', 19)
print(Student.sum)

0               #结果
张小喜
0

可以看到,在实例方法内也是可以通过ClassName.var_name这样的形式来访问类变量,还可以通过self.__class__.var_name这种形式来访问类变量。


类方法

既然有了实例方法,那当然也有类方法,类方法用来操作类变量。

与类相关联的方法称为类方法,或者说类可以调用的方法称为类方法。类方法第一个参数必须要有,但不再是self,而是cls(约定俗成,不怕挨打也可以自定义)。

  • 示例:
class Student():
    sum = 0
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.__class__.sum += 1
        print('当前班级人数: ' + str(self.__class__.sum))

student1 = Student('张小喜', 19)
student2 = Student('李小乐', 18)
student3 = Student('王小欢', 17)

当前班级人数: 1             #结果
当前班级人数: 2
当前班级人数: 3

可以看到,在实例方法(不仅仅是构造函数)中也可以对类变量进行操作。但在类方法中操作类变量更简单。

  • 示例:
class Student():
    sum = 0
    
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod                #装饰器
    def plus_sum(cls):
        cls.sum += 1
        print(cls.sum)

student1 = Student('张小喜', 19)
Student.plus_sum()
student2 = Student('李小乐', 18)
Student.plus_sum()
student3 = Student('王小欢', 17)
Student.plus_sum()

1               #结果
2
3

在类外部可以通过ClassName.method_name()这样的形式调用类方法,类方法的形参cls表示的就是这个类,比如上面例子中的cls表示的是Student这个类。不是所有形参带self的方法都是实例方法,因为类方法的形参cls也可以自定义为self

类方法不仅仅可以被类直接调用,也是可以通过对象调用的,但不建议通过对象直接调用类方法。


静态方法

静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,主要是一些逻辑属于类,但是和类本身没有交互,即在静态方法中,不会涉及到类中的方法和属性的操作。可以理解为将静态方法存在此类的名称空间中。事实上,在Python引入静态方法之前,通常是在全局名称空间中创建函数。

  • 示例:
class Student():
    sum = 0
    
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def plus_sum(cls):
        cls.sum += 1

    @staticmethod               #装饰器
    def add(x, y):
        print(Student.sum)
        print('This is a static method.')

student1 = Student('张小喜', 19)
Student.plus_sum()
student1.add(1, 2)
Student.add(1, 2)

1               #结果
This is a static method.
1
This is a static method.

对于静态方法,通过staticmethod装饰器来定义,静态方法可以被对象和类调用。静态方法的内部也可以访问类变量,但静态方法和类方法的内部无法访问实例方法。


成员可见性

成员可见性分为公有的和私有的。类下面的成员分为变量和方法,因此类的成员可见性可以看成是变量和方法的可见性。

类有内外之分,所以对于变量和方法的调用也有内外之分。比如对于方法,类外部通过ClassName.method_name()来调用;类内部通过self.method_name()来调用。

所有在类内部对变量的修改都应该通过方法来完成,而不应该在类外部直接访问变量来修改,因为在方法内可以规范变量的修改。

那么该如何禁止在类外部直接调用类的成员呢?可以通过更改成员可见性来完成。成员可见性是公有的(public)成员都是可以在类外部直接访问的;成员可见性是私有的(private)成员都是不能在类外部直接访问的。

在Python中,通过__method_name()这样的双下划线形式来表示该方法是私有的,不要画蛇添足的在方法名后面也加上双下划线,类似__init__()这样的形式是Python系统内置变量的命名风格。

  • 示例:
class Student():
    sum = 0
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.__score = 0

    def mark(self, score):
        if score < 0:
            return '不允许打负分'
        self.__score = score
        print(self.name + '同学本次考试分数为:' + str(self.__score))

student1 = Student('张小喜', 19)
student2 = Student('李小乐', 18)
result = student1.mark(58)
student1.__score = -1
print(student1.__score)
print(student1.__dict__)
print(student2.__dict__)
print(student2.__score)

张小喜同学本次考试分数为:58                #结果
-1
{'name': '张小喜', 'age': 19, '_Student__score': 58, '__score': -1}
{'name': '李小乐', 'age': 18, '_Student__score': 0}
Traceback (most recent call last):
  File "e:\Python practice\nine\c3.py", line 24, in <module>
    print(student2.__score)
AttributeError: 'Student' object has no attribute '__score'

上面例子中为什么可以直接访问student1.__score呢?并不是该私有变量没有生效,只是通过student1.__score = -1新增了一个变量__score,真正的私有变量是_Student__score,只能通过student1._Student__score这样的方式去读取原来方法中的私有变量__score,Python虽然没有机制阻止这样读取,但不建议这么做。

正是因为原来方法中定义的__score变量在存储过程中被改名为_Student__score,私有变量才达到了外部无法直接访问的目的,动态添加的私有变量是无效的。


继承

面向对象有三大特性:继承性、封装线、多态性。继承就是为了避免编写重复的代码,一般来说,建议一个模块编写一个类。Python支持多继承,即一个子类允许有多个父类。

打个比方,对于一个学生来说,他属于学生类,同时他也是一个人类,所以学生类继承于人类,学生类是人类的子类,人类是学生类的父类,每个人类都有自己的姓名与名字。

  • 示例:
class Human():              #假如该模块名为c3.py
    sum = 0
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def get_name(self):
        print(self.name)
from c3 import Human

class Student(Human):
    
    def do_homework(self):
        print('homework')

print(Student.sum)
student1 = Student('张小喜', 19)                #实例化
print(student1.sum)
print(student1.name)
print(student1.age)
student1.get_name()

0               #结果
0
张小喜
19
张小喜

可以看到,继承时子类会继承父类的属性和方法。

  • 示例:
class Human():              #假如该模块名为c3.py
    sum = 0
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def get_name(self):
        print(self.name)
from c3 import Human

class Student(Human):
    
    def __init__(self, school, name, age):
        self.school = school
        Human.__init__(self, name, age)             #子类方法显式调用父类方法
        
    def do_homework(self):
        print('homework')

student1 = Student('人民路小学', '张小喜', 19)
print(student1.school)
print(student1.name)
print(student1.age)

人民路小学              #结果
张小喜
19

上面的调用方式虽然可行,但是非常别扭,不建议这样调用。继承时,如果直接通过父类来调用方法,需要带上参数self,如果是对象调用则不需要带上self

子类方法调用父类方法还可以通过super关键字来完成。super可以使用在子类的任意方法内。

  • 示例:
class Human():              #假如该模块名为c3.py
    sum = 0
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def get_name(self):
        print(self.name)
from c3 import Human

class Student(Human):
    
    def __init__(self, school, name, age):
        self.school = school
        super(Student, self).__init__(name, age)                #super调用父类方法
        
    def do_homework(self):
        print('homework')

student1 = Student('人民路小学', '张小喜', 19)
print(student1.school)
print(student1.name)
print(student1.age)

人民路小学              #结果
张小喜
19

当子类方法或变量的名字与父类方法或变量的名字同名时,子类的方法或变量优先,子类的方法或变量会覆盖父类的方法或变量。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值