Python:类和对象

注意:本文引用自专业人工智能社区Venus AI

更多AI知识请参考原站 ([www.aideeplearning.cn])

面向对象编程 VS 面向过程编程


在使用计算机语言进行代码编写时,常见的两种思路是面向对象编程和面向过程编程:


(1)面向过程:根据业务逻辑从上到下写代码。
(2)面向对象:将数据与函数绑定到一起,分类进行封装,每个程序员只要负责分配给自己的分类,这样能够更快速地开发程序,减少了重复代码。


举一个生活中吃北京烤鸭的例子来解释这两种思路:


第一种方式(面向过程):第一步养鸭子;第二步鸭子长成;第三步杀鸭子;第四步配置佐料;第五步腌制烤鸭;第六步烤制烤鸭;第七步吃。
第二种方式(面向对象):第一步找个养烤鸭的农户;第二步找个厨子;第三步吃。


面向对象的思维解决问题的重点是当遇到一个需求的时候,不用自己去实现。例如之后要学习的PyTorch就是一种深度学习中面向对象编程的思想。读者甚至不需要知道深度学习中常见的诸如卷积算法等操作如何用代码编写,因为PyTorch深度学习框架已经把这些基础功能封装成了函数,想使用的时候直接调用函数名字即可,这为初学者们入门深度学习提供了极大的便利。

类与对象

1. 类的创建


类是对具有相同特征的对象进行抽象的概念,这些特征包含静态的和动态的,分别对应类的属性和方法。例如,鸟类的静态特征是长着一双翅膀,动态特征是会飞;而狗类的静态特征是嗅觉灵敏,动态特征是会“汪汪”叫。因此,再加上类的名字,这三个成分就组成的类的概念。


举例:
(1)狗类的设计:
类名:狗(Dog);
属性:品种、毛色、性别、名字、腿的数量等;
方法:(行为/功能):叫、跑、咬人、吃、摇尾巴等。


如何把日常生活中的事物抽象成程序中的类?实际上,拥有相同(或者类似)属性和行为的对象都可以抽象出一个类。常用的一个方法为名词提炼法:一般名词都是类。


举例:
(1)飞机发射7颗炮弹轰掉了一个舰队。
飞机:可以抽象成“飞机类”;
炮弹:可以抽象成“武器类”;
舰队:可以抽象成“船类”。
(2)小美在公园中牵着一条叼着热狗的狗。
小美:人类;
公园:场景类;
热狗:食物类;
狗:狗类。

详细代码示例

一般,使用class语句来创建一个新类,class之后为类的名称(通常首字母大写)并以冒号结尾,代码如下:

#lei.py
class Car():    
    # 方法    
    def getCarInfo(self):    
        print('车轮子个数:%d, 颜色%s'%(self.wheelNum, self.color))    
    def move(self):    
        print("车正在移动...")

在类中,可以定义所使用的方法,类中的方法与普通的函数只有一个特别的区别:它们必须有一个额外的第一个参数名称, 按照惯例它的名称是self。它表示类创建的实例本身,指向当前创建对象的内存地址。某个对象调用其方法时,Python解释器会把这个对象作为第一个参数传递给self,所以开发者只需要传递后面的参数即可。

#lei.py
# 定义类    
class Car():    
    # 定义移动方法    
    def move(self):    
        print('车在行驶...')    
    # 定义鸣笛方法    
    def toot(self):    
        print("车在嘟嘟..")    
# 创建一个对象,并用变量Wuling来保存它的引用    
Wuling = Car()    
Wuling.color = 'white' #使用‘.’符号的方法添加类属性:车的颜色    
Wuling.wheelNum = 4 #使用‘.’符号的方法添加类属性:轮子数量    
Wuling.move() #使用‘.函数名()’的语法调用类中函数:车的行驶    
print(Wuling.color) #打印实例五菱的颜色属性    
print(Wuling.wheelNum) #打印实例五菱的车轮数量属性
车在行驶...
white
4

在上面的实例中,我们给五菱神车添加了两个对象属性:wheelNum和color,试想一下,如果再次创建一个对象的话,创建之后需要重新进行属性添加,这样做是很麻烦的。那么是否可以在创建对象时,就顺便把属性也给予了呢? 这就是init()函数的作用。代码如下:

