Python面向对象下

上周我们系统讲述了面向对象编程的概念,学会了如何定义类和构造实例。

并且我们知道了与类有关的变量有两种,一种是类变量,它能够作用所有实例;一种是实例变量,它仅作用于当前实例。

本周我们将学习类中的函数(在类中叫方法),学完本周的内容,你就可以“指挥”类和实例去处理具体的事情了。此外,我们还会见识到如何将类进行封装,如何禁止用户随便对类进行修改。

知识结构
在这里插入图片描述

上周我们讲到,类体中的函数叫做方法。与类相关的方法有三种:静态方法、实例方法、类方法。

我们先对三种方法分别用一句话描述,然后再详细讲述。

静态方法:就是单纯把函数放到类体中,可以被类和实例调用。
实例方法:实例方法同实例变量一样,仅对当前实例产生作用,仅能被实例调用。
类方法:类变量同类方法一样,可以对所有实例产生作用,可以被类和实例调用。

类的方法

静态方法

回顾上周的内容,一个完整的类应该起码包括:类名、父类名、类变量、构造方法和实例变量。

看下面一张图:
在这里插入图片描述

我们现在爸静态方法添加进来
在这里插入图片描述

可以看到,静态方法由两部分构成,一部分是静态方法的装饰器,另一部分是一个函数

装饰器解决了这样一个问题:它把函数常用的一些特征和功能封装起来,当需要给某个函数添加这些功能的时候,可以在函数前面加上这个装饰器,用法就是@装饰器名称。

python已经把静态方法相关特征封装到装饰器“staticmethod”中。因此,在定义静态方法的时候,只需要在函数之前添加“@staticmethod”就可以了。

静态方法的函数部分的定义和普通函数完全一致。

静态方法在类体之内可以直接调用,在类体内的实例方法中用self.静态方法名调用;在类体内的类方法cls.静态方法来调用;在类体之外调用需要使用实例名.静态方法名或者类名.静态方法名。

class BankAccount(object):
    interest_rate = 0.05
    def __init__(self,name,this_id,password,money):
        self.name=name
        self.this_id=this_id
        self.password=password
        self.money=money
    
    @staticmethod
    def fun():
        print('我是一个静态方法')
        
#类调用静态方法
BankAccount.fun()
#根据BankAccount创建实例tom_account
tom_account=BankAccount("Tom","12345","1111",1000)
#实例调用静态方法
tom_account.fun()
我是一个静态方法
我是一个静态方法

在这个练习中,我们主要学习BackAccount.fun( )使用类名.静态方法名调用了静态方法,tom_account.fun( )使用实例名.静态方法名调用了静态方法。
这里的静态方法,我们只是简单地打印出了“我是静态方法”的字符串。
待会我们学完实例方法和类方法后,我们会见到静态方法如何在类体中使用,这也是静态方法最常用的场景。

实例方法

实例方法是类体中最常见的方法。上一关讲的构造方法即是魔法方法(方法名前后各有两个下划线),又是实例方法。
构造方函数的语法:

def __init__(self,参数1,参数2....):
    函数体
#使用__init__()函数来定义构造方法,函数的第一个参数应该为self,后面跟着其他参数,self.在类体内表示,使用self.来访问实例的变量或者方法。

构造方法不同于静态方法的重要一点就是“函数的第一个参数应该为self”,这也是所有实例方法必须遵守的规则。
在学习实例变量的时候,我们还提到:在类体中,所有的实例变量应该以self.开头,意思是使用self.来访问实例的变量或者方法。
因此在实例方法中,如果要定义或者改变实例变量,也应该使用self.开头。
需求:现在针对银行账户类定义一个实例方法,用来改变实例的密码。使用银行账户类创建一个银行实例,然后利用该实例方法改变实例的密码。

class BankAccount(object):
    interest_rate = 0.05
    def __init__(self,name,this_id,password,money):
        self.name = name
        self.this_id=this_id
        self.password=password
        self.money=money
        
    #定义一个修改密码的实例方法
    def chang_password(self,new_password):
        self.password=new_password

tom_account=BankAccount('Tom','12345','12345',1000)
print("%s原来的密码是%s" % (tom_account.name, tom_account.password))
#调用实例方法修改密码
tom_account.chang_password('111111111111111')
print("%s现在的密码是%s" % (tom_account.name,tom_account.password))
Tom原来的密码是12345
Tom现在的密码是111111111111111

