Java
Java 的数组和变量在存储上是有区别的。在 Java 中,变量是一种基本的数据结构,用于存储单个值。而数组则是一种复合数据类型,用于存储一系列相同类型的值。
在 Java 中,变量存储在栈(stack)中,而数组则存储在堆(heap)中。栈是一种线性数据结构,用于存储基本数据类型的值和对象的引用。栈的大小是固定的,由系统自动分配和释放,而且存储在栈中的数据生命周期较短。当一个方法被调用时,系统为该方法创建一个栈帧(stack frame),用于存储方法的参数、局部变量和方法的返回值。当方法执行完毕时,栈帧会被销毁。
堆是一种动态数据结构,用于存储对象和数组。堆的大小是可变的,由 JVM 进行管理,存储在堆中的数据生命周期较长。当创建一个数组时,Java 在堆中分配一块连续的内存区域,用于存储数组的元素。数组的大小和元素类型在创建时就已经确定,而且数组中的元素可以是基本数据类型或对象类型。
因此,Java 的数组和变量存储位置不同,变量存储在栈中,而数组存储在堆中。这也是 Java 中数组和变量使用和赋值时的一些注意事项。例如,数组的元素可以通过下标访问和修改,而变量只能直接使用其值进行操作。
C++
C++ 中的变量和数组在存储位置上是不同的。变量通常被存储在栈(stack)上,而数组通常被存储在堆(heap)上或全局数据区(data segment)。
栈是一种先进后出(Last-In-First-Out,LIFO)的数据结构,主要用于存储函数调用时的临时变量和函数参数。栈中的变量会随着函数的调用和返回而动态地分配和释放内存,因此它们的生命周期比较短,只在函数的执行期间存在。在 C++ 中,局部变量和函数参数通常被存储在栈上。
堆是一种动态内存池,用于存储动态分配的内存。在 C++ 中,通过 new 和 delete 运算符可以在堆上动态地分配和释放内存。数组在堆上分配内存的方法如下:
int* arr = new int[10];
在这里,arr 指向堆中分配的一个大小为 10 的整型数组。
全局数据区是用于存储全局变量、静态变量和常量的一段内存区域。在程序运行期间,这段区域的内存会一直存在,直到程序结束。在 C++ 中,静态数组和全局数组通常被存储在全局数据区。
因此,C++ 中的变量和数组在存储位置上是不同的,变量通常存储在栈上,而数组通常存储在堆上或全局数据区。这也意味着,在 C++ 中使用变量和数组时需要注意它们的生命周期和存储位置,以避免出现一些常见的问题,如空指针、野指针等。
Python
在Python中,变量和数组存储位置是不同的。
Python中的变量可以指向任何类型的数据,因此其存储方式与C/C++/Java等语言不同。在Python中,变量实际上是一个指针,指向该变量所代表的对象在内存中的地址。因此,Python中的变量存储的是地址,而不是实际的值。
而Python中的数组通常是使用列表(List)来实现的,列表是一种可变的有序集合。Python中的列表实际上是一个数组,存储方式与C/C++/Java等语言的数组类似,但是列表中的元素可以是任何类型的数据。列表中的元素存储在内存中的连续空间中,可以通过索引来访问和修改。
因此,Python中的变量和数组的存储位置是不同的。
Python、C++ 和 Java 都支持格式化输出,但它们的实现方法略有不同。
Python 中格式化输出可以使用字符串的 format() 方法或 % 操作符。下面是两种不同的写法:
使用 format() 方法:
name = "Tom"
age = 20
print("My name is {}, and I'm {} years old".format(name, age))
使用 % 操作符:
name = "Tom"
age = 20
print("My name is %s, and I'm %d years old" % (name, age))
C++ 中格式化输出可以使用 printf() 函数或 std::cout 对象。下面是两种不同的写法:
使用 printf() 函数:
#include <stdio.h>
int main()
{
char name[] = "Tom";
int age = 20;
printf("My name is %s, and I'm %d years old\n", name, age);
return 0;
}
使用 std::cout 对象:
#include <iostream>
using namespace std;
int main()
{
char name[] = "Tom";
int age = 20;
cout << "My name is " << name << ", and I'm " << age << " years old" << endl;
return 0;
}
Java 中格式化输出可以使用 System.out.printf() 方法。下面是一个例子:
public class Main {
public static void main(String[] args) {
String name = "Tom";
int age = 20;
System.out.printf("My name is %s, and I'm %d years old\n", name, age);
}
}
总的来说,这三种编程语言在格式化输出方面都有各自的特点和使用方式,需要根据具体的应用场景选择适合的方式。
str.format()
方法通过字符串中的大括号{}
来识别替换字段 replacement field
,从而完成字符串的格式化。替换字段
由字段名 field name
和转换字段 conversion field
以及格式说明符 format specifier
组成,即一般形式为 {字段名!转换字段:格式说明符}。simple field name
和复合字段名 compound field name
。而转换字段和格式说明符都是可选的。form的完整格式是{字段名!转换字符:格式说明符}。其中字段名师必须的,而且可以分为简单字段名和复合字段名。
简单字段名由三中写法:
{}
{0}
大括号内省略字段名,传递位置参数。
{}
# 省略字段名传递位置参数
print('我叫{},今年{}岁。'.format('小明', 18))
"""
我叫小明,今年18岁。
"""
# 大括号个数可以少于位置参数的个数
print('我爱吃{}和{}。'.format('香蕉', '苹果', '大鸭梨'))
"""
我爱吃香蕉和苹果。
"""
# 大括号个数多于位置参数的个数则会报错
# print('我还吃{}和{}。'.format('西红柿'))
"""
IndexError: tuple index out of range
"""
可以通过数字形式的简单字段名传递位置参数。
# 通过数字形式的简单字段名传递位置参数
print('身高{0},家住{1}。'.format(1.8, '铜锣湾'))
"""
身高1.8,家住铜锣湾
"""
# 数字形式的简单字段名可以重复使用。
print('我爱{0}。\n她今年{1}。\n我也爱{0}。'.format('阿香', 17))
"""
我爱阿香。
她今年17。
我也爱阿香。
"""
# 体会把所有位置参数整体当成元组来取值
print('阿香爱吃{1}、{3}和{0}。'.format(
'榴莲', '臭豆腐', '皮蛋', '鲱鱼罐头', '螺狮粉'))
"""
阿香爱吃臭豆腐、鲱鱼罐头和榴莲。
"""
# 尝试一下越界错误
# print('{1}'.format('错误用法'))
"""
IndexError: tuple index out of range
"""
使用变量名形式的简单字段名传递关键字参数。
# 使用变量名形式的简单字段名传递关键字参数
print('我大哥是{name},今年{age}岁。'.format(name='阿飞', age=20))
"""
我大哥是阿飞,今年20岁。
"""
# 关键字参数的顺序可以随意调换
print('我大哥是{name},今年{age}岁。'.format(age=20, name='阿飞'))
"""
我大哥是阿飞,今年20岁。
"""
{}
不能和数字形式的字段名 {非负整数}
同时使用。# 混合使用数字形式和变量名形式的字段名
# 可以同时传递位置参数和关键字参数
print('这是一个关于{0}、{1}和{girl}的故事。'.format(
'小明', '阿飞', girl='阿香'))
"""
这是一个关于小明、阿飞和阿香的故事。
"""
# 但是关键字参数必须位于位置参数之后
# print('这是一个关于{0}、{1}和{girl}的故事。'.format(
# '小明', girl='阿香' , '阿飞'))
"""
SyntaxError: positional argument follows keyword argument
"""
# 数字也可以省略
print('这是一个关于{}、{}和{girl}的故事。'.format(
'小明', '阿飞', girl='阿香'))
# 但是省略字段名不能和数字形式的字段名同时出现
# print('这是一个关于{}、{1}和{girl}的故事。'.format(
# '小明', '阿飞', girl='阿香'))
"""
ValueError: cannot switch from automatic field numbering to manual field specification
"""
str.format()
方法还可以使用 *元组
和 **字典
的形式传参,两者可以混合使用。 位置参数、关键字参数、*元组
和 **字典
也可以同时使用,但是要注意,位置参数要在关键字参数前面,*元组
要在 **字典
前面。
# 使用元组传参
infos = '钢铁侠', 66, '小辣椒'
print('我是{},身价{}亿。'.format(*infos))
"""
我是钢铁侠,身家66亿。
"""
print('我是{2},身价{1}亿。'.format(*infos))
"""
我是小辣椒,身家66亿。
"""
# 使用字典传参
venom = {'name': '毒液', 'weakness': '火'}
print('我是{name},我怕{weakness}。'.format(**venom))
"""
我是毒液,我怕火。
"""
# 同时使用元组和字典传参
hulk = '绿巨人', '拳头'
captain = {'name': '美国队长', 'weapon': '盾'}
print('我是{}, 我怕{weapon}。'.format(*hulk, **captain))
print('我是{name}, 我怕{1}。'.format(*hulk, **captain))
"""
我是绿巨人, 我怕盾。
我是美国队长, 我怕拳头。
"""
# 同时使用位置参数、元组、关键字参数、字典传参
# 注意:
# 位置参数要在关键字参数前面
# *元组要在**字典前面
tup = '鹰眼',
dic = {'weapon': '箭'}
text = '我是{1},我怕{weakness}。我是{0},我用{weapon}。'
text = text.format(
*tup, '黑寡妇', weakness='男人', **dic)
print(text)
"""
我是黑寡妇,我怕男人。我是鹰眼,我用箭。
"""
.
点号[]
中括号.
点号传递位置参数
{数字.属性名}
class Person(object):
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
p = Person('zhangsan',18,'female')
print('姓名是{0.name},年龄是{0.age},性别是{0.gender}'.format(p))
print('姓名是{.name}'.format(p)) # 只有一个替换字段时,可以省略数字
[]
中括号# 中括号用法:用列表传递位置参数
infos = ['阿星', 9527]
food = ['霸王花', '爆米花']
print('我叫{0[0]},警号{0[1]},爱吃{1[0]}。'.format(
infos, food))
"""
我叫阿星,警号9527,爱吃霸王花。
"""
# 中括号用法:用元组传递位置参数
food = ('僵尸', '脑子')
print('我叫{0[0]},年龄{1},爱吃{0[1]}。'.format(
food, 66))
"""
我叫僵尸,年龄66,爱吃脑子。
"""
# 中括号用法:用字典传递位置参数
dic = dict(name='阿星', pid=9527)
print('我是{[name]}!'.format(
dic))
# 多个替换字段,不能省略数字
print('我是{0[name]},警号{0[pid]}。'.format(
dic))
"""
我是阿星!
我是阿星,警号9527。
"""
转换字段 conversion field
的取值有三种,前面要加 !
:
s
:传递参数之前先对参数调用 str()
r
:传递参数之前先对参数调用 repr()
a
:传递参数之前先对参数调用 ascii()
ascii()
函数类似repr()
函数,返回一个可以表示对象的字符串。 但是对于非ASCII
字符,使用\x
,\u
或者\U
转义。
# 转换字段
print('I am {!s}!'.format('Bruce Lee 李小龙'))
print('I am {!r}!'.format('Bruce Lee 李小龙'))
print('I am {!a}!'.format('Bruce Lee 李小龙'))
"""
I am Bruce Lee 李小龙!
I am 'Bruce Lee 李小龙'!
I am 'Bruce Lee \u674e\u5c0f\u9f99'!
"""
格式说明符使用过于复杂,且实际使用场景不多,暂不讨论。
Python、C++、Java 都是常用的编程语言,它们在数据类型和类型转换方面有一些不同点,具体如下:
Python、C++ 和 Java 支持的数据类型略有不同。下面是它们支持的一些常见数据类型:
Python、C++ 和 Java 都支持类型转换,但它们的实现方法略有不同。
总的来说,这三种编程语言在数据类型和类型转换方面都有各自的特点和使用方式,需要根据具体的应用场景选择适合的编程语言和数据类型。
位运算是一种基于二进制的运算,可以对二进制数的每一位进行操作。Python、Java和C++都支持位运算,下面是它们的区别:
Python: Python支持位运算符,包括按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、左移位(<<)、右移位(>>),但是Python中没有无符号位移运算符。Python的整数类型是可以自动扩展的,不需要考虑溢出问题。
Java: Java同样支持位运算符,包括按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、左移位(<<)、右移位(>>)和无符号右移位(>>>)。Java中的整数类型有固定的位数,超出范围会出现溢出问题。
C++: C++同样支持位运算符,包括按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、左移位(<<)、右移位(>>)和无符号右移位(>>>)。与Java不同的是,C++中的整数类型也有固定的位数,超出范围也会出现溢出问题。C++也支持位域,可以将一个变量的几个位用来存储不同的信息。
PS:Python讲解进制抓换,位运算,补充位运算
虽然C++、Java和Python的for循环语法有些差异,但其本质都是实现循环执行一段代码,直到达到指定的循环次数或者满足某些条件后退出循环。
下面是这三种语言中for循环的主要差异:
C++的for循环通常用于实现计数循环,其语法如下:
for (初始化表达式; 布尔表达式; 更新表达式) {
// 代码块
}
其中,初始化表达式是一条语句,用于初始化一个计数器变量;布尔表达式是一个返回布尔值的表达式,用于测试计数器变量是否达到指定的值;更新表达式用于更新计数器变量的值。
C++遍历容器的几种方法
C++ 中遍历容器(如数组、vector、map 等)的方法有多种,下面介绍其中的几种:
使用 for 循环可以遍历数组和 vector 等容器,例如:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vec = {1, 2, 3, 4, 5};
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i] << " ";
}
return 0;
}
C++11 引入了 for-each 循环,可以方便地遍历容器中的元素,例如:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vec = {1, 2, 3, 4, 5};
for (int x : vec)
{
cout << x << " ";
}
return 0;
}
使用迭代器可以遍历所有类型的容器,包括 vector、list、set、map 等,例如:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vec = {1, 2, 3, 4, 5};
for (vector<int>::iterator it = vec.begin(); it != vec.end(); it++)
{
cout << *it << " ";
}
return 0;
}
使用 auto 关键字可以让编译器自动推导容器类型和迭代器类型,使代码更简洁,例如:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vec = {1, 2, 3, 4, 5};
for (auto it = vec.begin(); it != vec.end(); it++)
{
cout << *it << " ";
}
return 0;
}
以上是常见的遍历容器的方法,根据具体的应用场景选择适合的方法即可。
Java的for循环也常用于计数循环,其语法如下:
for (初始化表达式; 布尔表达式; 更新表达式) {
// 代码块
}
Java的for循环与C++的for循环语法基本相同,只是Java的for循环还支持对数组和集合进行迭代操作。
for (数据类型 变量名: 数组名/集合名) {
// 代码块
}
Python的for循环主要用于迭代操作,其语法如下:
for 变量 in 序列:
// 代码块
其中,序列可以是列表、元组、字符串等可迭代对象。在每一次迭代过程中,循环变量会被赋值为序列中的下一个元素。
需要注意的是,Python中的for循环不支持类似于C++和Java中的计数循环,但可以通过range函数实现类似的功能。例如:
for i in range(10):
// 代码块
这段代码会执行10次循环,每次循环i的值会依次为0~9。
Python的for循环可以迭代(遍历)以下对象:
需要注意的是,虽然Python的for循环可以迭代大多数容器类型(container),但并不是所有容器类型都是可迭代的(iterable)。例如数字(integer)和布尔(boolean)等非容器类型就不是可迭代的。
此外,我们还可以使用range()函数生成一个由整数组成的序列,并用for循环遍历这个序列。例如:
for i in range(5):
print(i)
这个代码片段将输出从0到4的整数。
字符串是计算机程序中非常常见的数据类型之一。以下是一些常见的字符串操作:
length
或 size
方法。+
或 concat
方法。substring
方法或者正则表达式。split
方法或者正则表达式。replace
方法或者正则表达式。find
方法或者正则表达式。stoi
、stof
或者 parseFloat
、parseInt
等方法。<
、>
、<=
、>=
、==
、!=
操作符或者 compareTo
方法。以下是 C++,Java 和 Python 中一些常见的字符串操作及其对比:
在 C++ 中,可以使用 size()
或者 length()
函数来获取字符串的长度。在 Java 中,可以使用 length()
方法来获取字符串的长度。在 Python 中,可以使用 len()
函数来获取字符串的长度。
str = "Hello, world!"
print(len(str)) # 输出 13
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello, world!";
cout << str.size() << endl; // 输出 13
return 0;
}
public class Main {
public static void main(String[] args) {
String str = "Hello, world!";
System.out.println(str.length()); // 输出 13
}
}
在 C++ 中,可以使用 ==
或者 !=
运算符来比较字符串是否相等。在 Java 中,可以使用 equals()
方法来比较字符串是否相等。在 Python 中,可以使用 ==
或者 !=
运算符来比较字符串是否相等。
str1 = "Hello, world!"
str2 = "Hello, Python!"
if str1 == str2:
print("相等")
else:
print("不相等") # 输出 "不相等"
#include <iostream>
#include <string>
using namespace std;
int main() {
string str1 = "Hello, world!";
string str2 = "Hello, C++!";
if (str1 == str2) {
cout << "相等" << endl;
} else {
cout << "不相等" << endl; // 输出 "不相等"
}C++
return 0;
}
public class Main {
public static void main(String[] args) {
String str1 = "Hello, world!";
String str2 = "Hello, Java!";
if (str1.equals(str2)) {
System.out.println("相等");
} else {
System.out.println("不相等"); // 输出 "不相等"
}
}
}
在 C++ 中,可以使用 find
函数来查找字符串中是否包含某个子串。在 Java 中,可以使用 indexOf
方法来查找字符串中是否包含某个子串。在 Python 中,可以使用 in
关键字来查找字符串中是否包含某个子串。
str = "Hello, world!"
if "world" in str:
print("包含")
else:
print("不包含") # 输出 "包含"
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello, world!";
if (str.find("world") != string::npos) {
cout << "包含" << endl;
} else {
cout << "不包含" << endl; // 输出 "包含"
}
return 0;
}
在 C++,Java 和 Python 中,字符串切片操作用于从一个字符串中获取子字符串。但是,这些语言中的切片操作略有不同。
C++
在 C++ 中,可以使用 substr
函数来获取字符串的子串,其语法如下:
string substr (size_t pos, size_t len) const;
其中,pos
参数指定要从哪个位置开始获取子串,len
参数指定要获取的子串的长度。如果省略 len
参数,则会返回从 pos
开始到字符串末尾的所有字符。
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello, world!";
string sub = str.substr(7, 5);
cout << sub << endl; // 输出 "world"
return 0;
}
Java
在 Java 中,可以使用 substring
方法来获取字符串的子串,其语法如下:
String substring(int beginIndex, int endIndex);
其中,beginIndex
参数指定要从哪个位置开始获取子串(包括该位置的字符),endIndex
参数指定要获取的子串的结束位置(不包括该位置的字符)。如果省略 endIndex
参数,则会返回从 beginIndex
开始到字符串末尾的所有字符。
public class Main {
public static void main(String[] args) {
String str = "Hello, world!";
String sub = str.substring(7, 12);
System.out.println(sub); // 输出 "world"
}
}
Python
在 Python 中,可以使用切片操作符 []
来获取字符串的子串,其语法如下:
string[start:end:step]
其中,start
参数指定要从哪个位置开始获取子串(包括该位置的字符),end
参数指定要获取的子串的结束位置(不包括该位置的字符),step
参数指定要跳过的字符数(默认为 1。为正数则从左向右输出,为负数则从右向左输出)。如果省略 start
参数,则会从字符串的开头开始获取子串;如果省略 end
参数,则会一直获取到字符串的末尾。
str = "Hello, world!"
sub = str[7:12]
print(sub) # 输出 "world"
s = "ABCDEFG"
s[1:4]#BCD
s[-3:]#EFG
s[:]#ABCDEF
s[::-1]#GFEDCBA
s[::-2]#GECA 倒着取且步长为2
s[0:6:-2]#无法取到任何值,注意方向,从0向左取
s[6:0:-2]#GECA
在 C++、Java 和 Python 中,字符串连接操作是非常常见的字符串操作之一。下面是在这三种语言中实现字符串连接的一些方法的对比:
使用 +
运算符
在 C++、Java 和 Python 中,都可以使用 +
运算符将两个字符串拼接起来。
#include <iostream>
#include <string>
using namespace std;
int main() {
string str1 = "Hello";
string str2 = "world!";
string newStr = str1 + ", " + str2 + "!";
cout << newStr << endl; // 输出 "Hello, world!"
return 0;
}
public class Main {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "world!";
String newStr = str1 + ", " + str2 + "!";
System.out.println(newStr); // 输出 "Hello, world!"
}
}
str1 = "Hello"
str2 = "world!"
newStr = str1 + ", " + str2 + "!"
print(newStr) # 输出 "Hello, world!"
使用 concat
方法
在 Java 中,还可以使用 concat
方法将两个字符串拼接起来。
public class Main {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "world!";
String newStr = str1.concat(", ").concat(str2).concat("!");
System.out.println(newStr); // 输出 "Hello, world!"
}
}
使用 join
方法
在 Python 中,可以使用 join
方法将多个字符串拼接成一个字符串。
strList = ["Hello", "world!"]
newStr = ", ".join(strList) + "!"
print(newStr) # 输出 "Hello, world!"
总的来说,在 C++、Java 和 Python 中,都可以使用类似于 +
运算符的方式来实现字符串连接,但是在 Java 和 Python 中还提供了其他的方法来实现字符串连接。
在 C++、Java 和 Python 中,字符串分割操作也是比较常见的字符串操作之一。下面是在这三种语言中实现字符串分割的一些方法的对比:
使用 split
方法
在 Java 和 Python 中,都提供了 split
方法,可以将一个字符串按照指定的分隔符分割成多个子串。
在 Java 中,可以使用 split
方法将一个字符串按照指定的正则表达式分割成多个子串,返回一个字符串数组。
public class Main {
public static void main(String[] args) {
String str = "apple,banana,orange";
String[] strArr = str.split(",");
for (String s : strArr) {
System.out.println(s);
}
}
}
在 Python 中,可以使用 split
方法将一个字符串按照指定的分隔符分割成多个子串,返回一个字符串列表。
str = "apple,banana,orange"
strList = str.split(",")
for s in strList:
print(s)
使用 istringstream
(C++)
在 C++ 中,可以使用 istringstream
类将一个字符串按照指定的分隔符分割成多个子串,返回一个字符串流对象。
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
int main() {
string str = "apple,banana,orange";
vector<string> strVec;
istringstream iss(str);
string s;
while (getline(iss, s, ',')) {
strVec.push_back(s);
}
for (string s : strVec) {
cout << s << endl;
}
return 0;
}
总的来说,在 Java 和 Python 中都提供了比较方便的 split
方法,可以快速地将一个字符串按照指定的分隔符分割成多个子串,而在 C++ 中需要使用 istringstream
类来实现类似的功能。
在 C++、Java 和 Python 中,字符串替换操作也是常见的字符串操作之一。下面是在这三种语言中实现字符串替换的一些方法的对比:
使用 replace
方法
在 Java 和 Python 中,都提供了 replace
方法,可以将一个字符串中指定的子串替换为另一个字符串。
在 Java 中,可以使用 replace
方法将一个字符串中指定的子串替换为另一个字符串。
public class Main {
public static void main(String[] args) {
String str = "hello, world!";
String newStr = str.replace("world", "Java");
System.out.println(newStr);
}
}
在 Python 中,可以使用 replace
方法将一个字符串中指定的子串替换为另一个字符串。
str = "hello, world!"
newStr = str.replace("world", "Python")
print(newStr)
使用算法库(C++)
在 C++ 中,可以使用 algorithm
库中的 replace
函数将一个字符串中指定的子串替换为另一个字符串。
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int main() {
string str = "hello, world!";
replace(str.begin(), str.end(), ',', ':');
cout << str << endl;
return 0;
}
使用正则表达式(C++、Java、Python)
在 C++、Java 和 Python 中,都支持正则表达式操作,可以使用正则表达式来实现字符串替换操作。
在 C++ 中,可以使用 regex
库来实现正则表达式操作。
#include <iostream>
#include <regex>
#include <string>
using namespace std;
int main() {
string str = "hello, world!";
regex reg(",");
string newStr = regex_replace(str, reg, ":");
cout << newStr << endl;
return 0;
}
在 Java 中,可以使用 replaceAll
方法来实现正则表达式操作。
public class Main {
public static void main(String[] args) {
String str = "hello, world!";
String newStr = str.replaceAll(",", ":");
System.out.println(newStr);
}
}
在 Python 中,可以使用 re
模块来实现正则表达式操作。
import re
str = "hello, world!"
newStr = re.sub(",", ":", str)
print(newStr)
总的来说,在 Java 和 Python 中都提供了比较方便的 replace
方法,可以快速地将一个字符串中指定的子串替换为另一个字符串,而在 C++ 中需要使用 algorithm
库中的 replace
函数或者 regex
库来实现类似的功能。同时,正则表达式在三种语言中都可以用来实现字符串替换操作。
在 C++、Java 和 Python 中,字符串与其他数据类型之间的转换是非常常见的操作。下面是在这三种语言中实现字符串转换的一些方法的对比:
字符串转整数
在 C++ 中,可以使用 stoi
函数将一个字符串转换为整数。
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "123";
int num = stoi(str);
cout << num << endl;
return 0;
}
在 Java 中,可以使用 Integer.parseInt
方法将一个字符串转换为整数。
public class Main {
public static void main(String[] args) {
String str = "123";
int num = Integer.parseInt(str);
System.out.println(num);
}
}
在 Python 中,可以使用 int
函数将一个字符串转换为整数。
str = "123"
num = int(str)
print(num)
整数转字符串
在 C++ 中,可以使用 to_string
函数将一个整数转换为字符串。
#include <iostream>
#include <string>
using namespace std;
int main() {
int num = 123;
string str = to_string(num);
cout << str << endl;
return 0;
}
在 Java 中,可以使用 String.valueOf
方法将一个整数转换为字符串。
public class Main {
public static void main(String[] args) {
int num = 123;
String str = String.valueOf(num);
System.out.println(str);
}
}
在 Python 中,可以使用 str
函数将一个整数转换为字符串。
num = 123
str = str(num)
print(str)
字符串转浮点数
在 C++ 中,可以使用 stof
函数将一个字符串转换为浮点数。
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "3.14";
float num = stof(str);
cout << num << endl;
return 0;
}
在 Java 中,可以使用 Float.parseFloat
方法将一个字符串转换为浮点数。
public class Main {
public static void main(String[] args) {
String str = "3.14";
float num = Float.parseFloat(str);
System.out.println(num);
}
}
在 Python 中,可以使用 float
函数将一个字符串转换为浮点数。
str = "3.14"
num = float(str)
print(num)
浮点数转字符串
在 C++ 中,可以使用 to_string
函数将一个浮点数转换为字符串。
#include <iostream>
#include <string>
using namespace std;
int main() {
float num = 3.14;
string str = to_string(num);
cout << str << endl;
return 0;
}
在 Java 中,可以使用 String.valueOf
方法将一个浮点数转换为字符串。
public class Main {
public static void main(String[] args) {
float num = 3.14f;
String str = String.valueOf(num);
System.out.println(str);
}
}
在 Python 中,可以使用 str
函数将一个浮点数转换为字符串。
num = 3.14
str = str(num)
print(str)
字符串转布尔值
在 C++ 中,可以使用 stoi
函数将一个字符串转换为布尔值。
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "true";
bool value = stoi(str);
cout << value << endl;
return 0;
}
在 Java 中,可以使用 Boolean.parseBoolean
方法将一个字符串转换为布尔值。
public class Main {
public static void main(String[] args) {
String str = "true";
boolean value = Boolean.parseBoolean(str);
System.out.println(value);
}
}
在 Python 中,可以使用 bool
函数将一个字符串转换为布尔值。
str = "True"
value = bool(str)
print(value)
布尔值转字符串
在 C++ 中,可以使用 to_string
函数将一个布尔值转换为字符串。
#include <iostream>
#include <string>
using namespace std;
int main() {
bool value = true;
string str = to_string(value);
cout << str << endl;
return 0;
}
在 Java 中,可以使用 String.valueOf
方法将一个布尔值转换为字符串。
public class Main {
public static void main(String[] args) {
boolean value = true;
String str = String.valueOf(value);
System.out.println(str);
}
}
在 Python 中,可以使用 str
函数将一个布尔值转换为字符串。
value = True
str = str(value)
print(str)
总的来说,在这些语言中,字符串与其他数据类型之间的转换非常容易,并且提供了方便的内置函数和方法来完成这些操作。
获取长度:len
查找内容:find,index,rfind,rindex
判断:startswith,endswith,isdigit,isalnum,isspace
计算出现次数:count
替换内容:replace
切割字符串:split,rsplit,splitlines,partition,rpartition
修改大小写:capitalize,title,upper,lower
空格处理:ljust,rjust,center,lstrip,rstrip,strip
字符串拼接:join
注意:Python中字符串是不可变的,所有的字符串相关方法,都不会改变原来的字符串,都是返回一个结果,在这个新的的返回值里,保留了运行后的结果。
len函数可以获取字符串的长度。
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(len(mystr)) # 17 获取字符串的长度
查找相关的方法,使用方式大致相同,但是略有区别。
find
查找指定内容在字符串中是否存在,如果存在就返回该内容在字符串中第一次出现的开始位置索引值,如果不存在,则返回-1.
语法格式:
S.find(sub[, start[, end]]) -> int
示例:
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.find('好风光')) # 10 '好风光'第一次出现时,'好'所在的位置
print(mystr.find('你好')) # -1 '你好'不存在,返回 -1
print(mystr.find('风', 12)) # 15 从下标12开始查找'风',找到风所在的位置试15
print(mystr.find('风光',1,10)) # -1 从下标1开始到12查找"风光",未找到,返回 -1
rfind
类似于 find()函数,不过是从右边开始查找。
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.rfind('好')) # 14
index
跟find()方法一样,只不过,find方法未找到时,返回-1,而str未找到时,会报一个异常。
语法格式:
S.index(sub[, start[, end]]) -> int
rindex
类似于 index(),不过是从右边开始。
python提供了非常丰富的方法,可以用来对一个字符串进行判断。
startswith
判断字符串是否以指定内容开始。 语法格式:
S.startswith(prefix[, start[, end]]) -> bool
示例:
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.startswith('今')) # True
print(mystr.startswith('今日')) # False
endswith
判断字符串是否以指定内容结束。
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.endswith('好风光')) #True
print(mystr.endswith('好日子')) #False
isalpha
判断字符串是否是纯字母。
mystr = 'hello'
print(mystr.isalpha()) # True
mystr = 'hello world'
print(mystr.isalpha()) # False 因为中间有空格
isdigit
判断一个字符串是否是纯数字,只要出现非0~9的数字,结果就是False.
mystr = '1234'
print(mystr.isdigit()) # True
mystr = '123.4'
print(mystr.isdigit()) # False
mystr = '-1234'
print(mystr.isdigit()) # False
isalnum
判断是否由数字和字母组成。只要出现了非数字和字母,就返回False.
mystr = 'abcd'
print(mystr.isalnum()) # True
mystr = '1234'
print(mystr.isalnum()) # True
mystr = 'abcd1234'
print(mystr.isalnum()) # True
mystr = 'abcd1234_'
print(mystr.isalnum()) # False
isspace
如果 mystr 中只包含空格,则返回 True,否则返回 False.
mystr = ''
print(mystr.isspace()) # False mystr是一个空字符串
mystr = ' '
print(mystr.isspace()) # True 只有空格
mystr = ' d'
print(mystr.isspace()) # False 除了空格外还有其他内容
count
返回 str在start和end之间 在 mystr里面出现的次数。
语法格式:
S.count(sub, start, end) -> int
示例:
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.count('好')) # 3. '好'字出现三次
替换字符串中指定的内容,如果指定次数count,则替换不会超过count次。
mystr = '今天天气好晴朗,处处好风光呀好风光'
newstr = mystr.replace('好', '坏')
print(mystr) # 今天天气好晴朗,处处好风光呀好风光 原字符串未改变!
print(newstr) # 今天天气坏晴朗,处处坏风光呀坏风光 得到的新字符串里,'好'被修改成了'坏'
newstr = mystr.replace('好','坏',2) # 指定了替换的次数
print(newstr) # 今天天气坏晴朗,处处坏风光呀好风光 只有两处的'好'被替换成了'坏'
c++的替换是直接改变原来的字符串,而python和java的替换是返回新的字符串,区分。
内容分隔主要涉及到split,splitlines,partition和rpartition四个方法。
split
以指定字符串为分隔符切片,如果 maxsplit有指定值,则仅分隔 maxsplit+1 个子字符串。返回的结果是一个列表。
mystr = '今天天气好晴朗,处处好风光呀好风光'
result = mystr.split() # 没有指定分隔符,默认使用空格,换行等空白字符进行分隔
print(result) #['今天天气好晴朗,处处好风光呀好风光'] 没有空白字符,所以,字符串未被分隔
result = mystr.split('好') # 以 '好' 为分隔符
print(result) # ['今天天气', '晴朗,处处','风光呀,'风光']
result = mystr.split("好",2) # 以 '好' 为分隔符,最多切割成3份
print(result) # ['今天天气', '晴朗,处处', '风光呀好风光']
rsplit
用法和split基本一致,只不过是从右往左分隔。
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.rsplit('好',1)) #['今天天气好晴朗,处处好风光呀', '风光']
splitlines
按照行分隔,返回一个包含各行作为元素的列表。
mystr = 'hello \nworld'
print(mystr.splitlines())
s = '''
今天天气好晴朗!
处处好风光呀好风光!
'''
result = s.splitlines()
print(result)#['', '今天天气好晴朗!', '处处好风光呀好风光!']
partition
把mystr以str分割成三部分,str前,str和str后,三部分组成一个元组
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.partition('好')) # ('今天天气', '好', '晴朗,处处好风光呀好风光')
rpartition
类似于 partition()函数,不过是从右边开始.
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.rpartition('好')) # ('今天天气好晴朗,处处好风光呀', '好', '风光')
PS:partition和rpartition只会将字符串分成三部分
修改大小写的功能只对英文有效,主要包括,首字母大写capitalize,每个单词的首字母大写title,全小写lower,全大写upper.
capitalize
第一个单词的首字母大写。
mystr = 'hello world'
print(mystr.capitalize()) # Hello world
title
每个单词的首字母大写。
mystr = 'hello world'
print(mystr.title()) # Hello World
lower
所有都变成小写。
mystr = 'hElLo WorLD'
print(mystr.lower()) # hello world
upper
所有都变成大写。
mystr = 'hello world'
print(mystr.upper()) #HELLO WORLD
引入:
username = input("请输入用户名:")#请输入用户名:admin '\n'
print(len(username))#9
如果输入了admin加上四个空格时空格也会被读取到,如果写入数据里后,之后再登录时不直观,易出错,不希望字符串参与到数据库中,所以一般需要一些去除空格的操作。
Python为我们提供了各种操作字符串里表格的方法。
ljust
返回指定长度的字符串,并在右侧使用空白字符补全(左对齐)。
str = 'hello'
print(str.ljust(10)) # hello 在右边补了五个空格
rjust
返回指定长度的字符串,并在左侧使用空白字符补全(右对齐)。
str = 'hello'
print(str.rjust(10)) # hello在左边补了五个空格
center
返回指定长度的字符串,并在两端使用空白字符补全(居中对齐)
str = 'hello'
print(str.center(10)) # hello 两端加空格,让内容居中
上面三个类似于C/C++ 的\t,但是比较灵活
lstrip
删除 mystr 左边的空白字符。
mystr = ' he llo '
print(str.lstrip()) #he llo 只去掉了左边的空格,中间和右边的空格被保留
rstrip
删除 mystr 右边的空白字符。
mystr = ' he llo '
print(str.rstrip()) # he llo右边的空格被删除
strip
删除两断的空白字符。
str = ' he llo '
print(str.strip()) #he llo
把参数进行遍历,取出参数里的每一项,然后再在后面加上mystr
语法格式:
S.join(iterable)
join的作用常用在拼接列表的字符串
示例:
mystr = 'a'
print(mystr.join('hxmdq')) #haxamadaq 把hxmd一个个取出,并在后面添加字符a. 最后的 q 保留,没有加 a
print(mystr.join(['hi','hello','good'])) #hiahelloagood
作用:可以把列表或者元组快速的转变成为字符串,并且以指定的字符分隔。
txt = '_'
print(txt.join(['hi','hello','good'])) #hi_hello_good
print(txt.join(('good','hi','hello'))) #good_hi_hello
'hello' + 'world'
的结果是 'helloworld'
'hello'*2
的结果是hellohello
#查找:find,rfind
#例子:截取指定字符串
path = "https://www.bilibili.com/video/BV1R7411F7JV/?p=32&spm_id_from=pageDriver&vd_source=547f6e585f4f9c01d3cdb8fe3dcd38e9"
name = path[path.rfind('=') + 1:] #path.rfind('=')返回的是下标
print(name)#547f6e585f4f9c01d3cdb8fe3dcd38e9
path = "https://www.bilibili.com/video/BV1R7411F7JV/?p=32&spm_id_from=pageDriver&vd_source=547f6e585f4f9c01d3cdb8fe3dcd38e9"
print(path.rfind('bilibili'))#12
#判断:startswith,endswith,isdigit,isalnum,isspace
#返回值都是bool类型
path = "https://www.bilibili.com/video/BV1R7411F7JV/?p=32&spm_id_from=pageDriver&vd_source=547f6e585f4f9c01d3cdb8fe3dcd38e9"
result = path.startswith('http')#返回的是bool类型
print(result)#True
s = "a1234"
print(s.isdigit)
#练习
'''
模拟文件上传,键盘输入文件名abc.jpg,判断文件名abc是否大于6位以上,扩展名是否是:jpg,gif,png格式,如果不是则提示上传失败,如果名字不满足条件,而扩展名满足条件则随机生成一个6位数子组成的文件名,打印成功上传xxxx.png
'''
import random
file = input()
if file.endswith('jpg') or file.endswith('gif') or file.endswith('png'):
i = file.find('.')
name = file[:i]
if len(name)<6:
n = random.randint(100000,999999)
file = str(n) + file[i:] #T:所有的字符串操作都不会改变原来的字符串
print("成功上传%s"%file)
else:
print("文件格式错误")
'''
模拟随机产生验证码
'''
filename = ""
s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456"
for i in range(4):
index = random.randint(0,len(s)-1)
filename += s[index]
print(filename)
计算机只能处理数字(其实就是数字0和数字1),如果要处理文本,就必须先把文本转换为数字才能处理。最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是255(二进制11111111=十进制255),0 - 255被用来表示大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码。
ASCII码表使用7位二进制表示一个字符,它的区间范围时0~127,一共只能表示128个字符,仅能支持英语。随着计算机科学的发展,西欧语言、希腊语、泰语、阿拉伯语、希伯来语等语言的字符也被添加到码表中,形成了一个新的码表ISO8859-1(又被称为Latin1)码表。ISO8859-1使用8位二进制表示一个字符串,完全兼容ASCII码表。
Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
使用chr和ord方法,可以实现字符和编码之间的相互转换。
print(ord('a')) # 使用ord方法,可以获取一个字符对应的编码
print(chr(100)) # 使用chr方法,可以获取一个编码对应的字符
使用Unicode为每种语言的每个字符都设定了唯一的二进制编码,但是它还是存在一定的问题,不够完美。
例如,汉字 “你” 转换成为一个字符结果是0x4f60
,转换成为二进制就是 01001111 01100000
,此时就有两个问题:
1001111 01100000
到底是一个汉字 “你” ,还是两个 Latin1 字符?为了解决这个问题,就出现了一些编码规则,按照一定的编码规则对Unicode数字进行计算,得出新的编码。在中国常用的字符编码有 GBK
,Big5
和utf8
这三种编码规则。
使用字符串的encode方法,可以将字符串按照指定的编码格式转换称为二进制;使用decode方法,可以将一个二进制数据按照指定的编码格式转换成为字符串。
s1 = '你'.encode('utf8') # 将字符 你 按照utf8格式编码称为二进制
print(type(s1)) # <class 'bytes'>
print(s1) # b'\xe4\xbd\xa0'
s2 = s1.decode('utf8') # 将二进制按照utf8格式解码称为字符串
print(s2)
s3 = '你'.encode('gbk') # 将字符 你 按照gbk格式转换称为二进制
print(s3) # b'\xc4\xe3'
s4 = s3.decode('gbk') # 将二进制按照gbk格式解码称为字符
print(s4)
思考:文字产生乱码的原因以及解决方案。
定义列的格式:[元素1, 元素2, 元素3, ..., 元素n]
变量tmp的类型为列表
tmp = ['xiaoWang',180, 65.0]
列表中的元素可以是不同类型的
namesList = ['xiaoWang','xiaoZhang','xiaoHua']
print(namesList[0])
print(namesList[1])
print(namesList[2])
结果:
xiaoWang
xiaoZhang
xiaoHua
我们对于可变数据(例如,列表,数据库等)的操作,一般包含增、删、改、查四个方面。
添加元素有一下几个方法:
append会把新元素添加到列表末尾
#定义变量A,默认有3个元素
A = ['xiaoWang','xiaoZhang','xiaoHua']
print("-----添加之前,列表A的数据-----A=%s" % A)
#提示、并添加元素
temp = input('请输入要添加的学生姓名:')
A.append(temp)
print("-----添加之后,列表A的数据-----A=%s" % A)
insert(index, object) 在指定位置index前插入元素object
strs = ['a','b','m','s']
strs.insert(3,'h')
print(strs) # ['a', 'b', 'm', 'h', 's']
通过extend可以将另一个集合中的元素逐一添加到列表中
a = ['a','b','c']
b = ['d','e','f']
a.extend(b)
print(a) # ['a', 'b', 'c', 'd', 'e', 'f'] 将 b 添加到 a 里
print(b) # ['d','e','f'] b的内容不变
PS:python中的+号可以用在那些方面
我们是通过指定下标来访问列表元素,因此修改元素的时候,为指定的列表下标赋值即可。
#定义变量A,默认有3个元素
A = ['xiaoWang','xiaoZhang','xiaoHua']
print("-----修改之前,列表A的数据-----A=%s" % A)
#修改元素
A[1] = 'xiaoLu'
print("-----修改之后,列表A的数据-----A=%s" % A)
所谓的查找,就是看看指定的元素是否存在,以及查看元素所在的位置,主要包含一下几个方法:
python中查找的常用方法为:
#待查找的列表
nameList = ['xiaoWang','xiaoZhang','xiaoHua']
#获取用户要查找的名字
findName = input('请输入要查找的姓名:')
#查找是否存在
if findName in nameList:
print('在列表中找到了相同的名字')
else:
结果1:(找到)
结果2:(没有找到)
说明:
in的方法只要会用了,那么not in也是同样的用法,只不过not in判断的是不存在
index用来查找元素所在的位置,如果未找到则会报错;count用来计算某个元素出现的次数。它们的使用和字符串里的使用效果一致。
>>> a = ['a', 'b', 'c', 'a', 'b']
>>> a.index('a', 1, 3) # 注意是左闭右开区间
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: 'a' is not in list
>>> a.index('a', 1, 4)
3
>>> a.count('b')
2
>>> a.count('d')
0
类比现实生活中,如果某位同学调班了,那么就应该把这个条走后的学生的姓名删除掉;在开发中经常会用到删除这种功能。
列表元素的常用删除方法有:
movieName = ['加勒比海盗','骇客帝国','第一滴血','指环王','霍比特人','速度与激情']
print('------删除之前------movieName=%s' % movieName)
del movieName[2]
print('------删除之后------movieName=%s' % movieName)
movieName = ['加勒比海盗','骇客帝国','第一滴血','指环王','霍比特人','速度与激情']
print('------删除之前------movieName=%s' % movieName)
movieName.pop()
print('------删除之后------movieName=%s' % movieName)
movieName.pop(-1)#['加勒比海盗','骇客帝国','第一滴血','指环王','霍比特人']
movieName.pop(8)#报错
movieName = ['加勒比海盗','骇客帝国','第一滴血','指环王','霍比特人','速度与激情']
print('------删除之前------movieName=%s' % movieName)
movieName.remove('指环王')
print('------删除之后------movieName=%s' % movieName)
list1 = ['火腿','酸奶','酸奶','辣条','薯条','面包','薯条','酸奶','酸奶']
for i in list1:
if i == '酸奶':
list1.remove(i)
print(list1)#['火腿', '辣条', '薯条', '面包', '薯条', '酸奶', '酸奶']
#结果发现如果有连续的元素时会出现漏删操作,因为删除后列表元素减一,但是for循环的下标不变,继续进行,所以跳过了下一个元素。
#优化:
n = 0
while n < len(list1):
if list1[n] == '酸奶':
list1.remove('酸奶')
else:
n+=1
#错误写法
#for i in range(len(list1)):
# if list1[i] == '酸奶':
# list1.remove('酸奶')
# i -= 1
# print(list1)
#正确写法1
result_li = []
for i in list1:
if i != elem:
result_li.append(i)
list1 = result_li
print(li)
#正确写法2
for i in list1[::-1]:
if i == '酸奶':
list1.remove(i)#remove从左往右寻找删除
print(list1)
sort方法是将list按特定顺序重新排列,默认为由小到大,参数reverse=True可改为倒序,由大到小。
reverse方法是将list逆置。
>>> a = [1, 4, 2, 3]
>>> a
[1, 4, 2, 3]
>>> a.reverse() # 逆置,不排序
>>> a
[3, 2, 4, 1]
>>> a.sort() # 默认从小到大排序
>>> a
[1, 2, 3, 4]
>>> a.sort(reverse=True) # 从大到小排序
>>> a
[4, 3, 2, 1]
请删除列表 words = ['hello','',','good','hi','','yes','','no']
里所有的空字符串。
为了更有效率的输出列表的每个数据,可以使用循环来完成
namesList = ['xiaoWang','xiaoZhang','xiaoHua']
length = len(namesList) # 获取列表长度
i = 0
while i<length:
print(namesList[i])
i+=1
Copy
结果:
xiaoWang
xiaoZhang
xiaoHua
Copy
while 循环是一种基本的遍历列表数据的方式,但是最常用也是最简单的方式是使用 for 循环
namesList = ['xiaoWang','xiaoZhang','xiaoHua']
for name in namesList:
print(name)
Copy
结果:
xiaoWang
xiaoZhang
xiaoHua
Copy
# 使用中间变量
a = 4
b = 5
c = 0
c = a
a = b
b = c
print(a)
print(b)
Copy
手动实现冒泡排序(难)
nums = [5, 1, 7, 6, 8, 2, 4, 3]
for j in range(0, len(nums) - 1):
for i in range(0, len(nums) - 1 - j):
if nums[i] > nums[i + 1]:
a = nums[i]
nums[i] = nums[i+1]
nums[i+1] = a
print(nums)
Copy
有一个列表names,保存了一组姓名names=['zhangsan','lisi','chris','jerry','henry']
,再让用户输入一个姓名,如果这个姓名在列表里存在,提示用户姓名已存在;如果这个姓名在列表里不存在,就将这个姓名添加到列表里。
类似while循环的嵌套,列表也是支持嵌套的
一个列表中的元素又是一个列表,那么这就是列表的嵌套
此处重点掌握怎么操作被嵌套的列表
>>> schoolNames = [
... [1, 2, 3],
... [11, 22, 33],
... [111, 222, 333]
... ]
>>> schoolNames[1][2] # 获取数字 33
33
>>> schoolNames[1][2] = 'abc' # 把 33 修改为 'abc'
>>> schoolNames
[[1, 2, 3], [11, 22, 'abc'], [111, 222, 333]]
>>> schoolNames[1][2][2] # 获取 'abc' 里的字符c
'c'
Copy
也就是说,操作嵌套列表,只要把要操作元素的下标当作变量名来使用即可。
一个学校,有3个办公室,现在有8位老师等待工位的分配,请编写程序,完成随机的分配
import random
# 定义一个列表用来保存3个办公室
offices = [[],[],[]]
# 定义一个列表用来存储8位老师的名字
names = ['A','B','C','D','E','F','G','H']
i = 0
for name in names:
index = random.randint(0,2)
offices[index].append(name)
i = 1
for tempNames in offices:
print('办公室%d的人数为:%d'%(i,len(tempNames)))
i+=1
for name in tempNames:
print("%s"%name,end='')
print("\n")
print("-"*20)
Copy
运行结果如下:
所谓的列表推导式,就是指的轻量级循环创建列表
list1 = [i*2 for i in range(1,10,2)]
查看以下代码,说出打印的结果。
a = 12
b = a
b = 13
print(b)
print(a)
nums1 = [1, 5, 8, 9, 10, 12]
nums2 = nums1
nums2[0] = 100
print(nums2)
print(nums1)
Copy
思考:
Python中的赋值运算都是引用(即内存地址)的传递。对于可变类型来说,修改原数据的值,会改变赋值对象的值。
使用列表的 copy 方法,或者 copy 模块就可以赋值一个列表。
使用列表的copy方法,可以直接将原来的列表进行复制,变成一个新的列表,这种复制方式是浅复制。
nums1 = [1, 5, 8, 9, 10, 12]
nums2 = nums1.copy() # 调用列表的copy方法,可以复制出一个新的列表
nums2[0] = 100
# 修改新列表里的数据,不会影响到原有列表里的数据
print(nums2)
print(nums1)
Copy
除了使用列表的copy方法以外,Python还提供了copy模块来复制一个对象。copy模块提供了浅复制和深复制两种方式,它们的使用方式相同,但是执行的效果有一定的差异。
浅拷贝是对于一个对象的顶层拷贝,通俗的理解是:拷贝了引用,并没有拷贝内容。
import copy
words1 = ['hello', 'good', ['yes', 'ok'], 'bad']
# 浅拷贝只会拷贝最外层的对象,里面的数据不会拷贝,而是直接指向
words2 = copy.copy(words1)
words2[0] = '你好'
words2[2][0] = 'no'
print(words1) # ['hello', 'good', ['no', 'ok'], 'bad']
# wrods2 里的 yes 被修改成了 no
print(words2) # ['你好', 'good', ['no', 'ok'], 'bad']
Copy
深拷贝是对于一个对象所有层次的递归拷贝。
import copy
words1 = ['hello', 'good', ['yes', 'ok'], 'bad']
# 深拷贝会将对象里的所有数据都进行拷贝
words2 = copy.deepcopy(words1)
words2[0] = '你好'
words2[2][0] = 'no'
print(words1) # ['hello', 'good', ['yes', 'ok'], 'bad']
print(words2) # ['你好', 'good', ['no', 'ok'], 'bad']
Copy
列表和字符串一样,也支持切片,切片其实就是一种浅拷贝。
words1 = ['hello', 'good', ['yes', 'ok'], 'bad']
words2 = words1[:]
words2[0] = '你好'
words2[2][0] = 'no'
print(words1) # ['hello', 'good', ['no', 'ok'], 'bad']
print(words2) # ['你好', 'good', ['no', 'ok'], 'bad']
Python的元组与列表类似,不同之处在于元组的元素不能修改。元组使用小括号,列表使用方括号。
>>> aTuple = ('et',77,99.9)
>>> aTuple
('et',77,99.9)
Copy
说明: python中不允许修改元组的数据,包括不能删除其中的元素。
index和count与字符串和列表中的用法相同
>>> a = ('a', 'b', 'c', 'a', 'b')
>>> a.index('a', 1, 3) # 注意是左闭右开区间
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: tuple.index(x): x not in tuple
>>> a.index('a', 1, 4)
3
>>> a.count('b')
2
>>> a.count('d')
0
Copy
定义只有一个元素的元组,需要在唯一的元素后写一个逗号
>>> a = (11)
>>> a
11
>>> type(a)
int
>>> a = (11,) # 只有一个元素的元组,必须要在元素后写一个逗号
>>> a
(11,)
>>> type(a)
tuple
Copy
# 第1种方式,使用中间变量
a = 4
b = 5
c = 0
c = a
a = b
b = c
print(a)
print(b)
# 第2种方式,直接交换。
a, b = 4, 5
a, b = b, a
print(a)
print(b)
当存储的数据要动态添加、删除的时候,我们一般使用列表,但是列表有时会遇到一些麻烦。
# 定义一个列表保存,姓名、性别、职业
nameList = ['xiaoZhang', '男', '木匠'];
# 当修改职业的时候,需要记忆元素的下标
nameList[2] = '铁匠'
# 如果列表的顺序发生了变化,添加年龄
nameList = ['xiaoWang', 18, '男', '铁匠']
# 此时就需要记忆新的下标,才能完成名字的修改
nameList[3] = 'xiaoxiaoWang'
Copy
有没有方法,既能存储多个数据,还能在访问元素的很方便就能够定位到需要的那个元素呢?
答:
字典
定义字典的格式:**{键1:值1, 键2:值2, 键3:值3, …, 键n:值n}**
变量info为字典类型:
info = {'name':'班长', 'id':100, 'sex':'f', 'address':'地球亚洲中国上海'}
info['name'] # 字典使用键来获取对应的值
Python中的字典是一种可变的数据结构,可以用于存储键值对。下面是一些常用的字典操作:
1.创建字典
使用大括号{}或者dict()函数创建一个空的字典:
my_dict = {}
my_dict = dict()
使用大括号{}创建包含键值对的字典:
my_dict = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
2.添加或修改键值对
通过指定键来添加或修改对应的值:
my_dict['key4'] = 'value4'
my_dict['key1'] = 'new_value1'
PS:setdefault()
函数
字典的setdefault()函数是一种用于向字典中添加键值对的方法。如果字典中已经存在指定的键,则setdefault()函数会返回该键的值,否则会将键值对添加到字典中。
setdefault()函数的语法如下:
dict.setdefault(key, default_value)
其中,key是要查找或添加的键,default_value是可选参数,表示如果指定的键不存在时要添加的默认值。如果没有指定default_value,则默认为None。
setdefault()函数的作用如下:
例如,以下代码演示了如何使用setdefault()函数:
>>> d = {'a': 1, 'b': 2}
>>> d.setdefault('a', 3)
1
>>> d.setdefault('c', 3)
3
>>> print(d)
{'a': 1, 'b': 2, 'c': 3}
在上面的示例中,字典d已经存在键’a’,因此第一个setdefault()调用返回1。而字典d中不存在键’c’,因此第二个setdefault()调用将添加一个新的键值对{‘c’: 3}到字典中,并返回默认值3。最终,字典d中包含三个键值对。
3.删除键值对
使用del关键字删除指定的键值对:
del my_dict['key3']
使用pop()方法删除指定的键值对,并返回对应的值:
value = my_dict.pop('key2')
使用popitem()方法从后往前删除一个键值对,并返回对应的键和值(返回的是一个元组):
key, value = my_dict.popitem()
4.访问字典中的值
通过指定键来访问对应的值:
value = my_dict['key1']
使用get()方法访问指定键对应的值,如果键不存在则返回指定的默认值:
value = my_dict.get('key2', 'default_value')
使用keys()方法返回字典中所有的键:
keys = my_dict.keys()#dict_keys(['name', 'id', 'sex', 'address'])
使用values()方法返回字典中所有的值:
values = my_dict.values()#dict_values(['班长', 100, 'f', '地球亚洲中国上海'])
使用items()方法返回字典中所有的键值对:
items = my_dict.items()#dict_items([('name', '班长'), ('id', 100), ('sex', 'f'), ('address', '地球亚洲中国上海')])
for i in items:
print(i)
'''
('name', '班长')
('id', 100)
('sex', 'f')
('address', '地球亚洲中国上海')
'''
for k,v in items:
print(k,v)
'''
name 班长
id 100
sex f
address 地球亚洲中国上海
'''
5.其他操作
1.使用len()
函数获取字典中键值对的数量:
length = len(my_dict)
2.使用in关键字检查指定的键是否存在于字典中:
if 'key1' in my_dict:
print('Key exists in the dictionary')
3.使用clear()
方法清空字典中所有的键值对:
my_dict.clear()
4.使用update
方法合并字典
它用于将一个字典中的键值对添加到另一个字典中。
update()
方法的语法如下:
dict.update([other])
其中,other
是要添加到当前字典中的字典或键值对序列。如果 other
是字典,则将其键值对添加到当前字典中。如果 other
是键值对序列,则将其作为一个列表添加到当前字典中。如果键已经存在,则将其对应的值更新为新的值。
例如,假设我们有两个字典 dict1
和 dict2
:
dict1 = {"a": 1, "b": 2}
dict2 = {"b": 3, "c": 4}
现在我们想将 dict2
中的键值对添加到 dict1
中,可以使用 update()
方法:
dict1.update(dict2)
print(dict1)
输出结果为:
{'a': 1, 'b': 3, 'c': 4}
注意,update()
方法会修改原字典 dict1
,并返回 None
。如果要创建一个新的字典而不修改原字典,则可以使用以下语句:
new_dict = dict1.copy()
new_dict.update(dict2)
5.fromkeys
方法
fromkeys()
是一个dict
类方法,用于创建一个新的字典,其中包含指定键列表的键和所有值为默认值None
(或指定的默认值)。fromkeys()
方法的语法如下:
dict.fromkeys(seq[, value])
其中,seq
参数是指定的键列表,可以是一个可迭代对象,例如一个列表、元组或集合。value
参数是可选的,它是指定的默认值。如果省略了value
参数,则默认值为None
。
下面是一个示例:
keys = ['a', 'b', 'c']
my_dict = dict.fromkeys(keys)
print(my_dict)
# Output: {'a': None, 'b': None, 'c': None}
my_dict = dict.fromkeys(keys,10)
print(my_dict)
# Output: {'a': 10, 'b': 10, 'c': 10}
my_dict = dict.fromkeys(keys,[10,20])
print(my_dict)
# Output: {'a': [10,20], 'b': [10,20], 'c': [10,20]}
在上面的示例中,我们创建了一个包含三个键的字典,并使用fromkeys()
方法将它们初始化为None
。
说明:
在习惯上:
for循环遍历
您可以使用for循环遍历字典中的所有键值对。以下是一个示例代码,它使用for循环遍历字典,并打印出每个键值对:
my_dict = {"apple": 3, "banana": 5, "orange": 2}
for key, value in my_dict.items():
print(key, value)
在上面的代码中,my_dict.items()
方法返回一个包含字典中所有键值对的列表,然后for
循环迭代该列表,并将每个键值对的键和值分别赋值给key
和value
变量。然后,print
函数用于打印出每个键值对。
集合(set)是一个无序的不重复元素序列,可以使用大括号 { } 或者 set() 函数创建集合。
注意:创建一个空集合必须用 set() 而不是 { },因为 { } 是用来创建一个空字典。
创建格式:
parame = {value01,value02,...}
#或者
set(value)
语法格式如下:
s.add(x)
将元素 x 添加到集合 s 中,如果元素已存在,则不进行任何操作。
>>>thisset = set(("Google", "Runoob", "Taobao"))
>>> thisset.add("Facebook")
>>> print(thisset)
{'Taobao', 'Facebook', 'Google', 'Runoob'}
还有一个方法,也可以添加元素,且参数可以是列表,元组,字典等,语法格式如下:
s.update( x )
x 可以有多个,用逗号分开。
>>>thisset = set(("Google", "Runoob", "Taobao"))
>>> thisset.update({1,3})
>>> print(thisset)
{1, 3, 'Google', 'Taobao', 'Runoob'}
>>> thisset.update([1,4],[5,6])
>>> print(thisset)
{1, 3, 4, 5, 6, 'Google', 'Taobao', 'Runoob'}
语法格式如下:
s.remove( x )
将元素 x 从集合 s 中移除,如果元素不存在,则会发生错误。
>>>thisset = set(("Google", "Runoob", "Taobao"))
>>> thisset.remove("Taobao")
>>> print(thisset)
{'Google', 'Runoob'}
>>> thisset.remove("Facebook") # 不存在会发生错误
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'Facebook'
>>>
此外还有一个方法也是移除集合中的元素,且如果元素不存在,不会发生错误。格式如下所示:
s.discard( x )
Copy
>>>thisset = set(("Google", "Runoob", "Taobao"))
>>> thisset.discard("Facebook") # 不存在不会发生错误
>>> print(thisset)
{'Taobao', 'Google', 'Runoob'}
我们也可以设置随机删除集合中的一个元素,语法格式如下:
s.pop()
Copy
thisset = set(("Google", "Runoob", "Taobao", "Facebook"))
x = thisset.pop()
print(x)
print(thisset)
方法 | 描述 |
---|---|
add() | 为集合添加元素 |
clear() | 移除集合中的所有元素 |
copy() | 拷贝一个集合 |
pop() | 随机移除元素 |
remove() | 移除指定元素 |
union | 返回两个集合的并集 |
update() | 给集合添加元素 |
difference() | 返回多个集合的差集 |
difference_update() | 移除集合中的元素,该元素在指定的集合也存在。 |
discard() | 删除集合中指定的元素 |
intersection() | 返回集合的交集 |
intersection_update() | 删除集合中的元素,该元素在指定的集合中不存在。 |
isdisjoint() | 判断两个集合是否包含相同的元素,如果没有返回 True,否则返回 False。 |
issubset() | 判断指定集合是否为该方法参数集合的子集。 |
issuperset() | 判断该方法的参数集合是否为指定集合的子集 |
symmetric_difference() | 返回两个集合中不重复的元素集合。 |
symmetric_difference_update() | 移除当前集合中在另外一个指定集合相同的元素,并将另外一个指定集合中不同的元素插入到当前集合中。 |
有一个无序且元素数据重复的列表nums, nums=[5,8,7,6,4,1,3,5,1,8,4]
,要求对这个列表里的元素去重,并进行降序排序。
# 方法一:调用列表的sort方法
nums2 = list(set(nums))
nums2.sort(reverse=True)
print(nums2)
# 方法二:使用sorted内置函数
print(sorted(list(set(nums)),reverse=True))
使用Python内置的eval函数,可以执行字符串里的Python代码。使用这种方式,可以将字符串转换成为其他类型的数据。
x = '1+1'
print(eval(x)) # 2
print(type(eval(x))) # <class 'int'>
y = '{"name":"zhangsan","age":18}'
print(eval(y))
print(type(eval(y))) # <class 'dict'>
print(eval('1 > 2')) # False
eval('input("请输入您的姓名:")')
JSON(JavaScriptObjectNotation, JS对象简谱)是一种轻量级的数据交换格式,它基于 ECMAScript 的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。JSON本质是一个字符串
JSON的功能强大,使用场景也非常的广,目前我们只介绍如何使用Python的内置JSON模块,实现字典、列表或者元组与字符串之间的相互转换。
使用json的dumps方法,可以将字典、列表或者元组转换成为字符串。
import json
person = {'name': 'zhangsan', 'age': 18}
x = json.dumps(person)
print(x) # {"name": "zhangsan", "age": 18}
print(type(x)) # <class 'str'>
nums = [1, 9, 0, 4, 7]
y = json.dumps(nums)
print(y) # [1, 9, 0, 4, 7]
print(type(y)) # <class 'str'>
words = ('hello','good','yes')
z = json.dumps(words)
print(z) # ["hello", "good", "yes"]
print(type(z)) # <class 'str'>
使用json的loads方法,可以将格式正确的字符串转换成为字典、列表。
x = '{"name": "zhangsan", "age": 18}'
person = json.loads(x)
print(person) # {'name': 'zhangsan', 'age': 18}
print(type(person)) # <class 'dict'>
y = '[1, 9, 0, 4, 7]'
nums = json.loads(y)
print(nums) # [1, 9, 0, 4, 7]
print(type(nums)) # <class 'list'>
字符串、列表、元组、字典和集合,它们有很多相同点,都是由多个元素组合成的一个可迭代对象,它们都有一些可以共同使用的方法。
在Python里,常见的算数运算符,有一些可以使用于可迭代对象,它们执行的结果也稍有区别。
运算符 | Python 表达式 | 结果 | 描述 | 支持的数据类型 |
---|---|---|---|---|
| [1, 2] + [3, 4] | [1, 2, 3, 4] | 合并 | 字符串、列表、元组 |
- | {1,2,3,4} - {2,3} | {1,4} | 集合求差集 | 集合 |
* | [‘Hi!’] * 4 | [‘Hi!’, ‘Hi!’, ‘Hi!’, ‘Hi!’] | 复制 | 字符串、列表、元组 |
in | 3 in (1, 2, 3) | True | 元素是否存在 | 字符串、列表、元组、字典 |
not in | 4 not in (1, 2, 3) | True | 元素是否不存在 | 字符串、列表、元组、字典 |
加法运算符可以用于字符串、列表和元组,用来拼接多个可迭代对象,不能用于字典和集合(思考:为什么字典和集合不能使用)。
>>> "hello " + "world"
'hello world'
>>> [1, 2] + [3, 4]
[1, 2, 3, 4]
>>> ('a', 'b') + ('c', 'd')
('a', 'b', 'c', 'd')
减法只能用于集合里,用来求两个集合的差集。
>>> {1, 6, 9, 10, 12, 3} - {4, 8, 2, 1, 3}
{9, 10, 12, 6}
加法运算符可以用于字符串、列表和元组,用来将可迭代对象重复多次,同样不能用于字典和集合。
>>> 'ab' * 4
'ababab'
>>> [1, 2] * 4
[1, 2, 1, 2, 1, 2, 1, 2]
>>> ('a', 'b') * 4
('a', 'b', 'a', 'b', 'a', 'b', 'a', 'b')
in和not in成员运算符可以用于所有的可迭代对象。但是需要注意的是,in 和 not in 在对字典进行判断时,是查看指定的key是否存在,而不是value.
>>> 'llo' in 'hello world'
True
>>> 3 in [1, 2]
False
>>> 4 in (1, 2, 3, 4)
True
>>> "name" in {"name":"chris", "age":18}
True
通过for … in … 我们可以遍历字符串、列表、元组、字典、集合等可迭代对象。
>>> a_str = "hello world"
>>> for char in a_str:
... print(char,end=' ')
...
h e l l o w o r l d
>>> a_list = [1, 2, 3, 4, 5]
>>> for num in a_list:
... print(num,end=' ')
...
1 2 3 4 5
>>> a_turple = (1, 2, 3, 4, 5)
>>> for num in a_turple:
... print(num,end=" ")
1 2 3 4 5
可迭代对象都可以使用 enumerate 内置类进行包装成一个 enumerate 对象。对enumerate进行遍历,可以同时得到一个可迭代对象的下标和元素。
nums = [12, 9, 8, 5, 4, 7, 3, 6]
# 将列表 nums 包装成 enumerate 对象
for i, num in enumerate(nums): # i表示元素下标,num表示列表里的元素
print('第%d个元素是%d' % (i, num))
Python使用引用计数来跟踪对象的引用,当一个对象的引用计数变为0时,Python会立即释放该对象的内存。但是,引用计数并不能解决循环引用的问题,Python还使用垃圾收集器(Garbage Collector)来处理循环引用。Python的垃圾收集器使用标记-清除算法(Mark and Sweep Algorithm)来找到和清除无法访问的循环引用对象。
C++没有内置的垃圾回收机制,程序员需要手动管理内存,例如使用new和delete操作符来分配和释放内存。虽然C++有一些第三方库来帮助程序员管理内存,但是这些库的性能和效率并不如手动管理内存。
Java也有垃圾回收机制,但与Python不同的是,Java使用的垃圾回收机制是自动的。Java垃圾回收器在应用程序运行时自动回收无用的对象,从而避免了内存泄漏和野指针问题。Java的垃圾回收器使用标记-清除算法和复制算法(Copying Algorithm)来处理垃圾对象。
Python的垃圾回收机制主要使用两种算法:引用计数算法和标记-清除算法。
1.引用计数算法
引用计数算法是Python最基本的垃圾回收算法。在Python中,每个对象都有一个引用计数器,记录有多少个变量引用了这个对象。当引用计数器为0时,说明没有任何变量指向该对象,Python就会回收该对象的内存。
举例来说:
a = [1, 2, 3]
b = a #a给b赋值是把a的引用地址给了b,python的变量类似与指针
c = b
在这个例子中,a、b、c都指向同一个列表对象[1, 2, 3],它的引用计数为3。如果我们执行以下代码:
del a
del b
del c
当没有变量引用这个对象时,那么这个列表对象的引用计数就变为了0,Python就会回收该对象的内存。
2.标记-清除算法
引用计数算法只能处理没有循环引用的对象,当对象之间存在循环引用时,引用计数算法就无法正常工作了。这时Python就需要使用标记-清除算法来处理垃圾对象。
标记-清除算法分为两个阶段:标记阶段和清除阶段。
在标记阶段,Python会从根对象开始,遍历所有可达对象,并给它们打上标记。根对象可以是Python程序中所有全局变量、局部变量、以及当前线程的调用栈中的变量。
在清除阶段,Python会遍历所有未标记的对象,将它们回收掉。这些未标记的对象就是垃圾对象,可以被回收掉。
举例来说:
class A:
pass
class B:
pass
a = A()
b = B()
a.b = b
b.a = a
在这个例子中,对象a和b之间存在循环引用,无法使用引用计数算法回收它们的内存。此时Python会使用标记-清除算法来处理它们的内存回收。在标记阶段,Python会从根对象开始,遍历所有可达对象并打上标记,a和b都会被打上标记。在清除阶段,Python会回收未被标记的对象,这里没有未被标记的对象,所以a和b都会被保留下来,直到没有任何变量引用它们为止。
定义函数的格式如下:
def 函数名():
代码
示例:
# 定义一个函数,能够完成打印信息的功能
def printInfo():
print('------------------------------------')
print(' 人生苦短,我用Python')
print('------------------------------------')
print(printInfo)#打印函数名返回的是函数地址
定义了函数之后,就相当于有了一个具有某些功能的代码,想要让这些代码能够执行,需要调用它
调用函数很简单的,通过 函数名() 即可完成调用
# 定义完函数后,函数是不会自动执行的,需要调用它才可以
printInfo()
要求:定义一个函数,能够计算两个数字之和,并且调用这个函数让它执行
在Python、C++和Java中,函数传递参数的基本概念是相似的,都是将值或者引用传递给函数。但是,这些编程语言在实现参数传递时有一些区别:
1.参数传递方式
2.参数类型
3.默认参数
4.参数数量
总的来说,Python、C++和Java在函数参数传递方面有一些区别,但基本概念是相似的。在选择使用哪种语言时,需要根据具体情况来决定。
定义一个add2num(a, b)函数,来计算任意两个数字之和:
def add2num(a, b):
c = a+b
print c
add2num(11, 22) # 调用带有参数的函数时,需要在小括号中,传递数据
注意点:
用函数时参数的顺序
>>> def test(a,b):
... print(a,b)
...
>>> test(1,2) # 位置参数
1 2
>>> test(b=1,a=2) # 关键字参数
2 1
>>>
>>> test(b=1,2) # 关键字参数写在位置参数之前会导致出错
File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
library = ["a","b","c","d","e","f"]
#形参
def add_book(bookname):
library.append(bookname)
print("引入成功")
def show_book(books):
for book in books:
print(book)
#调用
add_book('g')
show_book(library)
如果一个变量,既能在一个函数中使用,也能在其他的函数中使用,这样的变量就是全局变量
打个比方:有2个兄弟 各自都有手机,各自有自己的小秘密在手机里,不让另外一方使用(可以理解为局部变量);但是家里的电话是2个兄弟都可以随便使用的(可以理解为全局变量)
# 定义全局变量
a = 100
def test1():
print(a) # 虽然没有定义变量a但是依然可以获取其数据
def test2():
print(a) # 虽然没有定义变量a但是依然可以获取其数据
# 调用函数
test1()
test2()
Copy
运行结果:
100
100
总结1:
全局变量
看如下代码:
总结2:
变量名 = 数据
此时理解为定义了一个局部变量,而不是修改全局变量的值函数中进行使用时可否进行修改呢?
代码如下
总结3:
global 全局变量的名字
那么这个函数中即使出现和全局变量名相同的变量名 = 数据
也理解为对全局变量进行修改,而不是定义局部变量# 可以使用一次global对多个全局变量进行声明
global a, b
# 还可以用多次global声明都是可以的
# global a
# global b
Python提供了两个内置函数globals()和locals()可以用来查看所有的全局变量和局部变量。
def test():
a = 100
b = 40
print(locals()) # {'a': 100, 'b': 40}
test()
x = 'good'
y = True
print(globals()) # {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x101710630>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/jiangwei/Desktop/Test/test.py', '__cached__': None, 'test': <function test at 0x101695268>, 'x': 'good', 'y': True}
拆包和装包
问题:当需求比较大时,如下面无法满足要求
#求和
def get_sum(a,b):
r = a + b
print(r)
get_sum(1,2)#可行
get_sum(2,6,9)#不可行
*args
装包:
def demo(*args)
此时装包
a,*b,c = 1,2,3,4,5,6
print(a)#1
print(b)#[2,3,4]
print(c)#5
def get_sum(*a):
print(a)#
get_sum(1,2)#(1,2)
get_sum(2,6,9)#(2,6,9)
拆包:
调用的时候拆包
函数(*list),函数(*tuple),函数(*set)
def get_sum(*args):
print(*args)#
s = 0
for i in args:
s += i
print(s)
re = [23,434,34,34,54,213,234]
get_sum(re)#报错
报错原因:args装包后是([23, 434, 34, 34, 54, 213, 234],)
解决方案:传参数时拆包后传入,即传入*re
def get_sum(*args):
print(*args)#
s = 0
for i in args:
s += i
print(s)
re = [23,434,34,34,54,213,234]
get_sum(*re)#参数加上*就是拆包
**kwargs
关键字参数,在函数调用的时候必须传递关键字参数,才可以将其转换成key:value,转到字典中。
装包
def show_book(**kwargs):
print(kwargs)
show_book()#{}
show_book(bookname='西游记',author='吴承恩',number=5)#{'bookname':'西游记','author':'吴承恩','number':5}
拆包
def show_book(**kwargs):
print(kwargs) #{}
for k,v in kwargs.items():
print(k,v)
book = {'bookname':'西游记','author':'吴承恩','number':5}
show_book(**book)
'''
bookname 西游记
author 吴承恩
number 5
'''
def show_book(*args,**kwargs):
print(args)
print(kwargs)
book = {'bookname':'坏小孩','author':'zzz','number':5}
show_book('龙少','小芳',**book)
调用函数时,缺省参数的值如果没有传入,则取默认值。
下例会打印默认的age,如果age没有被传入:
def printinfo(name, age=35):
# 打印任何传入的字符串
print("name: %s" % name)
print("age %d" % age)
# 调用printinfo函数
printinfo(name="miki") # 在函数执行过程中 age取默认值35
printinfo(age=9 ,name="miki")
以上实例输出结果:
name: miki
age: 35
name: miki
age: 9
总结:
在形参中默认有值的参数,称之为缺省参数
注意:带有默认值的参数一定要位于参数列表的最后面
>>> def printinfo(name, age=35, sex):
... print name
...
File "<stdin>", line 1
SyntaxError: non-default argument follows default argument
有时可能需要一个函数能处理比当初声明时更多的参数, 这些参数叫做不定长参数,声明时不会命名。
基本语法如下:
def functionname([formal_args,] *args, **kwargs):
"""函数_文档字符串"""
function_suite
return [expression]
注意:
*
的变量args会存放所有未命名的变量参数,args为元组**
的变量kwargs会存放命名参数,即形如key=value的参数, kwargs为字典.def test(a, b, *args, **kwargs):
"函数在声明时,需要两个参数"
print('a={},b={},args={},kwargs={}'.format(a,b,args,kwargs))
test(2, 3, '你好', 'hi', 'how do you do', name="zhangsan", age=18)
# a=2,b=3,args=('你好', 'hi', 'how do you do'),kwargs={'name': 'zhangsan', 'age': 18}
b = ('hi', '大家好', '今天天气真好')
d = {'name': "zhangsan", "age": 19}
# 注意,在传入参数时的星号问题。
test(10, 20, *b, **d)
# a=10,b=20,args=('hi', '大家好', '今天天气真好'),kwargs={'name': 'zhangsan', 'age': 19}
# 如果在传值时,不使用星号,会把后面的参数当做 args
test(10,20,b,d)
# a=10,b=20,args=(('hi', '大家好', '今天天气真好'), {'name': 'zhangsan', 'age': 19}),kwargs={}
def sum_nums_3(a, *args, b=22, c=33, **kwargs):
print(a)
print(b)
print(c)
print(args)
print(kwargs)
sum_nums_3(100, 200, 300, 400, 500, 600, 700, b=1, c=2, mm=800, nn=900)
说明:
在 Python 中,引用是指将变量名和对象之间的关系。当您使用变量名引用对象时,实际上是引用该对象的内存地址,而不是对象本身。因此,当您将一个变量赋值给另一个变量时,两个变量都指向相同的内存地址,因此它们引用相同的对象。
例如,考虑以下代码:
a = [1, 2, 3]
b = a
在这里,变量 a
和 b
都是对列表 [1, 2, 3]
的引用,它们指向相同的内存地址。如果您更改其中一个变量的值,则另一个变量也会受到影响,因为它们引用相同的对象:
a.append(4)
print(b) # 输出 [1, 2, 3, 4]
因此,在 Python 中,理解引用非常重要,因为它们可以使您在代码中更有效地使用内存和处理对象。
如果函数传入的是可变类型则实参有可能改变也有可能不变,但是如果传入的是不可变对象则函数体内对形参的修改不会导致实参的变化。
在 Python 中,函数可以修改可变类型的实参,但无法修改不可变类型的实参。
可变类型的实参包括列表、字典和集合等,这些类型的对象是可以在原地进行修改的。因此,如果您将一个可变类型的实参传递给一个函数,并在函数中修改它,则这些更改也将影响原始对象。
例如,以下代码演示了如何在函数中修改列表类型的实参:
def append_item(lst, item):
lst.append(item)
my_list = [1, 2, 3]
append_item(my_list, 4)
print(my_list) # 输出 [1, 2, 3, 4]
在这里,append_item
函数将一个元素附加到传递的列表 lst
中。由于 my_list
是一个可变的列表对象,因此 append_item
函数可以在原地修改它。
不可变类型的实参包括整数、字符串和元组等,这些类型的对象是无法在原地进行修改的。因此,如果您将一个不可变类型的实参传递给一个函数,并在函数中尝试修改它,则会引发一个错误。
例如,以下代码演示了试图修改字符串类型的实参将引发的错误:
def capitalize_string(s):
s[0] = s[0].upper()
my_string = "hello"
capitalize_string(my_string) # 引发 TypeError 错误
在这里,capitalize_string
函数试图将传递的字符串 s
的第一个字符大写化。但是,由于字符串是不可变的对象,因此无法在原地进行修改,这将导致 TypeError
错误。
总之,您可以在 Python 中通过修改可变类型的实参来更改函数外部的对象,但是如果您试图修改不可变类型的实参,则将引发一个错误。
总结
Python、C++和Java都是面向对象编程语言,它们都支持函数返回值。但是,它们的函数返回值有一些区别。
在Python中,函数可以通过使用return
语句返回一个值。如果函数没有使用return
语句,则默认返回None
对象。Python的函数可以返回任何类型的对象,包括字符串、数字、列表、元组、字典和自定义对象。
C++和Java中的函数返回值必须明确指定类型。在C++中,函数的返回值类型必须在函数声明或定义中指定。在Java中,函数返回值类型必须在函数声明中指定。函数可以返回任何基本数据类型或对象类型。
C++和Java还支持返回指针和引用。在C++中,可以返回指向任何类型的指针或引用。在Java中,可以返回任何对象的引用。
另外,C++和Java中的函数可以有多个返回值,这些返回值可以使用结构体或类封装在一起。在Python中,可以使用元组或字典返回多个值。
总的来说,虽然Python、C++和Java都支持函数返回值,但它们在返回值类型的指定和多返回值的处理上有所区别。
现实生活中的场景:
我给儿子10块钱,让他给我买个冰淇淋。这个例子中,10块钱是我给儿子的,就相当于调用函数时传递到参数,让儿子买冰淇淋这个事情最终的目标,我需要让他把冰淇淋带回来,此时冰淇淋就是返回值
开发中的场景:
定义了一个函数,完成了获取室内温度,想一想是不是应该把这个结果给调用者,只有调用者拥有了这个返回值,才能够根据当前的温度做适当的调整
综上所述:
想要在函数中把结果返回给调用者,需要在函数中使用return
如下示例:
def add2num(a, b):
c = a+b
return c # return 后可以写变量名
或者
def add2num(a, b):
return a+b # return 后可以写计算表达式
return后面可以是一个值,也可以是多个值,多个值时会将多个值封装到一个元组中,将元组作为整体返回。
def add2num(a, b):
return a,b # 返回一个元组(a,b)
a,b = add2num(1,2)
print(a,b)#1 2
在本小节刚开始的时候,说过的“买冰淇淋”的例子中,最后儿子给你冰淇淋时,你一定是从儿子手中接过来 对么,程序也是如此,如果一个函数返回了一个数据,那么想要用这个数据,那么就需要保存
保存函数的返回值示例如下:
#定义函数
def add2num(a, b):
return a+b
#调用函数,顺便保存函数的返回值
result = add2num(100,98)
#因为result已经保存了add2num的返回值,所以接下来就可以使用了
print(result)
结果:
198
>>> def test(a,b):
... "用来完成对2个数求和" # 函数第一行写一个字符串作为函数文档
... print("%d"%(a+b))
...
>>>
>>> test(11,22) # 函数可以正常调用
33
>>>
>>> help(test) # 使用 help 查看test函数的文档说明
Help on function test in module __main__:
test(a, b)
用来完成对2个数求和
def get_info(name: str, age: int):
"""
接收用户的名字和年龄,拼接一个字符串并返回
:param name: 接收一个名字
:param age: 接收用户的年龄,必须是 0-200 间的一个整数
:return: 返回拼接好的字符串
"""
return "我的名字叫 %s,今年是 %d 岁" % (name, age)
get_info("吴彦祖", 19)
get_info(520, 19) # 注意,形参上标注的类型只是提高代码的可读性,并不会限制实参的类型
help(get_info)
通过前面的学习知道一个函数可以调用其他函数。
如果一个函数在内部不调用其它的函数,而是自己本身的话,这个函数就是递归函数。
举个例子,我们来计算阶乘 n! = 1 * 2 * 3 * ... * n
解决办法1:使用循环来完成
def cal(num):
result,i = 1,1
while i <= num:
result *= i
i+= 1
return result
print(cal(3))
看阶乘的规律
1! = 1
2! = 2 × 1 = 2 × 1!
3! = 3 × 2 × 1 = 3 × 2!
4! = 4 × 3 × 2 × 1 = 4 × 3!
...
n! = n × (n-1)!
解决办法2:使用递归来实现
def factorial(num):
result = 1
if num == 1:
return 1
result = num * factorial(num -1)
return result
print(cal(3))
原理
用lambda关键词能创建小型匿名函数。这种函数得名于省略了用def声明函数的标准步骤。
lambda函数的语法只包含一个语句,如下:
lambda 参数列表: 运算表达式
如下实例:
sum = lambda arg1, arg2: arg1 + arg2
# 调用sum函数
print("Value of total : %d" % sum( 10, 20 ))
print("Value of total : %d" % sum( 20, 20 ))
以上实例输出结果:
Value of total : 30
Value of total : 40
Lambda函数能接收任何数量的参数但只能返回一个表达式的值
匿名函数可以执行任意表达式(甚至print函数),但是一般认为表达式应该有一个计算结果供返回使用。
python在编写一些执行脚本的时候可以使用lambda,这样可以接受定义函数的过程,比如写一个简单的脚本管理服务器。
函数作为参数传递
>>> def fun(a, b, opt):
... print("a = " % a)
... print("b = " % b)
... print("result =" % opt(a, b))
...
>>> add = lambda x,y:x+y
>>> fun(1, 2, add) # 把 add 作为实参传递
a = 1
b = 2
result = 3
练习:
有一个列表
students = [
{'name': 'zhangsan', 'age': 18, 'score': 92},
{'name': 'lisi', 'age': 20, 'score': 90},
{'name': 'wangwu', 'age': 19, 'score': 95},
{'name': 'jerry', 'age': 21, 'score': 98},
{'name': 'chris', 'age': 17, 'score': 100},
]
要求,对上述列表里的数据按照score进行升序排序。
Python中使用函数作为参数的内置函数和类:
函数名或类名 | 功能 | 参数描述 |
---|---|---|
sorted函数 | 用来将一个无序列表进行排序 | 函数参数的返回值规定按照元素的哪个属性进行排序 |
filter类 | 用来过滤一个列表里符合规定的所有元素,得到的结果是一个迭代器 | 函数参数的返回值指定元素满足的过滤条件 |
map类 | 将列表里的每一项数据都执行相同的操作,得到的结果是一个迭代器 | 函数参数用来指定列表里元素所执行的操作 |
reduce函数 | 对一个序列进行压缩运算,得到一个值。python3以后,这个方法被移到了functools模块 | 函数参数用来指定元素按照哪种方式合并 |
在Python中,函数其实也是一种数据类型。
def test():
return 'hello world'
print(type(test)) # <class 'function'>
函数对应的数据类型是 function
,可以把它当做是一种复杂的数据类型。
既然同样都是一种数据类型,我们就可以把它当做数字或者字符串来处理。
在Python中,我们还可以定义一个变量,让它来指向一个函数,相当于给函数起了一个别名。
def test():
return 'hello wrold'
fun = test # 定义了一个变量fun,让它指向了 test 这个函数
print(fun()) # 使用fun()可以直接调用test这个函数
print(id(fun)) # 1819677672040
print(id(test)) # 1819677672040
注意:在定义一个变量表示一个函数时,函数后面不能加括号!加括号表示的是调用这个函数。
def test():
return 'hello world'
result = test() # 这种写法是调用test函数,并把函数的返回值赋值给result变量
print(result()) # 这里会报错 TypeError: 'str' object is not callable
fun = test # 这种写法是给test函数起了一个别名,注意,这里的test后面不能加()
fun() # 可以使用别名调用这个函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,同样,我们还可以把一个函数当做另一个函数的返回值。这种函数的使用方式我们称之为高阶函数。
def test(age,action):
if age < 18:
print('您还没满十八岁,请退出')
action() # 把参数action直接当做一个函数来调用
def smoke():
print('我已经年满十八岁了,我想抽烟')
my_action = smoke # 定义一个变量my_action,让它指向smoke函数
test(21, my_action) # 将my_action传给 test 函数作为它的参数
test(21,smoke) # 还可以不再定义一个新的变量,直接传入函数名
def test():
print('我是test函数里输入的内容')
def demo():
print('我是demo里输入的内容')
return test # test 函数作为demo函数的返回值
result = demo() # 我是demo里输入的内容 调用 demo 函数,把demo函数的返回值赋值给 result
print(type(result)) # <class 'function'> result 的类型是一个函数
result() # 我是demo里输入的内容 我是test函数里输入的内容 既然result是一个函数,那么就可以直接使用() 调用这个函数
demo()() # 我是demo里输入的内容 我是test函数里输入的内容
函数只是一段可执行代码,编译后就“固化”了,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数了。函数还可以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题。
在函数里面还可以定义函数,可以嵌套多层,执行需要被调用。
def outer():
a = 100
def inner(): # inner这个函数是在outer函数内部定义的
b = 200
b += a #内部函数可以使用外部函数的变量
#a += b #内部函数不能修改外部函数的变量
nonlocal a #如果想修改外部函数的变量,需要在内部函数变量前面加上nonlocal
a += b #此时内部函数可以修改外部函数的变量
result = locals()#locals表示查看函数中的局部变量,已字典的形式返回(详见局部变量,全局变量)
print(result)
inner() # inner函数只在outer函数内部可见
outer()
# inner() 这里会报错,在outer函数外部无法访问到inner函数
内部函数可以使用外部函数的变量
举例:
a = 100
def outer():
a = 200
def inner():
a = 300
#nonlocal a ##如果想修改外部函数的变量,需要在内部函数变量前面加上nonlocal
global a
a -= 50
print('内部函数:',a)
print(a)
inner()
outer()
print(a)
变量或者函数的搜索规则:寻找引用时先寻找内层函数,再寻找外层函数,再寻找全局环境,再寻找系统环境builtis模块,没有的话报错 在内部环境中加上global后便可以修改全局变量,加上nonglobal边可以修改外部环境的变量
闭包在装饰器中用。
闭包特点:
闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数块+引用环境)。
def outer(n):
num = n
def inner():
return num+1
return inner #返回出去的是inner函数的地址
print(outer(3)()) # 4
print(outer(5)()) # 5
在这段程序中,函数 inner 是函数 outer 的内嵌函数,并且 inner 函数是outer函数的返回值。我们注意到一个问题:内嵌函数 inner 中引用到外层函数中的局部变量num,Python解释器会这么处理这个问题呢? 先让我们来看看这段代码的运行结果,当我们调用分别由不同的参数调用 outer 函数得到的函数时,得到的结果是隔离的(相互不影响),也就是说每次调用outer函数后都将生成并保存一个新的局部变量num,这里outer函数返回的就是闭包。 如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).
闭包里默认不能修改外部变量。
def outer(n):
num = n
def inner():
num = num + 1
return num
return inner
print(outer(1)())
上述代码运行时会报错!
UnboundLocalError: local variable 'num' referenced before assignment
在python里,只要看到了赋值语句,就会认为赋值语句的左边是一个局部变量。num = num + 1
这段代码里,num
在=
的左边,python解析器会认为我们要修改inner
函数里num
这个局部变量,而这个变量使用之前是未声明的,所以会报错。
我们分析过,报错的原因在于当我们在闭包内修改外部变量时,会被python解析器误会为内部函数的局部变量。所以,解决方案就在于,我们需要想办法,让解析器知道我们不是要修改局部变量,而是要修改外部变量。
def outer(n):
num = n
def inner():
nonlocal num # 修改前使用nonlocal关键字对 num 变量进行说明
num = num + 1
return num
return inner
print(outer(2)())
def foo():
print('foo')
def func():
print('func')
foo = func
foo() # func
foo 表示是函数,函数可以理解为变量,存放的是函数的地址,foo = func表示更给了函数存放的地址。
#### 第一波 ####
def foo():
print('foo')
foo # 表示是函数
foo() # 表示执行foo函数
#### 第二波 ####
def foo():
print('foo')
foo = lambda x: x + 1
foo() # 执行lambda表达式,而不再是原来的foo函数,因为foo这个名字被重新指向了另外一个匿名函数
函数名仅仅是个变量,只不过指向了定义的函数而已,所以才能通过 函数名()调用,如果 函数名=xxx被修改了,那么当在执行 函数名()时,调用的就不知之前的那个函数了
引入例子
初创公司有N个业务部门,基础平台部门负责提供底层的功能,如:数据库操作、redis调用、监控API等功能。业务部门使用基础功能时,只需调用基础平台提供的功能即可。如下:
############### 基础平台提供的功能如下 ###############
def f1():
print('f1')
def f2():
print('f2')
def f3():
print('f3')
def f4():
print('f4')
############### 业务部门A 调用基础平台提供的功能 ###############
f1()
f2()
f3()
f4()
############### 业务部门B 调用基础平台提供的功能 ###############
f1()
f2()
f3()
f4()
目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先进行验证。
方案一:
跟每个业务部门交涉,每个业务部门自己写代码,调用基础平台的功能之前先验证。这样一来基础平台就不需要做任何修改了。
############### 基础平台提供的功能如下 ###############
def f1():
# 验证1
# 验证2
# 验证3
print('f1')
def f2():
# 验证1
# 验证2
# 验证3
print('f2')
def f3():
# 验证1
# 验证2
# 验证3
print('f3')
def f4():
# 验证1
# 验证2
# 验证3
print('f4')
############### 业务部门不变 ###############
### 业务部门A 调用基础平台提供的功能###
f1()
f2()
f3()
f4()
### 业务部门B 调用基础平台提供的功能 ###
f1()
f2()
f3()
f4()
代码量太多,冗余。
方案二:
只对基础平台的代码进行重构,其他业务部门无需做任何修改
############### 基础平台提供的功能如下 ###############
def check_login():
# 验证1
# 验证2
# 验证3
pass
def f1():
check_login()
print('f1')
def f2():
check_login()
print('f2')
def f3():
check_login()
print('f3')
def f4():
check_login()
print('f4')
写代码要遵循开放封闭
原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
如果将开放封闭原则应用在上述需求中,那么就不允许在函数 f1 、f2、f3、f4的内部进行修改代码。
方案三:
def w1(func):
def inner():
# 验证1
# 验证2
# 验证3
func()
return inner
@w1
def f1():
print('f1')
@w1
def f2():
print('f2')
@w1
def f3():
print('f3')
@w1
def f4():
print('f4')
对于上述代码,也是仅仅对基础平台的代码进行修改,就可以实现在其他人调用函数 f1 f2 f3 f4 之前都进行【验证】操作,并且其他业务部门无需做任何操作。
分析:单独以f1为例
def w1(func):
def inner():
# 验证1
# 验证2
# 验证3
func()
return inner
@w1 #f1 = w1(f1)
def f1():
print('f1')
python解释器就会从上到下解释代码,步骤如下:
没错, 从表面上看解释器仅仅会解释这两句代码,因为函数在 没有被调用之前其内部代码不会被执行。
从表面上看解释器着实会执行这两句,但是 @w1 这一句代码里却有大文章, @函数名 是python的一种语法糖。
执行w1函数
执行w1函数 ,并将 @w1 下面的函数作为w1函数的参数,即:**@w1 等价于 w1(f1)** 所以,内部就会去执行:
def inner():
#验证 1
#验证 2
#验证 3
f1() # func是参数,此时 func 等于 f1
return inner# 返回的 inner,inner代表的是函数,非执行函数 ,其实就是将原来的 f1 函数塞进另外一个函数中
w1的返回值
将执行完的w1函数返回值 赋值 给@w1下面的函数的函数名f1 即将w1的返回值再重新赋值给 f1,即:
f1 = def inner():
#验证 1
#验证 2
#验证 3
原来f1()
return inner
所以,以后业务部门想要执行 f1 函数时,就会执行 新f1 函数,在新f1 函数内部先执行验证,再执行原来的f1函数,然后将原来f1 函数的返回值返回给了业务调用者。
如此一来, 即执行了验证的功能,又执行了原来f1函数的内容,并将原f1函数返回值 返回给业务调用者。
# 定义函数:完成包裹数据
def makeBold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
# 定义函数:完成包裹数据
def makeItalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makeBold
def test1():
return "hello world-1"
@makeItalic
def test2():
return "hello world-2"
@makeBold
@makeItalic
def test3():
return "hello world-3"
print(test1())
print(test2())
print(test3())
运行结果:
<b>hello world-1</b>
<i>hello world-2</i>
<b><i>hello world-3</i></b>
def check_time(action):
def do_action():
action()
return do_action
@check_time
def go_to_bed():
print('去睡觉')
go_to_bed()
上面代码理解装饰器执行行为可理解成
result = check_time(go_to_bed) # 把go_to_bed 当做参数传入给 check_time函数,再定义一个变量用来保存check_time的运行结果
result() # check_time 函数的返回值result是一个函数, result()再调用这个函数,让它再调用go_to_bed函数
def check_time(action):
def do_action(a,b):
action(a,b)
return do_action
@check_time
def go_to_bed(a,b):
print('{}去{}睡觉'.format(a,b))
go_to_bed("zhangsan","床上")
def test(cal):
def do_cal(*args,**kwargs):
cal(*args,**kwargs)
return do_cal
@test
def demo(*args):
sum = 0
for x in args:
sum +=x
print(sum)
demo(1, 2, 3, 4)
def test(cal):
def do_cal(*args,**kwargs):
return cal(*args,**kwargs) # 需要再这里写return语句,表示调用函数,获取函数的返回值并返回
return do_cal
@test
def demo(a,b):
return a + b
print(demo(1, 2)) #3
总结:
def outer_check(time):
def check_time(action):
def do_action():
if time < 22:
return action()
else:
return '对不起,您不具有该权限'
return do_action
return check_time
@outer_check(23)
def play_game():
return '玩儿游戏'
print(play_game())
以下代码不要求掌握,如果能看懂最好,如果能自己手动写出来,那就太棒了!
def outer_check(base_permission):
def check_permission(action):
def do_action(my_permission):
if my_permission & base_permission:
return action(my_permission)
else:
return '对不起,您不具有该权限'
return do_action
return check_permission
READ_PERMISSION = 1
WRITE_PERMISSION = 2
EXECUTE_PERMISSION = 4
@outer_check(base_permission=READ_PERMISSION)
def read(my_permission):
return '读取数据'
@outer_check(base_permission=WRITE_PERMISSION)
def write(my_permission):
return '写入数据'
@outer_check(base_permission=EXECUTE_PERMISSION)
def execute(my_permission):
return '执行程序'
print(read(5))
在Python中有一个概念叫做模块(module)。
说的通俗点:模块就好比是工具包,要想使用这个工具包中的工具(就好比函数),就需要导入这个模块
比如我们经常使用工具 random,就是一个模块。使用 import random 导入工具之后,就可以使用 random 的函数。
下面来挨个的看一下。
在Python中用关键字import
来引入某个模块,比如要引入系统模块 math,就可以在文件最开始的地方用import math
来引入。
语法:
import 模块1,模块2,... # 导入方式
模块名.函数名() # 使用模块里的函数
示例:
import math
#这样才能正确输出结果
print math.sqrt(2)
#这样会报错
print(sqrt(2))
有时候我们只需要用到模块中的某个函数,只需要引入该函数即可,此时可以用下面方法实现:
from 模块名 import 函数名1,函数名2....
不仅可以引入函数,还可以引入一些全局变量、类等
例如,要导入模块fib的fibonacci函数,使用如下语句:
from fib import fibonacci
注意
把一个模块的所有内容全都导入到当前的命名空间也是可行的,只需使用如下声明:
from modname import *
注意
In [1]: import time as tt # 导入模块时设置别名为 tt
In [2]: time.sleep(1)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-2-07a34f5b1e42> in <module>()
----> 1 time.sleep(1)
NameError: name 'time' is not defined
In [3]:
In [3]: tt.sleep(1) # 使用别名才能调用方法
In [4]:
In [4]: from time import sleep as sp # 导入方法时设置别名
In [5]: sleep(1)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-5-82e5c2913b44> in <module>()
----> 1 sleep(1)
NameError: name 'sleep' is not defined
In [6]:
In [6]: sp(1) # 使用别名才能调用方法
In [7]:
为了方便程序员开发代码,Python提供了很多内置的模块给程序员用来提高编码效率。常见的内置模块有:
OS全称OperationSystem,即操作系统模块,这个模块可以用来操作系统的功能,并且实现跨平台操作。
import os
os.getcwd() # 获取当前的工作目录,即当前python脚本工作的目录
os.chdir('test') # 改变当前脚本工作目录,相当于shell下的cd命令
os.rename('毕业论文.txt','毕业论文-最终版.txt') # 文件重命名
os.remove('毕业论文.txt') # 删除文件
os.rmdir('demo') # 删除空文件夹
os.removedirs('demo') # 删除空文件夹
os.mkdir('demo') # 创建一个文件夹
os.chdir('C:\\') # 切换工作目录
os.listdir('C:\\') # 列出指定目录里的所有文件和文件夹
os.name # nt->widonws posix->Linux/Unix或者MacOS
os.environ # 获取到环境配置
os.environ.get('PATH') # 获取指定的环境配置
os.path.abspath(path) # 获取Path规范会的绝对路径
os.path.exists(path) # 如果Path存在,则返回True
os.path.isdir(path) # 如果path是一个存在的目录,返回True。否则返回False
os.path.isfile(path) # 如果path是一个存在的文件,返回True。否则返回False
os.path.splitext(path) # 用来将指定路径进行分隔,可以获取到文件的后缀名
Copy
该模块提供对解释器使用或维护的一些变量的访问,以及与解释器强烈交互的函数。
import sys
sys.path # 模块的查找路径
sys.argv # 传递给Python脚本的命令行参数列表
sys.exit(code) # 让程序以指定的退出码结束
sys.stdin # 标准输入。可以通过它来获取用户的输入
sys.stdout # 标准输出。可以通过修改它来百变默认输出
sys.stderr # 错误输出。可以通过修改它来改变错误删除
Copy
math模块保存了数学计算相关的方法,可以很方便的实现数学运算。
import math
print(math.fabs(-100)) # 取绝对值
print(math.ceil(34.01)) #向上取整
print(math.factorial(5)) # 计算阶乘
print(math.floor(34.98)) # 向下取整
print(math.pi) # π的值,约等于 3.141592653589793
print(math.pow(2, 10)) # 2的10次方
print(math.sin(math.pi / 6)) # 正弦值
print(math.cos(math.pi / 3)) # 余弦值
print(math.tan(math.pi / 2)) # 正切值
Copy
random 模块主要用于生成随机数或者从一个列表里随机获取数据。
print(random.random()) # 生成 [0,1)的随机浮点数
print(random.uniform(20, 30)) # 生成[20,30]的随机浮点数
print(random.randint(10, 30)) # 生成[10,30]的随机整数
print(random.randrange(20, 30)) # 生成[20,30)的随机整数
print(random.choice('abcdefg')) # 从列表里随机取出一个元素
print(random.sample('abcdefghij', 3)) # 从列表里随机取出指定个数的元素
Copy
练习: 定义一个函数,用来生成由数字和字母组成的随机验证码。该函数需要一个参数,参数用来指定验证码的长度。
datetime模块主要用来显示日期时间,这里主要涉及 date
类,用来显示日期;time
类,用来显示时间;dateteime
类,用来显示日期时间;timedelta
类用来计算时间。
import datetime
print(datetime.date(2020, 1, 1)) # 创建一个日期
print(datetime.time(18, 23, 45)) # 创建一个时间
print(datetime.datetime.now()) # 获取当前的日期时间
print(datetime.datetime.now() + datetime.timedelta(3)) # 计算三天以后的日期时间
Copy
除了使用datetime模块里的time类以外,Python还单独提供了另一个time模块,用来操作时间。time模块不仅可以用来显示时间,还可以控制程序,让程序暂停(使用sleep函数)
print(time.time()) # 获取从1970-01-01 00:00:00 UTC 到现在时间的秒数
print(time.strftime("%Y-%m-%d %H:%M:%S")) # 按照指定格式输出时间
print(time.asctime()) #Mon Apr 15 20:03:23 2019
print(time.ctime()) # Mon Apr 15 20:03:23 2019
print('hello')
print(time.sleep(10)) # 让线程暂停10秒钟
print('world')
Copy
calendar模块用来显示一个日历,使用的不多,了解即可。
calendar.setfirstweekday(calendar.SUNDAY) # 设置每周起始日期码。周一到周日分别对应 0 ~ 6
calendar.firstweekday()# 返回当前每周起始日期的设置。默认情况下,首次载入calendar模块时返回0,即星期一。
c = calendar.calendar(2019) # 生成2019年的日历,并且以周日为其实日期码
print(c) #打印2019年日历
print(calendar.isleap(2000)) # True.闰年返回True,否则返回False
count = calendar.leapdays(1996,2010) # 获取1996年到2010年一共有多少个闰年
print(calendar.month(2019, 3)) # 打印2019年3月的日历
Copy
hashlib是一个提供字符加密功能的模块,包含MD5和SHA的加密算法,具体支持md5,sha1, sha224, sha256, sha384, sha512等算法。 该模块在用户登录认证方面应用广泛,对文本加密也很常见。
import hashlib
# 待加密信息
str = '这是一个测试'
# 创建md5对象
hl = hashlib.md5('hello'.encode(encoding='utf8'))
print('MD5加密后为 :' + hl.hexdigest())
h1 = hashlib.sha1('123456'.encode())
print(h1.hexdigest())
h2 = hashlib.sha224('123456'.encode())
print(h2.hexdigest())
h3 = hashlib.sha256('123456'.encode())
print(h3.hexdigest())
h4 = hashlib.sha384('123456'.encode())
print(h4.hexdigest())
Copy
HMAC算法也是一种一种单项加密算法,并且它是基于上面各种哈希算法/散列算法的,只是它可以在运算过程中使用一个密钥来增增强安全性。hmac模块实现了HAMC算法,提供了相应的函数和方法,且与hashlib提供的api基本一致。
h = hmac.new('h'.encode(),'你好'.encode())
result = h.hexdigest()
print(result) # 获取加密后的结果
Copy
copy模块里有copy和deepcopy两个函数,分别用来对数据进行深复制和浅复制。
import copy
nums = [1, 5, 3, 8, [100, 200, 300, 400], 6, 7]
nums1 = copy.copy(nums) # 对nums列表进行浅复制
nums2 = copy.deepcopy(nums) # 对nums列表进行深复制
Copy
UUID是128位的全局唯一标识符,通常由32字节的字母串表示,它可以保证时间和空间的唯一性,也称为GUID。通过MAC地址、时间戳、命名空间、随机数、伪随机数来保证生产的ID的唯一性。随机生成字符串,可以当成token使用,当成用户账号使用,当成订单号使用。
方法 | 作用 |
---|---|
uuid.uuid1() | 基于MAC地址,时间戳,随机数来生成唯一的uuid,可以保证全球范围内的唯一性。 |
uuid.uuid2() | 算法与uuid1相同,不同的是把时间戳的前4位置换为POSIX的UID。不过需要注意的是python中没有基于DCE的算法,所以python的uuid模块中没有uuid2这个方法。 |
uuid.uuid3(namespace,name) | 通过计算一个命名空间和名字的md5散列值来给出一个uuid,所以可以保证命名空间中的不同名字具有不同的uuid,但是相同的名字就是相同的uuid了。namespace并不是一个自己手动指定的字符串或其他量,而是在uuid模块中本身给出的一些值。比如uuid.NAMESPACE_DNS,uuid.NAMESPACE_OID,uuid.NAMESPACE_OID这些值。这些值本身也是UUID对象,根据一定的规则计算得出。 |
uuid.uuid4() | 通过伪随机数得到uuid,是有一定概率重复的 |
uuid.uuid5(namespace,name) | 和uuid3基本相同,只不过采用的散列算法是sha1 |
一般而言,在对uuid的需求不是很复杂的时候,uuid1或者uuid4方法就已经够用了,使用方法如下:
import uuid
print(uuid.uuid1()) # 根据时间戳和机器码生成uuid,可以保证全球唯一
print(uuid.uuid4()) # 随机生成uuid,可能会有重复
# 使用命名空间和字符串生成uuid.
# 注意一下两点:
# 1. 命名空间不是随意输入的字符串,它也是一个uuid类型的数据
# 2. 相同的命名空间和想到的字符串,生成的uuid是一样的
print(uuid.uuid3(uuid.NAMESPACE_DNS, 'hello'))
print(uuid.uuid5(uuid.NAMESPACE_OID, 'hello'))
在安装Python时,同时还会安装pip软件,它是Python的包管理工具,可以用来查找、下载、安装和卸载Python的第三方资源包。
可以直接在终端中输入pip命令,如果出错,可能会有两个原因:
和运行Python命令一样,如果想要运行 pip 命令同样也需要将pip命令的安装目录添加到环境变量中。
对第三方包的管理主要包含查找、安装和卸载三个部分的操作。
使用 pip install <包名>
命令可以安装指定的第三方资源包。
pip install ipython # 安装ipython包
Copy
使用 install 命令下载第三方资源包时,默认是从 pythonhosted下载,由于各种原因,在国内下载速度相对来说比较慢,在某些时候甚至会出现连接超时的情况,我们可以使用国内镜像来提高下载速度。
如果只是想临时修改某个第三方资源包的下载地址,在第三方包名后面添加 -i 参数,再指定下载路径即可,格式为pip install <包名> -i <国内镜像路径>
pip install ipython -i https://pypi.douban.com/simple
Copy
除了临时修改pip的下载源以外,我们还能永久改变pip的默认下载路径。
在当前用户目录下创建一个pip的文件夹,然后再在文件夹里创建pip.ini文件并输入一下内容:
[global]
index-url=https://pypi.douban.com/simple
[install]
trusted-host=pypi.douban.com
Copy
使用 pip install <包名>
命令可以用来卸载指定的第三方资源包。
pip uninstall ipython # 卸载ipython包
Copy
使用pip list
或者 pip freeze
命令可以来管理第三方资源包。这两个命令的功能一致,都是用来显示当前环境里已经安装的包,区别在于pip list
会列出所有的包,包括一些无法uninstall的包;而pip freeze
只会列出我们安装的第三方包。
开发中,我们通常会使用很多第三方的资源包,我们在将程序部署到服务器的时候,不仅要把代码上传到服务器,同时还需要把代码里用到的第三方资源包告诉服务器。那么这里就有两个问题:
除了使用pip 命令管理第三方资源包以外,我们还能使用pycharm来对第三方包进行管理。
除了使用系统提供的内置模块以外,我们还能自己写一个模块供自己的程序使用。一个py文件就是一个模块,所以,自定义模块很简单,基本上相当于创建一个py文件。但是,需要注意的是,如果一个py文件要作为一个模块被别的代码使用,这个py文件的名字一定要遵守标识符的命名规则。
创建一个模块非常简单,安装标识符的命名规则创建一个py文件就是一个模块。但是问题是,我们需要把创建好的这个py文件放在哪个位置,在代码中使用 import
语句才能找到这个模块呢?
Python内置sys模块的path属性,列出了程序运行时查找模块的目录,只需要把我们创建好的模块放到这些任意的一个目录里即可。
import sys
print(sys.path)
[
'C:\\Users\\chris\\Desktop\\Test',
'C:\\Users\\chris\\AppData\\Local\\Programs\\Python\\Python37\\python37.zip',
'C:\\Users\\chris\\AppData\\Local\\Programs\\Python\\Python37\\DLLs',
'C:\\Users\\chris\\AppData\\Local\\Programs\\Python\\Python37\\lib',
'C:\\Users\\chris\\AppData\\Local\\Programs\\Python\\Python37',
'C:\\Users\\chris\\AppData\\Roaming\\Python\\Python37\\site-packages',
'C:\\Users\\chris\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages'
]
Copy
__all__
的使用使用from <模块名> import *
导入一个模块里所有的内容时,本质上是去查找这个模块的__all__
属性,将__all__
属性里声明的所有内容导入。如果这个模块里没有设置__all__
属性,此时才会导入这个模块里的所有内容。
模块里以一个下划线_
开始的变量和函数,是模块里的私有成员,当模块被导入时,以_
开头的变量默认不会被导入。但是它不具有强制性,如果一个代码强行使用以_
开头的变量,有时也可以。但是强烈不建议这样使用,因为有可能会出问题。
test1.py:模块里没有__all__
属性
a = 'hello'
def fn():
print('我是test1模块里的fn函数')
Copy
test2.py:模块里有__all__
属性
x = '你好'
y = 'good'
def foo():
print('我是test2模块里的foo函数')
__all__ = ('x','foo')
Copy
test3.py:模块里有以_
开头的属性
m = '早上好'
_n = '下午好'
def _bar():
print('我是test3里的bar函数')
Copy
demo.py
from test1 import *
from test2 import *
from test3 import *
print(a)
fn()
print(x)
# print(y) 会报错,test2的__all__里没有变量 y
foo()
print(m)
# print(_n) 会报错,导入test3时, _n 不会被导入
import test3
print(test3._n) # 也可以强行使用,但是强烈不建议
Copy
__name__
的使用在实际开中,当一个开发人员编写完一个模块后,为了让模块能够在项目中达到想要的效果,这个开发人员会自行在py文件中添加一些测试信息,例如:
test1.py
def add(a,b):
return a+b
# 这段代码应该只有直接运行这个文件进行测试时才要执行
# 如果别的代码导入本模块,这段代码不应该被执行
ret = add(12,22)
print('测试的结果是',ret)
Copy
demo.py
import test1.py # 只要导入了tets1.py,就会立刻执行 test1.py 代码,打印测试内容
Copy
为了解决这个问题,python在执行一个文件时有个变量__name__
.在Python中,当直接运行一个py文件时,这个py文件里的__name__
值是__main__
,据此可以判断一个一个py文件是被直接执行还是以模块的形式被导入。
def add(a,b):
return a+b
if __name__ == '__main__': # 只有直接执行这个py文件时,__name__的值才是 __main__
# 以下代码只有直接运行这个文件才会执行,如果是文件被别的代码导入,下面的代码不会执行
ret = add(12,22)
print('测试的结果是',ret)
Copy
在自定义模块时,需要注意一点,自定义模块名不要和系统的模块名重名,否则会出现问题!
一个模块就是一个 py 文件,在 Python 里为了对模块分类管理,就需要划分不同的文件夹。多个有联系的模块可以将其放到同一个文件夹下,为了称呼方便,一般把 Python 里的一个代码文件夹称为一个包。
现有以下包newmsg
,包里由两个模块,分别是sendmsg.py
、recvmsg.py
文件。在包的上级文件夹里,有一个test.py
文件,目标是在test.py
文件里引入newmsg
的两个模块。
目录结构如下图所示:
sendmsg.py文件里的内容如下:
def send_msg():
print('------sendmsg方法被调用了-------')
Copy
recvmsg.py文件里的内容如下:
def recv_msg():
print('-----recvmsg方法被调用了--------')
Copy
可以使用以下几种方式来导入模块,使用模块里的方法。
1>. 直接使用包名.模块模块名导入指定的模块。
2>. 使用from xxx import xxx
方式导入指定模块。
3>. 使用__init__.py
文件,导入包里的指定模块。
可以在newmsg
里创建__init__.py
文件,在该文件里导入指定的内容。
在__init__.py
文件里编写代码:
from . import sendmsg # 导入指定的模块 . 代表的是当前文件夹
Copy
test.py文件里的代码
import newmsg # 导入时,只需要输入包名即可。在包名的__init__.py文件里,导入了指定模块
newmsg.sendmsg.sendm_msg() # 可以直接调用对应的方法
# newmsg.recvmsg.recv_msg() 不可以使用 recvmsg 模块,因为 __init__.py文件里没有导入这个模块
Copy
4.> 使用__init__.py
文件,结合__all__
属性,导入包里的所有模块。
在newmsg
包里的__init__.py
文件里编写代码:
__all__ = ["sendmsg","recvmsg"] # 指定导入的内容
Copy
test.py文件代码:
from newmsg import * # 将newmsg里的__inint__.py文件里,__all__属性对应的所有模块都导入
sendmsg.sendmsg()
recvmsg.recvmsg()
Copy
总结
__init__.py
文件,那么这个文件夹就称之为包
__init__.py
文件有什么用__init__.py
控制着包的导入行为。__init__.py
为空仅仅是把这个包导入,不会导入包中的模块。可以在__init__.py
文件中编写内容。
newmsg/__init__.py
文件:
print('hello world')
Copy
别的模块在引入这个包的时候,会自动调用这段代码。
__all__
在__init__.py
文件中,定义一个__all__
变量,它控制着 from 包名 import *时导入的模块。
newmsg/__init__.py
文件:
__all__ = ['sendmsg','recvmsg']
Python、Java 和 C++ 都是面向对象编程语言,但它们之间还是有一些区别的。
1.语法风格:
2.继承:
3.多态性:
4.内存管理:
Python面向对象学习目标
面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程的思路是将数据与函数按照执行的逻辑顺序组织在一起,数据与函数分开考虑,面向过程基本是由函数组成的。
面向过程编程的关注点在于怎么做
特点:
面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)和面相过程编程,是两种不同的编程方式。
面向对象编程的关注点在于谁来做
相比较函数,面向对象是更大的封装,根据职责在 一个对象中封装多个方法
特点:
类和对象是面向对象编程的两个核心概念。
类是对一群具有相同特征或者行为 的事物的一个统称,是抽象的,不能直接使用
类就相当于制造飞机时的图纸,是一个模板。这个模板只规定了飞机的某些特征(例如大小,颜色,型号等等)和行为(例如起飞,降落,飞行等等),它并不是一个具体的飞机,而是对飞机的一个抽象概念。它出现的目的,是为了让我们的创建飞机对象。
对象是由类创建出来的一个具体存在,可以直接使用。由哪一个类创建出来的 对象,就拥有在哪一个类中定义的属性和方法。 对象 就相当于用图纸制造的飞机。在开发中,应该先有类,在类里定义好属性和行为,再根据类来创建对象。
例如:定义了一个狗类,这个狗类有以下属性:
现在根据这个类创建出了两条狗,这两条狗分别是 哈士奇、灰色、母、二哈
和 中华田园犬、黄色、公、旺财
。我们发现,这两条狗都具有 品种、颜色、性别和名字这些属性,但是每条狗对应的属性值却不一样。
在程序开发中,要设计一个类,通常需要满足一下三个要素:
名词提炼法:分析整个业务流程,出现的名词,通常就是找到的类。
在Python中,对象几乎是无处不在的,我们可以使用dir
内置函数来查看这个对象里的方法。
面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法了!
在Python中要定义一个只包含方法的类,语法格式如下:
class 类名:
def 方法1(self,参数列表):
pass
def 方法2(self,参数列表):
pass
Copy
当一个类定义完成之后,要使用这个类来创建对象,语法格式如下:
对象变量名 = 类名()
Copy
需求
分析
class Cat:
"""这是个猫类"""
def eat(self):
print("小猫在吃东西")
def drink(self):
print("小猫在喝水")
tom = Cat() # 创建了一个Cat对象
tom.eat()
tom.drink()
hello_kitty = Cat() # 又创建了一个新的Cat对象
hello_kitty.eat()
hello_kitty.drink()
Copy
思考:tom
和 hello_kitty
是同一个对象吗?
python支持动态属性,当一个对象创建好了以后,直接使用 对象.属性名 = 属性值
就可以很方便的给对象添加一个属性。
tom = Cat()
tom.name = 'Tom' # 可以直接给 tom 对象添加一个 name 属性
Copy
这种方法很方便,但是,不建议使用这种方式给对象添加属性。
哪个对象调用了方法,方法里的self
指的就是谁。 通过 self.属性名
可以访问到这个对象的属性;通过 self.方法名()
可以调用这个对象的方法。
class Cat:
def eat(self):
print("%s爱吃鱼" %self.name)
tom = Cat()
tom.name = 'Tom' # 给 tom 对象添加了一个name属性
tom.eat() # Tom爱吃鱼
lazy_cat = Cat()
lazy_cat.name = "大懒猫"
lazy_cat.eat() # 大懒猫爱吃鱼
Copy
上述代码中,我们是先创建对象,然后再给对象添加 name
属性,但是这样做会有问题。
tom = Cat()
tom.eat()
tom.anme = "Tom"
Copy
程序运行时会报错:
AttributeError: 'Cat' object has no attribute 'name'
错误提示:'Cat'对象没有 'name' 属性
Copy
在日常开发中,不推荐在类的外部直接给对象添加属性这种方式。对象应该具有哪些属性,我们应该封装在类的内部。
Python 里有一种方法,叫做魔法方法。Python 的类里提供的,两个下划线开始,两个下划线结束的方法,就是魔法方法,魔法方法在恰当的时候就会被激活,自动执行。 魔法方法的两个特点:
__init__
方法__init__()
方法,在创建一个对象时默认被调用,不需要手动调用。在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对 __init__
方法进行改造。
class Cat:
"""这是一个猫类"""
def __init__(self,name): # 重写了 __init__ 魔法方法
self.name = name
def eat(self):
return "%s爱吃鱼"%self.name
def drink(self):
return '%s爱喝水'%self.name
"""
tom = Cat()
TypeError: __init__() missing 1 required positional argument: 'name'
这种写法在运行时会直接报错!因为 __init__ 方法里要求在创建对象时,必须要传递 name 属性,如果不传入会直接报错!
"""
tom = Cat("Tom") # 创建对象时,必须要指定name属性的值
tom.eat() # tom爱吃鱼
注意:
__init__()
方法在创建对象时,会默认被调用,不需要手动的调用这个方法。__init__()
方法里的self参数,在创建对象时不需要传递参数,python解释器会把创建好的对象引用直接赋值给self__del__
方法创建对象后,python解释器默认调用__init__()
方法;
而当删除对象时,python解释器也会默认调用一个方法,这个方法为__del__()
方法。
class Student:
def __init__(self,name,score):
print('__init__方法被调用了')
self.name = name
self.score = score
def __del__(self):
print('__del__方法被调用了')
s = Student('lisi',95)
del s
input('请输入内容')
PS:
class Student:
def __init__(self,name,score):
print('__init__方法被调用了')
self.name = name
self.score = score
def __del__(self):
print('__del__方法被调用了')
s = Student('lisi',95)
s1 = s
s2 = s
print(sys.getrefcount(s)) #4 PS:调用这个函数也引用一次s
del s1
print("删除s1后的",s.name)
print(sys.getrefcount(s)) #3
del s2
print("删除s2后的",s.name)
print(sys.getrefcount(s)) #2
del s
print("删除s后的",s.name)
注意:python的垃圾回收机制,当所有的指向都被删除时对象才会被回收,才会调用
__del__
方法 PS:查看引用的次数函数sys包中的sys.getrefcount()
__str__
方法__str__
方法返回对象的描述信息,使用print()
函数打印对象时,其实调用的就是这个对象的__str__
方法。
class Cat:
def __init__(self,name,color):
self.name = name
self.color = color
tom = Cat('Tom','white')
# 使用 print 方法打印对象时,会调用对象的 __str__ 方法,默认会打印类名和对象的地址名
print(tom) # <__main__.Cat object at 0x0000021BE3B9C940>
Copy
如果想要修改对象的输出的结果,可以重写 __str__
方法。
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
return '哈哈'
p = Person('张三',18)
print(p) # 哈哈 打印对象时,会自动调用对象的 __str__ 方法
Copy
一般情况下,我们在打印一个对象时,可能需要列出这个对象的所有属性。
class Student:
def __init__(self,name,score):
self.name = name
self.score = score
def __str__(self):
return '姓名是:{},成绩是{}分'.format(self.name,self.score)
s = Student('lisi',95)
print(s) # 姓名是:lisi,成绩是95分
Copy
__repr__
方法__repr__
方法和__str__
方法功能类似,都是用来修改一个对象的默认打印内容。在打印一个对象时,如果没有重写__str__
方法,它会自动来查找__repr__
方法。如果这两个方法都没有,会直接打印这个对象的内存地址。
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __repr__(self):
return 'helllo'
class Person:
def __repr__(self):
return 'hi'
def __str__(self):
return 'good'
s = Student('lisi', 95)
print(s) # hello
p = Person()
print(p) # good
Copy
__call__
方法对象后面加括号,触发执行。
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
Copy
__init__
方法,当删除一个对象时,会自动调用__del__
方法。__str__
和__repr__
方法,都会修改一个对象转换成为字符串的结果。一般来说,__str__
方法的结果更加在意可读性,而__repr__
方法的结果更加在意正确性(例如:datetime模块里的datetime类)思考:
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
p1 = Person('zhangsan',18)
p2 = Person('zhangsan',18)
print(p1 == p2)
Copy
上述代码中,使用==
运算符比较两个对象,结果是True还是False?==
到底比较的是什么?
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.name == other.name and self.age == other.age
# def __ne__(self, other):
def __lt__(self, other):
return self.age < other.age
# def __gt__(self, other):
def __le__(self, other):
return self.age <= other.age
# def __ge__(self, other):
s1 = Student('zhangsan', 18)
s2 = Student('zhangsan', 18)
s3 = Student('lisi', 20)
print(s1 == s2)
print(s1 != s2)
print(s1 > s2)
print(s1 >= s2)
print(s1 <= s2)
print(s1 <= s2)
Copy
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __add__(self, other):
return self.age + other
def __sub__(self, other):
return self.age - other
def __mul__(self, other):
return self.age * other
def __truediv__(self, other):
return self.age / other
def __mod__(self, other):
return self.age % other
def __pow__(self, power, modulo=None):
return self.age ** power
s = Student('zhangsan', 18)
print(s + 1) # 19
print(s - 2) # 16
print(s * 2) # 36
print(s / 5) # 3.6
print(s % 5) # 3
print(s ** 2) # 324
Copy
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __int__(self):
return self.age
def __float__(self):
return self.age * 1.0
def __str__(self):
return self.name
def __bool__(self):
return self.age > 18
s = Student('zhangsan', 18)
print(int(s))
print(float(s))
print(str(s))
print(bool(s))
if s:
print('hello')
使用内置函数dir
可以查看一个对象支持的所有属性和方法,Python中存在着很多的内置属性。
Python中支持动态属性,可以直接通过点语法直接给一个对象添加属性,代码更加的灵活。但是在某些情况下,我们可能需要对属性进行控制,此时,就剋使用__slots__实现。
class Person(object):
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
p = Person('张三', 18)
p.name = '李四'
# 对象p只能设置name和age属性,不能再动态添加属性
# p.height = 180 # 报错
Copy
表示类的描述信息。
class Foo:
""" 描述类信息,这是用于看片的神奇 """
def func(self):
pass
print(Foo.__doc__)
#输出:类的描述信息
Copy
module 表示当前操作的对象在那个模块;class 表示当前操作的对象的类是什么。
test.py
class Person(object):
def __init__(self):
self.name = 'laowang'
main.py
from test import Person
obj = Person()
print(obj.__module__) # 输出 test 即:输出模块
print(obj.__class__) # 输出 test.Person 即:输出类
Copy
以字典的形式,显示对象所有的属性和方法。
class Province(object):
country = 'China'
def __init__(self, name, count):
self.name = name
self.count = count
def func(self, *args, **kwargs):
print('func')
# 获取类的属性,即:类属性、方法、
print(Province.__dict__)
# 输出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}
obj1 = Province('山东', 10000)
print(obj1.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 10000, 'name': '山东'}
obj2 = Province('山西', 20000)
print(obj2.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 20000, 'name': '山西'}
Copy
_getitem_
、_setitem__
和__delitem__
方法这三个方法,是将对象当做字典一样进行操作。
class Foo(object):
def __getitem__(self, key):
print('__getitem__', key)
def __setitem__(self, key, value):
print('__setitem__', key, value)
def __delitem__(self, key):
print('__delitem__', key)
obj = Foo()
result = obj['k1'] # 自动触发执行 __getitem__
obj['k2'] = 'laowang' # 自动触发执行 __setitem__
del obj['k1'] # 自动触发执行 __delitem__
在面向对象开发中,使用类创建出来的实例是一个对象,那么,类是否是一个对象呢?
通过类创建的对象被称为 实例对象,对象属性又称为实例属性,记录对象各自的数据,不同对象的同名实例属性,记录的数据各自独立,互不干扰。
class Person(object):
def __init__(self,name,age):
# 这里的name和age都属于是实例属性,每个实例在创建时,都有自己的属性
self.name = name
self.age = age
# 每创建一个对象,这个对象就有自己的name和age属性
p1 = Person('张三',18)
p2 = Person("李四",20)
Copy
类属性就是类对象所拥有的属性,它被该类的所有实例对象所共有,类属性可以通过类对象或者实例对象访问。
class Dog:
type = "狗" # 类属性
dog1 = Dog()
dog2 = Dog()
# 不管是dog1、dog2还是Dog类,都可以访问到type属性
print(Dog.type) # 结果:狗
print(dog1.type) # 结果:狗
print(dog2.type) # 结果:狗
Copy
1> 尽量避免类属性和实例属性同名。如果有同名实例属性,实例对象会优先访问实例属性。
class Dog(object):
type = "狗" # 类属性
def __init__(self):
self.type = "dog" # 对象属性
# 创建对象
dog1 = Dog()
print(dog1.type) # 结果为 “dog” 类属性和实例属性同名,使用 实例对象 访问的是 实例属性
Copy
2> 类属性只能通过类对象修改,不能通过实例对象修改
lass Dog(object):
type = "狗" # 类属性
# 创建对象
dog1 = Dog()
dog1.type = "dog" # 使用 实例对象 创建了对象属性type
print(dog1.type) # 结果为 “dog” 类属性和实例属性同名,访问的是实例属性
print(Dog.type) # 结果为 "狗" 访问类属性
# 只有使用类名才能修改类属性
Dog.type = "土狗"
print(Dog.type) # 土狗
dog2 = Dog()
print(dog2.type) # 土狗
Copy
3> 类属性也可以设置为私有,前边添加两个下划线。 如:
class Dog(object):
count = 0 # 公有的类属性
__type = "狗" # 私有的类属性
print(Dog.count) # 正确
print(Dog.__type) # 错误,私有属性,外部无法访问。
在实际开发中,对象的某些属性或者方法可能只希望在对象的内部别使用,而不希望在外部被访问到,这时就可以定义私有属性和私有方法。
在定义属性或方法时,在属性名或者方法名前增加两个下划线__
,定义的就是私有属性或方法。
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
self.__money = 2000 # 使用 __ 修饰的属性,是私有属性
def __shopping(self, cost):
self.__money -= cost # __money 只能在对象内部使用
print('还剩下%d元'%self.__money)
def test(self):
self.__shopping(200) # __shopping 方法也只能在对象内部使用
p = Person('张三',18)
# print(p.__money) 这里会报错,不能直接访问对象内部的私有属性
p.test()
# p.__shopping() 这里会报错,__shopping 只能在对象内部使用,外部无法访问
Copy
私有属性不能直接使用,私有方法不能直接调用。但是,通过一些代码,我们也可以在外部访问一个对象的私有属性和方法。
使用方式:在私有属性名或方法名前添加 _类名
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
self.__money = 2000
def __shopping(self, cost):
self.__money -= cost
p = Person('李四',20)
print(p._Person__money) # 使用对象名._类名__私有属性名 可以直接访问对象的私有属性
p._Person__shopping(100) # 使用对象名._类名__函数名 可以直接调用对象的私有方法
print(p._Person__money)
Copy
注意:在开发中,我们强烈不建议使用 对象名._类名__私有属性名
的方式来访问对象的私有属性!
在实际开发中,如果对象的变量使用了__
来修饰,就说明它是一个私有变量,不建议外部直接使用和修改。如果硬要修改这个属性,可以使用定义get
和set
方法这种方式来实现。
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
self.__money = 2000 # __money 是私有变量,外部无法访问
def get_money(self): # 定义了get_money 方法,在这个方法里获取到 __money
return self.__money # 内部可以访问 __money 变量
def set_money(self,money): # 定义了set_money 方法,在这个方法里,可以修改 __money
self.__money = money
p = Person('王五',21)
# 外部通过调用 get_money 和 set_money 这两个公开方法获取和修改私有变量
print(p.get_money())
p.set_money(8000)
print(p.get_money())
@classmethod
来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls
作为第一个参数。class Dog(object):
__type = "狗"
# 类方法,用classmethod来进行修饰
@classmethod
def get_type(cls):
return cls.__type
print(Dog.get_type())
Copy
使用场景:
@staticmethod
来进行修饰class Dog(object):
type = "狗"
def __init__(self):
name = None
# 静态方法
@staticmethod
def introduce(): # 静态方法不会自动传递实例对象和类对象
print("犬科哺乳动物,属于食肉目..")
dog1 = Dog()
Dog.introduce() # 可以用 实例对象 来调用 静态方法
dog1.introduce() # 可以用 类对象 来调用 静态方法
Copy
使用场景:
注意点:
class Dog:
def demo_method(self):
print("对象方法")
@classmethod
def demo_method(cls):
print("类方法")
@staticmethod
def demo_method(): # 被最后定义
print("静态方法")
dog1 = Dog()
Dog.demo_method() # 结果: 静态方法
dog1.demo_method() # 结果: 静态方法
PS:python面向对象中静态方法和类方法有什么区别
区别:
相同:
PS:普通方法与两者的区别
不同:
class A(object):
def __init__(self):
print("这是 init 方法")
def __new__(cls):
print("这是 new 方法")
return object.__new__(cls)
A()
总结
__new__
至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供__new__
必须要有返回值,返回实例化出来的实例,这点在自己实现__new__
时要特别注意,可以return父类__new__
出来的实例,或者直接是object的__new__
出来的实例__init__
有一个参数self,就是这个__new__
返回的实例,__init__
在__new__
的基础上可以完成一些其它初始化的动作,__init__
不需要返回值举个常见的单例模式例子,我们日常使用的电脑上都有一个回收站,在整个操作系统中,回收站只能有一个实例,整个系统都使用这个唯一的实例,而且回收站自行提供自己的实例。因此回收站是单例模式的应用。
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,单例模式是一种对象创建型模式。
# 实例化一个单例
class Singleton(object):
__instance = None
__is_first = True
def __new__(cls, age, name):
if not cls.__instance:
cls.__instance = object.__new__(cls)
return cls.__instance
def __init__(self, age, name):
if self. __is_first: # 不会再创建第二个对象
self.age = age
self.name = name
Singleton. __is_first = False
a = Singleton(18, "张三")
b = Singleton(28, "张三")
print(id(a))
print(id(b))
print(a.age) # 18
print(b.age) # 18
a.age = 19
print(b.age)
在现实生活中,继承一般指的是子女继承父辈的财产,父辈有的财产,子女能够直接使用。
继承是面向对象软件设计中的一个概念,与多态、封装共为面向对象的三个基本特征。继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。
class Animal:
def __int__(self):
pass
"""动物类"""
def sleep(self):
print('正在睡觉')
class Dog(Animal):
"""Dog类继承自Animal类"""
def __init__(self):
pass
class Cat(Animal): # 定义类时,在括号后面传入父类的类名,表示子类继承父类
"""Cat类继承自Animal类"""
def __int__(self):
pass
# Dog 和 Cat 都继承自Animal类,可以直接使用Animal类里的sleep方法
dog = Dog()
dog.sleep()
cat = Cat()
cat.sleep()
在Python中,继承可以分为单继承、多继承和多层继承。
继承概念:子类用于父类的所有的方法和属性。
继承语法:
class 类名(父类名):
pass
Copy
Dog类继承自Animal,XiaoTianQuan又继承自Dog类,那么XiaoTianQuan类就具有了Animal类里的所有属性和方法。
子类拥有父类以及父类的父类中封装的所有属性和方法。
思考:
XiaoTianQuan能否调用Animal的run()方法? XiaoTianQUan能够调用Cat里的方法?
子类可以拥有多个父类,并且具有所有父类的属性和方法。
语法格式:
class 子类名(父类名1,父类名2...)
pass
Copy
思考:
如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪个父类的方法? 说明:开发中,应该尽量避免这种容易产生混淆的情况。如果多个父类之间存在同名的属性后者方法,应该尽量避免使用多继承。
__mro__
可以用来查看方法的搜索顺序。method resolution order
的简称,主要用于在多继承时判断方法属性的调用顺序。print(C.__mro__)
输出结果:
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
__mro__
的输出结果从左至右的顺序查找。object
是Python中所有对象的基类,提供了一些内置的属性和方法,可以时候用dir
函数查看。
object
为基类的类,推荐使用为了保证代码在Python2.x和Python3.x中都能够运行,在定义类时,如果一个类没有父类,建议统一继承自’object’
class 类名(object):
pass
Python中的身份运算符用来判断两个对象是否相等;isinstance用来判断对象和类之间的关系;issublcass用啊里判断类与类之间的关系。
身份运算符用来比较两个对象的内存地址,看这两个对象是否是同一个对象。
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person('张三', 18)
p2 = Person('张三', 18)
p3 = p1
print(p1 is p2) # False
print(p1 is p3) # True
Copy
instance内置函数,用来判断一个实例对象是否是由某一个类(或者它的子类)实例化创建出来的。
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, name, age, score):
super(Student, self).__init__(name, age)
self.score = score
class Dog(object):
def __init__(self, name, color):
self.name = name
self.color = color
p = Person('tony', 18)
s = Student('jack', 20, 90)
d = Dog('旺财', '白色')
print(isinstance(p, Person)) # True.对象p是由Person类创建出来的
print(isinstance(s, Person)) # True.对象s是有Person类的子类创建出来的
print(isinstance(d, Person)) # False.对象d和Person类没有关系
Copy
issubclass 用来判断两个类之间的继承关系。
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, name, age, score):
super(Student, self).__init__(name, age)
self.score = score
class Dog(object):
def __init__(self, name, color):
self.name = name
self.color = color
print(issubclass(Student, Person)) # True
print(issubclass(Dog, Person)) # False
面向对象的三大特性:
代码实现:
class ArmyDog(object):
def bite_enemy(self):
print('追击敌人')
class DrugDog(object):
def track_drug(self):
print('追查毒品')
class Person(object):
def work_with_army(self, dog):
dog.bite_enemy()
def work_with_drug(self, dog):
dog.track_drug()
ad = ArmyDog()
dd = DrugDog()
p = Person()
p.work_with_army(ad)
p.work_with_drug(dd)
思考:这段代码设是否有问题?
新增需求:此时,又多了一个犬种,就又需要在Person类里新建一个方法,让这个方法操作新的狗。
class XiaoTianDog(object):
def eat_moon(self):
print('哮天犬把月亮吃了')
class Person(object):
def work_with_xiaotian(self, dog): # 添加方法
dog.eat_moon()
Person 类总是不断的添加新的功能,每次都需要改动Person类的源码,程序的扩展性太差了!
代码实现:
class Dog(object):
def work(self): # 父类提供统一的方法,哪怕是空方法
pass
class ArmyDog(Dog): # 继承 Dog
def work(self): # 子类重写方法,并且处理自己的行为
print('追击敌人')
class DrugDog(Dog):
def work(self):
print('追查毒品')
class Person(object):
def work_with_dog(self, dog):
dog.work() # 使用小狗可以根据对象的不同而产生不同的运行效果, 保障了代码的稳定性
# 子类对象可以当作父类来使用
dog = Dog()
ad = ArmyDog()
dd = DrugDog()
p = Person()
p.work_with_dog(dog)
p.work_with_dog(ad) # 同一个方法,只要是 Dog 的子类就可以传递,提供了代码的灵活性
p.work_with_dog(dd) # 并且传递不同对象,最终 work_with_dog 产生了不同的执行效果