前言
在本节中我们将编写两个类:
- Person:一个创建并处理人员相关信息的类。
- Manager:一个定制化的Person,修改了所继承的行为。
步骤一:创建实例
在python中,模块名使用小写字母开头,而类名使用大写字母开头,这是通用的惯例。我们新的模块文件的命名为person.py。
class Person:
一个模块文件中可以编写任意多的函数和类,但是如果添加了太多与模块不相关的内容,那么我们person.py的名称就意义不大了。当模块拥有一个单一、内聚的用途的时候,他们工作的会更好。
编写构造函数
给实例属性赋初值得常见方法是在_init_构造函数方法中将初值赋给self。构造函数包含了每次创建一个实例时python都会自动运行的代码。
class Person:
def __init__(self, name, job, pay):
self.name = name
self.job = job
self.pay = pay
我们传入的数据作为构造函数方法的参数附加到一个实例上,并且将它们赋给self以长期持有。self就是新创建的实例对象,而name、job、pay则是状态信息,即保存在对象中供随后使用的描述性数据。
测试
编写测试代码:
class Person:
def __init__(self, name, job, pay):
self.name = name
self.job = job
self.pay = pay
if __name__ == "__main__":
bob = Person("Bob Smith", "dev", "10k")
sue = Person(name="Sue jones", job="test", pay="8k")
print("{0} {1} {2}".format(bob.name, bob.job, bob.pay))
print(sue.name, sue.job, sue.pay)
__name__模块检查用于解决只有当文件作为测试运行而不是导入的时候,才执行文件底部的测试语句。只有当把文件作为顶层脚本运行的时候才测试它,因为这个时候的__name__是__main__。但当做模块导入的时候,则__name__等于模块名称。
两种使用代码的方式:
C:\code>python person.py
Bob Smith dev 10k
Sue jones test 8k
Python 3.7.4 (default, Oct 12 2019, 19:06:48)
[Clang 11.0.0 (clang-1100.0.33.8)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import person
>>>
当被导入时,文件定义了类却没有使用它。当直接运行时,文件创建了类的两个实例,并且打印了每个实例的两个属性。
步骤二:添加行为方法
我们要把操作对象的代码编写到类方法中,而不是分散在整个整个程序中。这里就一种封装的思想,就是把操作逻辑的包装到接口之后,这样每种操作在我们的程序中只会编写一次,通过这种方式,如果将来需要修改,那么只需修改一个版本。
class Person:
def __init__(self, name, job, pay):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
if __name__ == "__main__":
bob = Person("Bob Smith", "dev", 10000)
sue = Person(name="Sue jones", job="test", pay=8000)
print("{0} {1} {2}".format(bob.name, bob.job, bob.pay))
print(sue.name, sue.job, sue.pay)
print(bob.lastName(), sue.lastName())
sue.giveRaise(0.10)
print(sue.pay)
C:\code>python person.py
Bob Smith dev 10K
Sue jones test 8000
Smith jones
8800
方法只是附加给类并旨在处理类的实例的常规函数。实例是方法调用的主体,并且会自动传给方法的self参数。
- 在第一个调用 bob.lastName()中,bob是传给self的隐含主体。
- 在第二个调用 sue.lastName()中,sue被传给了self。
步骤三:运算符重载
当我们需要跟踪一个对象的时候,我们发现实例对象默认的显示格式并不是很友好,它显示的是对象的类名以及在内存中的地址。
print("bob:{}".format(bob))
print("sue:{}".format(sue))
bob:<__main__.Person object at 0x1056621d0>
sue:<__main__.Person object at 0x105662210>
我们可以通过使用运算符重载来做出更友好的输出——在一个类中编写这样的方法,当方法在类的实例上运行的时候,方法拦截并处理内置的操作。我们可以使用排在__init__之后第二个常用的运算符重载方法__repr__,以及__repr__的孪生兄弟__str__。每次一个实例转化为其打印字符串的时候,该方法都会自动运行。打印一个对象会显示该对象的__str__或__repr__方法(要么是自己定义的,要么是从父类继承的)所返回的内容。
class Person:
def __init__(self, name, job, pay):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
def __str__(self):
return '[Person_str:%s, %s, %s]' % (self.name, self.job, self.pay)
def __repr__(self):
return '[Person_repr:%s, %s, %s]' % (self.name, self.job, self.pay)
if __name__ == "__main__":
bob = Person("Bob Smith", "dev", 10000)
sue = Person(name="Sue jones", job="test", pay=8000)
print("bob:{}".format(bob))
print("sue:{}".format(sue))
运行方式一
C:\code>python person.py
bob:[Person_str:Bob Smith, dev, 10000]
sue:[Person_str:Sue jones, test, 8000]
运行方式二
Python 3.7.4 (default, Oct 12 2019, 19:06:48)
[Clang 11.0.0 (clang-1100.0.33.8)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from person import Person
>>> bob = Person("Bob Smith", "dev", 10000)
>>> bob
[Person_repr:Bob Smith, dev, 10000]
>>> print(bob)
[Person_str:Bob Smith, dev, 10000]
程序提示:调用print函数的时候使用__str__而交互式命令行调用__repr__。
步骤四:继承,编写子类(多态)
场景:我们来定义一个管理人员,管理人员每次涨工资的时候,除了像往常一样接受传入的百分比外,还会获得一个默认10%的额外奖金。即:如果一个管理人员加薪10%,那么它实际得到20%的增长。
class Manager(Person):
def giveRaise(self, percent, bonus=.10):
现在有两种方式编写Manager的定制方法:一种好的方式和不好的方式。
- 不好的方式
class Manager(Person):
def giveRaise(self, percent, bonus=.10):
self.pay = int(self.pay * (1 + percent + bonus))
- 好的方式
class Manager(Person):
def giveRaise(self, percent, bonus=.10):
Person.giveRaise(self, percent + bonus)
不好的方式是直接复制粘贴代码,基本会使未来的维护工作翻倍。通过类直接调用有效的避开了继承的限制,从而能将调用直接定位到类树的某个特定版本。对于通过类名调用的方式,你需要自己给self传入一个实例,Manger类中对于giveRaise这样的一个内部代码,self已经是调用的主体,因此直接可以通过self传入对应的实例。
下面是本模块完整的代码:
class Person:
def __init__(self, name, job, pay):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
def __str__(self):
return '[%s_str:%s, %s, %s]' % (self.__class__.__name__, self.name, self.job, self.pay)
def __repr__(self):
return '[%s_repr:%s, %s, %s]' % (self.__class__.__name__, self.name, self.job, self.pay)
class Manager(Person):
def giveRaise(self, percent, bonus=.10):
Person.giveRaise(self, percent + bonus)
if __name__ == "__main__":
sue = Person(name="Sue jones", job="test", pay=10000)
print("sue:{}".format(sue))
sue.giveRaise(.10)
print("sue:{}".format(sue))
tom = Manager("Tom Jones", "mgr", 10000)
print("tom:{}".format(tom))
tom.giveRaise(.10)
print("tom:{}".format(tom))
C:\code>python person.py
sue:[Person_str:Sue jones, test, 10000]
sue:[Person_str:Sue jones, test, 11000]
tom:[Manager_str:Tom Jones, mgr, 10000]
tom:[Manager_str:Tom Jones, mgr, 12000]
我们从上面的结果可以看到,sue被分配到person中的giveRaise方法,而tom被分配到Manager中的giveRaise的方法。根据所传递的对象的类型,将会自动运行合适的版本,这就是多态。
步骤五:定制和调用父类的构造函数
我们创建Manager对象的时候,为它提供一个mgr的参数似乎是没有意义的,这已经包含在类中了。我们可以重新定制Manager中的__init__方法,提供mgr字符串。
class Person:
def __init__(self, name, job, pay):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
def __str__(self):
return '[%s_str:%s, %s, %s]' % (self.__class__.__name__, self.name, self.job, self.pay)
def __repr__(self):
return '[%s_repr:%s, %s, %s]' % (self.__class__.__name__, self.name, self.job, self.pay)
class Manager(Person):
def __init__(self, name, pay):
Person.__init__(self, name, 'mgr', pay) # 调用父类的方法
def giveRaise(self, percent, bonus=.10):
Person.giveRaise(self, percent + bonus)
if __name__ == "__main__":
sue = Person(name="Sue jones", job="test", pay=10000)
print("sue:{}".format(sue))
sue.giveRaise(.10)
print("sue:{}".format(sue))
tom = Manager("Tom Jones", 10000) # 可以减少“mgr”参数
print("tom:{}".format(tom))
tom.giveRaise(.10)
print("tom:{}".format(tom))
网友评论