这里我们定义了一个名为chang_password的实例方法,它的第一个参数是self,第二个参数是new_password.
这个实例方法的作用是把new_password赋值给实例变量self.password.
在调用实例方法chang_password的时候,我们不用对参数self进行赋值,只需要对self后面的参数赋值就可以了。也就是说11111对应的参数是new_password.
实例方法chang_password实现的功能就是把实例变量password改为新的值new_password。
实例方法change_password实现的功能是把实例变量password改为新的值new_password。
实例tom_account调用了实例change_password,因此相应的实例变量self.password发生了改变。

我们说过,静态方法知识把函数放到了类体里面,它能做的事情和函数完全一致。下面我们尝试把静态方法和实例方法结合起来使用。
场景(需求):实例tom_account一次性向银行存了100万,成为了银行的大客户,银行要把tom的存款利率提高2%。

class BankAccount(object):
    interest_rate = 0.05
    def __init__(self,name,this_id,password,money):
        self.name=name
        self.this_id =this_id
        self.password=password
        self.money=money
    
    #提高利率的静态方法
    @staticmethod
    def raise_interest(old_interest_rate):
        new_interest_rate=old_interest_rate + 0.02
        return new_interest_rate
    
    #存款的实例方法
    def save_money(self,money):
        self.money=self.money+money
        if self.money>=1000000:
            print('%s存款超过了100万,变成了大客户,利率提高百分之2'%self.name)
            #调用静态方法
            self.interest_rate=self.raise_interest(self.interest_rate)

tom = BankAccount("Tom", "123456", "888888", 1000)
print("%s原来的存款利率为%f" %(tom.name, tom.interest_rate))
# 调用实例方法
tom.save_money(1000000)
print("%s现在的存款利率为%f" %(tom.name, tom.interest_rate))
Tom原来的存款利率为0.050000
Tom存款超过了100万,变成了大客户,利率提高百分之2
Tom现在的存款利率为0.070000

类的定义、类的变量和构造方法(请同学解释)先来分析一下静态方法:
我们定义了名为raise_interest的静态方法,静态方法有一个参数old_interest_rate,静态方法的作用是对这个参数加0.02并赋值给new_interest_rate,最后返回这个新利率。
接着,我们定义了一个存款的实例方法save_money,第一个参数self是固定格式,第二个参数是money,表示存款的数额。
我们首先把实例变量self.money增加相应的存款数额,然后判断新的存款数额是否大于100万,如果大于100万,就调用静态方法,并把返回值赋值给实例变量self.interest_rate。
类体之外的代码依次是创建实例tom,打印出tom原来的利率,调用tom的存款实例方法,打印出tom现在的利率。
顺便一提的是,前面老师说过实例方法仅仅能被实例调用,但是实际上它也能被类“强行调用”。
调用方法为类名.实例方法(实例名,其他参数),这里的实例名替代了self参数的位置。但是Python不建议这样使用实例方法。
实际上,Python里面有很多可以但是不被提倡的操作,比如我们后面要讲的私有变量、私有方法也可以在类体外部进行调用。这些操作也是Python生态的一部分。

类方法

前面讲到,类方法同类变量一样,可以对所有实例产生作用,可以被类和实例调用,这是类方法在用法上的特点。
在语法上,类方法既需要静态方法的装饰器,又像实例方法一样,需要加一个功能与self相同的东西,写作cls。cls是类的英文单词class的缩写。
之前我们学习过,self.的含义是:使用self.来访问实例的变量或者方法;也提到过cls.的含义是:使用cls.来访问类的变量或者方法。
还记得上周我们在类体之外改变了类变量interest_rate,老师还提到,由于Python的封装性,它不羁爱你一在类体外对类变量进行操作,如果要操作,最好的方式也是使用类方法。
需求:现在尝试建立一个类方法,让它能够调整interest_rate这个类变量。

class BankAccount(object):
    interest_rate = 0.05
    def __init__(self,name,this_id,password,money):
        self.name=name
        self.this_id=this_id
        self.password=password
        self.money=money
    @classmethod
    def change_interest_rate(cls,x):
        cls.interest_rate=cls.interest_rate+x

print("银行账户类原来的类变量interest_rate是%f" % BankAccount.interest_rate)
BankAccount.change_interest_rate(-0.01)
print("银行账户类现在的类变量interest_rate是%f" % BankAccount.interest_rate)
tom = BankAccount("Tom", "123456", "888888", 1000)
print('Tom的利率是%d'%tom.interest_rate)
银行账户类原来的类变量interest_rate是0.050000
银行账户类现在的类变量interest_rate是0.040000
0.04

