AI摘要:面向对象编程(OOP)是一种程序设计思想,将对象作为程序的基本单元,对象包含数据和操作数据的函数。与面向过程的程序设计不同,面向对象的程序设计将计算机程序视为一组对象的集合,对象之间通过消息传递进行交互。通过一个例子,可以看到面向过程和面向对象在程序流程上的不同。面向过程的程序使用字典表示学生的成绩,通过函数来处理学生成绩;而面向对象的程序将学生的成绩表示为一个对象,对象拥有属性和方法,可以自己处理数据并打印出来。类是创建实例的模板,实例是具体的对象,方法是与实例绑定的函数。继承和多态是面向对象编程的重要特点,继承可以将父类的功能直接拿来使用,子类可以新增自己特有的方法,多态可以根据对象的类型调用相应的方法。获取对象信息可以使用type()、isinstance()和dir()函数,可以判断对象的类型、判断对象是否是某种类型、获取对象的属性和方法。实例属性和类属性是对象的属性,实例属性是通过实例变量绑定的,类属性是在类中定义的属性,类的所有实例都可以访问到。
一、定义
定义:面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
举一例说明面向过程和面向对象在程序流程上的不同,假设我们要处理学生的成绩表,为了表示一个学生的成绩:
# 面向过程的程序可以用一个dict表示
std1 = {'name':'Michael','score':98}
std2 = {'name':'Bob','score':81}
# 处理学生成绩可以通过函数实现,比如打印学生的成绩
def print_score(std):
print('%s:%s' % (std['name'], std['score']))
print_score(std1)
print_score(std2)
Michael:98
Bob:81
如果采用面向对象的程序设计思想,首先思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有name和score这两个属性(Property)。
如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()
Bart Simpson: 59
Lisa Simpson: 87
二、类(Class)和实例(Instance)
(一)定义
类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
在Python中,通过class关键字定义类:
class Student(object): #
pass
class
后面紧接着是类名,即Student
,类名通常是大写开头的单词,紧接着是(object)
,表示该类是从哪个类继承下来的。
定义好了Student类
,就可以根据Student类
创建出Student的实例
,创建实例是通过类名+()
实现的:
bart = Student()
bart
<__main__.Student at 0x1ee22272dd0>
变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样;
Student
__main__.Student
Student本身则是一个类。
可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性:
bart.name = 'Bart Simpson'
bart.name
'Bart Simpson'
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去:
class Students(object):
def __init__(self, name, score):
self.name = name
self.score = score
注意到__init__方法
的第一个参数永远是self
,表示创建的实例本身,因此,在__init__方法
内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。
有了__init__方法
,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法
匹配的参数,但self
不需要传,Python解释器自己会把实例变量传进去:
bart = Students('Bart Simpson', 59)
bart.name
'Bart Simpson'
bart.score
59
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且调用时不用传递该参数。此外,可以用默认参数、可变参数、关键字参数和命名关键字参数。
(二)数据的封装
面向对象编程的一个重要特点就是数据封装。在上面的Student类
中,每个实例就拥有各自的name
和score
这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:
def print_score(std):
print('%s:%s' % (std.name, std.score))
print_score(bart)
Bart Simpson:59
Student实例
本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类
的内部定义访问数据的函数,这样,就把“数据”给封装起来了。
这些封装数据的函数是和Student类
本身是关联起来的,我们称之为类的方法:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
bart = Student('Bart Simpson', 59)
bart.print_score()
Bart Simpson: 59
封装的另一个好处是可以给Student类增加新的方法,比如get_grade:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'
lisa = Student('Lisa', 99)
bart = Student('Bart', 59)
print(lisa.name, lisa.get_grade())
print(bart.name, bart.get_grade())
Lisa A
Bart C
总结:
类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.age = 8
lisa.age
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[41], line 4
2 lisa = Student('Lisa Simpson', 87)
3 bart.age = 8
----> 4 lisa.age
AttributeError: 'Student' object has no attribute 'age'
三、访问限制
从前面Student类的定义来看,外部代码可以自由地修改一个实例的name、score属性:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
bart = Student('Bart Simpson', 59)
bart.score
59
bart.score = 99
bart.score
99
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
。在Python中,实例的变量名如果以__
开头,就变成了一个私有变量,只有内部可访问:
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s:%s' % (self.__name, self.__score))
bart = Student('Bart Simpson', 59)
bart.__name
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[6], line 11
8 print('%s:%s' % (self.__name, self.__score))
10 bart = Student('Bart Simpson', 59)
---> 11 bart.__name
AttributeError: 'Student' object has no attribute '__name'
这样即可确保外部代码不能随意修改对象内部的状态。
如果需要外部代码获取name和score,可以给Student类增加get_name
和get_score
方法:
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def get_name(self):
return self.__name
def get_score(self):
return self.__score
bart = Student('Bart Simpson', 59)
a = Student.get_name(bart)
b = Student.get_score(bart)
print(a,':',b)
Bart Simpson : 59
如果要允许外部代码修改score,可以再给Student类增加set_score
方法:
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def get_name(self):
return self.__name
def get_score(self):
return self.__score
def set_score(self, score): # 在定义方法实,可以对参数做检查,避免传入无效参数
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
bart = Student('Bart Simpson', 59)
bart.set_score(87)
print(bart.get_score())
87
在Python中,变量名类似__xxx__
的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量;
下划线开头的实例变量名,比如_name,外部是可以访问的,但按照约定俗成的规定,要视为私有变量,不要随意访问;
双下划线开头的实例变量也不是一定不能从外部访问,Python解释器对外把__name变量
改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name变量;但不同版本的Python解释器可能会把__name改成不同的变量名,因此不要这样访问变量名。
一种错误写法示例:
>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 设置__name变量!
>>> bart.__name
'New Name'
表面上看,外部代码“成功”地设置了__name变量
,但实际上这个__name变量
和class内部的__name变量
不是一个变量!内部的__name变量
已经被Python解释器自动改成了_Student__name
,而外部代码给bart新增了一个__name变量。
四、继承和多态
(一)继承
当定义一个class的时候,可以从某个现有的class继承,新的class称为子类,被继承的class被称为父类。
比如一个名为Animal的class,有一个run()方法可以打印:
class Animal(object):
def run(self):
print('Animal is running...')
当需要编写Dog和Cat类时,就可以直接从Animal类继承:
class Dog(Animal):
pass
class Cat(Animal):
pass
通过继承,子类获得了父类的全部功能:
dog = Dog()
dog.run()
cat = Cat()
cat.run()
Animal is running...
Animal is running...
同时可以给子类添加一些方法:
class Dog(Animal):
def run(self):
print('Dog is running...')
def eat(self):
print('Eating meat...')
可以对代码做一些改进:
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
dog = Dog()
dog.run()
cat = Cat()
cat.run()
Dog is running...
Cat is running...
当子类和父类存在相同的run()方法
时,子类的run()
覆盖了父类的run()
,在运行代码的时候,会优先调用子类的run()
。
(二)多态
a = list() #a是list类型
b = Animal() #b是Animal类型
c = Dog() #c是Dog()类型
可以使用isinstance()判断变量的类型:
isinstance(a, list)
True
isinstance(b, Animal)
True
isinstance(c,Dog)
True
Dog从Animal继承,因此Dog的实例c的数据类型即是Dog,也是Animal。
isinstance(c, Animal)
True
总结:在继承关系中,如果一个实例的数据类型是某个子类,那么它的数据类型可以被看做是父类。
def run_twice(animal):
animal.run()
animal.run()
run_twice(Animal())
Animal is running...
Animal is running...
run_twice(Cat())
Cat is running...
Cat is running...
定义一个Tortoise类型,也从Animal派生:
class Tortoise(Animal):
def run(self):
print('Tortoise is running slowly...')
run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...
新增一个Animal的子类
,不必对run_twice()
做任何修改。
传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法。
开闭原则:
对扩展开放:允许新增Animal子类;数。
对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。
总结:继承可以把父类的所有功能都直接拿过来,不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
五、获取对象信息
判断对象的类型可以使用以下办法:
(一)使用type()
type(123)
int
type('str')
str
type(None)
NoneType
如果一个变量指向一个函数或者类,也可以使用type()判断:
type(abs)
builtin_function_or_method
如果要在if语句中判断,就需要比较两个变量的type类型是否相同:
type(123) == type(456)
True
type(123) == int
True
type('abc') == int
False
type('abc') == type('123')
True
type('abc') == str
True
type('abc') == type(123)
False
如果要判断一个对象是否是函数怎,可以使用types模块中定义的常量:
import types
def fn():
pass
type(fn) == types.FunctionType
True
type(abs) == types.BuiltinFunctionType
True
type(lambda x:y) == types.LambdaType
True
type((x for x in range(10))) == types.GeneratorType
True
(二)使用isinstance()
对于class的继承关系来说,使用type()不方便,要判断class的类型可以使用isinstance()函数。
如果继承关系是object -> Animal -> Dog -> Husky,那么通过isinstance()可以判断一个对象是否是某种类型:
class Animal(object):
pass
class Dog(Animal):
pass
class Husky(Dog):
pass
a = Animal()
d = Dog()
h = Husky()
isinstance(h, Husky)
True
isinstance(h, Dog)
True
isinstance(d, Husky)
False
isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。
能用type()判断的基本类型也可以用isinstance()判断。
isinstance('a', str)
True
isinstance(123, int)
True
isinstance(b'a', bytes)
True
还可以判断一个变量是否是某些类型中的一种:
isinstance([1, 2, 3], (list, tuple))
True
isinstance((1, 2, 3), (list, tuple))
True
(三)使用dir()
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list。比如,获得一个str对象的所有属性和方法:
dir('ABC')
['__add__',
'__class__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__getnewargs__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rmod__',
'__rmul__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'capitalize',
'casefold',
'center',
'count',
'encode',
'endswith',
'expandtabs',
'find',
'format',
'format_map',
'index',
'isalnum',
'isalpha',
'isascii',
'isdecimal',
'isdigit',
'isidentifier',
'islower',
'isnumeric',
'isprintable',
'isspace',
'istitle',
'isupper',
'join',
'ljust',
'lower',
'lstrip',
'maketrans',
'partition',
'removeprefix',
'removesuffix',
'replace',
'rfind',
'rindex',
'rjust',
'rpartition',
'rsplit',
'rstrip',
'split',
'splitlines',
'startswith',
'strip',
'swapcase',
'title',
'translate',
'upper',
'zfill']
类似__xxx__
的属性和方法在Python中都是有特殊用途的,比如__len__
方法返回长度。在Python中,如果你调用len()函数
试图获取一个对象的长度,实际上,在len()函数
内部,它自动去调用该对象的__len__()
方法,所以,下面的代码是等价的:
len('ABC')
3
'ABC'.__len__()
3
自己写的类,如果也想用len(myObj)可以写一个__len__()方法:
class MyDog(object):
def __len__(self):
return 100
dog = MyDog()
len(dog)
100
配合getattr()
、setattr()
以及hasattr()
可以直接操作一个对象的状态:
class MyObject(object):
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x
obj = MyObject()
配合getattr()
、setattr()
以及hasattr()
,可以直接操作一个对象的状态:测试对象属性:属性name,则返回False。
getattr(object, name[, default]):
name,则返回False。
getattr()函数
用于获取对象的属性值。它接受三个参数:对象object、属性名name和可选的默认值default。如果对象object具有属性name,则返回该属性的值;如果对象object没有属性name,则返回默认值default(如果提供了默认值);如果没有提供默认值,则会抛出AttributeError异常。
setattr(object, name, value):
setattr()函数
用于设置对象的属性值。它接受三个参数:对象object、属性名name和属性值value。如果对象object具有属性name,则将其设置为value;如果对象object没有属性name,则会创建一个新的属性,并将其设置为value。
hasattr(object, name):
hasattr()函数
用于检查对象是否具有指定的属性。它接受两个参数:对象object和属性名name。如果对象object具有属性name,则返回True;如果对象object没有属性name,则返回False。
hasattr(obj, 'x') # 有属性'x'吗?
True
obj.x
9
hasattr(obj, 'y') # 有属性'y'吗?
False
setattr(obj, 'y', 19) # 设置一个属性'y'
hasattr(obj, 'y') # 有属性'y'吗?
True
getattr(obj, 'y') # 获取属性'y'
19
obj.y # 获取属性'y'
19
如果试图获取不存在的属性,会抛出AttributeError的错误:
getattr(obj, 'z') # 获取属性'z'
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[52], line 1
----> 1 getattr(obj, 'z') # 获取属性'z'
AttributeError: 'MyObject' object has no attribute 'z'
可以传入一个default参数,如果属性不存在,就返回默认值:
getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
也可以获得对象的方法:
hasattr(obj, 'power') # 有属性'power'吗?
True
getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x000002A390FEE170>>
fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x000002A390FEE170>>
fn() # 调用fn()与调用obj.power()是一样的
81
(六)实例属性和类属性
Python是动态语言,根据类创建的实例可以任意绑定属性。给实例绑定属性的方法是通过实例变量,或者通过self变量:
class Student(object):
def __init__(self,name):
self.name = name
s = Student('Bob')
s.score = 90
如果Student类本身需要绑定一个属性,可以直接在class中定义属性,这种属性是类属性,归Student类所有:
class Student(object):
name = 'Student'
定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到。
class Student(object):
name = 'Student'
s = Student()
print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
print(Student.name) # 打印类的name属性
Student
s.name = 'Michael' # 给实例绑定name属性
print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
del s.name # 如果删除实例的name属性
print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
总结: 在编写程序的时候,不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当删除实例属性后,再使用相同的名称,访问到的将是类属性。
评论 (0)