Python面向对象设计特点
- 具有面向对象的所有特征:封装、继承、多态;
- 所有类的父类为object;
- 子类可以对父类的任何方法都可能进行重写;
- Python中没有提供重载和访问控制,但是属性可以用特殊名进行访问控制
类
定义类
类定义格式如下:
class ClassName:
suite
class ClassName(base_Class):
suite
属性和方法
在java和C++中,分别提供了this引用和this指针,表示当前的调用对象或指针,在Python中,则提供了类似的self参数,在对方法进行调用时,会自动提供self参数,但是必须在参数列表中包含该参数,因此,方法的定义格式如下:
def method(self):
suite
有了self参数,类的属性就是通过self参数来存取,如:
def method(self,x,y):
self.x = x
self.y = y
完整的类定义如下:
class Animal:
def __init__(self,name="Animal",age=0):
self.name = name
self.age = age
def eat(self):
print("{0} eat".format(self.name))
def __eq__(self, other):
return self.name == other.name and self.age == other.age
def __repr__(self):
return "Animal('{0}',{1}".format(self.name, self.age)
def __str__(self):
return "Animal({0},{1}".format(self.name, self.age)
其中以__
开头和结尾的方法都来自于基类object,当定义好类后,就可以导入类所在模块后使用了,如:
import Module_Animal
dog = Module_Animal.Animal("Dog")
dog.eat()
如果想要定义一个私有的属性,则在属性前面加上__
就变为私有了,如:
class Animal:
def __init__(self,name="Animal",age=0):
self.__name = name
self.age = age
if __name__ == "__main__":
dog = Dog()
print(dog.__name) # AttributeError: 'Dog' object has no attribute '__name'
基本特殊方法
__new()__
和__init()__
方法:
和其他语言不同,Python中要创建一个对象,当调用dog = Module_Animal.Animal("Dog")
时,执行了两个步骤:
- 第一步,使用
__new__()
方法创建该对象; - 第二步,使用
__init__()
方法对其进行初始化;
在创建类时,应该重写__init__()
方法进行初始化,__new__()
不必须进行重写。在创建对象时如果发现没有提供,则自动调用object.__new__()
。
如果重写了__init__()
方法并且想要调用基类该方法,则可以通过super()方法:
def __init__(self):
super().__init__()
__eq__(self, other)
和__ne__(self, other)
方法:
这两个方法用于两个对象之间进行比较,对于__eq__()
,如果self和other相等,则返回True;对于__ne__()
,如果self和other不等,则返回True,如:
dog = Animal("Dog")
dog.eat()
cat = Animal("cat")
cat.eat()
print(dog == cat) # return False
isEqual = dog.__eq__(cat)
isNotEqual = dog.__ne__(cat)
print(isEqual) # return False
print(isNotEqual) # return True
- 默认情况下,所有的自定义类都支持
==
进行比较,但总是返回False(除非dog == dog),因此可以通过__eq__()
来实现比较操作。 - 如果提供了
__eq__()
方法但没有提供__ne__()
方法,Python会自动提供__ne__()
和!=
操作符;如果没有提供__eq__()
,调用结果为NotImplemented。 - 默认情况下,自定义类的所有实例都是可哈希运算的,可以调用
__hash__()
方法,也可以作为字典的键,但是如果重写了__eq__()
方法,则该类的实例就不是可哈希运算的了。 - 应避免非同类型间进行比较,可用内置函数isInstance(obj,Obj)处理,第一个参数为对象,第二个为类型,如:
def __eq__(self, other):
if not isinstance(other,Animal):
return NotImplementedError
return self.name == other.name and self.age == other.age
- 对于比较操作符,Python中都提供了特殊的方法:
__le__(self,other)
:self <= other,返回Ture__lt__(self,other)
:self < other,返回Ture__ge__(self,other)
:self >= other,返回Ture__gt__(self,other)
:self > other,返回Ture__ne__(self,other)
:self != other,返回Ture__eq__(self,other)
:self == other,返回Ture
__repr__()
和__str__()
方法:
调用内置repr()函数时,会调用给定对象的__repr__()
方法,该方法用于返回一个特殊的字符串,该字符串可通过内置eval()
函数生成一个Python对象,如:
def __repr__(self):
return "Animal('{0}',{1})".format(self.name,self.age)
if __name__ == "__main__":
cat = Animal("Cat")
ani = eval(repr(cat)) # return Animal('Cat',0)
print(ani) # Animal(Cat,0)
调用内置函数str()
时,会调用给定对象的__str__()
方法,该方法也会返回一个字符串,和__repr__()
的区别主要是该方法产生的字符串主要是便于理解,而不是为了传递给eval()
函数。
如:
def __str__(self):
return "Animal({0},{1})".format(self.name, self.age)
print(str(ani)) # Animal(Cat,0)
- 如果实现了
__repr__()
而没有实现__str__()
时,则在调用str()、print(obj)时也会执行__repr__()
方法。
__hash__()
方法:
默认情况下,所有的类都是可以哈希运算的,但是如果类实现了__eq__()
方法,那么则不可进行哈希运算,通过提供该方法可以使类能狗进行哈希运算,实现如下:
def __hash__(self):
return hash(id(self))
内置hash()
方法根据独一无二的ID来计算哈希值,内置id()
方法返回一个独一无二的整数。
继承和多态
Python中定义一个类继承另一个类格式如下:
class Subclass(BaseClass):
suite
子类可以对父类的方法进行重写,如果子类想调用父类的方法,可以使用super()
进行调用,如:
class Animal:
def __init__(self,name="Animal",age=0):
self.__name = name
self.age = age
def eat(self):
print('Animal is eating')
def shout(self):
print("Animal is shouting")
class Dog(Animal):
def __init__(self,name="Dog",age=0):
# 调用Animal的__init__()方法
super().__init__()
self.name = name
self.age = age
def shout(self):
print("Dog is Shouting")
def eat(self):
print("Dog is eating")
class Cat(Animal):
def __init__(self,name="Cat"):
self.name = name
def shout(self):
print("Cat is shouting")
def eat(self):
print("Cat is eating")
在java等面向对象语言中,多态是指子类的引用指向父类的实例,但是在Python中,可能不能用这种方式来理解,因为Python中定义一个变量或对象并不需要声明数据类型,因此对于Python而言,其多态的表现形式就是:如果子类对父类方法进行了覆盖,则子类对象调用该方法时,会调用子类对方法的实现。如:
if __name__ == "__main__":
ani = Animal()
dog = Dog()
cat = Cat()
ani.eat() # Animal is eating
dog.eat() # Dog is eating
cat.eat() # Cat is eating
python提供了isinstance(o,Obj)
方法,可以判断对象o是否是Obj类型,因此,在使用多态时,可以这样使用:
# 定义一个函数
def animal_eat(animal):
animal.eat()
if isinstance(dog,Animal):
animal_eat(dog) # Dog is eating
if isinstance(cat,Animal):
animal_eat(cat) # Cat is eating
property装饰器
在面向对象的设计中,常常要将属性进行封装,提供setter/getter方法对属性进行操作,Python中也可以提供setter/getter进行对属性的封装,从而保证数据的安全性,但是并不推荐使用,因为有更优的方式可以属性的安全性,下面逐步进行分析。
现在定义一个类:
import math
class Circle:
def __init__(self, radius, x=0, y=0):
self.radius = radius
def area(self):
return math.pi * (self.radius ** 2)
if __name__ == "__main__":
c1 = Circle(2)
c1.radius = -3
print(c1.area())
print(c1.radius) # -3
可以看到,属性radius有可能被设置为不合理的值,因此,可以将radius设置为私有属性,同时提供setter/getter方法对其进行设置,修改该类实现如下:
class Circle:
def __init__(self, radius, x=0, y=0):
if radius <= 0:
self.__radius = 1
self.__radius = radius
def set_radius(self, radius):
assert radius >= 0, "radius can't be zero"
self.__radius = radius
def get_radius(self):
return self.__radius
if __name__ == "__main__":
c1 = Circle(2)
c1.set_radius(-3) # AssertionError: radius can't be zero
print(c1.area())
print(c1.get_radius()) # 2
通过setter/getter可以将属性封装起来,可以保证属性的安全性,但是这种方式比较繁琐,在Python中,更倾向于使用Property装饰器,使用方式如下:
import math
class Circle:
def __init__(self, radius, x=0,y=0):
self.radius = radius
@property # 创建一个radius特性,并且作为getter方法
def radius(self):
return self.__radius
def area(self):
return (self.radius ** 2)*math.pi
@radius.setter # 设置radius的setter方法
def radius(self, radius):
assert radius >0, "radius can't be zero"
self.__radius = radius
if __name__ == "__main__":
c1 = Circle(2)
c1.radius = 7 #调用radius.setter
print(c1.radius) # 调用radius.getter
print(c1.area)
Property装饰器是一个函数,该函数以一个方法作为参数,并返回修饰后的版本。但是通常并不使用该方法,而是通过@
符号来标记,如上例所示。property()是一个内置函数,至多可以接受四个参数:get参数、set参数、delete参数、docstring参数。@property
相当于property(get)。
在上例中,创建了一个__radius私有属性,然后通过property装饰器进行其getter、setter的设置。需要注意:
- getter和setter有同样的名称,如def radius(self);
- 当使用@property创建特性后,每个创建的特性都包含getter、setter、deleter、docstring等属性,并且都是可用的。
- getter为@property设置的方法,其他属性由python设置。
当创建radius装饰器后,就可以通过radius.setter和radius.getter获取和设置__radius属性了,同时保证了私有属性的安全性,如:
c1.radius = 0 # AssertionError: radius can't be zero
c1.radius = 3 # 调用radius(self,radius),相当于radius.setter
print(c1.radius) # 2 调用radius.setter