现在我们来分析一下代码。类方法的定义需要首先使用装饰器classmethod进行装饰,然后定义一个函数,函数的第一个参数应该是cls。
本例中,我们还添加了一个参数 x ,表示利率变动的大小。
在类方法的函数体内,使用 cls.interest_rate 表示类变量 interest_rate ,将 x 加到它身上后重新赋值给 cls.interest_rate 。
在类体之外,我们首先打印出当前银行账户类的类变量interest_rate,然后使用类名.类方法来调用我们定义的类方法,传入的参数是-0.01,表示类变量interest_rate减少0.01,最后打印出了新的类变量值。
需求:类方法也可以使用实例名.类方法的方式调用,请你尝试。

class BankAccount(object):
    interest_rate = 0.05
    def __init__(self, name, this_id, password, money):
        self.name = name
        self.this_id = this_id
        self.password = password
        self.money = money
    @classmethod
    def change_interest_rate(cls, x):
        cls.interest_rate = cls.interest_rate + x
tom = BankAccount("Tom", "123456", "888888", 1000)
print("银行账户类原来的类变量interest_rate是%f" % BankAccount.interest_rate)
# 补充下面一行代码,使用实例调用类方法
tom.change_interest_rate(0.02)
print("银行账户类现在的类变量interest_rate是%f" % BankAccount.interest_rate)
# jerry = BankAccount("Jerry", "123", "11111", 2000)
print('Tom的利率是%f'%tom.interest_rate)
银行账户类原来的类变量interest_rate是0.050000
银行账户类现在的类变量interest_rate是0.070000
Tom的利率是0.070000

可以得到的结论是,使用实例名.类方法的方式也能调用类方法,并且也能对类方法中涉及的类变量进行修改。
在这里插入图片描述

类的封装性

封装性是类的三大特征之一,它的意思是类的一些变量和方法不能从外部进行访问和调用。

私有变量

默认下类的变量是公有的,比如实例变量可以通过实例名.实例变量在类体外进行访问,类变量可以通过实例名.类变量或者类名.类变量在类体外进行访问。
如果想要这些变量成为私有变量,私有变量只能在类内部使用,不能在类外部被调用,可以在变量前加上双下划线__。
例如对于银行账户类,我们之前定义了一个实例方法叫做save_money,作用是为某个实例存一笔钱。
实际应用中,当我们存款时,银行会给我们一个交易单号来作为这笔交易的凭证。为了安全起见,交易单号应该是唯一的、随机的且不可改变的。
回顾之前的知识,我们可以取随机数来确保随机性;可以取用户this_id和当前时间来确保唯一性;对于不可改变性,我们可以使用私有变量存储。
因此,生成这个单号的关键代码应该是:单号对应的私有变量 = 用户this_id + 当前时间 + 随机数 ,请实现该需求。

import time
import random
class BankAccount(object):
    interest_rate = 0.05
    def __init__(self, name, this_id, password, money):
        self.name = name
        self.this_id = this_id
        self.password = password
        self.money = money
    # 提高利率的静态方法
    @staticmethod
    def raise_interest(old_interest_rate):
        new_interest_rate = old_interest_rate + 0.02
        return new_interest_rate
    # 存款的实例方法
    def save_money(self,money):
        self.money=self.money+money
        if self.money>=1000000:
            print("%s存款超过了100万,变成了大客户,利率提高百分之2" % self.name)
        #添加私有变量
        self.__trading_number = self.this_id+str(time.time())+str(random.random())
        print("交易成功,您的交易单号为%s"%self.__trading_number)
tom = BankAccount("Tom", "123456", "888888", 1000)
tom.save_money(1000000)
Tom存款超过了100万,变成了大客户,利率提高百分之2
交易成功,您的交易单号为1234561588679533.9068190.5216114203037693

self.__trading_number = self.this_id+str(time.time())+str(random.random())
其中,赋值运算符左边是一个实例私有变量,右边是实例this_id、当前时间、随机数三个字符串的拼接。
现在,这个实例变量self.__trading_number是私有的,我们不能在类体之外访问它。你可以这样做一下试试。
运行下面代码,看看能否在类体之外访问私有变量。

