1、面向对象
面向过程: 关注做事的步骤(对事),最直观的想法 ,中间插入东西变化会很大,不好改,所以大型项目驾驭不了
面向对象: 关注找出对象(对人),事情是由人相互的交互 去完成的。对象(人)的描述 分为静态的属性(高矮胖瘦) 和 动态的方法(跳舞)
以下棋为例:
面向过程: 1开始游戏 ->2黑子先走-> 3绘制画面 -> 4判断输赢 -> 5轮到白子 -> 6 绘制画面 -> 7判断 输赢 -> 8返回步骤2 -> 9 输出最后结果
面向对象:棋手 棋盘 裁判
变化由对象间交互完成: 黑方走哪个位置,白方如何应对
变中求不变: 事是人做的,事可变化万千,但人不变
2、封装:定义类
封装:把相关的信息,聚集在一起打包,方便重用,和信息隐藏
2.1 类与对象
在Python中,使⽤class关键字定义类,类是⼀个抽象描述,并不是真正的存在,抽象概念,指的是一类物体
需要把它初始化才会产⽣⼀个真正的事物(实例对象),我们称之为对象
在编程过程中,拥有⾏为(函数)和数据的是对象,⽽不是类
class Person:
pass # An empty block
p = Person()
print(p)
输出结果:
<__main__.Person object at 0x0000022256BD16D0>
创建了类Person的对象(构造函数,创建类实例),将其打印出来
class Person1:
def sayHi(self):
print('Hello, how are you?')
p1 = Person1()
p1.sayHi()
输出结果:
Hello, how are you?
成员函数,创建对象调用
类的成员函数(方法)定义时 必须要传入self参数,类似于C++ this,指向实例对象的实体,即p1
调用的时候 默认前面第一个有个参数是其对象(一定要省,加了就报错了)类似于(错误代码)p1.sayHi(p1)
(正确代码)p1.sayHi()
2.2 类成员变量
1、Python 中所有的类成员(包括数据成员)默认都是公有的,没有权限控制符
2、如果你使⽤的数据成员名称以双下划线前缀,⽐如 __privatevar,Python 的名称管理体系会有效地把它作为私有变量(约定俗成的规定)
3、这样就有⼀个惯例,如果某个变量只想在类或对象中使⽤,就应该以单下划线前缀。⽽其他的名称都将作为公共的,可以被其他类或者对象使⽤。记住这只是⼀个惯例,并不是Python所要求的(与双下划线前缀不同)
注意,在访问类内属性的时候,必须加 self,如:self.name
class Person2:
def __init__(self, name):
self.name = name
def sayHi(self):
print('Hello, my name is', self.name)
p2 = Person2('张三')
p2.sayHi()
输出结果:
Hello, my name is 张三
def __init__(self, name)
:类似于c++构造函数,对象创建的初始化
Person2('张三')
:将字符串 张三 传进去
class Person3:
# 统计人口数量
population = 0
# 类的初始化
def __init__(self, name):
self.name = name
print('(Initializing %s)' % self.name)
Person3.population += 1
# 成员函数
def sayHi(self):
print('Hi, my name is %s.' % self.name)
def howMany(self):
if Person3.population == 1:
print('I am the only person here.')
else:
print('We have %d persons here.' % Person3.population)
zs = Person3('张三')
zs.sayHi()
zs.howMany()
ls = Person3('李四')
ls.sayHi()
ls.howMany()
zs.sayHi()
zs.howMany()
print("\n\n")
输出结果:
(Initializing 张三)
Hi, my name is 张三.
I am the only person here.
(Initializing 李四)
Hi, my name is 李四.
We have 2 persons here.
Hi, my name is 张三.
We have 2 persons here.
class Actor: # 定义类(只是想法,并不分配资源,分配资源要等实例化)
name = '赵丽颖' # 类属性
age = 35
def act(self): # 类行为(方法):形参this必须有,代表类的实例
print(self.name," 会演戏")
def sing(self):
print(self.name," 会唱歌")
obj = Actor() # 创建类的实例对象
print(obj.name) # 访问对象的属性
print(obj.age)
obj.act() # 调用对象的方法(无参,但其实有参,它是隐藏的第一个实参,为类的实例)
obj.sing()
运行结果
2.3 动态添加属性和方法
可以在之后使用时再加,边运行边改变,动态语言特点,逐行解析。C/C++静态语言 先编译后执行 而不是逐行编译执行,所以 定义好了之后就不能改了
class Actor: # 定义类(只是想法)
name = '赵丽颖' # 类属性
age = 35
def act(self): # 类行为(方法):形参this必须有,代表类的实例
print(self.name," 会演戏")
def sing(self):
print(self.name," 会唱歌")
obj = Actor() # 创建类的实例对象
# 动态语言:可动态添加属性和方法(python是动态语言,不像c++静态语言,定义完后,后面不能修改)
obj.addr="成都" # 动态添加属性
print(obj.addr)
def show(self):
print(self.name,self.age,self.addr)
obj.show = show; # 动态添加方法(注意定义函数之后要加入这一句表示其是对象的方法)
obj.show(obj) # 动态添加的要传入对象势力,不能隐藏 之前会先实例化 actor() 分配空间把引用地址 放到obj里面
obj.sing()
运行结果:
3、类的继承
描述种类繁多的对象间的关系,加括号 表示继承自哪一个父类
# 基类(父类)
class SchoolMember:
def __init__(self, name, age):
self.name = name
self.age = age
print('(Initialized SchoolMember: %s)' % self.name)
def tell(self):
print('Name:"%s" Age:"%s"' % (self.name, self.age))
# 派生类(子类)可以直接调用父类的属性 和 方法,不用重复写父类的属性和方法
class Teacher(SchoolMember):
def __init__(self, name, age, salary):
SchoolMember.__init__(self, name, age) # 先调用基类的
self.salary = salary
print('(Initialized Teacher: %s)' % self.name) # 调用父类的对象,使用自己的参数
def tell(self): #派生类
SchoolMember.tell(self) # 调用基类的函数
print('Salary: "%d"' % self.salary) # 调用自己的类中的成员变量
class Student(SchoolMember):
def __init__(self, name, age, marks):
SchoolMember.__init__(self, name, age)
self.marks = marks
print('(Initialized Student: %s)' % self.name) # 把父类的参数也继承了
def tell(self):
SchoolMember.tell(self) # 基类有自己的tell函数,调用基类的函数,加上自己的语句
print('Marks: "%d"' % self.marks)
t = Teacher('张老师', 40, 30000)
s = Student('王二', 22, 75)
members = [t, s] # 列表有两个成员
for member in members:
member.tell()
输出结果:
(Initialized SchoolMember: 张老师)
(Initialized Teacher: 张老师)
(Initialized SchoolMember: 王二)
(Initialized Student: 王二)
Name:“张老师” Age:“40”
Salary: “30000”
Name:“王二” Age:“22”
Marks: “75”
3.1 定义父类
父类 通常是抽象出来的,语法上就是定义一个 普通的类:
class Animal: # 定义类(只是想法)
name = 'animal' # 类属性
def eat(self): # 类行为(方法):形参self必须有(类似this),代表类的实例
print(self.name," can eat")
def breath(self):
print(self.name," can breath")
def run(self):
print(self.name," can run")
3.2 子类继承
继承:复用(不用重复写父类方法)
扩展:子类Cat在继承父类Animal的能力eat,run基础上,可派生新能力,如catchMouse
隐藏:子类会把父类的同名方法挡住,只要有同名(不管参数怎么样),就会调用那个函数,参数不匹配报错。先查找子类的方法,没有才沿着继承树回溯,查找其父类的方法
class Cat(Animal): # 定义子类Cat继承于类Animal(父类)
name='cat'
def catchMouse(self): # 派生的新方法
print(self.name," can catchMouse")
def breath(self):
print(self.name," can breath from air")
c = Cat()
c.catchMouse()
c.run()
运行结果:
class Fish(Animal):
name='fish'
def swim(self): # 派生的新方法
print(self.name," can swim")
def breath(self,str): # 隐藏(遮挡 父类的同名函数)
print(self.name," can breath from ",str)
f = Fish()
f.eat()
f.swim()
#f.breath() 报错,参数部匹配
f.breath('water') # 参数要匹配,只要出现同名他就调用了
运行结果:
不支持自动匹配不同参数的同名函数(在C++里面是支持的),事实上定义 同名的函数它只认最后一个,调用前面一个对应的参数 会报错
class Fish(Animal):
name='fish'
def swim(self): # 派生的新方法
print(self.name," can swim")
def breath(self,str): # 隐藏(遮挡 父类的同名函数)
print(self.name," can breath from ",str)
def breath(self):
print(self.name,"second breath")
f = Fish()
f.eat()
f.swim()
#f.breath() # 报错,参数部匹配
f.breath('water') # 这个都无法顺利执行
报错:
class Fish(Animal):
name='fish'
def swim(self): # 派生的新方法
print(self.name," can swim")
def breath(self,str): # 隐藏(遮挡 父类的同名函数)
print(self.name," can breath from ",str)
def breath(self):
print(self.name,"second breath")
f = Fish()
f.eat()
f.swim()
f.breath() # 跟最后一个匹配
运行结果:
f.breath(); # 会报错参数个数不匹配,因子类会隐藏了父类的同名函数,
# 1.用默认参数解决
# def breath(self,str='water')
# print(self.name," can breath from ",str)
# 注意:用重载不行,下面的同名函数会覆盖上面的同名函数,不会智能匹配参数个数
# 2.用不定参
3.3 多重继承
class Father:
name ="father"
def football(self):
print(self.name,"play football good")
def talk(self):
print(self.name, "talk fast")
class Mother:
name ="mother"
def music(self):
print(self.name,"play music good")
def talk(self):
print(self.name, "talk slow")
class Son(Father,Mother): # 多重继承:儿子同时继承父母天赋
name ="son"
def draw(self):
print(self.name,"draw good")
s =Son()
s.football()
s.music()
s.draw()
s.talk() # python没有二义性问题(c++有),父母都有时,以前一个为准
编程思想的角度 尽量避免,增加了耦合性,事实上 继承都不要乱用(雁群和大雁的关系不是继承,临时的关系 和 固有的关系),继承滥用 会产生类的爆炸
3.4 多态
python 天生多态(动态语言,运行时动态绑定)根据传进来的子类的类型,自动选择对应子类的函数
class Animal: # 定义类(只是想法)
name = 'animal' # 类属性
def eat(self): # 类行为(方法):形参self必须有(类似this),代表类的实例
print(self.name," can eat")
def breath(self):
print(self.name," can breath")
def run(self):
print(self.name," can run")
class Cat(Animal): # 定义子类Cat继承于类Animal(父类)
name='cat'
def catchMouse(self): # 派生的新方法
print(self.name," can catchMouse")
def breath(self):
print(self.name," can breath from air")
def run(self):
print(self.name, "run on the ground")
class Fish(Animal):
name='fish'
def swim(self): # 派生的新方法
print(self.name," can swim")
def breath(self,str): # 隐藏(遮挡 父类的同名函数)
print(self.name," can breath from ",str)
def run(self):
print(self.name, "run in the water")
def sport(obj):
obj.run()
c = Cat()
f = Fish()
sport(c)
sport(f)
运行结果:
3.5 构造函数和析构函数
构造函数:类实例化对象的时候,如果没有自己定义 __init__ (self)
就会有一个默认构造函数(一旦定义就不会调用默认构造函数了),反正 会调用构造函数
class Line:
name = 'a line'
def __init__(self, len=3):
self.length = len
print("调用了构造函数")
def show(self):
print(self.name, "show", self.length)
l = Line()
运行结果:
析构函数:销毁类,把资源还回去,系统有默认的,也可以自己定义 __del__(self)
obj = Line() # 创建对象实例时,自动调用构造函数。
obj.show(); # 执行完自动析构(引用计数方式,进行垃圾回收)
运行完了就算报了错,还是执行析构函数的
class Line:
name = 'a line'
def __init__(self, len):
self.length = len
print("调用了构造函数")
def __del__(self):
print("析构函数调用,释放资源")
def show(self):
print(self.name, "show", self.length)
l = Line()
报错:
完整过程
class Line:
name = 'a line'
def __init__(self, len = 3):
self.length = len
print("调用了构造函数")
def __del__(self):
print("析构函数调用,释放资源")
def show(self):
print(self.name, "show", self.length)
l = Line(9)
l.show()
l2 = Line()
l2.show()
运行结果:
退出的时候把前面的两个对象都销毁了,释放资源(调用析构函数)
3.6 权限
之前所有的类成员 都是在哪都可以访问,设置私有属性,变量前面加__
可以通过 非私有的类方法 访问私有属性
但是其实python天生全开放,加__
只是在语法解析的时候 避免修改,C++严格的安全机制,编成机器码,源码是看不到的
class Girl: # 定义类(只是想法)
name = 'lili'
__age = 30 # 私有属性(以下划线开头)
def registerInfo(self):
print(self.name,'age is ',self.__age) # 私有属性仅内部能访问
def __getAge(self): # 私有方法(以下划线开头)
print('private __getAge is ',self.__age)
g = Girl()
g.registerInfo() # 通过非私有方法访问 私有属性
print(g.name)
# g.__getAge() # 报错: 私有方法外部不能访问
# print(g.__age) # 报错: 私有属性外部不能访问
print(g._Girl__age) # 通过这种方式,外面也能够访问“私有”变量;调试中是比较有用的:类前加_私有变量前加__
运行结果:
3.7 链式调用
能够链式调用的前提是 链上的函数的返回值都要是 对象本身(return self
)
本质上 还是引用的替换
lass Person:
def name(self, str):
self.name = str
return self
def age(self, str):
self.age = str
return self
def show(self):
print(self.name,self.age)
obj=Person()
obj.name("ashergu").age(23).show()
"""
等价于
obj.name("ashergu")
obj.age(23)
obj.show()
"""
例子:用链式调用,实现多关键词的信息查询
class Query():
def __init__(self):
self.query_condition = {}
def filter(self, **kwargs):
self.query_condition.update(kwargs)
return self
query = Query()
a = query.filter(name='lili').filter(age__gt=18, address='chengdu').filter(salary=12000) #键值不需要加",用等号不用冒号
print(query.query_condition) #//最后提交给数据库的查询信息
附:单个星号*
和双星号**
不同的作用
单个星号*
用于解包可迭代对象,例如元组或列表。它允许将可迭代对象拆分为单独的元素
双星号**
用于解包字典。它允许将字典中的键值对拆分为单独的关键字参数和对应的值
上面的代码中,使用**kwargs时,可以将任意数量的关键字参数传递给函数,并在函数内部将它们作为字典进行处理
def example_function(**kwargs):
for key, value in kwargs.items():
print(f"Key: {key}, Value: {value}")
# 调用函数并传递多个关键字参数
example_function(name="Alice", age=30, city="New York")
附:字典的update方法
update 是字典对象的一个方法,用于 将一个字典的键值对更新到另一个字典中。在之前提供的代码中,self.query_condition
是一个字典对象,而kwargs
也是一个字典对象,update 方法将 kwargs
中的键值对更新到 self.query_condition
字典中