一、定义
定义:面向对象编程——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): #
passclass后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的。
定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的:
bart = Student()
bart<__main__.Student at 0x1ee22272dd0>变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样;
Student__main__.StudentStudent本身则是一个类。
可以自由地给一个实例变量绑定属性,比如,给实例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.score59和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且调用时不用传递该参数。此外,可以用默认参数、可变参数、关键字参数和命名关键字参数。
(二)数据的封装
面向对象编程的一个重要特点就是数据封装。在上面的Student类中,每个实例就拥有各自的name和score这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:
def print_score(std):
print('%s:%s' % (std.name, std.score))
print_score(bart)Bart Simpson:59Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在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.ageAttributeError: '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.score59bart.score = 99
bart.score99如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__。在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.__nameAttributeError: '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)Trueisinstance(b, Animal)Trueisinstance(c,Dog)TrueDog从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)inttype('str')strtype(None)NoneType如果一个变量指向一个函数或者类,也可以使用type()判断:
type(abs)builtin_function_or_method如果要在if语句中判断,就需要比较两个变量的type类型是否相同:
type(123) == type(456)Truetype(123) == intTruetype('abc') == intFalsetype('abc') == type('123')Truetype('abc') == strTruetype('abc') == type(123)False如果要判断一个对象是否是函数怎,可以使用types模块中定义的常量:
import types
def fn():
passtype(fn) == types.FunctionTypeTruetype(abs) == types.BuiltinFunctionTypeTruetype(lambda x:y) == types.LambdaTypeTruetype((x for x in range(10))) == types.GeneratorTypeTrue(二)使用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)Trueisinstance(h, Dog)Trueisinstance(d, Husky)Falseisinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。
能用type()判断的基本类型也可以用isinstance()判断。
isinstance('a', str)Trueisinstance(123, int)Trueisinstance(b'a', bytes)True还可以判断一个变量是否是某些类型中的一种:
isinstance([1, 2, 3], (list, tuple))Trueisinstance((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'吗?Trueobj.x9hasattr(obj, 'y') # 有属性'y'吗?Falsesetattr(obj, 'y', 19) # 设置一个属性'y'hasattr(obj, 'y') # 有属性'y'吗?Truegetattr(obj, 'y') # 获取属性'y'19obj.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',如果不存在,返回默认值404404也可以获得对象的方法:
hasattr(obj, 'power') # 有属性'power'吗?Truegetattr(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属性Michaelprint(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问Studentdel s.name # 如果删除实例的name属性
print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了Student总结: 在编写程序的时候,不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当删除实例属性后,再使用相同的名称,访问到的将是类属性。
评论 (0)