class随笔(三)

编写并改进类

目标:编写两个类,Person--(创建并处理关于人员信息的一个类)和Manage--(一个定制的Person,修改了继承的行为)

步骤1:创建实例,模块使用小写字母开头,类名使用大写字母开头。

# Add record field initialization
class Person:
    def __init__(self, name, job, pay):    # 构造函数拥有三个参数
        self.name = name    # 创建构造函数时填入各个参数
        self.job = job    # self是新的实际对象
        self.pay = pay

有关Person类的第一件事情就是记录关于人员的基本信息,在python中,这叫做实例对象属性(name, job, pay),通常通过给类方法函数中的self属性赋值来创建。

赋给实例属性第一个值的通常方法是在__init__构造函数方法中将它们赋给self,构造函数方法包含了每次创建一个实例的时候python自动运行的代码。

self为新创建的实例对象,而name, job, pay变成了状态信息,即保存在对象中供随后使用的描述性数据。

虽然此处,参数名出现了两次(self.name = name),但实际上,name参数在__init__中是一个本地变量,但self.name是实例的一个属性,它暗示了方法调用的内容。通过self.name = name把本地的name赋给了self.name属性。

当产生一个实例的时候,会自动调用__init__构造函数,并且此函数会有特殊的第一个参数self。可以为它的参数提供默认值。(根据python语法规则,一个函数定义中,在第一个拥有默认值的参数之后的任何参数,都必须拥有默认值)

# Add defaults for constructor arguments
class Person:
    def __init__(self, name, job=None, pay=0):
        self.name = name
        self.job = job
        self.pay = pay

 

在添加更多功能之前,先来测试目前的代码:生成类的几个实例,并且显示构造函数所创建的它们的属性。

# Add incremental self-test code
class Person:
    def __init__(self, name, job=None, pay=0):
        self.name = name
        self.job = job
        self.pay = pay

bob = Person('Bob Smith')
sue = Person('Sue Jones', job='dev', pay=10000)    # 自动运行__init__函数
print(bob.name, bob.pay)    # 获取相关的属性
print(sue.name, sue.pay)

上述代码会打印:
Bob Smith 0
Sue Jones 10000

此处的bob对象针对job和pay接受了默认值,但sue显式地提供了值。类的每一个实例都有自己的一组self属性,类充当对象工厂。

 但是,当文件作为脚本运行的时候,或者当它作为一个模块导入的时候,顶层的print语句才最佳运行,那么每次导入文件的时候,都会看到测试代码的输出,所以需要进行一些变化,只有当文件为了测试而运行,而不是导入文件的时候,在文件底部运行测试语句才好安排。__name__检查模块的设计如下:

# Allow this file to be imported as well as run/tested
class Person:
    def __init__(self, name, job=None, pay=0):
        self.name = name
        self.job = job
        self.pay = pay

if __name__ == '__main__':    # when run for testing only
    # self-test code
    bob = Person('Bob Smith')
    sue = Person('Sue Jones', job='dev', pay=10000)
    print(bob.name, bob.pay)
    print(sue.name, sue.pay)

由上述代码可得:当文件作为顶层脚本运行的时候,测试它,因为此时__name__是__main__,会有测试代码输出。但当将它作为类库导入的时候,则不会有输出。

 

步骤2:添加行为方法,通过嵌入和处理列表及字符串这样的基本核心数据类型来完成工作。

# Process embedded built-in types: strings, mutability
class Person:
    def __init__(self, name, job=None, pay=0):
        self.name = name
        self.job = job
        self.pay = pay

if __name__ == '__main__':
    bob = Person('Bob Smith')
    sue = Person('Sue Jones', job='dev', pay=10000)
    print(bob.name, bob.pay)
    print(sue.name, sue.pay)
    print(bob.name.split()[-1])    # 得到bob的姓氏lastname
    sue.pay *= 1.10    # give a raise
    print(sue.pay)

上述代码输出:
Bob Smith 0
Sue Jones 10000
Smith
11000.0

直接对bob.name和sue.pay进行操作,通过基本操作来修改bob和sue的属性。

但是,像这样在类之外的硬编码操作可能会导致未来的维护问题(如果要修改代码,就有可能需要修改很多地方的分散代码)。所以,需要进行代码封装---把操作逻辑包装到界面之后,这样,每种操作只需要编码一次,如果将来需要修改,只需要修改一个版本即可。可以随意在副本内修改代码,而不会影响到原始代码。

# 为了便于维护,加入封装方法
class Person:
    def __init__(self, name, job=None, pay=0):
        self.name = name
        self.job = job
        self.pay = pay
    def lastName(self):    # 建立行为方法,类的属性lastName
        return self.name.split()[-1]    # 直接获取姓氏
    def giveRaise(self, percent):    # 加薪需要一个额外的参数
        self.pay = int(self.pay * ( 1+precent))    # 原地修改self.pay

