十一、面向对象
十一、面向对象
11.1、OOP编程思想
面向对象编程是在面向过程编程的基础上发展来的,它比面向过程编程具有更强的灵活性和扩展性。面向对象编程是程序员发展的分水岭,很多初学者会因无法理解面向对象而放弃学习编程。
面向对象编程(Object-oriented Programming,简称 OOP),是一种封装代码的方法。其实,在前面章节的学习中,我们已经接触了封装,比如说,将数据放进列表和字典中中,这就是一种简单的封装,是数据层面的封装;把常用的代码块打包成一个函数,这也是一种封装,是语句层面的封装。
代码封装,其实就是隐藏实现功能的具体代码,仅留给用户使用的接口,就好像使用计算机,用户只需要使用键盘、鼠标就可以实现一些功能,而根本不需要知道其内部是如何工作的。
本节所讲的面向对象编程,也是一种封装的思想,不过显然比以上两种封装更先进,它可以更好地模拟真实世界里的事物,并把描述特征的数据和代码块(函数)封装到一起。
11.2、类与实例对象
11.2.1、类和对象的概念
类是人们抽象出来的一个概念,所有拥有相同属性和功能的事物称为一个类;而拥有相同属性和功能的具体事物则成为这个类的实例对象。
11.2.2、声明类和实例化对象
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Person类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
案例演示:
class Person(object): pass p1=Person() p2=Person()
每一个人的实例对象都应该有自己的属性,比如姓名和年龄,实例变量的赋值如下:
p1 = Person() p2 = Person # 示例变量的增删改查 p1.name = "hao" p2.age = 18 p2.name = "dai" p2.age = 22 print(p1.gender) del p2.age
11.2.3、对象的属性初始化
在创建类时,我们可以手动添加一个 __init__()
方法,该方法是一个特殊的类实例方法,称为构造方法(或构造函数)。
__init__()
方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数。
在 __init__()
构造方法中,除了 self 参数外,还可以自定义一些参数,参数之间使用逗号“,”进行分割,从而完成初始化的工作。
class Person(object): def __init__(self, name, age): self.name = name self.age = age print(id(self)) # 实例化Person类的示例对象: 类示例对象=类名(示例话参数) hao = Person("hao", 18) summer = Person("hao", 22) print(id(hao))
注意到
__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。
11.2.4、实例方法
实例方法或者叫对象方法,指的是我们在类中定义的普通方法。只有实例化对象之后才可以使用的方法,该方法的第一个形参接收的一定是对象本身。
class Person(object): def __init__(self, name, age): self.name = name self.age = age def print_info(self): print("姓名: %s, 年龄:%s" % (self.name, self.age)) hao = Person("hao", 18) hao.print_info()
11.2.5、一切皆对象
在python语言中,一切皆对象!
我们之前学习过的字符串,列表,字典等等数据都是一个个的类,我们用的所有数据都是一个个具体的实例对象。
区别就是,那些类是在解释器级别注册好的,而现在我们学习的是自定义类,但语法使用都是相同的。所以,我们自定义的类实例对象也可以和其他数据对象一样可以进行传参、赋值等操作。
class Weapon: def __init__(self, name, av, color): self.name = name self.av = av self.color = color dao = Weapon("激光枪", 100, "red") class Hero: def __init__(self, name, sex, hp, ce, weapon, level=2, exp=2000, money=10000): # 类必不可少的方法,用于实例化 self.name = name # 英雄名字 self.sex = sex # 英雄性别 self.hp = hp # 英雄的生命值 self.level = level # 英雄的等级 self.exp = exp # 英雄的经验值 self.money = money # 英雄的金币 self.weapon = weapon # 英雄的武器 hao = Hero("hao", "male", 100, 80, dao) print(hao.weapon.color)
11.2.6、类对象和类属性
class Person: # 类属性 legs_num = 2 has_emotion = True def __init__(self, name, age): self.name = name self.name = age def play_fire(self): print("%s玩火" % self.name) hao = Person("hao", 20) summer = Person("summer", 22) print(hao.legs_num) print(hao.name) # Person: 一个类对象 print(Person.legs_num)
11.2.7、静态方法和类方法
静态方法
定义:使用装饰器@staticmethod
。参数随意,没有self
和cls
参数,但是方法体中不能使用类或实例的任何属性和方法;
调用:类对象或实例对象都可以调用。
class Cal(): @staticmethod def add(x, y): return x + y @staticmethod def mull(x, y): return x + y cal = Cal() print(cal.add(1, 4)) print(Cal.add(3, 4))
类方法
定义:使用装饰器@classmethod
。第一个参数必须是当前类对象,该参数名一般约定为cls
,通过它来传递类的属性和方法(不能传实例的属性和方法);
调用:类对象或实例对象都可以调用。
class Student: # 类属性 cls_number = 68 @classmethod def add_cls_number(cls): cls.cls_number += 1 print(cls.cls_number) Student.add_cls_number()
思考:
类对象.实例方法会怎么样?
类方法的意义是什么,在实例方法中使用类对象变量不可以吗?
11.3、面向对象之继承
面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。通过继承创建的新类称为子类或派生类,被继承的类称为基类、父类或超类。
class 派生类名(基类名) ...
11.3.1、继承的基本使用
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率。
实际上继承者是被继承者的特殊化,它除了拥有被继承者的特性外,还拥有自己独有得特性。例如猫有抓老鼠、爬树等其他动物没有的特性。同时在继承关系中,继承者完全可以替换被继承者,反之则不可以,例如我们可以说猫是动物,但不能说动物是猫就是这个道理,其实对于这个我们将其称之为“向上转型”。
诚然,继承定义了类如何相互关联,共享特性。对于若干个相同或者相识的类,我们可以抽象出他们共有的行为或者属相并将其定义成一个父类或者超类,然后用这些类继承该父类,他们不仅可以拥有父类的属性、方法还可以定义自己独有的属性或者方法。
同时在使用继承时需要记住三句话:
1、子类拥有父类非私有化的属性和方法。
2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
3、子类可以用自己的方式实现父类的方法。(下面会介绍)。
# 无继承方式 class Dog: def eat(self): print("eating....") def sleep(self): print("sleep...") def swimming(self): print("swimming...") class Cat: def eat(self): print("eating...") def sleep(self): print("sleep...") def climb_tree(self): print("climb_tree....") # 继承方式 class Anmal: def eat(self): print("eating...") def sleep(self): print("sleep...") class Dog(Anmal): def swimming(self): print("toshetou...") class Cat(Anmal): def climb_tree(self): print("climbl_tree...") houyi = Dog() houyi.swimming()
11.3.2、 重写父类方法和调用父类方法
class Person(object): def __init__(self, name, age): self.name = name self.age = age def sleep(self): print("基类sleep...") class Emp(Person): # def __init__(self, name, age, dep): # self.name = name # self.age = age # self.dep = dep def __init__(self, name, age, dep): # Person.__init__(name, age) self.dep = dep def sleep(self): if "不在公司": # print("子类sleep...") # 调用父类方法 # 方式1 :父类对象调用 父类对象.方法(self,其他参数) # Person.sleep(self) # 方式2: super关键字 super(子类对象,self).方法(参数)or super().方法(参数) super().sleep() hao = Emp("hao", 18, "IT部") hao.sleep() print(hao.dep) class Base: def __init__(self): self.func() def func(self): print('in base') class Son(Base): def func(self): print('in son') s = Son()
11.3.3、 多重继承
如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示:
class SubClassName (ParentClass1[, ParentClass2, ...]): ...
多继承有什么意义呢?还拿上面的例子来说,蝙蝠和鹰都可以飞,飞的功能就重复定义了。
class Animal: def eat(self): print("eating....") def sleep(self): print("sleep...") class Eagle(Animal): def fly(self): print("fly...") class Bat(Animal): def fly(self): print("fly...")
有同学肯定想那就放到父类Animal中,可是那样的话其他不会飞的动物还怎么继承Animal呢?所以,这时候多重继承就发挥功能了:
class Fly: def fly(self): print("fly...") class Eagle(Animal,Fly): pass class Bat(Animal, Fly): pass
11.3.4、 type 和isinstance
方法
class Animal: def eat(self): print("eating...") def sleep(self): print("sleep...") class Dog(Animal): def swin(self): print("swimming...") houyi = Dog() change = Dog() print(isinstance(houyi, Dog)) print(isinstance(houyi, Animal)) print(type(houyi))
11.3.5、dir()
方法和__dict__
属性
dir(obj)
可以获得对象的所有属性(包含方法)列表, 而obj.__dict__
对象的自定义属性字典
注意事项:
dir(obj)
获取的属性列表中,方法也认为属性的一种。返回的是listobj.__dict__
只能获取自己自定义的属性,系统内置属性无法获取。返回是dict
class Student: def __init__(self,name,score): self.mame=name self.score=score def test(self): pass hao = Student("hao",100) print("获取所有的属性列表") print(dir(hao)) print("获取自定义属性字段") print(hao.__dict__)
其中,类似__xx__
的属性和方法都是有特殊用途的。如果调用len()
函数视图获取一个对象的长度,其实在len()
函数内部会自动去调用该对象的__len__()
方法
11.4、面向对象之封装
封装是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
我们程序设计追求“高内聚,低耦合”
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉
- 低耦合:仅对外暴露少量的方法用于使用。
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露岀来。这就是封装性的设计思想。
11.4.1、私有属性
在class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name
、score
属性:
class Student(object): def __init__(self, name, score): self.name = name self.score = score summer = Student("summer", 23) hao = Student("hao", 22) summer.score = 100 print(summer.score)
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:
class Student(object): def __init__(self, name, score): self.name = name self.__score = score summer = Student("summer", 23) hao = Student("hao", 22) print(summer.__score)
改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name
和实例变量.__score
。
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
但是如果外部代码要获取name和score怎么办?可以给Student类增加get_score
这样的方法:
class Student(object): def __init__(self, name, score): self.name = name self.__score = score def get_score(self): return self.__score summer = Student("summer", 23) hao = Student("hao", 22) print(summer.get_score())
如果又要允许外部代码修改age怎么办?可以再给Student类增加set_score
方法:
class Student(object): def __init__(self, name, score): self.name = name self.__score = score def get_score(self): return self.__score def set_score(self, score): self.__score = score summer = Student("summer", 23) print(summer.get_score()) summer.set_score(100) print(summer.get_score())
你也许会问,原先那种直接通过xiao.score = 100
也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以设置值时做其他操作,比如记录操作日志,对参数做检查,避免传入无效的参数等等:
class Student(object): ... def set_score(self, score): if isinstance(score, int) and 0 <= score <= 100: self.set_score = score else: raise ValueError('error!')
注意
1、这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:
_类名__属性
,然后就可以访问了,如a._A__N
2、变形的过程只在类的内部生效,在定义后的赋值操作,不会变形
class Student(object): def __init__(self, name, score): self.name = name self.__socre = score def get_score(self): return self.__socre hao = Student("hao", 66) print(hao.__dict__) hao.__age = 19 print(hao.__dict__)
4、单下划线、双下划线、头尾双下划线说明:
-
__foo__
: 定义的是特殊方法,一般是系统定义名字 ,类似__init__()
之类的。 -
_foo
: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问。(约定成俗,不限语法) -
__foo
: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。
class Person(object): def __init__(self, name, score): self.name = name self.__score = score class Student(Person): def get_score(self): return self.__score def set_score(self, score): self.__score = score hao = Student("hao", 66) print(hao.__dict__) print(hao.get_score())
11.4.2、 私有方法
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的:
class Base: def foo(self): print("foo from Base") def test(self): self.foo() class Son(Base): def foor(self): print("foo from Son") s = Son() s.test() class Base: def __foo(self): print("foo from Base") def test(self): self.__foo() class Son(Base): def __foo(self): print("foo from Son") s = Son() s.test()
11.4.3、 property属性装饰器
使用接口函数获取修改数据 和 使用点方法设置数据相比, 点方法使用更方便,我们有什么方法达到 既能使用点方法,同时又能让点方法直接调用到我们的接口了,答案就是property属性装饰器:
class Student(object): def __init__(self, name, score, sex): self.__name = name self.__score = score self.__sex = sex @property def name(self): return self.__name @name.setter def name(self, name): if len(name) > 1: self.__name = name else: print("name的长度必须要大于1个长度") @property def score(self): return self.__score @score.setter def score(self, score): if score > 0 and score < 100: self.__score = score else: print("输入错误!") hao = Student("hao", 19, 'male') hao.name = "后羿" # 调用了score(self,self)函数设置数据 print(hao.name) # 调用score(self)函数获取数据 hao.score = 100 print(hao.score)
注意,使用 @property 装饰器时,接口名不必与属性名相同。
python提供了更加人性化的操作,可以通过限制方式完成只读、只写、读写、删除等各种操作
""" class Student(object): def __init__(self, name, score, sex): self.__name = name self.__score = score self.__sex = sex @property def name(self): return self.__name @name.setter def name(self, name): if len(name) > 1: self.__name = name else: print("name的长度必须要大于1个长度") @property def score(self): return self.__score @score.setter def score(self, score): if score > 0 and score < 100: self.__score = score else: print("输入错误!") hao = Student("hao", 19, 'male') hao.name = "后羿" # 调用了score(self,self)函数设置数据 print(hao.name) # 调用score(self)函数获取数据 hao.score = 100 print(hao.score) """ class Person: def __init__(self, name): self.__name = name def __get_name(self): return self.__name def __set_name(self, name): self.__name = name def __del_name(self): del self.__name # prooerty()中定义了读取,复制,删除的操作 # name = property(__get_name, __set_name, __del_name) name = property(__get_name, __set_name) hao = Person("hao") print(hao.name) # 合法:调用__get_name hao.name = "后羿" # 合法:调用__set_name print(hao.name) # property中没有添加__del_name函数,所以不能删除指定的属性 del p.name # 错误:AttributeError: can't delete Attribute
@property
广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。
11.5、面向对象之多态
11.5.1、java多态
在java里,多态是同一个行为具有不同表现形式或形态的能力,即对象多种表现形式的体现,就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
如下图所示:使用手机扫描二维码支付时,二维码并不知道客户是通过何种方式进行支付,只有通过二维码后才能判断是走哪种支付方式执行对应流程。
// 支付抽象类或者接口 public class Pay { public String pay() { System.out.println("do nothing!") return "success" } } // 支付宝支付 public class AliPay extends Pay { @Override public String pay() { System.out.println("支付宝pay"); return "success"; } } // 微信支付 public class WeixinPay extends Pay { @Override public String pay() { System.out.println("微信Pay"); return "success"; } } // 银联支付 public class YinlianPay extends Pay { @Override public String pay() { System.out.println("银联支付"); return "success"; } } // 测试支付 public static void main(String[] args) { // 测试支付宝支付多态应用 Pay pay = new AliPay(); pay.pay(); // 测试微信支付多态应用 pay = new WeixinPay(); pay.pay(); // 测试银联支付多态应用 pay = new YinlianPay(); pay.pay(); } // 输出结果如下: 支付宝pay 微信Pay 银联支付
多态存在的三个必要条件:
- 继承
- 重写
- 父类引用指向子类对象
比如:
Pay pay = new AliPay();
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
11.5.2、java的抽象类与接口类
这样实现当然是可行的,但其实有一个小小的问题,就是Pay类当中的pay方法多余了。因为我们使用的只会是它的子类,并不会用到Pay这个父类。所以我们没必要实现父类Pay中的pay方法,做一个标记,表示有这么一个方法**,子类实现的时候需要实现它就可以了。
这就是抽象类和抽象方法的来源,我们可以把Pay做成一个抽象类,声明pay是一个抽象方法。抽象类是不能直接创建实例的,只能创建子类的实例,并且抽象方法也不用实现,只需要标记好参数和返回就行了。具体的实现都在子类当中进行。说白了抽象方法就是一个标记,告诉编译器凡是继承了这个类的子类必须要实现抽象方法,父类当中的方法不能调用。那抽象类就是含有抽象方法的类。
我们写出Pay变成抽象类之后的代码:
public abstract class Pay { abstract public String pay(); }
很简单,因为我们只需要定义方法的参数就可以了,不需要实现方法的功能,方法的功能在子类当中实现。由于我们标记了pay这个方法是一个抽象方法,凡是继承了Pay的子类都必须要实现这个方法,否则一定会报错。
抽象类其实是一个擦边球,我们可以在抽象类中定义抽象的方法也就是只声明不实现,也可以在抽象类中实现具体的方法。在抽象类当中非抽象的方法,子类的实例是可以直接调用的,和子类调用父类的普通方法一样。但假如我们不需要父类实现方法,我们提出提取出来的父类中的所有方法都是抽象的呢?针对这一种情况,Java当中还有一个概念叫做接口,也就是interface,本质上来说interface就是抽象类,只不过是只有抽象方法的抽象类。
所以刚才的Pay通过接口实现如下:
interface Pay { String pay(); }
把Pay变成了interface之后,子类的实现没什么太大的差别,只不过将extends关键字换成了implements。另外,子类只能继承一个抽象类,但是可以实现多个接口。早先的Java版本当中,interface只能够定义方法和常量,在Java8以后的版本当中,我们也可以在接口当中实现一些默认方法和静态方法。
接口的好处是很明显的,我们可以用接口的实例来调用所有实现了这个接口的类。也就是说接口和它的实现是一种要宽泛许多的继承关系,大大增加了灵活性。
以上虽然全是Java的内容,但是讲的其实是面向对象的内容,如果没有学过Java的小伙伴可能看起来稍稍有一点点吃力,但总体来说问题不大,没必要细扣当中的语法细节,get到核心精髓就可以了。
11.5.3、Python的抽象类和接口类
在Python中定义一个接口类,我们需要abc模块
(抽象类基类,Abstract Base Classes)中的两个工具abstractmethod, ABCMeta,详情如下:
工具 | 说明 |
---|---|
abstractmethod | 抽象类的装饰器,接口类中的接口需要使用此装饰器 |
ABCMeta | 抽象类元类 |
from abc import ABCMeta, abstractmethod # (抽象方法) class Payment(metaclass=ABCMeta): # metaclass 元类 metaclass = ABCMeta表示Payment类是一个规范类 def __init__(self, name, money): self.money = money self.name = name @abstractmethod # @abstractmethod表示下面一行中的pay方法是一个必须在子类中实现的方法 def pay(self, *args, **kwargs): pass class AliPay(Payment): def pay(self): # 支付宝提供了一个网络上的联系渠道 print('%s通过支付宝消费了%s元' % (self.name, self.money)) class WeChatPay(Payment): def pay(self): # 微信提供了一个网络上的联系渠道 print('%s通过微信消费了%s元' % (self.name, self.money)) class Order(object): @staticmethod def account(pay_obj): pay_obj.pay() pay1 = WeChatPay("hao", 100) pay2 = AliPay("summer", 200) order = Order() order.account(pay1) order.account(pay1)
11.6、反射
反射这个术语在很多语言中都存在,并且存在大量的运用,今天我们说说什么是反射,反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力,在python中一切皆对象(类,实例,模块等等都是对象),那么我们就可以通过反射的形式操作对象相关的属性。
Python中的反射主要有下面几个方法:
# 1.hasattr(object,name): 判断对象中有没有一个name字符串对应的方法或属性 # 2.getattr(object, name, default=None): 获取对象name字符串属性的值,如果不存在返回default的值 # 3.setattr(object, key, value): 设置对象的key属性为value值,等同于object.key = value # 4.delattr(object, name): 删除对象的name字符串属性
应用1
class Person: def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender hao = Person("hao", 23, "summer") print(hao.name) print(hao.age) print(hao.gender) while 1: # 用户选择查看hao的哪一个信息 attr = input(">>>") if hasattr(hao, attr): val = getattr(hao, attr) print(val) else: val = input("hao 没有你该属性信息,请设置该属性值>>>") setattr(hao, attr, val)
应用2
class FTP(object): def __init__(self): self.run() def run(self): print(''' 提示: 上传: put 路径/文件名称 下载: get 路径/文件名称 ''' ) while 1: input_str = input(">>>") action, params = input_str.split(" ") if hasattr(self, action): getattr(self, action)() else: print("不存在该方法") def put(self): print("上传...") def get(self): print("下载...") ftp = FTP()
11.7、魔法方法
Python 里有一种方法,叫做魔法方法。Python 的类里提供的,两个下划线开始,两个下划线结束的方法,就是魔法方法,魔法方法在恰当的时候就会被激活,自动执行。
1.7.1、__new__()
方法
类名() 创建对象时,在自动执行 init()方法前,会先执行 object.__new__方法,在内存中开辟对象空间并返回
class Person(object): def __new__(cls, *args, **kwargs): print("__new__方法执行") # return object.__new__(cls) def __init__(self, name, age): print("__init__方法执行") self.name = name self.age = age hao = Person("hao", 23)
11.7.2、__str__
方法
改变对象的字符串显示。可以理解为使用print函数打印一个对象时,会自动调用对象的__str__
方法
class Person(object): def __init__(self, name, age): print("__init__方法执行") self.name = name self.age = age def __str__(self): return self.name hao = Person("hao", 23) print(hao)
11.7.3、__repr__
方法
在python解释器环境下,会默认显示对象的repr表示。
# __str__ 的返回结果可读性强。也就是说,__str__ 的意义是得到便于人们阅读的信息 # __repr__ 存在的目的在于调试,便于开发者使用 # 案例1: class A(): ... def __str__(self): ... return "str" ... ... def __repr__(self): ... return "repr" ... a = A() a repr print(a) str # 案例2: import datetime date = datetime.datetime.now() print(str(date)) # '2021-05-21 12:04:02.671735' print(repr(date)) # 'datetime.datetime(2021, 5, 21, 12, 4, 2, 671735)' # 案例3: import json dic = {"name": "yuan", "age": 23} data = json.dumps(dic) print(str(data)) print(repr(data))
11.7.4、__del__
方法
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
class Person(object): def __init__(self, name, age): self.name = name self.age = age def __del__(self): print("%s删除了" % self.name) hao = Person("hao", 23) # del hao
11.7.5、__eq__
方法
拥有__eq__
方法的对象支持相等的比较操作
class Person(object): def __init__(self, name, age): self.name = name self.age = age def __eq__(self, obj): return self.name == obj.name hao = Person("hao", 23) summer = Person("summer", 23) print(hao == summer)
11.7.6、__len__
方法
class G(object): def __len__(self): return 100 g=G() print(len(g))
11.7.7、item系列
class Person(object): def __init__(self, name, age): self.name = name self.age = age def __getitem__(self, item): print('obj[key]取值时,执行__getitem__') print("取值为:", self.__dict__[item]) def __setitem__(self, key, value): print('obj[key]=value赋值时,执行__setitem__') self.__dict__[key] = value def __delitem__(self, key): print('del obj[key]触发') self.__dict__.pop(key) # obj.["key"]的方式触发__xxxitem__魔法方法 hao = Person("hao", 23) name = hao["name"] # 触发__getitem__执行 hao["age"] = 18 # 触发__setattr__执行 del hao["age"] # 触发__delitem__
11.7.8、attr系列
class Person(object): def __init__(self, name, age): self.name = name self.age = age def __getattr__(self, item): print('获取 obj.attr时触发,属性不存在的时候才会触发') def __setattr__(self, key, value): print('obj.attr=value时触发,添加修改属性时可以使用') # self.key=value #这就无限递归了,你好好想想 self.__dict__[key] = value # 应该使用它 def __delattr__(self, item): print('del obj.attr 时触发,删除属性的时候会触发') # del self.item #无限递归了 self.__dict__.pop(item) # obj.attr的方式触发__xxxattr__魔法方法 hao = Person("hao", 23) print(hao.name) print(hao.__dict__)
1.8、异常机制
首先我们要理解什么叫做**“异常”**?
- 在程序运行过程中,总会遇到各种各样的问题和错误。
- 有些错误是我们编写代码时自己造成的:比如语法错误、调用错误,甚至逻辑错误。
- 还有一些错误,则是不可预料的错误,但是完全有可能发生的:比如文件不存在、磁盘空间不足、网络堵塞、系统错误等等。
这些导致程序在运行过程中出现异常中断和退出的错误,我们统称为异常。大多数的异常都不会被程序处理,而是以错误信息的形式展现出来。
异常的分类:
- 异常有很多种类型,Python内置了几十种常见的异常,无需特别导入,直接就可使用。
- 需要注意的是,所有的异常都是异常类,首字母是大写的!
异常的危害:
-
如果程序中一旦出现了异常的语句代码,则该异常就会立即中断程序的运行!
-
因此:为了保证程序的正常运行,提高程序健壮性和可用性。我们应当尽量考虑全面,将可能出现的异常进行处理,而不是留在那里,任由其发生。
11.8.1、基本语法
异常的基本结构:try except
# (1)通用异常 try: pass # 正常执行语句 except Exception as ex: pass # 异常处理语句 # (2)指定异常 try: pass # 正常执行语句 except <异常名>: pass # 异常处理语句 #(3) 捕获多个异常 # 捕获多个异常有两种方式,第一种是一个except同时处理多个异常,不区分优先级: try: pass # 正常执行语句 except (<异常名1>, <异常名2>, ...): pass # 异常处理语句 # 第二种是区分优先级的: try: pass # 正常执行语句 except <异常名1>: pass # 异常处理语句1 except <异常名2>: pass # 异常处理语句2 except <异常名3>: pass # 异常处理语句3 # 异常嵌套 try: try: with open("abc") as f: pass except NameError as e: print(e) except OSError as e: print("OSError:",e.strerror)
机制说明:
- 首先,执行try子句(在关键字try和关键字except之间的语句)
- 如果没有异常发生,忽略except子句,try子句执行后结束。
- 如果在执行try子句的过程中发生了异常,那么try子句余下的部分将被忽略。如果异常那么对应的except子句将被执行。
- 在Python的异常中,有一个通用异常:
Exception
,它可以捕获任意异常。
11.8.2、finally
try: pass # 正常执行语句 except Exception as e: pass # 异常处理语句 finally: pass # 无论是否发生异常一定要执行的语句,比如关闭文件,数据库或者socket
11.8.3、raise语句
很多时候,我们需要主动抛出一个异常。Python内置了一个关键字raise
,可以主动触发异常。
raise
可以抛出自定义异常,我们已将在前面看到了python内置的一些常见的异常类型。大多数情况下,内置异常已经够用了。但是有时候你还是需要自定义一些异常:自定义异常应该继承Exception
类,直接继承或者间接继承都可以,例如:
# 1.用户自定义异常类型 class TooLongExceptin(Exception): "this is user's Exception for check the length of name " def __init__(self, len): self.len = len def __str__(self): return "输入姓名长度是" + str(self.len) + ",超过长度了" try: name = input("enter your name:") if len(name) > 5: raise TooLongExceptin(len(name)) else: print(name) except TooLongExceptin as error: # 这里异常类型是用户自定义的 print("打印异常信息:", error)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!