import time
import random
class BankAccount(object):
    interest_rate = 0.05
    def __init__(self, name, this_id, password, money):
        self.name = name
        self.this_id = this_id
        self.password = password
        self.money = money
    # 提高利率的静态方法
    @staticmethod
    def raise_interest(old_interest_rate):
        new_interest_rate = old_interest_rate + 0.02
        return new_interest_rate
    # 存款的实例方法
    def save_money(self, money):
        self.money = self.money + money
        if self.money >= 1000000:
            print("%s存款超过了100万,变成了大客户,利率提高百分之2" % self.name)
            self.interest_rate = self.raise_interest(self.interest_rate)
        self.__trading_number = self.this_id + str(time.time()) + str(random.random())
        print("交易成功,您的交易单号为%s" % self.__trading_number)
tom = BankAccount("Tom", "123456", "888888", 1000)
tom.save_money(1000000)
print(tom.__trading_number)
Tom存款超过了100万,变成了大客户,利率提高百分之2
交易成功,您的交易单号为1234561588679692.36790510.7228706215820622



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-25-50c8e7f606f6> in <module>
     23 tom = BankAccount("Tom", "123456", "888888", 1000)
     24 tom.save_money(1000000)
---> 25 print(tom.__trading_number)


AttributeError: 'BankAccount' object has no attribute '__trading_number'

你肯定得到了错误,错误类型是AttributeError: ‘BankAccount’ object has no attribute ‘__trading_number’,也就是属性错误,银行账户类里面没有__trading_number这个属性。
这个例子我们定义的私有变量是个实例变量,当然类变量也可以定义成私有变量,定义方法是在类变量前加两个下划线,感兴趣的同学们可以试一下。
当然我们前面也提到过,私有变量也可以在类外部进行访问,只不过Python不建议这样做。访问的方法为实例名._类名私有变量名。你只需要知道这一点就可以了,最好不要这样做。

私有方法

私有方法和私有变量一样,也是为了封装一些不想被外部访问的方法。它们的命名规则也是一样的,即私有方法也是在方法名之前加两个下划线。
私有方法的应用场景:商家有时候会根据用户的存款来判断用户的偿付能力,从而决定是否同意用户分期付款等。商家查询用户的存款需要用户的授权,所以显示用户余额的实例方法应该定义成一个私有方法,这个私有方法能够被类体中的授权私有方法调用,但却不能在类体外调用。现在,在原来的类的基本机构基础上,创建一个私有方法__print_money(私有方法都是以两个下划线开头)和一个普通实例方法authorize(授权的英文单词)。

class BankAccount(object):
    interest_rate = 0.05
    def __init__(self, name, this_id, password, money):
        self.name = name
        self.this_id = this_id
        self.password = password
        self.money = money
    def __print_money(self):
        print("该用户的存款余额为%f元" % self.money)
    def authorize(self, shop_name):
        print("%s获得了%s的授权,现在可以打印该用户的余额了。" % (shop_name, self.name,))
        self.__print_money()
tom = BankAccount("Tom", "123456", "888888", 1000)
tom.authorize("工商学院电脑部")
工商学院电脑部获得了Tom的授权,现在可以打印该用户的余额了。
该用户的存款余额为1000.000000元

如果你尝试直接在类体外调用私有实例方法__print_money,程序一样会报错。感兴趣的同学可以自己尝试一下。
同学们对刚才我们的例子有没有很面熟的感觉?没错,商家使用支付宝的芝麻积分来评价用户就是用的这样类似的做法。

属性

类的封装性对变量的要求要比方法的要求严苛,在真正意义上的面向对象编程中,一个类可以有公有方法,但不应该有公有的实例变量。
这就意味着,我们无法在类体外直接访问所有的实例变量。
对于那些确实需要访问和修改的实例变量,我们使用公有的getter访问器进行访问,使用公有的setter访问器进行修改。
这种使用访问器的实例变量就叫做属性,我们先直接展示属性的定义方法,然后慢慢解释。

class BankAccount(object):
    interest_rate = 0.05
    def __init__(self, name, this_id, password, money):
        self.name = name
        self.__this_id = this_id
        self.__password = password
        self.__money = money
    def get_password(self):
        return self.__password
    def set_password(self, password):
        self.__password = password
tom = BankAccount("Tom", "123456", "888888", 1000)
print("%s原来的密码是%s" %(tom.name, tom.get_password()))
tom.set_password("666666")
print("%s现在的密码是%s" %(tom.name, tom.get_password()))
Tom原来的密码是888888
Tom现在的密码是666666