if __name__ == '__main__':
    bob = Person('Bob Smith')
    sue = Person('Sue Jones', job='dev', pay=10000)
    print(bob.name, bob.pay)
    print(sue.name, sue.pay)
    print(bob.lastName(), sue.lastName())    # 因为是类的方法,所以需要()
    sue.giveRaise(.10)    # 使用新方法
    print(sue.pay)

上述代码输出:
Bob Smith 0
Sue Jones 10000
Smith Jones
11000

方法是附加给类并旨在处理类的实例的函数。实例是方法调用的主体,并且会自动传递给方法的self参数。

代码分析:这一次,lastName逻辑已经封装到了方法中,可以对类的任何实例使用它。Python通过自动把实例传递给第一个参数,通常是self,从而告诉一个方法应该处理哪个实例:

  • 在第一个调用bob.lastName(), bob是隐藏的主体,传递给了self
  • 在第二个调用sue.lastName(), sue传递给了self

 

步骤3:运算符重载,常见的有__init__,__str__等。其中,__str__,每一个实例转换为其打印字符串时,__str__都会自动运行。

# Add __str__ overload method for printing objects
class Person:
    def __init__(self, name, job=None, pay=0):
        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: %s, %s]' % (self.name, self.pay)

if __name__ == '__main__':
    bob = Person('Bob Smith')
    sue = Person('Sue Jones', job='dev', pay=10000)
    print(bob)
    print(sue)
    print(bob.lastName(), sue.lastName())
    sue.giveRaise(.10)
    print(sue)

上述代码输出:
[Person: Bob Smith, 0]
[Person: Sue Jones, 10000]
Smith Jones
[Person: Sue Jones, 11000]

 

步骤4:通过子类定制行为

class Manager(Person):    # 从Person继承
    def giveRaise(self, percent, bonus=.10):    # 重新定义以定制Manager的加薪方法
        #self.pay = int(self.pay * (1+percent+bonus))
        Person.giveRaise(self, percent+bonus)

代码分析:若是按照self.pay = int(self.pay * (1+percent+bonus)来重新定义,那么相当于复制粘贴之前的代码。如果日后需要进行改变加薪方式,将必须修改两个地方,而不是一个地方的代码。所以,最好的扩展方法的方式是:使用扩展的参数来直接调用其最初的版本。

类方法的调用:可以在一个实例中调用,或者通过类来调用-----> instance.method(args)   或 class.method(instance, args)。通过类名调用的方式,需要自己给self发送一个实例,对于giveRaise这样的内部代码,self已经是调用的主体,并且由此将实例传递过去。

 

# Add customization of one behavior in a subclass
class Person:
    def __init__(self, name, job=None, pay=0):
        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: %s, %s]' % (self.name, self.pay)

class Manager(Person):
    def giveRaise(self, percent, bonus=.10):
        Person.giveRaise(self, percent+bonus)
    
if __name__ == '__main__':
    bob = Person('Bob Smith')
    sue = Person('Sue Jones', job='dev', pay=10000)
    print(bob)
    print(sue)
    print(bob.lastName(), sue.lastName())
    sue.giveRaise(.10)
    print(sue)
    tom = Manager('Tom Jones', 'mgr', 5000)    # 创建一个Manager实例,调用构造函数__init__
    tom.giveRaise(.10)    # 调用方法(Manager中的giveRaise方法)
    print(tom.lastName())    # 继承方法
    print(tom)    # 调用继承的__str__

上述代码输出:
[Person: Bob Smith, 0]
[Person: Sue Jones, 10000]
Smith Jones
[Person: Sue Jones, 11000]
Jones
[Person: Tom Jones, 6000]    

 

 

步骤5:定制构造函数,重新定义Manager中的__init__方法,从而提供mgr字符串,而且和giveRaise的定制一样,还可以通过类名的调用来运行Person中最初的__init__,以便它仍然会初始化对象的状态信息属性。

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)

这样的话,就可以直接用tom = Manager('Tom Jones', 5000)然后打印来测试代码。当需要在构造的时候运行更高的__init__方法,必须通过超类的名称手动调用它们。这样,可以明确指出哪个参数传递给超类的构造函数(Person.__init__(self, name, 'mgr', pay))。

 

步骤6:使用内省工具,现在在打印tom的时候,Manager依然会把它标记为Person,这是因为Manager是一种定制的和特殊化的Person,然后应尽可能用确切的类来显示对象。

其次,当前的显示格式只是显示了包含在__str__中的属性,而没有考虑未来有可能增加的属性。所以需要使用python的内省工具来解决这两个问题。

内省工具是特殊的属性和函数,允许我们访问对象实现的一些内部机制。常用如下:

instance.__class__属性提供了一个从实例到创建它的类的链接。类有一个__name__,还有一个__bases__提供超类的访问。我们可以使用这些来打印创建一个实例的类的名字,而不是通过硬编码。

