一、函数
当我们发现有一些代码需要重复使用,你想怎么解决?
函数:是组织好的,可重复使用的,用来实现单一,或相关联功能的代码块。 函数能提高应用的模块性,和代码的重复利用率。
如我们前面使用的print() range()函数,但你也可以自己创建函数,这被叫做用户自定义函数。
1.1 、函数的定义
函数 用关键字 def修饰,后接函数标识符名称和圆括号() 和一个: 冒号
函数内容: 要用一个tap 或四个空格缩进
如:定义一个打印函数
# 函数名 :我命名为了pr,函数内容里面只有一句打印语句
def pr():
print("你好,这是一个函数")
# 函数定义完 不会自己执行,只有在调用的时候才会执行
# 调用函数 如下:直接写函数名()
pr()
函数名命名规范:
由字母、数字、下画线 _ 组成,其中数字不能打头,一般小写
不能是 Python 关键字,但可以包含关键字
不能包含空格
1.2 、函数的参数
函数是能够重复使用 而且不单单针对一个常量,所以我们可能处理很多未知数据,那这些未知的数据 我们又无法确定 那就可以用 参数,
参数一般写在 函数的括号里面,然后在调用的时候传进行 最后在函数内容里使用
函数在定义的时候 可以有参 或 无参
有参 可以分为 :必传 ,可传
def fun(a,b=0):
pass
我们可以看到上面这个函数 a参数是必传,也就是在调用fun这个函数的时候 必须传一个值给a,b是可传 可不传 当你不传的时候b就默认为0
从调用看 参数分为:
位置传参
def fun01(a,b,c,d=1):
print(a)
print(b)
print(c)
print(d)
fun01(1,2,3)
如上 当我们调用函数时,发现 a=1,b=2,c=3,d=1,我们传参的时候没有指定传给谁 他会按位置顺序一 一 匹配
指定传参
def fun01(a,b,c,d=1):
print(a)
print(b)
print(c)
print(d)
fun01(b=1,c=2,a=3)
如上 当我们调用这个函数时,发现打印出来 3、1、2、1 因为我们在传参的时候指定参数名等于对应的值
可变参数
*args:接收到的所有按照位置参数方式传递进来的参数,是一个元组类型
**kw:接收到的所有按照指定参数方式传递进来的参数,是一个字典类型
def fun02(*args):
print(args[1])
fun02(2,3,4)
我们执行上面代码会打印出来一个3出来 ,相等于 传了多个参数他会把这多个参数装进一个元组传进去 如果你想取任意一个值 只需要取对应索引
def fun03(**kw):
print(kw)
fun03(a=1,b=3)
>>> {'a': 1, 'b': 3}
**kw 我们可以看到 我们必须要传一个 一对参数进去 键值 键和值都可以自定义
1.3 、返回值:return
当我们调用一个函数并传入参数时 ,我们希望得到一个他加工回来的返回值给我们,
这个返回值 可以有 也可以没有,没有相当于返回 None,没有情况比较注重过程
def fun04(a):
print(a)
return 20
fun04(7)
我们看到fun04这个函数 有一个参数a 他的函数体里 只有两句一个是打印这个参数,一个是返回 20 ,但在控制台 我们只看到 输出了 7 ,因为你在调用函数的时候 print(a)这个代码 就是要输出一个7到控制面板,但是return 20 ,他会返回一个20给调用的结果,因为你没有输出到控制台 所以他不会显示 你可以用 print(fun04(7)) 调用这个函数 他会输出一个7和20
注:函数体里面 在执行的过程一旦遇到return 就会结束这个函数 ,return 后面在写任何代码不会执行
练习:
1.定义一个函数 求两个数之间的和
2.定义一个函数,求三个数的最大值
3.定义一个函数,输入任意三个数字是否能构成三角形
二、面向对象
面向对象思想是一种程序设计思想(Object Oriented Programming,简称OOP),这里的对象泛指现实中一切事物,每种事物都具备自己的属性和行为。面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,描述成计算机事件的设计思想。 它区别于面向过程思想,强调的是通过调用对象的行为来实现功能,而不是自己一步一步的去操作实现。
举例
洗衣服:
面向过程:把衣服脱下来-->找一个盆-->放点洗衣粉-->加点水-->浸泡10分钟-->揉一揉-->清洗衣服-->拧干-->晾起来
面向对象:把衣服脱下来-->打开全自动洗衣机-->扔衣服-->按钮-->晾起来
区别:
面向过程:强调步骤。
面向对象:强调对象,这里的对象就是洗衣机。
特点
面向对象思想是一种更符合我们思考习惯的思想,它可以将复杂的事情简单化,并将我们从执行者变成了指挥者。
面向对象的语言中,包含了三大基本特征,即封装、继承和多态。
类和对象
环顾周围,你会发现很多对象,比如桌子,椅子,同学,老师等。桌椅属于办公用品,师生都是人类。那么什么是类呢?什么是对象呢?
什么是类
类:是一组相关属性和行为的集合。可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该类事物。
现实中,描述一类事物:
属性:就是该事物的状态信息。
行为:就是该事物能够做什么。
举例:小猫。
属性:名字、体重、年龄、颜色。
行为:走、跑、叫。
什么是对象
对象:是一类事物的具体体现。对象是类的一个实例(对象并不是找个女朋友),必然具备该类事物的属性和行为。
现实中,一类事物的一个实例:一只小猫。
举例:一只小猫。
属性:tom、5kg、2 years、yellow。
行为:溜墙根走、蹦跶的跑、喵喵叫。
类与对象的关系
类是对一类事物的描述,是抽象的。
对象是一类事物的实例,是具体的。
类是对象的模板,对象是类的实体。
类的定义
事物与类的对比
现实世界的一类事物:
属性:事物的状态信息。
行为:事物能够做什么。
class描述事物也是如此:
成员变量:对应事物的属性,类的全局变量
成员方法:对应事物的行为,类中的方法
一定要记住面向对象最为核心的3大特征:
1.封装
2.继承
3.多态
2.1 类的定义格式
定义了一个类
class MyClass:
def __init__(self, name, age):
self.name = name
self.age = age
def run(self):
print("{name} is running".format(name=self.name))
a = MyClass("拜登",70)
a.run()
我们通过分析上面的代码 来学习 类的定义
class Myclass:
我们分析第一行代码,我们通过一个关键字 calss 来定义一类 ,起名为 MyClass,
类的命名:一般遵循大驼峰式,首字符大写,后面每一个单词的首字符大写
def __init__(self, name, age):
self.name = name
self.age = age
我们看到有一个方法叫 __init__ 这是一个初始化方法,又叫类的 构造方法,当你对类进行实例化的时候,用来初始化实例的属性,name 和 age都是实例的属性
什么是实例 就是你创建出的对象, 就是你现在能看到的self 代表类的实例,类方法必须包含此参数,且为第一个参数
def run(self):
print("{name} is running".format(name=self.name))
我们又看到一个方法 run 这个是这个类的一个普通方法,实例化对象后能够做的行为,并不是所有的类都有的
a = MyClass("拜登",70)
a.run()
类实例化 是什么那 就是 a = MyClass("拜登",70) 这行代码,我们对类进行了实例化,创建了一个对象a,在实例化的过程中,我们给a对象赋予了两个属性就是 name = "拜登"和age=70,然后我们又用这个对象让他调了run这个方法
self
我们可以看到类里面每一个方法都有一个self参数,它并不是默认参数,这个参数在方法被调用时是不需要传参的,这与之前所学过函数内容相违背, self就是执行方法的对象
我们用一个id方法查看内存地址 ,来研究一下
class MyClass:
def __init__(self, name, age):
print("这是在__init__方法中",id(self))
self.name = name
self.age = age
def run(self):
print("{name} is running".format(name=self.name))
a = MyClass("拜登",70)
a.run()
print(id(a))
执行后的结果
这是在__init__方法中 2133936776024
拜登 is running
2133936776024
我们可以看到self 的地址和 a的地址一模一样 ,那我们可以证明 self 就是a, 类对函数被定义后 任何属于这个类的对象 都是可以使用 这个函数的,所以 谁来调用这个函数,谁就会被绑定self
对象属性的访问和修改
只能通过对象来进行
class MyClass:
def __init__(self, name, age):
self.name = name
self.age = age
def run(self):
print("{name} is running".format(name=self.name))
a = MyClass("拜登",70)
a.run()
print(a.age)
a.age = 20
print(a.age)
最后在段代码 我们 输出了对象的age属性,然后又更改为了20 在输出了
练习面向对象: 请编程 把下面student表数据保存 输出每一个人的各个科目和分数,并计算每个学生的总的分数。
姓名 | 英语 | 数学 | 语文 |
---|---|---|---|
曹操 | 75 | 61 | 95 |
刘备 | 62 | 43 | 62 |
貂蝉 | 86 | 23 | 62 |
2.2 封装
我们创建一个类,有些属性,有些方法,我们不希望被其他人使用,因为那样很容易就产生错误,那么这时,我们就需隐藏实现的细节,只对外公开我们想让他们使用的属性和方法,这就叫做封装。就好比把一些东西用一个盒子封装起来,只留一个口,内部让你看不见。
在python里,如果属性和方法前面是双下划线,那么这个属性和方法就变成了私有的属性
class Dog:
def __init__(self,name,age):
self.__name = name
self.__age = age
def __run(self):
print("run")
a = Dog("哈士奇",15)
a.__run()
print(a.__name,a.__age)
我们看到 属性 __name , __age 和方法 __run() 以双下划线开头,这样的方法和属性,类的对象是无法使用的,通过这种方法,就可以将不希望外部访问的属性和方法隐藏起来。
属性隐藏起来,并非不让外部使用,而是在使用时收到控制,比如年龄,如果年龄属性定义成age,那么就会出现这样的情况
a.age = 1000
你见过哪个动物的年龄有10000岁呢,这显然是有问题的,但age属性暴露出来了,别人在使用时就可能会犯这样的错误,所以,为了防止这样的错误发生,就可以将age属性设置成私有的,然后提供一个set_age方法,来设置给age 并且判断一下
class Dog:
def __init__(self,name,age):
self.__name = name
self.__age = age
def set_age(self, age):
if age > 100 or age < 1:
raise Exception("年龄范围错误")
self.__age = age
def get_age(self):
return self.__age
def __run(self):
print("run")
a = Dog("哈士奇",15)
a.set_age(5)
print(a.get_age())
2.3 继承
一个类可以继承一个或者继承多个父类,新建的类被称之为派生类或者子类,被继承的类是父类,可以称之为基类,超类,继承是实现代码重用的重要方式。
单继承
class Car(object):
def __init__(self, speed, brand):
self.speed = speed
self.brand = brand
def run(self):
print("{brand}在行驶".format(brand=self.brand))
# 燃油车
class Gasolinecar(Car):
def __init__(self, speed, brand, price):
super().__init__(speed, brand)
self.price = price
class Audi(Gasolinecar):
pass
honda = Gasolinecar(130, '本田', 13000)
honda.run()
audi_car = Audi(100, '奥迪', 10000)
audi_car.run()
print(issubclass(Audi, Gasolinecar)) # 判断Audi是Gasolinecar的子类
print(issubclass(Gasolinecar, Car))
继承,意味着子类将“拥有”父类的方法和属性,同时可以新增子类的属性和方法。在Gasolinecar类里,我没有写run方法,但是Gasolinecar的父类定义了run方法,因此,Gasolinecar也有这个方法,因此这个类的对象honda可以使用run方法。
Audi类没有定义任何方法,但是它继承了Gasolinecar,因此,Gasolinecar有的属性和方法,它都拥有,这里就包括了__init__方法。
super()可以用来调用父类的方法,Gasolinecar多传了一个price属性,其父类的__init__方法里有两个参数,因此,可以先调用父类的__init__方法初始化speed, brand,然后在初始化price
多继承
大部分面向对象的编程语言(除了C++)都只支持单继承,而不支持多继承,因为多继承不仅增加编程复杂度,而且容易导致莫名其妙的错误。
Python虽然支持多继承,但不推荐使用多继承,推荐使用单继承,这样可以保证编程思路更清晰,也可以避免不必要的麻烦。
当以一个子类有多个直接父类时,该子类会继承得到所有父类的方法,但是如果其中有多个父类包含同名方法会发生什么?此时排在前面的父类中的方法会“遮蔽”后面父类中的方法。
class Person():
def person_walk(self):
print("走路")
class Wolf():
def wolf_run(self):
print("奔跑")
class WolfMan(Person, Wolf):
def __init__(self):
pass
a = WolfMan()
a.person_walk()
a.wolf_run()
2.4 多态
面向对象的多态依赖于继承, 因为继承,使得子类拥有了父类的方法, 子类的方法与父类方法重名时是重写, 同一类事物,有多重形态, 这就是面向对象概念里的多态,多态使得不同的子类对象调用相同的 类方法,产生不同的执行结果,可以增加代码的外部调用灵活度。
class Base():
def print(self):
print("base")
class A(Base):
def print(self):
print("A")
a = A()
a.print()
父类和子类都有print方法,那么子类A的对象a调用print方法时,调用的是谁的print方法呢?
答案是子类的print方法,如果A类没有定义print方法,那么a.print()调用的是父类的print方法,但是A类定义了print方法,这种情况称之为重写,A类重写了父类的print方法。
继承时,子类“拥有”父类的方法和属性,但这种拥有不是真实意义上的拥有
class Base():
def print(self):
print("base")
class A(Base):
pass
print(id(Base.print))
print(id(A.print))
Base.print 和 A.print 的内存地址是相同的,这说明他们是同一个方法。执行A.print时,python会寻找print方法,它会先从A类的定义中寻找,没有找到,然后去A的父类里寻找print方法,如果还是找不到,就继续向上寻找。
这便是子类拥有父类属性和方法的真相
多态表现:同一类事物,有多重形态
class Animal:
def run(self):
raise NotImplementedError
class People(Animal):
def run(self):
print("人在行走")
class Pig(Animal):
def run(self):
print("猪在跑")
p1 = People()
p1.run()
p2 = Pig()
p2.run()
People和 Pig 都继承了Animal,都是动物,是同一类事物,他们都有run方法,但是最终的运行结果却不一样,这就是多态,同一类事物有多种形态。