1.封装
定义:
数据角度 :将基本数据类型 复合成一个自定义类型。
作用:可读性更高,将数据与对数据的操作相关联。
行为角度 :对类外提供必要的功能,隐藏实现的细节
作用:让调用者不必了解实现代码,也能调用我们写的功能
让调用者操作变得简单。
私有化成员:
定义 :变量名双下划线开头
如 self.__name = name
本质:障眼法,可以通过对象._类名__成员名调用。
2.继承
定义:重用现有类的功能,并在此功能上进行扩展。
多个类型有相同的行为,且概念统一,这个时候我们就可以将多个类型统一的行为抽出来做一个类,而这个类就为多个类的父类,当创建子类时继承父类,就可以继承父类中已经有的行为。
通俗一些的讲,就是子类继承父类,就相当于子类复制粘贴了父类代码。
继承语法:
class Person:
pass
class Student(Person):
Pass
# 代码1-1
代码1-1中class Person其实就相当于,class Person(object),python代码中,创建一个类时,当不选择父类时,默认继承object,因此:任何类都直接或间接继承object类。
鸭子原则
其实:默认继承object体现了鸭子原则的精神,关心的是对象的行为,而不是对象的类型,只要对象的行为符合我们的期待,我们就可以将其视为所需要的类型。为什么代码1-1Person后面可以不写object来明确认父,这是一种思想,想告诉写python的程序员,我管你什么类型和继承关系,你给我实现功能就好了。
代码案例:
class Duck:
def quack(self):
print("Quack!")
class Cat:
def meow(self):
print("Meow!")
def sound(animal):
animal.quack() # 调用Duck类中的quack方法
duck = Duck()
cat = Cat()
sound(duck) # 输出 "Quack!"
sound(cat) # 抛出 AttributeError,因为Cat类没有定义quack方法
上述代码:
因为Cat类没有定义quack方法,因此才抛出异常,若Cat类中有queck方法则正常输出,
这里就能感受到,python只关心对象的行为,而不关心对象的类型,运行时才确定对象类型。而python中这种思想体现在动态类型语言检测和灵活的面向对象编程上。
总结:鸭子原则时一种思想,我们只关系对象的行为,而不关心对象的类型,而在python中这种思想体现在动态类型语言检测和灵活的面向对象编程上。在动态类型语言检测上,我们是在运行程序时才确定对象类型,因此当我们将一个对象当作参数传入一个函数中时,该对象可以时任何类型,只要能实现函数中的对象就不会抛出异常,反之不能满足函数中对象的行为则会抛出类型中没有此方法的异常。
在面向对象的继承上:
即使你不继承父类,但是你有和父类一样的行为,那么你就有了这个父类的特性,
从而可以在python中就可以达到你就是继承这个父类的效果。
类型判断:isinstance, type
class Animal:
@staticmethod
def eat():
print("我吃吃吃")
class Dog(Animal):
@staticmethod
def run():
print("我跑跑跑")
class Bird(Animal):
@staticmethod
def fly():
print("我飞飞飞")
if __name__ == '__main__':
bird = Bird()
bird.fly()
bird.eat()
dog = Dog()
dog.eat()
dog.eat()
animal = Animal()
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True
print(isinstance(animal, Dog)) # False
print(type(dog) == Dog) # True
print(type(dog) == Animal) # False
print(type(animal) == Dog) # False
# 代码1-2
通过上述代码:我们可以得出结论,type和isinstance的区别,用法上的不同,type是判断一个对象是不是一个类型,是就返回True,不是返回False。而isinstance是判断一个对象是不是属于一种类型,像代码1-2中,dog对象虽然是Dog类型,但是Dog继承了Animal类型,所以dog对象也属于动物类型,用isinstance判断返回的是True。反之,animal对象不属于Dog类型,怎么理解,只有说儿子属于爸爸,没有说爸爸属于儿子,animal对象是Animal类型是Dog类型的父类。
构造函数的继承
若子类没有构造函数,将直接使用父类构造函数。
若子类有构造函数,将覆盖父类的构造函数。
若子类有构造函数,并且想使用父类的构造函数的成员,可以用super().init(参数,参数)
python可以多继承,但是我们一般都使用单继承,多实现的思想来设计模型。
继承父类变量的语法与代码
# 继承父类变量
class Car:
def __init__(self, brand="", speed=""):
self.brand = brand
self.speed = speed
class Electric(Car):
def __init__(self, brand="", speed="", capacity="", frequency=""):
super().__init__(brand, speed)
self.capacity = capacity
self.frequency = frequency
electric = Electric("1", "1", "1", "1")
print(electric.capacity, electric.frequency, electric.brand, electric.speed)
# 代码1-3
父类中是共性,子类中是个性。
3.多态
概念:对于父类的一个方法,在不同子类上有不同的体现。
重写:目的是张显个性
双下划线开头和双下划线结尾,这些是python的内置函数。
重写内置函数:
重写之前:
class Animal:
def __init__(self, name="", color=""):
self.name = name
self.color = color
animal = Animal("dog", "white")
print(animal) # <__main__.Animal object at 0x000001E829958948>
# 代码1-4
重写之后:
class Animal:
def __init__(self, name="", color=""):
self.name = name
self.color = color
def __str__(self):
return "name: %s, color: %s" % (self.name, self.color)
animal = Animal("dog", "white")
print(animal) # name: dog, color: white
面向接口编程:
先确定用法,后决定做法,这是一种软件架构设计思想,
在1991年python祖师爷就确定了print()的用法,就是打印一个对象__str__方法的返回值。
这里的重写只是语法上,随便百度就能知道,并不值钱,而这里面牵涉到的软件架构思想才是重中之重,非常重要。
下列是经常会重写的内置函数:
算数运算符:
代码案例:
class Animal:
def __init__(self, name="", age=0):
self.name = name
self.age = age
def __str__(self):
return "name: " + self.name + ", age: " + str(self.age)
def __add__(self, other):
if type(other) == int:
return Animal(self.name, self.age + other)
else:
return Animal(self.name, self.age + other.age)
animal = Animal("dog", 2)
print(animal + 1) # name: dog, age: 3
print(animal + animal) # name: dog, age: 4
在python中 animal + 1 相当于 animal.add(1)
我们再来看看重写__iadd__
class Animal:
def __init__(self, name="", age=0):
self.name = name
self.age = age
def __str__(self):
return "name: " + self.name + ", age: " + str(self.age)
def __iadd__(self, other):
if type(other) == int:
self.age += other
return self
else:
self.age += other.age
return self
animal = Animal("dog", 2)
print(id(animal)) # 140380752334848
animal += 1
print(id(animal)) # 140380752334848
print(animal) # name: dog, age: 3
而__iadd__则是 +=, 同理加减乘除的累计运算符都是比算数运算符多个i
值得一提的是,当一个对象没有__iadd__时使用 += 对象会调用__add__,但是返回的也是一个新对象。而当一个对象没有__add__时,使用+对象时会抛出异常。
比较运算符对应的内置函数:
这里写两个比较常用的重写的代码案例:
"""
图书列表设计
类:BookModel
数据:书名 - name , 价格 - number
行为1,重写__eq__使其在列表容器中能够实现,remove, in , index等功能。
行为2, 重写__lt__ 或 __gt__ 使其在列表容器中能实现,sort 功能
"""
class BookModel:
def __init__(self, name, price):
self.name = name
self.price = price
def __str__(self):
return self.name + " - " + str(self.price)
def __eq__(self, other):
return self.name == other.name
def __lt__(self, other):
return self.price < other.price
list_book = [
BookModel("Java", 200),
BookModel("Python", 100)
]
list_book.sort()
for item in list_book: # Python - 100 Java - 200
print(item)
list_book.remove(BookModel("Java", 200))
for item in list_book: # Python - 100
print(item)
lt:
当自定义类型重写了__lt__方法,自定义类型就可以比较大小,并且放入列表中还能使用列表的方法,如sort(),因为sort的实现代码是迭代列表,取列表中元素,让元素与元素之间比较,当列表中的对象类型没有重写__lt__方法时,会抛出异常。
eq:
当自定义类型写__eq__方法,就可以根据自己的业务逻辑来判断自定义对象是否相等,值得一提的是object默认使用对象的内存进行比较,实现这个方法,如果将自定义对象存入列表,将可以使用列表的romove方法,index方法等。