object.__dict__属性提供了一个字典,带有一个键值对,以便每个属性都附加到一个命名控件对象。下面是这些工具的使用情形(应该用交互模式,本段代码省略):

from person import Person
bob = Person('Bob Smith')
print(bob)
-------->[Person: Bob Smith, 0]    # show bob`s__str__

bob.__class__
--------><class 'person.Person'>    # show bob`s class and its name

bob.__class__.__name__
-------->'Person'

list(bob.__dict__.keys())    # 属性是实际上的字典
-------->['pay', 'job', 'name']

for key in bob.__dict__:
    print(key, '=>', bob.__dict__.[key])    # 显示键值对
--------> pay => 0, job => None, name => Bob Smith

所以,可以在超类中把接口投入使用,以显示准备的类名并格式化任何类的一个实例的所有属性。建立一个新的class,由于其__str__重载用于通用的内省工具,它将会对任何实例生效,并且由于这是一个类,所以它自动变成一个公用的工具,可以混合到想要使用它显示格式的任何类中。作为额外的好处,以后如果想要改变实例的显示,只需要修改这个类,下一次运行时,继承其__str__的每一个类都将自动选择新的格式。如下:

# File classtools.py
'Assorted class utilities and tools'
class AttrDisplay:
    def gatherAttrs(self):
        attrs = []
        for key in sorted(self.__dict__):
            attrs.append('%s=%s' % (key, getattr(self, key)))
        return ','.join(attrs)
    def __str__(self):
        return '[%s: %s]' % (self.__class__.__name__, self.gatherAttrs())

if __name__ == '__main__':
    class TopTest(AttrDisplay):
        count = 0
        def __init__(self):
            self.attr1 = TopTest.count
            self.attr2 = TopTest.count+1
            TopTest.count += 2
    class SubTest(TopTest):
        pass

X, Y = TopTest(), SubTest()
print(X)
print(Y)

 

那么,Person可以比AttrDisplay中继承更好的__str__方法,所以可以将Person类中的__str__删除。

类的最终形式即为:

# File person.py(final)
from classtools import AttrDisplay    # 导入display类

class Person(AttrDisplay):    # 继承AttrDisplay
    def __init__(self, name, job=None, pay=0):
        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+precent))

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__':
    bob = Person('Bob Smith')
    sue = Person('Sue Jones', job='dev', pay=10000)
    print(bob)
    print(sue)
    print(bob.lastName(), sue.lastName())
    sue.giveRaise(.10)
    print(sue)
    tom = Manager('Tom Jones', 5000)
    tom.giveRaise(.10)
    print(tom.lastName())
    print(tom)


上述代码输出:
[Person: job=None, name=Bob Smith, pay=0]
[Person: job=dev, name=Sue Jones, pay=10000]
Smith Jones
[Person: job=dev, name=Sue Jones, pay=11000]
Jones
[Manager: job=mgr, name=Tom Jones, pay=6000]

 

步骤7:把对象存储到数据库中,python自动选用shelve模块

# File makedb.py: store Person objects on a shelve database
from person import Person, Manager
bob = Person('Bob Smith')    # load our classes
sue = Person('Sue Jones', job='dev', pay=10000)    # re-create objects to be stored
tom = Manager('Tom Jones', 5000)

import shelve
db = shelve.open('persondb')    # filename where objects are stored
for object in (bob, sue, tom):
    db[object.name] = object    # use object`s name attr as key, store objects on shelve by key
db.close()    # close after making changes
import shelve
db = shelve.open('persondb')
len(db)
--------> 3
list(db.keys())
--------> ['Tom Jones', 'Sue Jones', 'Bob Smith']
bob = db['Bob Smith']    # Fetch bob by key
print(bob)    # runs __str__ from AttrDisplay
--------> [Person: job=None, name=Bob Smith, pay=0]
bob.lastName()
-------->    'Smith'    # runs lastName from Person
for key in db:
    print(key, '=>', db[key])    # iterate, fetch, print
--------> Tom Jones => [Manager: job=mgr, name=Tom Jones, pay=50000]
........
for key in stored(db):
    print(key, '=>', db[key])    # iterate by stored keys
--------> Bob Smith => [Person: job=None, name=Bob Smith, pay=0]
# File updatedb.py: update Person object on database
import shelve
db = shelve.open('persondb')    # reopen shelve with same filename
for key in stored(db):
    print(key, '\t=>', db[key])    #iterate to display database objects and prints with custom format
sue = db['Sue Jones']    # index by key to fetch
sue.giveRaise(.10)    # update in memory using class method
db['Sue Jones'] = sue    # assign to key to update in shelve
db.close()    # close after making changes

 

 

 

 

 

转载于:https://www.cnblogs.com/calvinsun/p/7139377.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值