Python面向对象设计特点
类
定义类
类定义格式如下:
class A: pass class B(A): pass
属性和方法
在java和C++中,分别提供了this引用和this指针,表示当前的调用对象或指针,在Python中,则提供了类似的self参数,在对方法进行调用时,会自动提供self参数,但是必须在参数列表中包含该参数,因此,方法的定义格式如下:
class A: def func(self): print("我是普通方法")
有了self参数,类的属性就是通过self参数来存取,如:
class A: def func(self,name,age): self.name = name self.age = age print("我是普通方法")
完整的类定义如下:
class A: def __init__(self,name,age): self.name = name self.age = age def func(self): print(666) 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) a1 = A('alex',1000) print(a1.name) #'alex" print(a1.func()) # 666 None print(a1.__eq__(a1)) # True print(a1.__str__()) #Animal(alex,1000 print(a1.__repr__()) #Animal('alex',1000
其中以_开头和结尾的方法都来自于基类object,当定义好类后,就可以了导入人类所在模块后使用了,
如:
import Module_A dog = Module_A dog.eat()
如果想要定义一个私有的属性,则在属性前面加上__就变成私有的了
如:
class A: def __init__(self,name='alex',age=1000): self.__name=name self.age=age if __name__ == "__main__": dog = A() print(dog.__name) #AttributeError: 'A' 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(A): pass
子类可以对父类的方法进行重写,如果子类想调用父类的方法,可以使用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装饰器,使用方式如下:
# class A: # def __init__(self,name,age): # self.name = name # self.age = age # def func(self): # print(666) # 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) # # a1 = A('alex',1000) # print(a1.name) #'alex" # print(a1.func()) # 666 None # print(a1.__eq__(a1)) # True # print(a1.__str__()) #Animal(alex,1000 # print(a1.__repr__()) #Animal('alex',1000 class A: def __init__(self,name='alex',age=1000): self.__name=name self.age=age if __name__ == "__main__": dog = A() print(dog.__name) #AttributeError: 'A' object has no attribute '__name'
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