#lei.py
# 定义汽车类    
class Car():    
    # 初始化函数,对象属性有默认值:4和white;也可以通过传参的方法对对象属性进行重新赋值    
    def __init__(self, wheelNum=4, color='white'):    
        self.wheelNum = wheelNum    
        self.color = color    
    # 定义类方法    
    def move(self):    
        print('车在行驶')    
# 创建对象五菱神车,不传参时,属性使用默认值    
Wuling = Car()     
print('五菱车的颜色为:',  Wuling.color)    
print('五菱车轮胎数量为:', Wuling.wheelNum)
# 创建对象自行车,传参时,新的传参值代替默认值    
Bicycle = Car(2, 'black')    
print('自行车的颜色为:',  Bicycle.color)    
print('自行车轮胎数量为:', Bicycle.wheelNum)  
五菱车的颜色为: white
五菱车轮胎数量为: 4
自行车的颜色为: black
自行车轮胎数量为: 2


注意:
(1)当创建“Wuling对象”后,在没有调用方法的前提下,Wuling就默认拥有了2个属性:wheelNum和color,原因是方法是在创建对象后,就立刻被默认调用了init()函数,不需要手动调用。
(2)init(self)中,默认有1个参数名字为self,如果在创建对象时传递了2个实参,那么除了self作为第一个形参外还需要2个形参,例如init(self, x, y)。
(3)init(self)中的self参数,不需要开发者传递,Python解释器会自动把当前的对象引用传递进去。

2. 魔法方法

魔法方法是在Python的类中被双下划线前后包围的方法。这些方法在类或对象进行特定的操作时会自动被调用,读者可以使用或重写这些魔法方法,给自定义的类添加各种特殊的功能来满足自己的需求。3.5.2节已经介绍过初始化魔法方法init,也是在定义类时最常见的魔法方法。除此之外,还有一些常见的魔法方法如下。

构造类的魔法方法


我们都知道一个最基本的魔术方法,init。通过此方法可以定义一个对象的初始操作。但init并不是第一个被调用的方法。

实际上,还有new方法,来实例化这个对象。然后,在创建时给初始化函数传递参数。在对象生命周期的另一端,也有del方法。

接下来看一看这三个方法。
new(cls, […])是在一个对象实例化的时候所调用的第一个方法,所以它才是真正意义上的构造方法。它的第一个参数是这个类,其他的参数是用来直接传递给init方法。


new 决定是否要使用该init方法,因为new可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果new没有返回实例对象,则init不会被调用。代码如下:

#lei.py
class Person(object):    
    def __new__(cls, *args, **kwargs):    
        print("__new__()方法被调用了")    
        print('这个是*agrs', *args)    
        print('这个是kwagrs', **kwargs)    
        # cls表示这个类,剩余所有的参数传给__init__()方法,    
        # 若不返回,则__init__()不会被调用    
        #return object.__new__(cls)  
    def __init__(self, name, age):    
        print("__init__()方法被调用了")    
        self.name = name    
        self.age = age    
        print(self.name, self.age)    
p = Person("张三", 20)  
__new__()方法被调用了
这个是*agrs 张三 20
这个是kwagrs

new()方法在什么场景使用呢? 当我们需要继承内置类时。例如,想要继承int、str、tuple,就无法使用init来初始化了,只能通过new来初始化数据。实际上,在日常的编写中,new()和del()这两个魔法函数一般都不常见,保持默认就好,经常编写的是init()函数。del()函数代码如下:

#lei.py
class Washer:    
    # 当一个实例被销毁时自动调用的方法。    
    def __del__(self):    
        """  
        当删除对象时,解释器会自动调用del方法  
        """    
        print('对象已删除!')    
haier = Washer()  

3. 类属性和类方法

3.1 类属性和实例属性

在了解了类的基本的信息之后,下面看一下类属性和实例属性这两个概念。 类属性顾名思义就是类所拥有的属性,分为共有属性和私有属性,私有属性通过“__属性名称”的方法进行定义。对于公有的类属性,可以在类外进行访问,私有属性在类外不可以直接访问,代码如下:

#lei.py
class People(object):    
    name = 'VenusAI'  #公有的类属性    
    __age = 7     #私有的类属性    
    def __init__(self):    
        pass    
