在上一章我们讲对象的初始化的时候讲到了这个问题,因为这里想扩充一点内容,并且将构造函数和析构函数放在一起进行对比。
python的构造和析构函数为固定的名字。
构造函数--------------------- init( self )
析构函数--------------------- del( self )
构造函数在类的构造的时候调用,但是不是一定会调用(在用__new__的时候);析构函数被python的垃圾回收器销毁的时候调用。
直接用代码说明问题:
class Simple:
def __init__( self ):
print( "constructor called, id={0}".format( id( self )))
def __del__( self ):
print( "destructor called, id={0}".format( id( self )))
def func( sef ):
print( "Simple func" )
a=Simple()
a.func()
执行结果为:
constructor called, id=2862159673272
Simple func
destructor called, id=2862159673272
析构函数在程序结束时被自动调用,为了看的更清楚一点我们再看另一个代码:
class FooType(object):
def __init__(self, id):
self.id = id
print self.id, 'born'
def __del__(self):
print self.id, 'died'
def make_foo():
print 'Making...'
ft = FooType(1)
print 'Returning...'
return ft
print 'Calling...'
ft = make_foo()
print 'End...'
执行结果为:
Calling...
Making...
1 born
Returning...
End...
1 died
在程序终止时调用了这个析构函数,不是在ft退出make_foo里的作用域时。再次强调析构函数是在程序结束时被调用。
关于第一个代码如果我们再对变量a实例化一次会怎么样呢:
class Simple:
def __init__( self ):
print( "constructor called, id={0}".format( id( self )))
def __del__( self ):
print( "destructor called, id={0}".format( id( self )))
def func( self ):
print( "Simple func" )
a=Simple()
a=Simple()
执行结果:
constructor called, id=2979293345720
constructor called, id=2979295044104
destructor called, id=2979293345720
destructor called, id=2979295044104
我们发现在这里析构函数不是在构建新对象之前销毁旧对象,而是在构建了新对象之后才销毁旧对象。
补充:
构造函数
如果基类和子类都有__init__,那么子类必须显式的调用的基类的__init__。
析构函数
如果基类和子类都有__del__,那么子类必须显式的调用的基类的__del__。
关于__new__的问题之后有时间补充,因为暂时用不上。
在很多面向对象编程语言中,我们通常会将对象的属性设置为私有的(private)或受保护的(protected),简单的说就是不允许外界访问,而对象的方法通常都是公开的(public),因为公开的方法就是对象能够接受的消息。在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头。
代码演示:
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
test.__bar()
print(test.__foo)
if __name__ == "__main__":
main()
执行结果:
Traceback (most recent call last):
File "C:/Users/MyPC/.PyCharmCE2018.3/config/scratches/python训练/test1.py", line 18, in
main()
File "C:/Users/MyPC/.PyCharmCE2018.3/config/scratches/python训练/test1.py", line 13, in main
test.__bar()
AttributeError: 'Test' object has no attribute '__bar'
但是,Python并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来“妨碍”对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们。
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
test._Test__bar()
print(test._Test__foo)
if __name__ == "__main__":
main()
执行结果
hello
__bar
hello
前面的部分我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我们没有办法检查赋给属性的值是否有效。我们之前的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)以及deleter(删除器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter、setter和deleter方法,使得对属性的访问既安全又方便。
class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age
# 访问器 - getter方法
@property
def name(self):
return self._name
# 访问器 - getter方法
@property
def age(self):
return self._age
# 修改器 - setter方法
@age.setter
def age(self, age):
self._age = age
@name.setter
def name(self, name):
self._name = name
# 删除器 - deleter方法
@name.deleter
def name(self):
del self.name
def play(self):
if self._age <= 16:
print('%s正在玩泥巴.' % self._name)
else:
print('%s正在写程序.' % self._name)
def main():
person = Person('王大锤', 12)
person.play()
person.age = 28
person.name="苏"
person.play()
if __name__ == '__main__':
main()
执行结果:
王大锤正在玩泥巴.
苏正在写程序.
总结:
1.如果想保持属性的私密性同时对属性进行操作,可以用装饰器@property来解决。
2.访问器语法:
@property
def name(self):
return self._name
3.修改器语法:
@name.setter
def name(self, value):
self._name = value
4.删除器语法:
@name.deleter
def name(self):
del self.name
__str__的功能与用法:
1.__str__功能:将实例对象按照自定义的格式用字符串的形式显示出来,提高可读性。
2.实例化的对象在打印或时会默认调用__str__方法,如果类没有重写这个方法,默认调用父类object的__str__方法。
3.object的__str__方法内部是pass,所以打印的是内存地址。如果当前类重写了这个方法,会自动调用重写后的方法。
代码演示:
class Student(object):
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self): #下面是重写后的方法
print("我要可视化实例内容了")
return "Student(%s,%d)"%(self.name,self.age)
s1 = Student("JACK",29)
print(s1)
执行结果:
我要可视化实例内容了
Student(JACK,29)
利用这个方法使用者可以清晰地看到自己实例化对象的参数,当然你也自建各种字符串。
动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__变量来进行限定。需要注意的是__slots__的限定只对当前类的对象生效,对子类并不起任何作用。
代码示例:
class Person(object):
# 限定Person对象只能绑定_name, _age和_gender属性
__slots__ = ('_name', '_age', '_gender')
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩飞行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)
def main():
person = Person('王大锤', 22)
person.play()
person._gender="male"
if __name__=="__main__":
main()
我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示。
from math import sqrt
class Triangle(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
@staticmethod
def is_valid(a, b, c):
return a + b > c and b + c > a and a + c > b
def perimeter(self):
return self._a + self._b + self._c
def area(self):
half = self.perimeter() / 2
return sqrt(half * (half - self._a) *
(half - self._b) * (half - self._c))
def main():
a, b, c = 3, 4, 5
# 静态方法和类方法都是通过给类发消息来调用的
if Triangle.is_valid(a, b, c):
t = Triangle(a, b, c)
print(t.perimeter())
# 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数
# print(Triangle.perimeter(t))
print(t.area())
# print(Triangle.area(t))
else:
print('无法构成三角形.')
if __name__ == '__main__':
main()
静态方法比较类似,Python还可以在类中定义类方法,类方法的第一个参数约定名为cls,它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象),通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象,代码如下所示。
from time import time, localtime, sleep
class Clock(object):
"""数字时钟"""
def __init__(self, hour=0, minute=0, second=0):
self._hour = hour
self._minute = minute
self._second = second
@classmethod
def now(cls):
ctime = localtime(time())
return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)
def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
"""显示时间"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)
def main():
# 通过类方法创建对象并获取系统时间
clock = Clock.now()
while True:
print(clock.show())
sleep(1)
clock.run()
if __name__ == '__main__':
main()
类之间的关系
简单的说,类和类之间的关系有三种:is-a、has-a和use-a关系。
可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,对应的原则称之为里氏替换原则。
子类一般满足:
1.子类获得了父类全部非私有的功能。
2.子类不能继承父类中的私有方法,也不能被调用父类的私有方法。
3.对于父类中扩展的非私有方法,子类可以拿来即用。
有两种继承的方法:
1.经典子类继承的语法:
class Parent(object):
def __init__(self,a):
self.a=a
def fuc(self):
pass
class Sun(Parent):
def __init__(self,a,b): #里面包括父类的变量以及子类的变量
Parent.__init__(self,a) #这里是关键 经典继承语法,里面包括父类的变量
self.b=b #子类变量初始化
def fuc(self):
#可以直接调用父类方法,但是如果要改写必须在子类里面用这个方式 多态
print("")
2.另一种子类继承的方法super( ):
class Parent(object):
def __init__(self,a):
self.a=a
def fuc(self,b):
pass
class Sun(Parent):
def __init__(self,a,c): #里面包括父类的变量以及子类的变量
super(Sun,self).__init__(a) #这里是关键
self.c=c #子类变量初始化
理解下面的代码
class Person(object):
"""人"""
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
print('%s正在愉快的玩耍.' % self._name)
def do(self):
if self._age >= 18:
print('%s正在写代码.' % self._name)
else:
print('%s只能玩泥巴.' % self._name)
class Student(Person):
"""学生"""
def __init__(self, name, age, grade):
super(Student,self).__init__(name, age)
self._grade = grade
@property
def grade(self):
return self._grade
@grade.setter
def grade(self, grade):
self._grade = grade
def study(self, course):
print('%s的%s正在学习%s.' % (self._grade, self._name, course))
class Teacher(Person):
"""老师"""
def __init__(self, name, age, title):
Person.__init__(self,name,age)
self._title = title
@property
def title(self):
return self._title
@title.setter
def title(self, title):
self._title = title
def teach(self, course):
print('%s%s正在讲%s.' % (self._name, self._title, course))
def main():
stu = Student('王小飞', 15, '初三')
stu.study('数学')
stu.do()
t = Teacher('苏', 28, 'phd')
t.teach('Python程序设计')
t.do()
if __name__ == '__main__':
main()
执行结果:
初三的王小飞正在学习数学.
王小飞只能玩泥巴.
苏phd正在讲Python程序设计.
苏正在写代码.
子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。
from abc import ABCMeta, abstractmethod
class Pet(object, metaclass=ABCMeta):
"""宠物"""
def __init__(self, nickname):
self._nickname = nickname
@abstractmethod
def make_voice(self):
"""发出声音"""
pass
class Dog(Pet):
"""狗"""
def make_voice(self):
print('%s: 汪汪汪...' % self._nickname)
class Cat(Pet):
"""猫"""
def make_voice(self):
print('%s: 喵...喵...' % self._nickname)
def main():
pets = [Dog('旺财'), Cat('凯蒂'), Dog('大黄')]
for pet in pets:
pet.make_voice()
if __name__ == '__main__':
main()
在上面的代码中,将Pet类处理成了一个抽象类,所谓抽象类就是不能够创建对象的类,这种类的存在就是专门为了让其他类去继承它。Python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过abc模块的ABCMeta元类和abstractmethod包装器来达到抽象类的效果,如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)。上面的代码中,Dog和Cat两个子类分别对Pet类中的make_voice抽象方法进行了重写并给出了不同的实现版本,当我们在main函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)。