1 面向对象技术简介
- 面向对象编程(Object-OrientedProgramming, OOP)是现代编程中最常用的设计范式之一。
- python作为一种支持多种编程范式的语言,自然也支持面向对象编程。
- 在python中,面向对象编程主要通过类(Class)和对象(Object)来实现。
1.1 面向对象编程
- python体系认为世间万物皆为对象。它把一些具体的实例,不断往高层次抽象化,从而形成一个模版、蓝图,以便扩展管理。
- 例如:人被抽象成 一个类(object),但是每个实例(个体)又是完全不同的,具体到抽象,抽象再到具体。
- 优点:
- 易维护和拓展
- 提升开发效率
- 代码可读性提升,利于他人阅读
1.2 面向对象核心特性
- 类(class):用来描述具有相同的属性(类属性)和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。
- 对象(object):一个类实例化之后就是一个对象,可以获取实例变量和实例方法等。
- 封装(Encapsulation):在构建类的时候对变量和方法赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法。
- 继承(Inheritance):“子承父业”,一个类可以派生出多个子类,在这个父类里定义的属性、方法自动被子类继承。
- 多态(Polymorphism):“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现。
- 方法重写(overwrite):在继承父类之后,可以修改原先的方法,增加或减少功能。
2 类与对象
2.1 类的定义
- 类是面向对象编程中的核心概念,它是对象的模板或蓝图,描述了一组具有相同特征的对象。
- 类定义了对象的状态(属性)和行为(方法)。
- 语法:
class classname:
声明1
声明2
...
声明n
2.2 类实例
class People:
# 静态属性
name='xiaoming'
# 操作静态属性的方法
def f(self):
return self.name
2.3 对象的定义
- 对象是类的实例,通过调用类的构造函数(init())来创建对象。
- 创建实例:通过对类的实例化从而实现创建实例。
- 示例:
class People:
name='xiaoming'
def f(self):
return "hello world"
p1=People()
print(p1.name)
print(p1.f())
2.4 类的方法
- 类中的方法是定义在类内部的函数,通常用于描述对象的行为。
- python使用 def 关键字来定义一个方法
- 类方法必须包含参数self, 且为第一个参数,self 代表的是类的实例。
2.5 类的属性
- 属性是类的变量,用于描述对象的状态。
- python中属性分为两类:
- 类属性:所有实例共享的属性
- 实例属性:每个实例独有的属性
2.5.1 类属性
- 类属性是与类相关联的属性。它们属于类本身,而不是类的任何特定实例。
- 无论创建多少个类的实例,它们都会共享相同的类属性。
- 类属性通常位于类的顶部,定义在类的任何方法之外。
- 类属性的一个常见用途是存储与类相关的常量或共享的状态信息。例如,可以在类属性中存储数据库连接信息、默认配置或类的共享状态。
- 示例:
class People:
# 定义类属性
name=''
age=''
def shout(self):
print('star:my name is',self.name)
print('star:my age is',self.age)
p1=People()
# 对象.属性
# 当使用对象.属性来访问类属性,值是对象自己的属性
p1.name='sun'
p1.shout()
p2=People()
p2.name='moon'
p2.shout()
# 类.属性
People.age=18
print(People.age)
2.5.2 实例属性
- 实例属性是与类的每个实例相关联的属性。每个类的实例都可以拥有自己的一组实例属性。
- 实例属性通常在类的构造方法(通常是__init__方法)内定义,并使用self关键字来访问。
- 实例属性的一个重要特点是每个实例都有自己独立的一组实例属性。这意味着一个实例的属性值可以与另一个实例不同,它们互不影响。
- 示例:
class round:
PI=3.14
def __init__(self,radius):
self.radius=radius
def getArea(self):
return self.PI*self.radius**2
def getPerimeter(self):
return 2*self.PI*self.radius
r1=round(1)
print(r1.getArea())
print(r1.getPerimeter())
2.6 动态修改类属性和实例属性
- 在python中,可以动态地修改类属性和实例属性。
- 不同的作用范围和用途:
- 类属性属于类本身,而实例属性属于类的每个实例
- 类属性通常用于存储与类相关的常量或共享状态信息
- 实例属性用于存储每个实例特有的数据和状态信息
- 示例:
class People:
num=333
def __init__(self,name,age):
self.name=name
self.age=age
p1=People('a',3)
print(p1.num)
# 修改类属性
p1.age=1
print(f'{p1.name}的年龄是:{p1.age}')
p2=People('b',9)
# 修改实例属性
p2.num=999
print(p2.num)
print(f'{p2.name}的年龄是:{p2.age}')
# 动态添加属性
p2.sex='男'
print(p2.sex)
3 类的专有方法
3.1 __init__函数
- __init__函数是Python中的一个特殊方法(也称为构造函数),它在创建一个对象时被调用。
- 它用于初始化对象的属性,并可以接受参数以提供初始化时的数据。每当创建一个类的实例时,python会自动调用这个方法。
- 通常,==在__init__方法中定义对象的属性,并对其进行初始化。==这样,在创建对象时就可以直接给属性赋值,而不需要再单独调用其他方法来初始化属性。
- 示例:
class People:
def __init__(self,name,age):
self.name=name
self.age=age
self.sex='男'
print('创建对象时调用')
p1=People('a',1)
print(p1)
3.2 __del__函数
- __del__函数是一个特殊的函数(也称为析构函数),用于在对象被删除时执行一些清理操作。它是Python中的一个魔术方法(也称为双下划线方法或特殊方法),用双下划线()包围起来。
- 当一个对象不再被引用或者程序结束时,Python解释器会自动调用该对象的__del__()方法来进行清理工作,例如关闭文件、释放资源等。
- __del__函数可以被重写,以实现自定义的清理逻辑。但需要注意的是,__del__函数的调用时机是不确定的,不应过度依赖它来管理资源,最好使用上下文管理器或者显式地调用清理方法来管理资源。
- 示例:
class People:
def __init__(self,name,age):
self.name=name
self.age=age
self.sex='男'
print('创建对象时调用')
def __del__(self):
print('对象被删除时调用')
p1=People('a',1)
print(p1)
3.3 __repr__函数
- __repr__函数是Python中的一个特殊函数,用于定义对象的字符串表示形式。它返回一个可打印的字符串,通常是一个合法的Python表达式,用于表示该对象的状态。
- __repr__函数在交互式环境中被调用,常用于调试和开发过程中。当调用内置的repr()函数时,会自动调用对象的__repr__函数。
- 示例:
class People:
def __init__(self,name,age):
self.name=name
self.age=age
self.sex='男'
print('创建对象时调用')
def __del__(self):
print('对象被删除时调用')
def __repr__(self):
print("__repr__ is called")
return f'Name:{self.name},Age:{self.age},Sex:{self.sex}'
p1=People('a',1)
print(p1)
print(repr(p1))
3.4 __str__函数
- __str__函数是一个内置方法,用于自定义对象的字符串表示。当通过print()函数打印一个对象时,实际上是调用了该对象的__str__方法,该方法返回一个字符串,表示对象的信息。在自定义一个类时,可以重写__str__方法,以便返回所期望的字符串表示
- 示例:
class People:
def __init__(self,name,age):
self.name=name
self.age=age
self.sex='男'
print('创建对象时调用')
def __del__(self):
print('对象被删除时调用')
def __str__(self):
print("__str__ is called")
return f'Name:{self.name},Age:{self.age},Sex:{self.sex}'
p1=People('a',1)
print(p1)
print(str(p1))
3.5 __repr__函数与__str__函数的区别
- 函数str() 用于将值转化为适于人阅读的形式,而repr()转化为供解释器读取的形式
- 某对象没有适于人阅读的解释形式的话,str()与repr()返回相同,所以print展示的都是str的格式。
- 顺序:优先调用重写的str方法;如果str方法不存在,则调用repr方法;如果repr方法也不存在,则默认输出
- 示例:
class People:
def __init__(self,name,age):
self.name=name
self.age=age
self.sex='男'
print('创建对象时调用')
def __del__(self):
print('对象被删除时调用')
def __str__(self):
print("__str__ is called")
return f'Name:{self.name},Age:{self.age},Sex:{self.sex}'
def __repr__(self):
print("__repr__ is called")
return f'Name:{self.name},Age:{self.age},Sex:{self.sex}'
p1=People('a',1)
print(p1)
print(str(p1))
print(repr(p1))
3.6 __lt__函数
- lt方法的作用是提供 < 运算符
- 在类中定义一个默认的比较方法(指明比较的条件与比较的形式)
- self:当前对象 other:另一个同类对象
- 示例:
class Student:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def __str__(self):
return f"Name: {self.name}\tAge: {self.age}\tSex: {self.sex}"
# 按年龄进行排序
def __lt__(self,other):
# 升序
return self.age < other.age
# # 按年龄进行排序,年龄相同的按姓名排序
# # 使用元组
# def __lt__(self,other):
# return (self.age,self.name) < (other.age,other.name)
s1=Student("zhangsan",20,"male")
s2=Student("lisi",15,"female")
s3=Student("wangwu",25,"female")
s4=Student("zhaoliu",22,"male")
s5=Student("xiaoqi",20,"male")
students=[s1,s2,s3,s4,s5]
students.sort()
for i in students:
print(i)
4 封装
- 封装是一种面向对象编程的概念,它指的是将对象的属性和方法隐藏起来,不直接暴露给外部,并通过公共接口来控制访问。
- 在python中,封装是一种将数据(属性)和操作数据的方法(方法)结合在一起的概念,通常是为了限制对对象内部状态的直接访问,防止外部干扰和误用对象的状态。
- 在程序中,封装可以保护对象的属性和方法,避免被意外修改或调用。这样可以提高程序的安全性和可维护性。另外,封装还可以控制对象的访问权限,比如只允许读取、禁止修改等。
- 封装的实现方式:
- 使用类定义私有属性,即在属性名称前添加双下划线“__”(例如,_ _my_property)。这样定义的属性只能在类内部使用,无法从类外部直接访问。
- 使用类定义私有方法,即在方法名称前添加双下划线“__(例如,_ _my_method)。这样定义的方法只能在类内部使用,无法从类外部直接调用。
- 示例:
class Work:
# 将变量定义为内部(私有)变量,只能通过内部调用
def __init__(self,name,salary):
self.__name=name
if salary<0:
raise ValueError("salary不能为负数")
self.__salary=salary
# 内部调用修改属性
def setSalary(self,salary):
if salary<0:
raise ValueError("salary不能为负数")
self.__salary=salary
# 内部调用获取属性
def getSalary(self):
return self.__salary
def __str__(self):
return f'Worker(name={self.__name},sakary={self.__salary})'
work1=Work('Jone',5000)
print(work1)
work1.setSalary(3000)
# work1.setSalary(-3000)# 抛出异常
# work1.__salary(2000)# 私有变量不能直接修改
print(work1)
- 通过这种方式,可以将对象的属性和行为封装在类内部,并限制外部直接访问或修改这些属性和方法。这可以提高代码的可维护性和安全性,因为外部无法意外地篡改对象的私有属性或方法。
- 注意事项:
- 提供公共接口(public method)来访问和修改对象的属性,比如get/set方法。
- 子类可以继承父类的私有属性和方法,但不能直接访问,必须通过公共接口来实现。
- 私有属性和方法仍然可以通过特殊方法进行访问,比如使用“_类名__属性名”来访问。但这种方式不建议使用,因为它打破了封装的原则,可能会导致程序出现意外行为。
5 继承
- 继承是一种非常重要的特性,它允许定义一个类(称为子类或派生类)继承另一个类(称为基类或父类)的属性和方法。
- 子类可以重用父类的代码,同时也可以添加或修改新的功能。
- 子类(派生类 DerivedClassName)会继承父类(基类 BaseClassName)的属性和方法。
5.1 super()函数
- 通过super()函数实现,调用父类的初始化方法或其他方法
- super()返回一个临时对象,这个对象代表了父类。通过这个对象,可以调用父类的初始化方法(init)或其他方法。
5.2 单继承
class DerivedClassName(BaseClassName):
声明1
声明2
...
声明n
class Animal:
def __init__(self,name,sex):
self.name=name
self.sex=sex
def run(self):
print(f'{self.name} is running')
def eat(self):
print(f'{self.name} is eating')
animal1=Animal('dog','公')
animal1.run()
animal1.eat()
class Dog(Animal):
def __init__(self,name,sex,age):
super().__init__(name,sex)
self.age=age
def look(self):
print(f'{self.name} is looking')
dog1=Dog('wang','公',3)
dog1.run()
dog1.look()
# 可以嵌套继承:b先继承a,c再继承b
class colourDog(Dog):
def __init__(self,name,sex,age,colour):
super().__init__(name,sex,age)
self.colour=colour
def listen(self):
print(f'{self.name} is listening')
colour1=colourDog('blackdog','公',3,'black')
colour1.run()
colour1.look()
colour1.listen()
class SubA:
Att=100
def __init__(self, a1):
self.__a1 = a1
def set_a1(self, a1):
self.__a1 = a1
def get_a1(self):
return self.__a1
def printA(self):
print(self.__a1)
class ChildA(SubA):
def __init__(self, a1, b1):
#在单继承中可以使用super()调用父类的成员
super().__init__(a1)
self.b1 = b1
def printB(self):
# print(self.a1)
#使用super()调用父类的printA()方法
super().printA()
# super方法可以调用父类定义的类属性
# print(super().Att)
# 不要使用super方法调用父类定义的对象属性
# print(super().a1)
print(self.b1)
c=ChildA("hello", "world")
# 子类不能直接访问父类的私有属性
# c.__a1='python'
# 子类可以通过继承的set_a1方法修改父类的私有属性
c.set_a1("python")
c.printB()
- 继承的特性:
- 重用代码:子类可以继承父类的属性和方法,无需重新编写
- 扩展功能:子类可以在继承的基础上添加新的属性和方法,或者修改继承的方法。
- 多态性:允许不同的子类对象对同一方法做出不同的响应。
- 封装:通过继承可以封装不同层级的细节,使得代码更加模块化。
5.3 多继承
- 若是父类中有相同的方法名,而在子类使用时未指定,从左到右查找父类中是否包含方法。
- 语法:
class DerivedClassName(Base1,Base2,Base3,...):
声明1
声明2
...
声明n
class A:
def __init__(self,a1):
self.a1=a1
def AFun(self):
print(f'A:我是a中的方法1:{self.a1}')
def show(self): # 同名方法
print("A:我也是A中的show方法")
class B:
def __init__(self,b1,b2):
self.b1=b1
self.b2=b2
def BFun(self):
print(f'B:我是b中的方法2:{self.b2}')
def show(self): # 同名方法
print("B:我是B中的show方法")
class C(A,B):
def __init__(self,a1,b1,b2,c1,c2):
# 多继承时,super方法只能继承第一个父类
A.__init__(self,a1)
B.__init__(self,b1,b2)
self.c1=c1
self.c2=c2
def CFun(self):
print(f'C:我是c中的方法1:{self.c1}')
c1=C(1,2,3,4,5)
c1.AFun()
c1.BFun()
c1.CFun()
c1.show()
5.4 方法重写
- 子类中出现与父类同名的方法是对父类的方法进行重新(覆盖),参数要一致
- 调用时会优先调用子类的方法,子类中没有再调用父类方法
class A:
def add(self,x,y):
print('A中的add方法')
return x+y
def sub(self,x,y):
print('A中的sub方法')
return x-y
def mul(self,x,y):
print('A中的mul方法')
return x*y
def div(self,x,y):
print('A中的div方法')
return x/y
class B(A):
def div(self,x,y):
print('子类B对父类A中的div方法进行覆盖')
if y==0:
return "Error:y not is zero"
else:
return x/y
a1=A()
print(a1.add(1,2))
print(a1.sub(3,2))
print(a1.mul(2,3))
# print(a1.div(2,0))
b1=B()
print(b1.add(1,2))
print(b1.sub(3,2))
print(b1.mul(2,3))
print(b1.div(2,0))
5.5 __bases__属性
class A:
pass
class B:
pass
class C(A):
pass
class D(B,C):
pass
print(C.__bases__)
print(D.__bases__)
6 多态
- 多态性(Polymorphism)指的是使用相同的接口(例如方法名)来执行不同类的特定任务。
- 多态性允许将对象作为参数传递给函数,而不需要知道对象的实际类型,从而实现灵活的代码设计。
- 实现多态性的基本方式:
- 使用继承:通过继承,可以创建一个基类,然后让不同的子类实现相同的方法。
- 使用鸭子类型(Duck Typing):在Python中,可以不显式声明一个对象的类型,而是通过检查对象是否有必要的方法和属性来使用它。
- 鸭子类型(Duck Typing)是一种编程语言中的动态类型机制,其核心思想是“如果它看起来像鸭子、叫起来像鸭子,那么它就是鸭子”。
- 这种类型机制强调的是对象的行为而不是其类型。
- 原理:通过不同的函数实现相同操作的不同版本。
class MyPrinter:
def print_info(self):
print("进行了打印操作")
# 继承MyPrinter类,保证类中一定包含print_info方法
class MyPrinterColor(MyPrinter):
def print_info(self):
print("进行了彩色打印操作")
class MyPrinterHD(MyPrinter):
def print_info(self):
print("进行了高清打印操作2")
class MyPrinter3D(MyPrinter):
def print_info(self):
print("进行了3D打印操作,立体的")
# python中,函数不光可以接受基础类型为参数,也可以接受对象类型为参数.
# 对象类型参数可以是自定义的类,也可以是内置的类,如list,dict,tuple等.
def myprint(obj):
print(type(obj))
#假定obj是一个对象,调用obj中的指定函数完成相应的操作
obj.print_info()
# 调用自定义类
# mp=MyPrinter()
# mp=MyPrinterColor()
# mp=MyPrinterHD()
mp=MyPrinter3D()
myprint(mp)
7 访问控制修饰符
- 公共访问修饰符:不加任何修饰符,默认情况下所有属性和方法都是公共的,可以被外部访问。
- 保护访问修饰符:在属性或方法名称前加一个下划线“_”,表示该属性或方法是受保护的,只能被类及其子类访问。
- 私有访问修饰符:在属性或方法名称前加两个下划线“__”,表示该属性或方法是私有的,只能被所属类访问,子类和外部均不能访问。
- ==这些访问修饰符并不是强制性的,只是一种约定俗成的方式。==如果违反了这些规则,程序仍然可以编译通过,但可能会导致一些意想不到的行为发生。
7.1 公共访问
- 所有类成员默认都是公共的,即可以从类内部和外部访问。
7.2 保护访问
7.3 私有访问
- 只能在该类中访问。
- 这种访问控制方式的目的是防止外部直接访问和修改对象的状态。
7.4 注意事项
- 访问修饰符只是通过命名约定来实现的,因此这些限制是可以被绕过的。
- 单下划线和双下划线的名称仅仅是一种惯例,并不会影响Python虚拟机或编译器的行为。
- 即使是私有成员变量也可以被访问和修改,但是这种行为会破坏对象的封装性,并且可能导致不可预测的后果。