p = People()    
print(p.name)           #正确    
print(People.name)     #正确    
#print(p.__age)          #错误,不能在类外通过实例对象访问私有的类属性    
#print(People.__age)    #错误,不能在类外通过类对象访问私有的类属性    
print(p._People__age) #这种特殊的访问方法可以在类外访问私有的类属性
VenusAI
VenusAI
7


注意:类属性是声明在类的内部,实例方法的外部的属性,即在class内,__init__(self)方法之前。


实例属性是从属于实例对象的属性,也称为“实例变量”,要点如下:


(1)实例属性一般在init()方法中通过如下代码定义“self.实例属性名 = 初始值”。


(2)在本类的其他实例方法中,也是通过self进行访问“self.实例属性名”。


(3)实例属性可修改、新增、删除。


需要注意的是,如果在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用修改,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性。当类属性与实例属性同名时,一个实例访问这个属性时实例属性会覆盖类属性,但类访问时不会。代码如下:

#lei.py
class People(object):    
    country = 'China' #类属性    
# 访问类属性    
print(People.country)    
# 实例化对象    
p = People()    
# 访问实例属性    
print(p.country)    
# 修改实例属性    
p.country = 'Japan'    
# 访问实例属性,实例属性会屏蔽掉同名的类属性    
print(p.country)       
# 访问类属性,会发现没有改变    
print(People.country)    
#通过类对象去引用修改类属性    
People.country = "UK"       
# 访问类属性    
print(People.country)
China
China
Japan
China
UK
3.2 实例方法、类方法和静态方法
1)实例方法

之前的例子中,在类中以def关键字定义的都可以称之为实例方法,不仅如此,类的初始化方法init()理论上也属于实例方法,只不过它比较特殊。实例方法最大的特点就是,它最少也要包含一个self参数,用于绑定调用此方法的实例对象“Python 会自动完成绑定”。实例方法通常会用类对象直接调用。

2)类方法

Python类方法和实例方法相似,它最少也要包含一个参数,只不过类方法中通常将其命名为cls,类方法是类对象所拥有的方法,需要用修饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数,可以通过实例对象和类对象去访问。类方法还有一个用途就是可以对类属性进行修改。代码如下:

#lei.py
class People(object):    
    country = 'China'    
    #类方法,用classmethod来进行修饰    
    @classmethod    
    def getCountry(cls):    
        return cls.country    
    @classmethod    
    def setCountry(cls,country):    
        cls.country = country    
p = People()    
print(p.getCountry())    #可以通过实例对象引用    
print(People.getCountry())    #可以通过类对象引用    
p.setCountry('Japan')     
print(p.getCountry())       
print(People.getCountry()) 
'''
China
China
Japan
Japan
'''
China
China
Japan
Japan

Out[8]:

'\nChina\nChina\nJapan\nJapan\n'


类方法什么时候使用呢?我们可以考虑一个场景。
假设有一个学生类和一个班级类,需实现的功能为:学生类继承自班级类,每实例化一个学生,班级人数都会增加。最后,需要实例化一些学生,并获取班级中的总人数。
思考:这个问题用类方法做比较合适,为什么?
我们要实例化的是学生,但是从学生实例中获取班级总人数,在逻辑上显然是不合理的。如果要获得班级总人数,生成一个班级实例是没有必要的。因此,编写一个类方法最为合适,这个方法能够访问并更新班级的总人数,而不需要创建班级实例。

3)静态方法


静态方法需要通过修饰器@staticmethod来进行修饰。静态方法是类中的函数。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。例如,笔者想定义一个关于时间操作的类,其中有一个获取当前时间的函数,代码如下:

#lei.py
import time    
class TimeTest(object):    
    def __init__(self, hour, minute, second):    
        self.hour = hour    
        self.minute = minute    
        self.second = second    
    @staticmethod    
    def showTime():    
        return time.strftime("%H:%M:%S", time.localtime())    
# 使用类对象调用静态方法    
print(TimeTest.showTime())    
# 实例化对象    
t = TimeTest(2, 10, 10)    
# 使用实例对象调用静态方法     
print(t.showTime())

 14:22:56

14:22:56

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值