目录
引入
一、函数
1、函数概述
2、参数和返回值
3、函数的调用
二、模块
1、模块概述
2、模块应用实例
三、类与对象
1、面向对象概述
2、类
3、类的特点
为了使程序实现的代码更加简单。需要把程序分成越来越小的组成部分,3种方式——函数、对象、模块。
C语言大括号{}里是函数体,python种缩进块里是函数体(配合def和冒号:)
函数外面定义的变量是全局变量(大写配合下划线)
函数内部定义的局部变量(小写配合下划线),只在内部有效
def foodprice(per_price,number):
sum_price = per_price*number
return sum_price
PER_PRICE = float(input('请输入单价:'))
NUMBER = float(input('请输入数量:'))
SUM_PRICE = foodprice(PER_PRICE,NUMBER)
print('一共',SUM_PRICE,'元')
==========运行结果==========
请输入单价:15
请输入数量:4
一共 60.0 元
若想在函数内部修改全局变量,并使之在整个程序生效用关键字global:
def foodprice(per_price,number):
global PER_PRICE
PER_PRICE = 10
sum_price = per_price*number
return sum_price
在python种,函数参数分为3种:位置参数、可变参数、关键字参数。参数的类型不同,参数的传递方式也不同。
①位置参数
位置参数:传入参数值按照位置顺序依次赋给参数。
传递方式:直接传入参数即可,如果有多个参数,位置先后顺序不可改变。若交换顺序,函数结果则不同
def sub(x,y):
return x-y
>>> sub(10,5)
5
>>> sub(5,10)
-5
②关键字参数
关键字参数:通过参数名指定需要赋值的参数,可忽略参数顺序
传递方式:2种,一是直接传入参数,二是先将参数封装成字典,再在封装后的字典前添加两个星号**传入
>>> sub(y=5,x=10)
5
>>> sub(**{'y':5,'x':10})
5
③默认值参数
当函数调用忘记给某个参数值时,会使用默认值代替(以防报错)
def sub(x=100,y=50):
return x-y
>>> sub()
50
>>> sub(51)
1
>>> sub(10,5)
5
④可变参数
可变参数:在定义函数参数时,我们不知道到底需要几个参数,只要在参数前面加上星号*即可。
def var(*param):
print('可变参数param中第3个参数是:',param[2])
print('可变参数param的长度是:',len(param))
>>> var('BUCT',1958,'Liming',100,'A')
可变参数param中第3个参数是: Liming
可变参数param的长度是: 5
除了可变参数,后也可有普通参数(普参需要用关键字参数传值):
def var(*param,str1):
print('可变参数param中第3个参数是:',param[2])
print('可变参数param的长度是:',len(param))
print('我是普通参数:',str1)
>>> var('BUCT',1958,'Liming',100,'A',str1 = '普参')
可变参数param中第3个参数是: Liming
可变参数param的长度是: 5
我是普通参数: 普参
⑤函数的返回值
若没有用return指定返回值,则返回一个空None
result = var('BUCT',1958,'Liming',100,'A',str1 = '普参')
>>> print(result)
None
若将函数改为:
def var(*param,str1):
print('可变参数param中第3个参数是:',param[2])
print('可变参数param的长度是:',len(param))
print('我是普通参数:',str1)
return param
>>> print(result)
('BUCT', 1958, 'Liming', 100, 'A')
自定义函数:先定义再调用
内置函数:直接调用,有的在特定模块里,需要先import相应模块
①嵌套调用
内嵌函数/内部函数:在函数内部再定义一个函数,作用域只在其相邻外层函数缩进块内部。
②使用闭包
闭包:函数式编程的一个重要的语法结构,在一个内部函数里对外部作用域(不是全局作用域)的变量进行引用。此时这个内部函数叫做闭包函数,如下sub2(b)就是引用了a,为闭包函数:
def sub(a):
def sub2(b):
result = a-b
return result
return sub2
print(sub(10)(5))
运行结果:5
注:闭包本质还是内嵌函数,不能再全局域访问,外部函数sub的局部变量对于闭包来说是全局变量,可以访问但是不能修改。
③递归调用
递归:严格说其属于算法范畴,不属于语法范围。函数调用自身的行为叫做递归。
两个条件:调用函数自身、设置了正确的返回条件。如计算正整数N的阶乘:
常规迭代算法:
def factorial(n):
result = n
for i in range(1,n):
result *= i
return result
print(factorial(10))
运行结果:3628800
迭代算法:
def factorial(n):
if n == 1:
return 1
else:
return n*factorial(n-1)
print(factorial(10))
运行结果:3628800
注:python默认递归深度为100层(限制),也可用sys.setrecursionlimit(x)指定递归深度。递归有危险(消耗时间和空间),因为它是基于弹栈和出栈操作;递归忘记返回时/未设置正确返回条件时,会使程序崩溃,消耗掉所有内存。
模块实际上是一种更为高级的封装。前面容器(元组,列表)是对数据的封装,函数是对语句的封装,类是方法和属性的封装,模块就是对程序的封装。——就是我们保存的实现特定功能的.py文件
命名空间:一个包含了一个或多个变量名称和它们各自对应的对象值的字典,python可调用局部命名空间和全局命名空间中的变量。
如果一个局部变量与全局变量重名,则再函数内部调用局部变量时,会屏蔽全局变量。
如果要修改函数内全局变量的值,需要借助global
①模块导入方法
方法一:
import modulename
import modulename1,modulename2
使用函数时modulename.functionname()即可
方法二:
from modulename import functionname1,functionname2
方法三:
import modulename as newname
相当于给模块起了个新名字,便于记忆也方便调用
②自定义模块和包
自定义模块:
编写的mine.py文件放在与调用程序同一目录下,在其他文件中使用时就可import mine使用。
自定义包:
在大型项目开发中,为了避免模块名重复,python引入了按目录来组织模块的方法,称为包(package)。
包是一个分层级的文件目录结构,定义了有模块、子包、子包下的子包等组成的命名空间。
只要顶层报名不与其他人重名,内部的所有模块都不会冲突。——P114
③安装第三方包
pip install xxxx
①日期和时间相关:datatime模块
导入:
from datetime import datetime
#不能import datetime,可以import datetime.datetime(因为datetime模块里还有一个datetime类)
获取当前日期时间:
>>> now = datetime.now()
>>> print(now)
2023-10-05 17:28:24.303285
获取指定日期时间:
>>> dt = datetime(2020,12,12,11,30,45)
>>> print(dt)
2020-12-12 11:30:45
datetime与timestamp互相转换:
把1970-1-1 00:00:00 UTC+00:00的时间作为epoch time,记为0,当前时间就是相对于epoch time的秒数,称为timestamp。
计算机中存储的当前时间是以timestamp表示的,与时区无关,全球各地计算机在任意时刻的timestamp是相同的。
>>> dt = dt.timestamp()
>>> print(dt)
1607743845.0
#再转换回去:
>>> dt = datetime.fromtimestamp(dt)
>>> print(dt)
2020-12-12 11:30:45
str转换为datetime:
用户输入的日期和时间类型是字符串,要处理日期和时间,必须把str转换为datetime
>>> test = datetime.strptime('2023-10-05 17:49:00','%Y-%m-%d %H:%M:%S') #特殊字符规定了格式
>>> print(test)
2023-10-05 17:49:00
datetime转换为str:
若已有datetime对象,要把它格式化为字符才能显示给用户
>>> now = datetime.now()
>>> print(now.strftime('%a,%b %d %H:%M'))
Thu,Oct 05 17:28
datetime加减计算:(再导入timedelta类)
>>> from datetime import datetime,timedelta
>>> now = datetime.now()
>>> now
datetime.datetime(2023, 10, 5, 17, 58, 9, 377124)
>>> now + timedelta(hours = 2)
datetime.datetime(2023, 10, 5, 19, 58, 9, 377124)
>>> now - timedelta(days = 3)
datetime.datetime(2023, 10, 2, 17, 58, 9, 377124)
本地时间转换为UTC时间:(再导入timedelta、timezone类)
本地时间为系统设定时区的时间,例如北京时间是UTC+8:00时区的时间,而UTC时间指UTC+0:00时区的时间。datetime里面有时区属性tzinfo。
>>> from datetime import datetime,timedelta,timezone
>>> new_utc = timezone(timedelta(hours = 8)) #创建新时区UTC+8:00
>>> new_utc1 = timezone(timedelta(hours = 7)) #创建新时区UTC+7:00
>>> now = datetime.now()
>>> now
datetime.datetime(2023, 10, 5, 18, 9, 17, 667655)
>>> test = now.replace(tzinfo = new_utc) #为当前时间强制设置新的时区
>>> test
datetime.datetime(2023, 10, 5, 18, 9, 17, 667655, tzinfo=datetime.timezone(datetime.timedelta(0, 28800)))
>>> test1 = now.replace(tzinfo = new_utc1) #为当前时间强制设置新的时区UTC+7:00
>>> test1
datetime.datetime(2023, 10, 5, 18, 9, 17, 667655, tzinfo=datetime.timezone(datetime.timedelta(0, 25200)))
时区转换:
先用datetime类提供的utcnow()方法获取当前UTC时间,再用astimezone()方法转换为任意时区的时间
②读写JSON数据:json模块
JSON是一种轻量级的数据交换格式,等同于python里面的字典格式,里面可以包含方括号括起来的数组(列表)。json模块专门解决json格式的数据,提供4种方法:dumps()、dump()、loads()、load()
dumps()、dump()实现序列化功能:
dumps()实现将数据序列化为字符串str,而使用dump()时必须传文件描述符,将序列化的字符串str保存到文件中。
>>> import json
>>> json.dumps('huang')
'"huang"'
>>> json.dumps(13.14)
'13.14'
>>> dict1 = {'name':'huang','school':'buct'}
>>> json.dumps(dict1)
'{"name": "huang", "school": "buct"}'
>>> with open("D:\\json_test.json","w",encoding = 'utf-8')as file_test:
json.dump(dict1,file_test,indent = 4)
运行结果:dump()方法将字典数据dict_test保存到D盘文件夹下的json_test.json文件中,里面的内容如下
{
"name": "huang",
"school": "buct"
}
loads()、load()是反序列化方法:
loads()只完成了反序列化,load()只接收文件描述符,完成了读取文件和反序列化。
>>> json.loads(json.dumps(dict1)) #loads直接操作程序中的字典
{'name': 'huang', 'school': 'buct'} #dumps将字典序列化成str,loads又使其恢复字典身份
>>> with open("D:\\json_test.json","r",encoding = 'utf-8')as file_test: #读刚才保存的序列化str字符串数据
test_loads = json.loads(file_test.read()) #用loads实现反序列化
file_test.seek(0) #重新定位在文件的第0位及开始位置
test_load = json.load(file_test) #用load实现反序列化
>>> print(test_loads)
{'name': 'huang', 'school': 'buct'}
>>> print(test_load)
{'name': 'huang', 'school': 'buct'}
③系统相关:sys模块
sys是python自带模块,包含了与系统相关的信息。可通过help(sys)或dir(sys)查看sys模块的可用方法(很多),下面列举几种。
sys.path包含输入模块的目录名列表:
>>> sys.path
['',
'D:\\python3.6.6\\Lib\\idlelib',
'D:\\python3.6.6\\python36.zip',
'D:\\python3.6.6\\DLLs',
'D:\\python3.6.6\\lib',
'D:\\python3.6.6',
'D:\\python3.6.6\\lib\\site-packages']
该命令获取了指定模块搜索路径的字符串集合。将写好的模块放在上面得到的某个路径下,就可以在使用import导入时正确找到,也可以用sys.path.append(自定义路径)添加模块路径。——“自定义模块乱放程序是找不到的!”
sys.argv在外部向程序内部传递参数:
????
sys.argv变量是一个包含了命令行参数的字符串列表,利用命令行向程序传递参数。其中脚本的名称是sys.argv列表的第一个参数。
④数学:math模块
math模块也是python自带模块,包含了和数学运算公式相关的信息——P125
⑤随机数:random模块
列举常用模块:
生成随机整数(需指定上下限,且下限小于上限):randint
import random
>>> random.randint(10,2390) #生成指定范围内的随机整数
2375
生成随机浮点数:random
>>> random.random()
0.9935870033845187
>>> random.uniform(10,100)
27.07308173076904
>>> random.uniform(100,10)
18.198994262912336
随机字符:choice
>>> random.choice('98%$333#@')
'3'
洗牌:shuffle
>>> test = ['a','B',1,2,5,'%']
>>> random.shuffle(test)
>>> print(test)
[5, 1, 2, 'a', 'B', '%']
面向对象编程(Object Oriented Programming,OOP),是一种程序设计思想,是以建立模型体现出来的抽象思维过程和面向对象的方法。
模型是用来反映现实世界中的事物特征的,是对事物特征和变化规律的抽象化,是更普遍、更集中、更深刻地描述客体的特征。
OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
术语简介:
1、类:是创建对象的代码段,描述了对象的特征、属性、要实现的功能,以及采用的方法等。
2、属性:描述了对象的静态特征。
3、方法:描述了对象的动态动作。
4、对象:对象是类的一个实例,就是模拟真实事件,把数据和代码段都集合到一起,即属性、方法的集合。
5、实例:就是类的实体。
6、实例化:创建类的一个实例过程。
7、封装:把对象的属性、方法、事件集中到一个统一的类中,并对调用者屏蔽其中的细节。
8、继承:一个类共享另一个类的数据结构和方法的机制称为继承。起始类称为基类、超类、父类,而继承类称为派生类、子类。继承类是对被继承类的拓展。
9、多态:一个同样的函数对于不同的对象可以具有不同的实现。
10、接口:定义了方法、属性的结构,为其成员提供违约,不提供实现。不能直接从接口创建对象,必须首先创建一个类来实现接口所定义的内容。
11、重载:一个方法可以具有许多不同的接口,但方法的名称是相同的。
12、事件:事件是由某个外部行为所引发的对象方法。
13、重写:在派生类中,对基类某个方法的程序代码及进行重新编码,使其实现不同的功能。
14、构造函数:是创建对象所调用的特殊方法。
15、析构函数:是释放对象时所调用的特殊方法。
①类的定义
类就是对象的属性和方法的拼接,静态的特征称为属性,动态的动作称为方法。
>>> class Person: #规定类名以大写字母开头
#属性
skincolor = "yellow"
high = 185
weight = 75
#方法
def goroad(self):
print("人走路动作的测试...")
def sleep(self):
print("睡觉,晚安!")
②类的使用(类实例化为对象)
>>> p = Person() #将类实例化为对象,注意后面要加括号()
③类的构造方法及专有方法
类的构造方法:__int__(self)。只要实例化一个对象,此方法就会在对象被创建时自动调用——实例化对象时是可以传入参数的,这些参数会自动传入__int__(self,param1,param2...)方法中,可以通过重写这个方法来自定义对象的初始化操作。
>>> class Bear:
def __init__(self,name):
self.name = name
def kill(self):
print("%s是保护动物不可猎杀"%self.name)
>>> a = Bear("狗熊")
>>> a.kill()
狗熊是保护动物不可猎杀
解释:与Person()相比,这里重写了__init__()方法,不然默认为__init__(self)。在Bear()中给了一个参数name,成了__init__(self,name),第一个参数self是默认的,所以调用时把“狗熊”传给了name。也可以给name默认参数,这样即使忘记传入参数,程序也不会报错:
>>> class Bear:
def __init__(self,name = "狗熊"):
self.name = name
def kill(self):
print("%s是保护动物不可猎杀"%self.name)
>>> b = Bear()
>>> b.kill()
狗熊是保护动物不可猎杀
>>> c = Bear("丹顶鹤")
>>> c.kill()
丹顶鹤是保护动物不可猎杀
④类的访问权限
在C++和JAVA中是通过关键字public、private来表明访问权限是共有的还是私有的。在python中,默认情况下对象的属性和方法是公开的、公有的,通过点(.)操作符来访问。如上面的kill()函数(方法)的访问,也可以访问变量:
>>> class Test:
name = "大连橡塑"
>>> a = Test()
>>> a.name
'大连橡塑'
若变量前面加上双下划线(__)就表示声明为私有变量就不可访问:
>>> class Test:
__name = "君欣旅店"
>>> a = Test()
>>> a.__name
Traceback (most recent call last):
File "", line 1, in
a.__name
AttributeError: 'Test' object has no attribute '__name'
可利用函数方法访问私有变量:
>>> class Test:
__name = "君欣旅店"
def getname(self):
return self.__name
>>> a = Test()
>>> a.getname()
'君欣旅店'
也可通过"_类名__变量名"格式访问私有变量:
>>> a._Test__name
'君欣旅店'
(由此可见python的私有机制是伪私有,python的类是没有权限控制的,变量可以被外界调用)
⑤获取对象信息
类实例化对象后(如a = Test()),对象就可以调用类的属性和方法:
即a.getname()、a.name、a.kill()这些
①封装
形式上看,对象封装了属性就是变量,而方法和函数是独立性很强的模块,封装就是一种信息掩蔽技术,使数据更加安全!
如列表实质上是python的一个序列对象,sort()就是其中一个方法/函数:
>>> list1 = ['E','C','B','A','D']
>>> list1.sort()
>>> list1
['A', 'B', 'C', 'D', 'E']
②多态
不同对象对同一方法响应不同的行动就是多态(内部方法/函数名相同,但是不同类里面定义的功能不同):
>>> class Test1:
def func(self):
print("这是响应1...")
>>> class Test2:
def func(self):
print("这是响应2...")
>>> x = Test1()
>>> y = Test2()
>>> x.func()
这是响应1...
>>> y.func()
这是响应2...
注意:self相当于C++的this指针。由同一个类可以生成无数个对象,这些对象都源于同一个类的属性和方法,当一个对象的方法被调用时,对象会将自身作为第一个参数传给self参数,接收self参数时,python就知道是哪个对象在调用方法了。
③继承
继承是子类自动共享父类数据和方法的机制。语法格式如下:
class ClassName(BaseClassName):
...
ClassName:子类名称,第一个字母必须大写。
BaseClassName/paraname:父类名称。
子类可继承父类的任何属性和方法,如下定义类Test_list继承列表list的属性和方法(append和sort):
>>> class Test_list(list):
pass
>>> list1 = Test_list()
>>> list1.append('B')
>>> list1
['B']
>>> list1.append('UCT')
>>> list1
['B', 'UCT']
>>> list1.sort()
>>> list1
['B', 'UCT']
使用类继承机制时的注意事项:
a、若子类中定义与父类同名的方法或属性,自动覆盖父类里对应的属性或方法。
b、子类重写父类中同名的属性或方法,若被重写的子类同名的方法里面没有引入父类同名的方法,实例化对象调用父类的同名方法就会出错:
import random
class Dog:
def __init__(self):
self.x = random.randint(1,100) #两个缩进
self.y = random.randint(1,100)
def run_Dog(self):
self.x += 1
print("狗狗的位置是:",self.x,self.y)
class Dog1(Dog):
pass
class Dog2(Dog):
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry:
print("狗想吃东西了!")
self.hungry = False
else:
print("狗吃饱了!")
调用:
>>> dog = Dog()
>>> dog.run_Dog()
狗狗的位置是: 62 69
>>> dog1 = Dog1()
>>> dog1.run_Dog()
狗狗的位置是: 29 89
>>> dog2 = Dog2()
>>> dog2.eat()
狗想吃东西了!
>>> dog2.eat()
狗吃饱了!
>>> dog2.run_Dog() #报错报错!!!
分析:子类Dog2重写了父类(基类)Dog的构造函数__init__(self),则父类构造函数里的方法被覆盖。要解决此问题,就要在子类里面重写父类同名方法时,先引入父类的同名方法,两种技术:a、调用未绑定的父类方法;b、使用super函数
a、调用未绑定的父类方法——语法格式:paraname.func(self)。父类名.方法名.(self)
对上面示例代码的Dog2类更改:
def __init__(self):
Dog.__init__(self) #加了这一行
self.hungry = True
调用:
>>> dog2 = Dog2()
>>> dog2.run_Dog()
狗狗的位置是: 85 16
b、使用super函数,该函数可以自动找到父类方法和传入的self参数,语法格式:super().func([parameter])。parameter为可选参数,若是self可省略。
对上面示例代码的Dog2类更改:
def __init__(self):
super().__init__() #加了这一行
self.hungry = True
调用:
>>> dog2 = Dog2()
>>> dog2.run_Dog()
狗狗的位置是: 96 82
使用super函数的方便之处在于不用写任何关于基类(父类)的名称,直接写重写的方法即可,会自动去父类去寻找,尤其在多重继承中,或者子类有多个祖先类时,能自动跳过多种层级去寻找。如果以后要更改父类,直接修改括号()里面的父类名称即可,不用再修改重写的同名方法里的内容。
④多重继承
一个子类同时继承多个父类的属性和方法:
class Classname(Base1,Base2,Base3):
...
虽然多重继承的机制可以使子类继承多个属性和方法,但是容易导致代码混乱,引起不可预见的Bug,一般尽量避免使用。