想象你面前有一部手机,这部手机有什么特征?
具体来说,__new__方法是在对象创建之前调用的,这时实例还不存在;而__init__方法是在对象创建之后调用,此时实例已经存在,所以可以访问并初始化实例的属性和方法,你现在可以回看上面的代码,我是不是在__init__初始化时就self._load_config(),就调用了类内部的方法,当然不限于类内部,你可以调用任何方法,哪怕是另一个类里的方法都可以,就比如我们有两个类,一个是MapLoader类负责加载地图数据,另一个是CoordinateConverter类负责将地理坐标转换为不同的投影坐标系。在初始化时,我们希望在加载地图的同时,调用CoordinateConverter的一个方法来转换坐标,这时候就派上用场了。再比如
classEngine:#发动机类defstart(self):print("发动机启动")classCar:#汽车类def__init__(self):self.engine=Engine()#创建发动机self.engine.start()#启动发动机self._check_oil()#检查油量def_check_oil(self):print("检查油量")#创建一个车对象my_car=Car()#输出:#发动机启动#检查油量这就像是:
因此,大多数初始化工作(如设置属性、调用方法、建立连接等)都需要在实例创建之后进行,这使得它们更适合放在__init__方法中。
综上所述,__init__方法负责对象的初始化工作,而__new__则主要用来控制对象的创建过程。正是因为如此,我们通常将初始化的逻辑放在__init__中,而不是在__new__中进行。
classExample:shared_value=100#类属性@classmethod#类方法defupdate_shared_value(cls,new_value):cls.shared_value=new_valuedef__init__(self,value):#构造方法self.value=valuedefupdate_value(self,new_value):#实例方法self.value=new_value@staticmethod#静态方法defutility_function(x,y):returnx+y二、类与对象:理解背后的设计思想对象的内存模型当我们创建一个对象时,计算机实际上做了什么?让我们来看看这个过程:
car1=Car("红色","丰田")#在内存中创建对象car2=car1#car2引用同一个对象car2.color="蓝色"print(car1.color)#输出"蓝色",因为car1和car2指向同一个对象在面向对象编程(OOP)中,理解对象的内存模型是掌握继承、封装和多态等核心概念的基础。当我们在代码中创建一个对象时,Python会先为对象分配一块内存空间,这块内存不仅存储了该对象的属性和方法,还为我们提供了对象的引用。建对象的过程包括三个步骤:分配内存、初始化属性、返回对象引用。这个过程看似简单,但它对我们后续理解OOP里的继承、封装和多态至关重要。
以继承为例,当一个子类继承父类时,子类不仅继承了父类的属性和方法,而且会在父类的基础上扩展或重写这些方法。理解对象的内存模型就知道,子类实例的内存空间会包含父类的所有属性和方法,同时子类可能会覆盖这些父类的实现。例如,假设有一个Animal类和一个继承它的Dog类。当我们创建Dog类的实例时,Dog对象不仅继承了Animal的属性(如name),还可能重写了Animal的sound()方法。内存中会为这个Dog实例分配空间,这块空间会包含父类Animal的数据,同时也会有Dog类特有的属性。
接下来是封装,理解对象内存模型帮助我们理解类如何隔离内部状态,并通过方法来限制对这些状态的访问。例如,考虑一个BankAccount类,其中的余额__balance被封装为私有属性,外部无法直接访问。通过封装,Python确保了银行账户的余额只能通过特定的方法(如deposit和withdraw)来修改,而不是直接赋值。内存中的私有属性是被限制访问的,这有助于我们在设计程序时控制数据的一致性和安全性。
多态指的是相同的方法可以在不同的对象上表现出不同的行为。理解内存模型对多态至关重要,因为它让我们知道,在运行时,Python是如何通过对象的引用来动态绑定不同类的方法的。举个例子,假设我们有一个make_sound()方法,它接收一个Animal类的对象,并调用对象的sound()方法。由于Dog和Cat类都继承自Animal,并且都重写了sound()方法,当我们通过make_sound()传入不同类型的对象时,程序会根据对象的实际类型调用相应的sound()方法。这就是动态绑定的实现,内存中的引用指向的是实际对象类型的方法,而不是静态的父类方法。
在Python中,所有数据类型(如数字、字符串、列表、元组、集合、字典、Numpy引入的数组等)都属于类
创建这些数据类型的变量就是在实例化它们对应的类。每个对象(变量)都有一个对应的类,它决定了对象的行为和操作方法。
当我们创建一个列表时,其实就是利用list类来构造一个实例
numbers=[1,2,3]#↓二者完全等价numbers=list([1,2,3])这就解释了为什么我们可以直接使用.append(),.extend(),.pop()等列表实例方法因为这些都是list类的实例方法啊。同样的,字符串也是str类的实例,可以调用实例方法.upper()、.lower()
text1="hello"#↓二者完全等价text2=str("hello")能不能直接调用类方法呢list类没有类方法,所有操作都要先创建实例
numbers=[1,2]numbers.append(3)#实例方法numbers.extend([4,5])#实例方法numbers.pop()#实例方法datetime类同时具有实例方法和类方法
path=Path("example.txt")#实例方法操作特定路径对应的对象实例。exists=path.exists()#判断文件是否存在is_file=path.is_file()#判断是否是文件parent=path.parent#获取父目录absolute=path.resolve()#返回绝对路径path.unlink()#删除文件这些实例方法都需要一个具体的路径作为操作对象,所以它们必须是实例方法。
从Path的设计可以学到:
所以,Path类不是“不需要实例方法”,而是它同时需要类方法和实例方法,来完成不同层次的操作需求。
通过一个银行账户的完整例子,一步步理解Python中的封装、私有属性和属性装饰器。
第1步:最基础的银行账户首先,让我们创建一个最简单的银行账户类:
classBankAccount:def__init__(self,owner,initial_balance):self.owner=ownerself.balance=initial_balance#创建账户张三account=BankAccount("张三",1000)--------------------------------------------------print(张三account.balance)#输出:1000#但是这样很危险,因为任何人都可以直接修改余额!张三account.balance=99999#这样就直接把钱改多了!这个版本存在严重的安全问题-任何人都可以直接修改账户余额!这显然不是我们想要的。
第2步:使用私有属性保护余额
classBankAccount:def__init__(self,owner,initial_balance):self.owner=ownerself.__balance=initial_balance#使用双下划线,变成私有属性张三account=BankAccount("张三",1000)--------------------------------------------------#报错!因为在外部会认为这个实例里面压根没有.__balance这个属性print(张三account.__balance)#AttributeError:'BankAccount'objecthasnoattribute'__balance'现在外部无法直接访问余额了,这提高了安全性。但是这样太极端了,连查看余额都做不到。
第3步:添加访问余额的方法
classBankAccount:def__init__(self,owner,initial_balance):self.owner=ownerself.__balance=initial_balancedefget_balance(self):returnself.__balancedefdeposit(self,amount):self.__balance+=amount张三account=BankAccount("张三",1000)--------------------------------------------------print(张三account.get_balance())#输出:1000张三account.deposit(500)print(张三account.get_balance())#输出:1500这样好多了!但是每次查看余额都要写get_balance(),不够优雅,也不符合习惯,我的余额明明是个属性你非得让我调用方法查看,我就要直接通过点属性查看余额。
第4步:使用@property让访问更自然
classBankAccount:def__init__(self,owner,initial_balance):self.owner=ownerself.__balance=initial_balance@propertydefbalance(self):returnself.__balance张三account=BankAccount("张三",1000)print(张三account.balance)#现在可以直接用account.balance了!#现在其实已经很棒了,既能查看余额,又不能随意修改余额account.balance=2000#这行会报错!第五步引入了setter方法,它提供了另一种修改余额的方式,但这不是必须的,尤其是如果您想要强制通过特定的方法来修改余额。如果您希望所有对余额的修改都必须通过deposit和withdraw方法进行,那么第四步就足够了。
第5步:添加安全的余额修改方式
这就是一个完整的封装示例-我们把余额数据藏起来(私有属性),但提供了安全的方法来访问和修改它(属性装饰器和方法)。这样既保证了数据的安全性,又保持了使用的便利性。
要设计一个宠物商店的管理系统。先从最基础的开始:
#第一步:创建一个基础的宠物类classPet:def__init__(self,name,age):self.name=nameself.age=agedefmake_sound(self):print("某种声音")#创建一个具体的宠物实例my_pet=Pet("小白",2)my_pet.make_sound()#输出:某种声音问题1:假设我们现在要添加一个狗类,它应该继承Pet类的所有特性,但还要有自己独特的叫声。你觉得应该怎么做?思考如下:
classDog(Pet):defmake_sound(self):print("汪汪汪!")#创建一个狗的实例my_dog=Dog("旺财",3)my_dog.make_sound()#输出:汪汪汪!问题2:如果我们想给狗添加一个新属性,比如品种(breed),你觉得应该怎么做?需要考虑:
classDog(Pet):def__init__(self,name,age,breed):#调用父类的__init__方法来初始化name和agesuper().__init__(name,age)#添加新的breed属性self.breed=breeddefmake_sound(self):print("汪汪汪!")#创建一个具体的狗实例my_dog=Dog("旺财",3,"金毛")my_dog.make_sound()#输出:汪汪汪!print(f"{my_dog.name}是一只{my_dog.breed}")#输出:旺财是一只金毛这里我们遇到了继承中一个重要的概念:super()。super().__init__(name,age)这行代码是在调用父类(Pet)的构造方法,如果我们不调用super().init(),那么dog实例就不会有name和age这些属性,即先按照普通宠物的方式初始化这些基本属性,然后我再添加狗特有的属性"
问题3:现在让我们更进一步。假设我们的宠物店开始提供宠物训练服务,不同的宠物有不同的训练方法。我们应该:
首先,我们在Pet基类中添加训练方法:
classPet:def__init__(self,name,age):self.name=nameself.age=age#添加一个训练等级属性self.training_level=0defmake_sound(self):print("某种声音")deftrain(self):"""基础训练方法"""self.training_level+=1print(f"{self.name}完成了基础训练,当前训练等级:{self.training_level}")现在,让我们为Dog类添加特殊的训练方法。狗狗可能需要学习一些特殊技能,比如"坐下"、"握手"等:
classDog(Pet):def__init__(self,name,age,breed):super().__init__(name,age)self.breed=breed#添加狗狗特有的技能列表self.skills=[]defmake_sound(self):print("汪汪汪!")deftrain(self):#先调用父类的基础训练super().train()#根据训练等级解锁新技能ifself.training_level==1:self.skills.append("坐下")elifself.training_level==2:self.skills.append("握手")elifself.training_level==3:self.skills.append("翻滚")print(f"{self.name}学会了新技能!当前已掌握的技能:{','.join(self.skills)}")如果我们现在要添加一个猫类(Cat),它应该有什么特殊的训练方法?
让我们来实现这个类:
如果我们现在要给宠物店添加一个"训练课程"的功能,可能需要:
要如何利用多态来处理不同的宠物类型?
fromabcimportABC,abstractmethod(抽象基类)classAnimal(ABC):@abstractmethoddefspeak(self):#这就是抽象基类的意义,只在这里说,需要实现speak,但是不写任何具体逻辑passclassDog(Animal):defspeak(self):return"汪汪汪!"classCat(Animal):defspeak(self):return"喵喵喵!"defanimal_speak(animal):returnanimal.speak()#使用示例dog=Dog()cat=Cat()print(animal_speak(dog))#输出:汪汪汪!print(animal_speak(cat))#输出:喵喵喵!这里的关键点是:
classCar:def__init__(self,fuel_type):self.fuel_type=fuel_typedefrefuel(self):ifself.fuel_type=="gasoline":return"加注汽油"elifself.fuel_type=="electric":return"充电"classDuck:defswim(self):print("鸭子游泳")classRobot:defswim(self):print("机器人游泳")defmake_it_swim(thing):thing.swim()#只要对象有swim方法就可以调用三、深入理解抽象类3.1抽象类的作用抽象类就像是一个规范或契约,它定义了一组必须实现的方法,但不提供具体实现。这样做的好处是:
fromabcimportABC,abstractmethodclassShape(ABC):@abstractmethoddefarea(self):pass@abstractmethoddefperimeter(self):passclassRectangle(Shape):def__init__(self,width,height):self.width=widthself.height=heightdefarea(self):returnself.width*self.heightdefperimeter(self):return2*(self.width+self.height)3.3接口与抽象类的区别虽然Python中没有显式的接口概念,但我们可以用抽象类模拟接口:
设计模式是在软件开发过程中反复出现的问题的解决方案。就像建筑师在设计建筑时会使用一些经过验证的结构模式一样,程序员也有一套处理常见编程问题的最佳实践。让我们看几个最常用的设计模式:
想象你正在开发一个数据库连接管理器。你希望整个程序只能创建一个数据库连接实例,以避免资源浪费。这就是单例模式的典型应用场景:
classDatabaseConnection:_instance=Nonedef__new__(cls):#如果实例不存在,创建一个新实例ifcls._instanceisNone:cls._instance=super().__new__(cls)#在这里初始化数据库连接cls._instance.connected=Falsereturncls._instancedefconnect(self):ifnotself.connected:#模拟数据库连接print("建立数据库连接...")self.connected=Trueelse:print("已经连接到数据库")#测试代码db1=DatabaseConnection()db2=DatabaseConnection()print(db1isdb2)#输出:True,证明是同一个实例1.3工厂模式(FactoryPattern)假设你在开发一个游戏,需要创建不同类型的敌人。每种敌人的创建过程可能都很复杂,这时工厂模式就很有用:
classEnemy:defattack(self):passclassZombie(Enemy):defattack(self):return"僵尸缓慢地攻击"classVampire(Enemy):defattack(self):return"吸血鬼快速地攻击"classEnemyFactory:@staticmethoddefcreate_enemy(enemy_type):ifenemy_type=="zombie":returnZombie()elifenemy_type=="vampire":returnVampire()else:raiseValueError("未知的敌人类型")#使用工厂创建敌人enemy=EnemyFactory.create_enemy("zombie")print(enemy.attack())#输出:僵尸缓慢地攻击二、高级特性与技巧2.1属性装饰器(@property)属性装饰器允许我们像访问属性一样访问方法,提供了更优雅的封装方式: