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
当子类方法或变量的名字与父类方法或变量的名字同名时,子类的方法或变量优先,子类的方法或变量会覆盖父类的方法或变量。