Python基础(七):面对对象编程

是小鱼呀·2023-01-01 14:47·36 次阅读

Python基础(七):面对对象编程

面向对象编程#

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

Copy
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()
  • Student这种数据类型就是对象

  • name和score就是对象拥有的属性

  • print_score就是操作数据的函数,对象的方法

  • bart和lisa就是实例化对象,是两个具体的Student

  • 所以,面向对象的设计思想是抽象出类,根据类创建实例。

  • 面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。

类和实例#

定义类,通过class关键字#

Copy
class Student(object): pass

class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的。通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

创建实例,通过类名+()实现的:#

Copy
>>> bart = Student() >>> bart <__main__.Student object at 0x10a67a590> >>> Student <class '__main__.Student'>

可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性:

Copy
>>> bart.name = 'Bart Simpson' >>> bart.name 'Bart Simpson'

定义__init__方法,填写属性#

Copy
`__init__`方法的第一个参数永远是`self`,表示创建的实例本身,因此,在`__init__`方法内部,就可以把各种属性绑定到`self`,因为`self`就指向创建的实例本身
Copy
class Student(object): def __init__(self, name, score): self.name = name self.score = score

数据封装#

直接在类内部定义访问数据的函数,这样就把数据给封装起来。封装数据的函数和类本身是关联起来的,成为类的方法

定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入:

Copy
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))

小结#

类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;

方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;

通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。

和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同

Copy
>>> bart = Student('Bart Simpson', 59) >>> lisa = Student('Lisa Simpson', 87) >>> bart.age = 8 >>> bart.age 8 >>> lisa.age Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'age'

访问限制#

由于外部代码可以通过直接调用实例变量的方法来操作数据,修改实例的属性,如下:

Copy
>>> bart = Student('Bart Simpson', 59) >>> bart.score 59 >>> bart.score = 99 >>> bart.score 99
  1. 想让内部属性不被外部访问,可以在属性前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。如下:
Copy
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))
Copy
>>> bart = Student('Bart Simpson', 59) >>> bart.__name Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute '__name'
  1. 如果外部代码要访问私有属性,可以在类中增加get_nameget_score这样的方法:
Copy
class Student(object): ... def get_name(self): return self.__name def get_score(self): return self.__score
  1. 如果要允许外部代码修改属性,可以在类中增加set_score方法:
Copy
class Student(object): ... def set_score(self, score): self.__score = score

你也许会问,原先那种直接通过bart.score = 99也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:

Copy
class Student(object): ... def set_score(self, score): if 0 <= score <= 100: self.__score = score else: raise ValueError('bad score')
  1. 特殊变量,变量名类似__xxx__的。可以直接访问的,不是private变量
  2. 以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
  3. 双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量。但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。
Copy
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') def get_grade(self): if self.__score >= 90: return 'A' elif self.__score >= 60: return 'B' else: return 'C' bart = Student('Bart Simpson', 59) print('bart.get_name() =', bart.get_name()) bart.set_score(60) print('bart.get_score() =', bart.get_score()) print('DO NOT use bart._Student__name:', bart._Student__name)

继承和多态#

  1. 父类
    定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

  2. 继承,如下:DogCat类,可以直接从Animal类继承

Copy
class Animal(object): def run(self): print('Animal is running...') class Dog(Animal): pass class Cat(Animal): pass dog = Dog() dog.run() cat = Cat() cat.run() 运行结果如下: Animal is running... Animal is running...

继承的好处:子类可以直接继承父类的全部功能。
2. 多态,子类的方法可以覆盖父类的

Copy
class Dog(Animal): def run(self): print('Dog is running...') class Cat(Animal): def run(self): print('Cat is running...') 结果如下: Dog is running... Cat is running...

在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行

Copy
a = list() # a是list类型 b = Animal() # b是Animal类型 c = Dog() # c是Dog类型 >>> isinstance(a, list) True >>> isinstance(b, Animal) True >>> isinstance(c, Dog) True >>> isinstance(c, Animal) True >>> b = Animal() >>> isinstance(b, Dog) False

对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在AnimalDogCat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

对扩展开放:允许新增Animal子类;

对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

Copy
def run_twice(animal): animal.run() animal.run() >>> run_twice(Animal()) Animal is running... Animal is running... >>> run_twice(Dog()) Dog is running... Dog is running... class Tortoise(Animal): def run(self): print('Tortoise is running slowly...') >>> run_twice(Tortoise()) Tortoise is running slowly... Tortoise is running slowly...

小结#

继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。

动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。

获取对象信息#

  1. 用type判断对象是什么类型
Copy
>>> type(123) <class 'int'> >>> type('str') <class 'str'> >>> type(None) <type(None) 'NoneType'>

判断一个对象是否是函数可以使用types模块中定义的常量:

Copy
>>> import types >>> def fn(): ... pass ... >>> type(fn)==types.FunctionType True
  1. 如果是class的类型可以用isinstance()函数。
    能用type()判断的基本类型也可以用isinstance()判断
Copy
继承关系是: object -> Animal -> Dog -> Husky >>> a = Animal() >>> d = Dog() >>> h = Husky() >>> isinstance(h, Husky) True >>> isinstance(h, Dog) True >>> isinstance(h, Animal) True >>> isinstance(d, Husky) False
  1. 获得一个对象的所有属性和方法,可以使用dir()函数。它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:
Copy
>>> dir('ABC') ['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

配合getattr()setattr()以及hasattr(),我们可以直接操作一个对象的状态:

Copy
>>> class MyObject(object): ... def __init__(self): ... self.x = 9 ... def power(self): ... return self.x * self.x ... >>> obj = MyObject() >>> 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

也可以获得对象的方法

Copy
>>> hasattr(obj, 'power') # 有属性'power'吗? True >>> getattr(obj, 'power') # 获取属性'power' <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>> >>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn >>> fn # fn指向obj.power <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>> >>> fn() # 调用fn()与调用obj.power()是一样的 81

小结#

通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。如果可以直接写:

Copy
sum = obj.x + obj.y

就不要写:

Copy
sum = getattr(obj, 'x') + getattr(obj, 'y')

一个正确的用法的例子如下:

Copy
def readImage(fp): if hasattr(fp, 'read'): return readData(fp) return None

假设我们希望从文件流fp中读取图像,我们首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。hasattr()就派上了用场。

实例属性和类属性#

实例绑定属性的方法是通过实例变量,或者通过self变量:

Copy
class Student(object): def __init__(self, name): self.name = name s = Student('Bob') s.score = 90

给类定义属性

Copy
class Student(object): name = 'Student'

当定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到。来测试一下:

Copy
>>> class Student(object): ... name = 'Student' ... >>> s = Student() # 创建实例s >>> 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

从上面的例子可以看出,在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

posted @   是小鱼呀  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
点击右上角即可分享
微信分享提示
目录