我们发现,为了让私有变量可以被访问和修改,我们实际上是定义了两个实例方法get_password和set_password,然后调用这两个方法来访问和修改密码。
这里的get_password和set_password就分别是私有变量__password的getter访问器和setter访问器。
然而,现在对私有变量的访问和修改都是通过调用实例方法来实现的,略显麻烦。
为了解决这个问题,Python预定义了属性装饰器property。
在定义getter访问器和setter访问器之前加上property装饰器,就可以使用变量名进行访问和直接使用赋值语句进行修改了。
现在,让我们把property装饰器加上,运行下面的代码,体会property装饰器的用法。

class BankAccount(object):
    interest_rate = 0.05
    def __init__(self, name, id, password, money):
        self.name = name
        self.__id = id
        self.__password = password
        self.__money = money
    @property
    def password(self):
        return self.__password
    @password.setter
    def password(self, password):
        self.__password = password
tom = BankAccount("Tom", "123456", "888888", 1000)
print("%s原来的密码是%s" %(tom.name, tom.password))
tom.password = "666666"
print("%s现在的密码是%s" %(tom.name, tom.password))
Tom原来的密码是888888
Tom现在的密码是666666

从定义访问器开始讲解。上述代码首先使用了property装饰器进行修饰,然后定义了一个实例方法,实例方法名称就是私有变量名称(去掉双下划线),函数体和之前相同。这就是getter访问器。
为了定义setter访问器,首先使用定义好的实例方法名称.setter作为装饰器,然后仍然用私有变量名称定义实例变量,函数体依然和前面相同。
这样,原来的私有实例变量__password就成为了一个属性,我们可以直接通过实例名.属性名进行访问和修改了。
需要注意的是,在定义属性的时候,首先应该定义getter访问器,再定义setter访问器。因为当只有定义了getter访问器,属性名.setter装饰器才是有效的。
总结
在这里插入图片描述

到目前为止,关于类的内容你已经学习大半,仅有类的继承性和多态性没有学习到了。
面向对象编程本身就是编程中最难啃的硬骨头,但是也是最有“滋味”的骨头。现在几乎所有的主流语言(C语言除外)都支持面向对象编程,面向对象编程也是解决大型工程问题的必备技能。
但是面向对象编程对于初学者来说确实有很大的难度。幸运的是,在使用Python解决一些日常问题的时候,基本用不到面向对象的编程方法,你现在要做的就是尽可能地去理解它,特别是它的编程思想。
当你在使用模块解决问题的时候,你会发现大多数的模块都是由封装好的一个个类构成,因此,理解了面向对象编程的思想会让你在借助模块等工具解决问题的时候更加得心应手。

作业

作业1

我们已经成功定义了一个银行账户类,银行账户有很多我们“熟悉”的方法。比如说银行账户类应该有贷款方法。为了确定贷款的利息,应该再增加一个信用评分方法,根据用户的信用评分确定利息。现在你实现贷款吧

题目要求:
在银行账户类中增加一个贷款loan的实例方法,传入贷款年限和金额参数;增加一个查看用户信用评分__credit_score的私有方法,返回值从列表[1,2,3,4]中选择;贷款方法调用信用评分的方法,根据信用评分的返回值计算贷款利率:loan_rate = 0.05*信用评分;按照利滚利的方式计算本息和:total_money = 贷款金额*(1+loan_rate)**贷款年限。最后打印出贷款信息。
动手实践
书写代码时请注意以下几点:
1、分别定义实例方法loan和静态方法__credit_score,静态方法之前别忘了加装饰器@ staticmethod;
2、当前情况还无法确定用户的真实信用评分,我们采用模拟的方法:使用random.choice(列表)来从列表中随机选择一个值。例如,random.choice([1,2,3]),返回的值可能是1,2或者3。请注意,在使用random之前别忘了import random;在私有方法中返回这个值,在实例方法中调用私有方法,得到信用评分实例变量self.score。
3、构造一个实例,然后调用贷款方法,记得打印出贷款信息来检查程序是否正确。

作业2

在定义类的构造方法时,Python要求你考虑的更加全面。具体表现在:对于在类中可能用到的实例变量,应该在定义构造方法时就给它们一个初始值。一般来说,数值类型的初始值设为0,字符串初始值设为空字符串,高级数据类型设为相应的空列表、空字典等。
现在你修改一下练习题1中的代码,让构造方法更加规范吧。

题目要求:
练习题1中,我们在贷款的实例方法中增加了五个实例变量,分别是self.loan_money,self.loan_year,self.score,self.loan_rate,self.total_money。现在请为这五个变量在构造方法中设置合理的初始值。
动手实践
本练习是为了让你学会使用更规范的定义类的方式,在构造方法中设置初始值的方法很简单,直接使用self.变量名=初始值就可以了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值