写两个类,一个Person类,一个Manager类
编写构造函数
赋给实例属性第一个值的通常方法是,__init__构造方法中将他们赋值给self,构造函数方法包含了每次创建一个实例的时候Python会自动运行的代码。
#Add record field initialization
class Person:
def __init__(self,name,job,pay):
#Constructor takes 3 arguments
self.name = name
#Fill out fields when created
self.job = job
#self is the new instance object
self.pay = pay
传入的数据作为构造函数方法的参数附加到一个实例上,并且将他们赋给self以保持持久。
在OO术语中,self就是新创建的实例对象,而name、job和pay变成了状态信息,即保存在对象中供随后使用的描述数据。
__init__其实没有什么奇妙的地方,当一个实例的时候,会自动调用它并且他有特殊的第一个参数。它仍然是一个常规特性。我们可以为它的参数提供默认值,从而当参数的值不可用的情况下,就不必提供参数值。
为了便于说明,使job参数成为可选的参数,他将默认为None,意味着所创建的人(目前)没有工作。如果job默认为None,使pay也默认为0,以保持一致性。事实上,我们必须为pay指定一个默认值,因为根据Python的语法规则,一个函数定义中,在第一个拥有默认值的参数之后的任何参数,都必须拥有默认值:
#Add default for constructor arguments
class Person:
def __init__(self,name,job=None,pay=0):
#Normal function args
self.name = name
self.job = job
self.pay = pay
def giveRaise(self,percent):
self.pay = int(self.pay*(1+percent))
def __str__(self):#Added method String to print
return "[Person(人物): %s,%s]"%(self.name,self.pay)
这段代码意味着当我们创建Person的时候,需要给name传入值,但是job和pay现在是可选的,如果忽略这两个值的话,他们默认为None和0。通常self参数由Python自动填充以引用实例对象,把值赋给属性就会将赋值给新的实例。
#Test the class
sue = Person("Sue Jones",job="dev",pay=10000)#Runs __init__ automaticlly
print(bob.name,bob.pay)
print(sue.name,sue.pay)
从技术上讲,bob和sue都是命令空间对象,像所有的类实例一样,它们每一个都拥有各自类所创建的状态信息的独立副本。由类的每一个实例都有自己的一组self属性,类通过这种方式来记录多个对象的信息是再自然不过的事情了,就像是内置类型一样,类充当一种对象工厂。
运算符重载
除了__init__之后,__str__方法,每次一个实例转换为其打印字符串的时候,__str__都会自动运行。
class manager(Person):#Define a subclass of Person
这行代码意味着我们定义了一个名为Manager的新类,它继承自超类Person并且可能向Person添加一些定制。
#扩展方法:不好的方式
class Manager(Person):#Inherit Person attrs
def giveRaise(self,percent,bonus=.10): #Redefine to custmize
self.pay = int(self.pay*(1+percent+bonus)) #Bad:cut-and-paste
#扩展方法:好的方式
class Manager(Person):
def giveRaise(self,percent,bonus=.10):
Person.giveRaise(self,percent+bbonus)
这段代码利用了一个事实:类方法总是可以在一个实例中调用,或通过类来调用。
下列二者相同:instance.method(args…) == class.method(instance,args…)
我们想要以为Manager定制构造函数逻辑的方式来自动提供一个的工作名。涉及代码,我们想要重新定义Manager中__init__方法,从而提供mgr字符串。而且和giveRaise的定制一样,我们还想通过类名的调用来运行Person中最初的__init__,以便它仍然会初始化对象的状态信息属性。
#Add customization of constructor 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 __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=100000)
print(bob)
print(sue)
print(bob.lastname(),sue.lastname())
sue.giveRaise(.10)
print(sue)
tom = Manager('Tom Jones',50000)
tom.giveRaise(.10)
print(tom.lastName())
print(tom)
class语句
class语句是对象的创建者并且是一个隐含的赋值运算——执行时,它会产生类对象,并把其引用值存储在前面所使用的变量名。此外,像def一样,class语句也是真正的可执行代码。直到Python抵达并运行定义的class语句前,你的类都不存在(一般都是在其所在模块被导入时,在这之前都不会存在)。
1> 一般形式
class是符合语句,其缩进语句的主体一般都出现在头一行下边。在头一行中,超类列在类名称之后的括号内,由逗号相隔。列出一个以上的超类会引起多重继承
class <name>(superclass,...):
data = value
def method(self,...):
self.mber = value
在class语句内,任何赋值语句都会产生类属性,而且还有特殊名称方法重载运算符。例如名为__init__的函数会在实例对象构造时调用
方法
1>.方法位于class语句的主体内,是由def语句建立的函数对象。从抽象的视角来看,方法替实例对象提供了要继承的行为。从程序设计来看,方法的工作方式与简单函数完全一致,只是有个重要差异:方法的第一个参数总是接受方法调用的隐性主体,也就是实例对象。
换句话说,Python会自动把实例方法的调用对应到类方法函数,如下所示:
instance.method(args...)
这会自动翻译成以下形式的类方法函数调用:
class.method(instance,args...)
除了方法属性名称是正常的继承外,第一个参数就是方法调用背后唯一的神奇之处。在类方法中,按惯例第一个参数通常通常称为self(严格说,只有其位置重要,而不是它的名称)。这个参数给方法提供了一个钩子,从而返回调用的主体,也就是实例对象:因为类可以许多实例对象,所以需要这个参数来和管理每个实例彼此各不相同的数据。
在python中,self一定要在程序代码中明确地写出:方法一定要通过self来取出或修改由当前方法调用或正在处理的实例的属性。这种让self明确化的本质是有意设计的:这个变量名存在,会让你明确脚本中使用的是实例属性名称,而不是本地作用域或全局作用域的变量名。
如下:
class NextClass: #Define class
def printer(self,text):
self.message = text
print(self.message)
变量名printer引用了一个函数对象。因为这是class语句的作用域中赋值,就会变成类对象的属性,被由这个类创建的每个实例所继承。通过实例予以调用。
x = NextClass()
x.printer('instance call')
x.message
当通过对实例进行点号运算调用它时,printer会先通过继承将其定位,然后self参数会自动赋值为实例对象(x)。text参数会获得在调用时传入的字符串(‘instance call’)。
方法能通过实例或类本身两种方法其中的任意一种进行调用。可以通过类的名称调用printer,只要明确地传递一个实例给self参数。
NextClass。printer(x,'class call')
x.message
2>.调用超类构造函数
方法一般是通过实例调用的。不过,通过类调用方法也扮演一些特殊的角色。常见的场景涉及了构造函数。就像所有属性__init__方法是由继承进行查找的。也就是说,在构造时,Python会找出并且只调用一个__init__。如果要保证子类的构造函数也会执行超类构造时的逻辑,一般都必须通过类明确地调用超类的__init__方法。
class Super:
def __init__(self,x):
...default code...
class sub(Super):
def __init__(self,x,y):
Super.__init__(self,x)
...custom code...
这是代码有可能直接调用运算符重载方法的环境之一,如果真的想运行超类的构造方法,自然只能用这种方式进行调用:没有这样的调用,子类会完全取代超类的构造函数。
继承
像class语句这样的命名空间工具的重点就是变量名继承。
在Python中,当对对象进行点号运算时,就会发生继承,而且涉及了搜索属性定义树(一个或多个命名空间)。每次使用object.attr形式的表达式时(object是实例或类对象),Python会从头至尾搜索命名空间,先从对象开始,寻找所能找到的第一个attr。这包括在方法中对self属性的引用。因为树中较低的定义会覆盖较高的定义,继承构成了专有化基础。
class Super:
def method(self):
print("in Super.method")
def delegate(self):
self.action()
class Inheritor(Super):
pass
class Replacer(Super):
def method(self):
print("in Replacer.method")
class Extender(Super):
def method(self):
print('starting Extender.method')
class Provider(Super):
def action(self):
print('in Provider.action')
class Sub(Super):
def method(self):
print('starting Sub.method')
Super.method(self)
print('ending Sub.method')
if __name__ == '__main__':
for klass in (Inheritor,Replacer,Extender):
print('\n'+klass.__name__+'...')
klass().method()
print('Provider...')
x = Provider()
x.delegate()
注:这个例子末尾的自我测试程序代码会在for循环中建立三个不同类实例。因为类是对象,你可将他们放在元组中并且通过通用方式创建实例。类也有特殊的__name__属性,就想模块。默认为类首行中的类名称的字符串。
注意上面例子中的Provider类是如何工作的。当通过Provider实例调用delegate方法时,有两个独立的继承搜索会发生:
1.在最初x.delegate的调用中,Python会搜索Provider实例和它上层的对象,知道在Super中找到delegate的方法。实例x会像往常一样传递给这个方法的self参数。
2.在Super.delegate方法中,self.action会对self以及它上层的对象启动新的独立继承搜索。因为self指的是Provider实例,在Provider子类中会找到action方法。