面向对象编程是最有效的软件编写方法之一。
class Person:
'''一个person类'''
def __init__(self, name=' ', age=1, sex=' ', address='', phonenum=''):
"""初始化属性name和age"""
self.name = name
self.age = age
self.sex = sex
self.address = address
self.phonenum = phonenum
self.Info = {'name': name,
'age': age,
'sex': sex,
'address': address,
'phonenum': phonenum,
}
def setname(self, name):
self.name = name
def getname(self):
return self.name
def setage(self, age):
self.age = age
def getage(self):
return self.age
在这里我们创建了一个Person类,这个类中有init、setname,getname,setage,getage等方法。
类中的函数称为方法。 你在前面学到的有关函数的一切都适用于方法,就目前而言, 唯一重要的差别是调用方法的方式。init()方法是一个特殊的方法,每当我们创建Person类的一个实例的时候,Python都会自动运行它。
在这个方法的名称中, 开头和末尾各有两个下划线,这是一种约定, 旨在避免Python默认方法与普通方法发生名称冲突。 务必确保__init__()的两边都有两个下划线, 否则当你使用类来创建实例时, 将不会自动调用这个方法, 进而引发难以发现的错误。
在这里__init__()方法定义成包含三个形参:self、name和age,在这个方法的定义中,形参self必不可少,而且必须位于其他形参的前面。
为何必须在方法定义中包含形参self呢? 因为Python调用这个方法来创建Person实例时, 将自动传入实参self。 每个与实例相关联的方法调用都自动传递实参self, 它是一个指向实例本身的引用, 让实例能够访问类中的属性和方法。 创建Person实例时, Python将调用Person类的方法__init__()。 我们将通过实参向Person()传递名字和年龄, self会自动传递, 因此不需要传递它。 每当根据Dog类创建实例时, 都只需给最后两个形参(name和age) 提供值。
以self为前缀的变量可供类中的所有方法使用, 可以通过类的任何实例来访问。
person1 = Person('Wu', 23, 'male', 'Jiangsu,Nanjing', '1234567890')
print(f"I'm {person1.name}")
print(person1.Info)
name = person1.getname()
print(f"My name is {name}")
我们通过Person类来实例化变量person1,通过person1 = Person('Wu', 23, 'male', 'Jiangsu,Nanjing', '1234567890')
这一语句,即实例化了一个Person类的对象person1,person1具有name,age,sex等属性。
要访问实例的属性, 可使用句点表示法。例如print(f"I'm {person1.name}")
,这里使用person1.name,就可以访问person1的name属性,句点表示法在Python中很有用,在这里,Python先找到实例person1,再获取与实例相关的属性name。
在调用方法上,同样可以使用句点表示法。例如name = person1.getname()
通过调用getname()方法来获取person1的name属性。
可使用类来模拟现实世界中的很多情景。类编写好后, 你的大部分时间将花在根据类创建的实例上。 你需要执行的一个重要任务是修改实例的属性。 可以直接修改实例的属性, 也可以编写方法以特定的方式进行修改。
class Car:
"""car类"""
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
return (str)(self.make + self.model + self.year)
def read_odometer(self):
print(f"This car has {self.odometer_reading} miles on it")
在构造函数中,有一个自有变量odometer_reading初始化为0,该变量并不需要从外界传入参数,可以理解一辆新车的里程数应该为0。
if __name__ == '__main__':
car1 = Car('Hongqi', 'L5', '2009')
print(car1.get_descriptive_name())
car1.read_odometer()
Hongqi L5 2009
This car has 0 miles on it
可以看到这里我通过Car类实例化一个对象car1,并给出一些属性值,然后我们使用句点表示法,使用了一些Car类的函数,输出car1的信息。
我们能以三种方式修改属性的值: 直接通过实例进行修改, 通过方法进行设置, 以及通过方法进行递增(增加特定的值) 。
car1.odometer_reading = 500
car1.read_odometer()
This car has 500 miles on it
假设这辆车已经开了500英里,那我们可以通过句点表示法直接访问car1实例的属性odometer_reading来修改为500,
def update_odometer(self, mileage):
self.odometer_reading = mileage
我们在Car类中定义一个新的函数update_odometer()方法,该方法有一个self参数和一个mileage,在这个方法中可以对odometer_reading重新设置值。
car1.update_odometer(500)
car1.read_odometer()
This car has 500 miles on it
通过调用car1的update_odometer()方法,我们可以对其里程数进行设置达到与通过访问属性进行设置一样的目的。
注释:Python的封装,在我看来是一种不够彻底的封装,如果能够在类的外部来对某个实例化的对象的属性进行直接访问,我认为是十分不安全的,因为稍微熟悉代码的人就可以对其做出修改。
面向对象的编程的特点之一就是继承。编写类时,并非总要从空白开始,如果要编写的类是另一个现成类的特殊版本,可以进行继承。被继承的类成为父类,新继承而来的类成为子类。即继承使类之间具有了父子关系,当父类做某种改动的时候,就可能会对子类造成某些影响,而子类做一些改动的时候,并不会对父类有什么影响。
子类继承了父类的所有属性和方法,同时还可以定义自己的属性和方法。
子类会继承父类的__init__()方法,并且通常调用调用父类的初始化方法,这并不是说一定要进行调用,
class ElectricCar(Car):
"""Electric Car类"""
def __init__(self, make, model, year, battery):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
self.battery = battery
我们写一个电动汽车类,在这里我们写了一个初始化函数,这个函数的参数与父类Car类非常相似,只是多了一个battery这个参数用来记录车辆现在的电量。
ecar1 = ElectricCar('Wuling', 'MiniEV', '2022', 300)
ecar1.read_odometer()
This MiniEV has 0 miles on it
虽然我没有在ElectricCar中定义函数read_odometer()函数,但是由于EletricCar是继承于Car类,所以有Car的所有方法。当然在ElectricCar类的__init__()函数在很多方面与父类Car的__init__()函数相似,所以我们也可以调用super()函数,让ElecteicCar实例包含这个方法中定义的所有属性。父类也称为超类(Superclas
s),名称super由此而来。
class ElectricCar(Car):
"""Electric Car类"""
def __init__(self, make, model, year, battery):
super().__init__(make, model, year)
self.battery = battery
在这里我调用父类Car的__init__()函数来初始化一部分数据。
注释:继承最大的好处是代码的复用性提高,很多代码完全没必要重复。而对于一些具有共同特征的类,我们可以抽象出来一个更底层的类作为父类,让其他类在这个父类上扩展。例如ElectricCar、Truck、Bus等有一些共同的特征像是制造商、型号、出产年份等信息,这些共有信息就可以抽象出一个底层的Car类。这样当我们为Electric、Truck、Bus等类添加某些共有的属性或方法时,就可以直接在Car类上进行更改,而不必对每个类都进行修改。其次,每个类所具有的方法或属性更加符合自己的需要,保持自己的个性。总之,继承确实能够帮助提高代码的复用性,并且使得程序逻辑更好。
显然,ElectricCar类可以做一些改进,初始化函数的battery到底是指该车的现有电量还是指最大电量呢,我们应该将这个在程序中体现出来,
class ElectricCar(Car):
"""Electric Car类"""
def __init__(self, make, model, year, max_battery, cur_battery=0):
super().__init__(make, model, year)
self.__MAXBATTERY = max_battery#我们将maxbattery设置为私有变量,让其不能在类外随意更改
self.cur_battery = cur_battery
def get_MaxBattery(self):
return self.__MAXBATTERY
def reset_MaxBattery(self, max_battery):
"""
更改私有变量必须要通过类的方法来进行更改。
"""
self.__MAXBATTERY = max_battery
def get_CurBattery(self):
return self.cur_battery
def set_CurBattery(self, cur_battery):
self.cur_battery = cur_battery
这里,我们将最大电量值设为一个私有属性,私有属性是指不能在类地外部被使用或直接访问。
注释:__private_attrs:两个下划线开头,声明该属性为私有,不能在类地外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。
我们要尽可能确保数据的安全,所以总共有两个电量值,一个是最大电量,这个值一旦被设定就不应该轻易更改,另一个则是目前电量,它是实时变化的。ElectricCar类继承于Car类,但是它又拥有属于自己的属性MAXBATTERY和cur_battery。并且拥有自己的方法get_MaxBattery()等。
ecar1 = ElectricCar('Wuling', 'MiniEV', '2022', 300, 0)
ecar1.read_odometer()
# print(ecar1.__MAXBATTERY)
print(ecar1.get_MaxBattery())
ecar1.reset_MaxBattery(250)
print(ecar1.get_MaxBattery())
ecar1.set_CurBattery(120)
print(ecar1.get_CurBattery())
This MiniEV has 0 miles on it
300
250
120
我们并不是一定要完全按照父类的方法来执行,有时候父类的某些方法对于子类来说并不合适,那我们就可以重写父类的方法。其原理就是重载函数。首先重写父类的方法,一定是方法名与父类中的方法名完全相同,这样才是重写。
当Python执行该类的实例,并调用该方法的时候,并不会执行父类的方法,因为子类中的同名方法已经将父类的方法覆盖掉。其实也是相当好理解,就是程序不会舍近求远,程序会优先执行与其关系最密切的类的方法。既然子类中有该方法,就不会去执行父类的方法,如果子类中没有该方法,就会向下寻找其父类的该方法。
属性并不是要求其是一个基本的数据类型,我们可以把一个类的实例作为属性,对于电动汽车来说,当我们逐渐丰富代码时,可以考虑把电池也作为一个类。
class Battery:
""" Battery类"""
def __init__(self, max_battery, cur_battery=0):
self.__MAXBATTERY = max_battery
self.cur_battery = cur_battery
def get_MaxBattery(self):
return self.__MAXBATTERY
def reset_MaxBattery(self, max_battery):
"""
更改私有变量必须要通过类的方法来进行更改。
"""
self.__MAXBATTERY = max_battery
def get_CurBattery(self):
return self.cur_battery
def set_CurBattery(self, cur_battery):
self.cur_battery = cur_battery
这是一个电池类,我们在原来的ElectricCar中抽象出来的。
class ElectricCar(Car):
"""Electric Car类"""
def __init__(self, make, model, year, max_battery, cur_battery=0):
super().__init__(make, model, year)
self.battery = Battery(max_battery, cur_battery)
def get_MaxBattery(self):
return self.battery.get_MaxBattery()
def reset_MaxBattery(self, max_battery):
self.battery.reset_MaxBattery(max_battery)
def get_CurBattery(self):
return self.battery.get_CurBattery()
def set_CurBattery(self, cur_battery=0):
self.battery.set_CurBattery(cur_battery)
可以看到,在ElectricCar类的__init__()方法中我们创建了一个属性battery,它是Battery类的实例self.battery = Battery(max_battery, cur_battery)
,这是一个很好的方法。
Python标准库是一组模块, 我们安装的Python都包含它。我们可以通过import关键字来导入这些模块。
from random import randint
if __name__ == '__main__':
number = randint(0, 1000)
standardTimes = 10
playerAnswer = -1
playerTimes = 0
while(playerAnswer != number and playerTimes < 15):
playerAnswer = int(input("input your guess: "))
playerTimes += 1
if playerAnswer > number:
print("too big")
elif playerAnswer < number:
print("too small")
else:
break
if playerTimes <= 10:
print("score:100, Good! ")
elif playerTimes <= 15:
print("score:75")
else:
print("score:60")
input your guess: 500
too small
input your guess: 750
too small
input your guess: 875
too small
input your guess: 937
too big
input your guess: 906
too small
input your guess: 921
too small
input your guess: 929
too big
input your guess: 925
too big
input your guess: 923
score:100, Good!
该程序是一个猜数字的游戏,由程序随机选出一个在01000之间的数字,然后由玩家来进行猜测。程序会提示玩家该次猜测是大于正确答案还是小于正确答案,并且玩家最多有15次猜测机会。通常来说,猜测一个在01000之间的数字,最多只需要10次机会,所以能在10次以内猜中正确答案的,会得到高的分数。
类名应采用驼峰命名法, 即将类名中的每个单词的首字母都大写, 而不使用下划线。 实例名和模块名都采用小写格式, 并在单词之间加上下划线。
对于每个类, 都应紧跟在类定义后面包含一个文档字符串。 这种文档字符串简要地描述类的功能, 并遵循编写函数的文档字符串时采用的格式约定。 每个模块也都应包含一个文档字符串, 对其中的类可用于做什么进行描述。
可使用空行来组织代码, 但不要滥用。 在类中, 可使用一个空行来分隔方法; 而在模块中, 可使用两个空行来分隔类。
需要同时导入标准库中的模块和你编写的模块时, 先编写导入标准库模块的import语句, 再添加一个空行, 然后编写导入你自己编写的模块的import语句。在包含多条import语句的程序中, 这种做法让人更容易明白程序使用的各个模块都来自何处。
s