前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python与C++、Java区别对比学习

Python与C++、Java区别对比学习

作者头像
CtrlX
发布2023-03-21 12:07:50
1.7K0
发布2023-03-21 12:07:50
举报
文章被收录于专栏:C++核心编程C++核心编程

变量,输入输出语句

Java,C++,Python中的数组和变量存储位置相同吗

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 运算符可以在堆上动态地分配和释放内存。数组在堆上分配内存的方法如下:

代码语言:javascript
复制
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、C++ 和 Java 都支持格式化输出,但它们的实现方法略有不同。

  1. Python 格式化输出

Python 中格式化输出可以使用字符串的 format() 方法或 % 操作符。下面是两种不同的写法:

使用 format() 方法:

代码语言:javascript
复制
name = "Tom"
age = 20
print("My name is {}, and I'm {} years old".format(name, age))

使用 % 操作符:

代码语言:javascript
复制
name = "Tom"
age = 20
print("My name is %s, and I'm %d years old" % (name, age))
  1. C++ 格式化输出

C++ 中格式化输出可以使用 printf() 函数或 std::cout 对象。下面是两种不同的写法:

使用 printf() 函数:

代码语言:javascript
复制
#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 对象:

代码语言:javascript
复制
#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;
}
  1. Java 格式化输出

Java 中格式化输出可以使用 System.out.printf() 方法。下面是一个例子:

代码语言:javascript
复制
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);
    }
}

总的来说,这三种编程语言在格式化输出方面都有各自的特点和使用方式,需要根据具体的应用场景选择适合的方式。

PS:python字符串的format方法

1. 概念:
  • str.format() 方法通过字符串中的大括号{} 来识别替换字段 replacement field,从而完成字符串的格式化。
  • 替换字段 由字段名 field name 和转换字段 conversion field 以及格式说明符 format specifier 组成,即一般形式为 {字段名!转换字段:格式说明符}。
  • 字段名分为简单字段名 simple field name 和复合字段名 compound field name。而转换字段和格式说明符都是可选的。
2. 字段名

form的完整格式是{字段名!转换字符:格式说明符}。其中字段名师必须的,而且可以分为简单字段名和复合字段名。

2.1 简单字段名

简单字段名由三中写法:

  • 省略字段名:{}
  • 使用非负十进制整数{0}
  • 变量名{name}
2.1.1 省略字段名

大括号内省略字段名,传递位置参数

  • 替换字段形式: {}
  • 注意:大括号个数可以少于位置参数的个数,反之不然。
代码语言:javascript
复制
# 省略字段名传递位置参数
print('我叫{},今年{}岁。'.format('小明', 18))
"""
我叫小明,今年18岁。
"""

# 大括号个数可以少于位置参数的个数
print('我爱吃{}和{}。'.format('香蕉', '苹果', '大鸭梨'))
"""
我爱吃香蕉和苹果。
"""

# 大括号个数多于位置参数的个数则会报错
# print('我还吃{}和{}。'.format('西红柿'))
"""
IndexError: tuple index out of range
"""
2.1.2 数字字段名

可以通过数字形式的简单字段名传递位置参数。

  • 数字必须是大于等于 0 的整数。
  • 带数字的替换字段可以重复使用。
  • 数字形式的简单字段名相当于把 format 中的所有位置参数整体当作一个元组,通过字段名中的数字进行取值。即 {0} 等价于 tuple[0],所以大括号内的数字不能越界。
代码语言:javascript
复制
# 通过数字形式的简单字段名传递位置参数
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
"""
2.1.3 变量字段名

使用变量名形式的简单字段名传递关键字参数

  • 关键字参数的位置可以随意调换。
代码语言:javascript
复制
# 使用变量名形式的简单字段名传递关键字参数
print('我大哥是{name},今年{age}岁。'.format(name='阿飞', age=20))
"""
我大哥是阿飞,今年20岁。
"""

# 关键字参数的顺序可以随意调换
print('我大哥是{name},今年{age}岁。'.format(age=20, name='阿飞'))
"""
我大哥是阿飞,今年20岁。
"""
2.1.4 简单字段名的混合使用
  • 混合使用数字形式和变量名形式的字段名,可以同时传递位置参数和关键字参数。
  • 关键字参数必须位于位置参数之后。
  • 混合使用时可以省略数字。
  • 省略字段名 {} 不能和数字形式的字段名 {非负整数} 同时使用。
代码语言:javascript
复制
# 混合使用数字形式和变量名形式的字段名
# 可以同时传递位置参数和关键字参数
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
"""
2.1.5 使用元组和字典传参

str.format() 方法还可以使用 *元组**字典 的形式传参,两者可以混合使用。 位置参数、关键字参数、*元组**字典 也可以同时使用,但是要注意,位置参数要在关键字参数前面,*元组 要在 **字典 前面。

代码语言:javascript
复制
# 使用元组传参
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)
"""
我是黑寡妇,我怕男人。我是鹰眼,我用箭。
"""
2.2 复合字段名
  • 同时使用了数字和变量名两种形式的字段名就是复合字段名
  • 复合字段名 支持两种操作符:
    • . 点号
    • [] 中括号
2.2.1 使用. 点号

传递位置参数

  • 替换字段形式:{数字.属性名}
  • 只有一个替换字段的时候可以省略数字
代码语言:javascript
复制
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))  # 只有一个替换字段时,可以省略数字
2.2.2 使用[]中括号
  • 用列表传递位置参数
  • 用元组传递位置参数
  • 用字典传递位置参数
代码语言:javascript
复制
# 中括号用法:用列表传递位置参数
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。
"""
3. 转换字段

转换字段 conversion field 的取值有三种,前面要加 !

  • s:传递参数之前先对参数调用 str()
  • r:传递参数之前先对参数调用 repr()
  • a:传递参数之前先对参数调用 ascii()

ascii() 函数类似 repr() 函数,返回一个可以表示对象的字符串。 但是对于非 ASCII 字符,使用 \x\u 或者 \U 转义。

代码语言:javascript
复制
# 转换字段
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'!
"""
4. 格式说明符

格式说明符使用过于复杂,且实际使用场景不多,暂不讨论。

进制转换、数据类型详解、类型转换、运算符

python,C++,java在数据类型和类型转换上的区别

Python、C++、Java 都是常用的编程语言,它们在数据类型和类型转换方面有一些不同点,具体如下:

  1. 数据类型

Python、C++ 和 Java 支持的数据类型略有不同。下面是它们支持的一些常见数据类型:

  • Python: int、float、bool、str、list、tuple、dict、set 等
  • C++: int、float、bool、char、double、long、short、unsigned 等
  • Java: int、float、double、char、boolean、byte、short、long 等
  1. 类型转换

Python、C++ 和 Java 都支持类型转换,但它们的实现方法略有不同。

  • Python:可以使用 int()、float()、bool()、str() 等函数进行类型转换。此外,Python 也支持自动类型转换。
  • C++:可以使用强制类型转换和隐式类型转换。强制类型转换需要使用括号加上转换后的类型来完成,例如 (int)3.14 将 3.14 转换为整数。隐式类型转换会自动将一种类型转换为另一种类型,例如将 int 转换为 float。
  • Java:可以使用强制类型转换和自动类型转换。强制类型转换需要使用括号加上转换后的类型来完成,例如 (int)3.14 将 3.14 转换为整数。自动类型转换会自动将一种类型转换为另一种类型,例如将 int 转换为 double。

总的来说,这三种编程语言在数据类型和类型转换方面都有各自的特点和使用方式,需要根据具体的应用场景选择适合的编程语言和数据类型。

python,java,C++位运算的区别

位运算是一种基于二进制的运算,可以对二进制数的每一位进行操作。Python、Java和C++都支持位运算,下面是它们的区别:

Python: Python支持位运算符,包括按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、左移位(<<)、右移位(>>),但是Python中没有无符号位移运算符。Python的整数类型是可以自动扩展的,不需要考虑溢出问题。

Java: Java同样支持位运算符,包括按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、左移位(<<)、右移位(>>)和无符号右移位(>>>)。Java中的整数类型有固定的位数,超出范围会出现溢出问题。

C++: C++同样支持位运算符,包括按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、左移位(<<)、右移位(>>)和无符号右移位(>>>)。与Java不同的是,C++中的整数类型也有固定的位数,超出范围也会出现溢出问题。C++也支持位域,可以将一个变量的几个位用来存储不同的信息。

PS:Python讲解进制抓换,位运算,补充位运算

流程控制语句

C++,java,python的for循环的区别

虽然C++、Java和Python的for循环语法有些差异,但其本质都是实现循环执行一段代码,直到达到指定的循环次数或者满足某些条件后退出循环。

下面是这三种语言中for循环的主要差异:

C++的for循环

C++的for循环通常用于实现计数循环,其语法如下:

代码语言:javascript
复制
for (初始化表达式; 布尔表达式; 更新表达式) {
    // 代码块
}

其中,初始化表达式是一条语句,用于初始化一个计数器变量;布尔表达式是一个返回布尔值的表达式,用于测试计数器变量是否达到指定的值;更新表达式用于更新计数器变量的值。

C++遍历容器的几种方法

C++ 中遍历容器(如数组、vector、map 等)的方法有多种,下面介绍其中的几种:

  1. 使用 for 循环

使用 for 循环可以遍历数组和 vector 等容器,例如:

代码语言:javascript
复制
#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;
}
  1. 使用 C++11 新特性的 for-each 循环

C++11 引入了 for-each 循环,可以方便地遍历容器中的元素,例如:

代码语言:javascript
复制
#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;
}
  1. 使用迭代器

使用迭代器可以遍历所有类型的容器,包括 vector、list、set、map 等,例如:

代码语言:javascript
复制
#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;
}
  1. 使用 auto 关键字

使用 auto 关键字可以让编译器自动推导容器类型和迭代器类型,使代码更简洁,例如:

代码语言:javascript
复制
#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循环

Java的for循环也常用于计数循环,其语法如下:

代码语言:javascript
复制
for (初始化表达式; 布尔表达式; 更新表达式) {
    // 代码块
}

Java的for循环与C++的for循环语法基本相同,只是Java的for循环还支持对数组和集合进行迭代操作。

代码语言:javascript
复制
for (数据类型 变量名: 数组名/集合名) {
    // 代码块
}

Python的for循环

Python的for循环主要用于迭代操作,其语法如下:

代码语言:javascript
复制
for 变量 in 序列:
    // 代码块

其中,序列可以是列表、元组、字符串等可迭代对象。在每一次迭代过程中,循环变量会被赋值为序列中的下一个元素。

需要注意的是,Python中的for循环不支持类似于C++和Java中的计数循环,但可以通过range函数实现类似的功能。例如:

代码语言:javascript
复制
for i in range(10):
    // 代码块

这段代码会执行10次循环,每次循环i的值会依次为0~9。

Python的for循环可以迭代(遍历)以下对象:

  1. 序列(sequence):例如字符串(string)、列表(list)、元组(tuple)等。
  2. 集合(set):例如集合(set)和冻结集合(frozenset)等。
  3. 映射(mapping):例如字典(dictionary)等。
  4. 迭代器(iterator):实现了__next__()和__iter__()方法的对象,例如生成器(generator)和文件对象(file object)等。

需要注意的是,虽然Python的for循环可以迭代大多数容器类型(container),但并不是所有容器类型都是可迭代的(iterable)。例如数字(integer)和布尔(boolean)等非容器类型就不是可迭代的。

此外,我们还可以使用range()函数生成一个由整数组成的序列,并用for循环遍历这个序列。例如:

代码语言:javascript
复制
for i in range(5):
    print(i)

这个代码片段将输出从0到4的整数。

字符串

C++,java,python的字符串常见操作对比

字符串是计算机程序中非常常见的数据类型之一。以下是一些常见的字符串操作:

  • 获取字符串长度:获取字符串中字符的个数,可以使用 lengthsize 方法。
  • 字符串连接:将两个字符串拼接起来,可以使用 +concat 方法。
  • 字符串切片:截取字符串中的一部分,可以使用下标、substring 方法或者正则表达式。
  • 分割字符串:将一个字符串分割成多个子串,可以使用 split 方法或者正则表达式。
  • 替换字符串:将字符串中的一个子串替换成另一个字符串,可以使用 replace 方法或者正则表达式。
  • 查找字符串:在一个字符串中查找指定的子串,可以使用 find 方法或者正则表达式。
  • 转换字符串:将一个字符串转换成另一种形式,比如将字符串转换成整数或者浮点数,可以使用 stoistof 或者 parseFloatparseInt 等方法。
  • 字符串比较:比较两个字符串的大小,可以使用 <><=>===!= 操作符或者 compareTo 方法。

以下是 C++,Java 和 Python 中一些常见的字符串操作及其对比:

字符串长度

在 C++ 中,可以使用 size() 或者 length() 函数来获取字符串的长度。在 Java 中,可以使用 length() 方法来获取字符串的长度。在 Python 中,可以使用 len() 函数来获取字符串的长度。

代码语言:javascript
复制
str = "Hello, world!"
print(len(str)) # 输出 13
代码语言:javascript
复制
#include <iostream>
#include <string>

using namespace std;

int main() {
  string str = "Hello, world!";
  cout << str.size() << endl; // 输出 13
  return 0;
}
代码语言:javascript
复制
public class Main {
  public static void main(String[] args) {
    String str = "Hello, world!";
    System.out.println(str.length()); // 输出 13
  }
}

字符串比较

在 C++ 中,可以使用 == 或者 != 运算符来比较字符串是否相等。在 Java 中,可以使用 equals() 方法来比较字符串是否相等。在 Python 中,可以使用 == 或者 != 运算符来比较字符串是否相等。

代码语言:javascript
复制
str1 = "Hello, world!"
str2 = "Hello, Python!"
if str1 == str2:
  print("相等")
else:
  print("不相等") # 输出 "不相等"
代码语言:javascript
复制
#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;
}
代码语言:javascript
复制
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 关键字来查找字符串中是否包含某个子串。

代码语言:javascript
复制
str = "Hello, world!"
if "world" in str:
  print("包含")
else:
  print("不包含") # 输出 "包含"
代码语言:javascript
复制
#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;
}
代码语言:javascript
复制

字符串切片

在 C++,Java 和 Python 中,字符串切片操作用于从一个字符串中获取子字符串。但是,这些语言中的切片操作略有不同。

C++

在 C++ 中,可以使用 substr 函数来获取字符串的子串,其语法如下:

代码语言:javascript
复制
string substr (size_t pos, size_t len) const;

其中,pos 参数指定要从哪个位置开始获取子串,len 参数指定要获取的子串的长度。如果省略 len 参数,则会返回从 pos 开始到字符串末尾的所有字符。

代码语言:javascript
复制
#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 方法来获取字符串的子串,其语法如下:

代码语言:javascript
复制
String substring(int beginIndex, int endIndex);

其中,beginIndex 参数指定要从哪个位置开始获取子串(包括该位置的字符),endIndex 参数指定要获取的子串的结束位置(不包括该位置的字符)。如果省略 endIndex 参数,则会返回从 beginIndex 开始到字符串末尾的所有字符。

代码语言:javascript
复制
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 中,可以使用切片操作符 [] 来获取字符串的子串,其语法如下:

代码语言:javascript
复制
string[start:end:step]

其中,start 参数指定要从哪个位置开始获取子串(包括该位置的字符),end 参数指定要获取的子串的结束位置(不包括该位置的字符),step 参数指定要跳过的字符数(默认为 1。为正数则从左向右输出,为负数则从右向左输出)。如果省略 start 参数,则会从字符串的开头开始获取子串;如果省略 end 参数,则会一直获取到字符串的末尾。

代码语言:javascript
复制
str = "Hello, world!"
sub = str[7:12]
print(sub) # 输出 "world"
代码语言:javascript
复制
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 中,都可以使用 + 运算符将两个字符串拼接起来。

代码语言:javascript
复制
#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;
}
代码语言:javascript
复制
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!"
  }
}
代码语言:javascript
复制
str1 = "Hello"
str2 = "world!"
newStr = str1 + ", " + str2 + "!"
print(newStr) # 输出 "Hello, world!"

使用 concat 方法

在 Java 中,还可以使用 concat 方法将两个字符串拼接起来。

代码语言:javascript
复制
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 方法将多个字符串拼接成一个字符串。

代码语言:javascript
复制
strList = ["Hello", "world!"]
newStr = ", ".join(strList) + "!"
print(newStr) # 输出 "Hello, world!"

总的来说,在 C++、Java 和 Python 中,都可以使用类似于 + 运算符的方式来实现字符串连接,但是在 Java 和 Python 中还提供了其他的方法来实现字符串连接。

字符串分割

在 C++、Java 和 Python 中,字符串分割操作也是比较常见的字符串操作之一。下面是在这三种语言中实现字符串分割的一些方法的对比:

使用 split 方法

在 Java 和 Python 中,都提供了 split 方法,可以将一个字符串按照指定的分隔符分割成多个子串。

在 Java 中,可以使用 split 方法将一个字符串按照指定的正则表达式分割成多个子串,返回一个字符串数组。

代码语言:javascript
复制
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 方法将一个字符串按照指定的分隔符分割成多个子串,返回一个字符串列表。

代码语言:javascript
复制
str = "apple,banana,orange"
strList = str.split(",")
for s in strList:
  print(s)

使用 istringstream(C++)

在 C++ 中,可以使用 istringstream 类将一个字符串按照指定的分隔符分割成多个子串,返回一个字符串流对象。

代码语言:javascript
复制
#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 方法将一个字符串中指定的子串替换为另一个字符串。

代码语言:javascript
复制
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 方法将一个字符串中指定的子串替换为另一个字符串。

代码语言:javascript
复制
str = "hello, world!"
newStr = str.replace("world", "Python")
print(newStr)

使用算法库(C++)

在 C++ 中,可以使用 algorithm 库中的 replace 函数将一个字符串中指定的子串替换为另一个字符串。

代码语言:javascript
复制
#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 库来实现正则表达式操作。

代码语言:javascript
复制
#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 方法来实现正则表达式操作。

代码语言:javascript
复制
public class Main {
  public static void main(String[] args) {
    String str = "hello, world!";
    String newStr = str.replaceAll(",", ":");
    System.out.println(newStr);
  }
}

在 Python 中,可以使用 re 模块来实现正则表达式操作。

代码语言:javascript
复制
import re

str = "hello, world!"
newStr = re.sub(",", ":", str)
print(newStr)

总的来说,在 Java 和 Python 中都提供了比较方便的 replace 方法,可以快速地将一个字符串中指定的子串替换为另一个字符串,而在 C++ 中需要使用 algorithm 库中的 replace 函数或者 regex 库来实现类似的功能。同时,正则表达式在三种语言中都可以用来实现字符串替换操作。

转换字符串

在 C++、Java 和 Python 中,字符串与其他数据类型之间的转换是非常常见的操作。下面是在这三种语言中实现字符串转换的一些方法的对比:

字符串转整数

在 C++ 中,可以使用 stoi 函数将一个字符串转换为整数。

代码语言:javascript
复制
#include <iostream>
#include <string>

using namespace std;

int main() {
  string str = "123";
  int num = stoi(str);
  cout << num << endl;
  return 0;
}

在 Java 中,可以使用 Integer.parseInt 方法将一个字符串转换为整数。

代码语言:javascript
复制
public class Main {
  public static void main(String[] args) {
    String str = "123";
    int num = Integer.parseInt(str);
    System.out.println(num);
  }
}

在 Python 中,可以使用 int 函数将一个字符串转换为整数。

代码语言:javascript
复制
str = "123"
num = int(str)
print(num)

整数转字符串

在 C++ 中,可以使用 to_string 函数将一个整数转换为字符串。

代码语言:javascript
复制
#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 方法将一个整数转换为字符串。

代码语言:javascript
复制
public class Main {
  public static void main(String[] args) {
    int num = 123;
    String str = String.valueOf(num);
    System.out.println(str);
  }
}

在 Python 中,可以使用 str 函数将一个整数转换为字符串。

代码语言:javascript
复制
num = 123
str = str(num)
print(str)

字符串转浮点数

在 C++ 中,可以使用 stof 函数将一个字符串转换为浮点数。

代码语言:javascript
复制
#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 方法将一个字符串转换为浮点数。

代码语言:javascript
复制
public class Main {
  public static void main(String[] args) {
    String str = "3.14";
    float num = Float.parseFloat(str);
    System.out.println(num);
  }
}

在 Python 中,可以使用 float 函数将一个字符串转换为浮点数。

代码语言:javascript
复制
str = "3.14"
num = float(str)
print(num)

浮点数转字符串

在 C++ 中,可以使用 to_string 函数将一个浮点数转换为字符串。

代码语言:javascript
复制
#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 方法将一个浮点数转换为字符串。

代码语言:javascript
复制
public class Main {
  public static void main(String[] args) {
    float num = 3.14f;
    String str = String.valueOf(num);
    System.out.println(str);
  }
}

在 Python 中,可以使用 str 函数将一个浮点数转换为字符串。

代码语言:javascript
复制
num = 3.14
str = str(num)
print(str)

字符串转布尔值

在 C++ 中,可以使用 stoi 函数将一个字符串转换为布尔值。

代码语言:javascript
复制
#include <iostream>
#include <string>

using namespace std;

int main() {
  string str = "true";
  bool value = stoi(str);
  cout << value << endl;
  return 0;
}

在 Java 中,可以使用 Boolean.parseBoolean 方法将一个字符串转换为布尔值。

代码语言:javascript
复制
public class Main {
  public static void main(String[] args) {
    String str = "true";
    boolean value = Boolean.parseBoolean(str);
    System.out.println(value);
  }
}

在 Python 中,可以使用 bool 函数将一个字符串转换为布尔值。

代码语言:javascript
复制
str = "True"
value = bool(str)
print(value)

布尔值转字符串

在 C++ 中,可以使用 to_string 函数将一个布尔值转换为字符串。

代码语言:javascript
复制
#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 方法将一个布尔值转换为字符串。

代码语言:javascript
复制
public class Main {
  public static void main(String[] args) {
    boolean value = true;
    String str = String.valueOf(value);
    System.out.println(str);
  }
}

在 Python 中,可以使用 str 函数将一个布尔值转换为字符串。

代码语言:javascript
复制
value = True
str = str(value)
print(str)

总的来说,在这些语言中,字符串与其他数据类型之间的转换非常容易,并且提供了方便的内置函数和方法来完成这些操作。

PS:Python中字符串的常见操作

获取长度: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

len函数可以获取字符串的长度。

代码语言:javascript
复制
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(len(mystr))  # 17 获取字符串的长度
查找

查找相关的方法,使用方式大致相同,但是略有区别。

find

查找指定内容在字符串中是否存在,如果存在就返回该内容在字符串中第一次出现的开始位置索引值,如果不存在,则返回-1.

语法格式:

代码语言:javascript
复制
S.find(sub[, start[, end]]) -> int

示例:

代码语言:javascript
复制
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()函数,不过是从右边开始查找。

代码语言:javascript
复制
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.rfind('好')) # 14

index

跟find()方法一样,只不过,find方法未找到时,返回-1,而str未找到时,会报一个异常。

语法格式:

代码语言:javascript
复制
S.index(sub[, start[, end]]) -> int

rindex

类似于 index(),不过是从右边开始。

判断

python提供了非常丰富的方法,可以用来对一个字符串进行判断。

startswith

判断字符串是否以指定内容开始。 语法格式:

代码语言:javascript
复制
S.startswith(prefix[, start[, end]]) -> bool

示例:

代码语言:javascript
复制
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.startswith('今'))  # True
print(mystr.startswith('今日')) # False

endswith

判断字符串是否以指定内容结束。

代码语言:javascript
复制
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.endswith('好风光')) #True
print(mystr.endswith('好日子')) #False

isalpha

判断字符串是否是纯字母。

代码语言:javascript
复制
mystr = 'hello'
print(mystr.isalpha())  # True
mystr = 'hello world'
print(mystr.isalpha()) # False 因为中间有空格

isdigit

判断一个字符串是否是纯数字,只要出现非0~9的数字,结果就是False.

代码语言:javascript
复制
mystr = '1234'
print(mystr.isdigit()) # True
mystr = '123.4'
print(mystr.isdigit()) # False
mystr = '-1234'
print(mystr.isdigit()) # False

isalnum

判断是否由数字和字母组成。只要出现了非数字和字母,就返回False.

代码语言:javascript
复制
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.

代码语言:javascript
复制
mystr = ''
print(mystr.isspace()) # False mystr是一个空字符串
mystr = '  '
print(mystr.isspace()) # True 只有空格
mystr = ' d'
print(mystr.isspace()) # False 除了空格外还有其他内容
计数

count

返回 str在start和end之间 在 mystr里面出现的次数。

语法格式:

代码语言:javascript
复制
S.count(sub, start, end) -> int

示例:

代码语言:javascript
复制
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.count('好'))  # 3. '好'字出现三次
替换

替换字符串中指定的内容,如果指定次数count,则替换不会超过count次。

代码语言:javascript
复制
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 个子字符串。返回的结果是一个列表。

代码语言:javascript
复制
mystr = '今天天气好晴朗,处处好风光呀好风光'
result = mystr.split() # 没有指定分隔符,默认使用空格,换行等空白字符进行分隔
print(result) #['今天天气好晴朗,处处好风光呀好风光'] 没有空白字符,所以,字符串未被分隔

result = mystr.split('好')  # 以 '好' 为分隔符
print(result) # ['今天天气', '晴朗,处处','风光呀,'风光']

result = mystr.split("好",2) # 以 '好' 为分隔符,最多切割成3份
print(result) # ['今天天气', '晴朗,处处', '风光呀好风光']

rsplit

用法和split基本一致,只不过是从右往左分隔。

代码语言:javascript
复制
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.rsplit('好',1))  #['今天天气好晴朗,处处好风光呀', '风光']

splitlines

按照行分隔,返回一个包含各行作为元素的列表。

代码语言:javascript
复制
mystr = 'hello \nworld'
print(mystr.splitlines())
s = '''
今天天气好晴朗!
处处好风光呀好风光!
'''
result = s.splitlines()
print(result)#['', '今天天气好晴朗!', '处处好风光呀好风光!']

partition

把mystr以str分割成三部分,str前,str和str后,三部分组成一个元组

代码语言:javascript
复制
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.partition('好'))  # ('今天天气', '好', '晴朗,处处好风光呀好风光')

rpartition

类似于 partition()函数,不过是从右边开始.

代码语言:javascript
复制
mystr = '今天天气好晴朗,处处好风光呀好风光'
print(mystr.rpartition('好'))   # ('今天天气好晴朗,处处好风光呀', '好', '风光')

PS:partitionrpartition只会将字符串分成三部分

修改大小写

修改大小写的功能只对英文有效,主要包括,首字母大写capitalize,每个单词的首字母大写title,全小写lower,全大写upper.

capitalize

第一个单词的首字母大写。

代码语言:javascript
复制
mystr = 'hello world'
print(mystr.capitalize()) # Hello world

title

每个单词的首字母大写。

代码语言:javascript
复制
mystr = 'hello world'
print(mystr.title()) # Hello World

lower

所有都变成小写。

代码语言:javascript
复制
mystr = 'hElLo WorLD'
print(mystr.lower()) # hello world

upper

所有都变成大写。

代码语言:javascript
复制
mystr = 'hello world'
print(mystr.upper())  #HELLO WORLD
空格处理

引入:

代码语言:javascript
复制
username = input("请输入用户名:")#请输入用户名:admin    '\n'
print(len(username))#9

如果输入了admin加上四个空格时空格也会被读取到,如果写入数据里后,之后再登录时不直观,易出错,不希望字符串参与到数据库中,所以一般需要一些去除空格的操作。

Python为我们提供了各种操作字符串里表格的方法。

ljust

返回指定长度的字符串,并在右侧使用空白字符补全(左对齐)。

代码语言:javascript
复制
str = 'hello'
print(str.ljust(10))  # hello     在右边补了五个空格

rjust

返回指定长度的字符串,并在左侧使用空白字符补全(右对齐)。

代码语言:javascript
复制
str = 'hello'
print(str.rjust(10))  #      hello在左边补了五个空格

center

返回指定长度的字符串,并在两端使用空白字符补全(居中对齐)

代码语言:javascript
复制
str = 'hello'
print(str.center(10))  #  hello   两端加空格,让内容居中

上面三个类似于C/C++ 的\t,但是比较灵活

lstrip

删除 mystr 左边的空白字符。

代码语言:javascript
复制
mystr = '    he   llo      '
print(str.lstrip())  #he   llo      只去掉了左边的空格,中间和右边的空格被保留

rstrip

删除 mystr 右边的空白字符。

代码语言:javascript
复制
mystr = '    he   llo      '
print(str.rstrip())  #    he   llo右边的空格被删除

strip

删除两断的空白字符。

代码语言:javascript
复制
str = '    he   llo      '
print(str.strip())  #he   llo
字符串拼接

把参数进行遍历,取出参数里的每一项,然后再在后面加上mystr

语法格式:

代码语言:javascript
复制
S.join(iterable)

join的作用常用在拼接列表的字符串

示例:

代码语言:javascript
复制
mystr = 'a'
print(mystr.join('hxmdq'))  #haxamadaq  把hxmd一个个取出,并在后面添加字符a. 最后的 q 保留,没有加 a
print(mystr.join(['hi','hello','good']))  #hiahelloagood

作用:可以把列表或者元组快速的转变成为字符串,并且以指定的字符分隔。

代码语言:javascript
复制
txt = '_'
print(txt.join(['hi','hello','good'])) #hi_hello_good
print(txt.join(('good','hi','hello'))) #good_hi_hello
字符串运算符
  1. 字符串和字符串之间能够使用加法运算符,作用是将两个字符串拼接成为一个字符串。例如:'hello' + 'world'的结果是 'helloworld'
  2. 字符串和数字之间可以做乘法运算,结果是将指定的字符串重复多次。例如:'hello'*2的结果是hellohello
  3. 字符串和字符串之间,如果使用比较运算符进行计算,会获取字符对应的编码,然后进行比较。
  4. 除上述几种运算符以外,字符串默认不支持其他运算符。
示例
代码语言:javascript
复制
#查找: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

  1. find和index的区别是find没找到会返回-1,index没找到会报错。
  2. 如果查找的是多个字母,则返回子母首地址的下标
代码语言:javascript
复制
#判断: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)
代码语言:javascript
复制
#练习
'''
模拟文件上传,键盘输入文件名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方法,可以实现字符和编码之间的相互转换。

代码语言:javascript
复制
print(ord('a'))  # 使用ord方法,可以获取一个字符对应的编码
print(chr(100))  # 使用chr方法,可以获取一个编码对应的字符

编码规则

使用Unicode为每种语言的每个字符都设定了唯一的二进制编码,但是它还是存在一定的问题,不够完美。

例如,汉字 “你” 转换成为一个字符结果是0x4f60,转换成为二进制就是 01001111 01100000,此时就有两个问题:

  1. 1001111 01100000 到底是一个汉字 “你” ,还是两个 Latin1 字符?
  2. 如果Unicode进行了规定,每个字符都使用n个八位来表示,对于Latin1字符来说,又会浪费很多存储空间。

为了解决这个问题,就出现了一些编码规则,按照一定的编码规则对Unicode数字进行计算,得出新的编码。在中国常用的字符编码有 GBK,Big5utf8这三种编码规则。

使用字符串的encode方法,可以将字符串按照指定的编码格式转换称为二进制;使用decode方法,可以将一个二进制数据按照指定的编码格式转换成为字符串。

代码语言:javascript
复制
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的类型为列表

代码语言:javascript
复制
tmp = ['xiaoWang',180, 65.0]

列表中的元素可以是不同类型的

二、使用下标获取列表元素

代码语言:javascript
复制
namesList = ['xiaoWang','xiaoZhang','xiaoHua']
print(namesList[0])
print(namesList[1])
print(namesList[2])

结果:

代码语言:javascript
复制
xiaoWang
xiaoZhang
xiaoHua

列表的增删改查

我们对于可变数据(例如,列表,数据库等)的操作,一般包含增、删、改、查四个方面。

一、添加元素

添加元素有一下几个方法:

  • append 在末尾添加元素
  • insert 在指定位置插入元素
  • extend 合并两个列表
append

append会把新元素添加到列表末尾

代码语言:javascript
复制
#定义变量A,默认有3个元素
A = ['xiaoWang','xiaoZhang','xiaoHua']

print("-----添加之前,列表A的数据-----A=%s" % A)

#提示、并添加元素
temp = input('请输入要添加的学生姓名:')
A.append(temp)

print("-----添加之后,列表A的数据-----A=%s" % A)
insert

insert(index, object) 在指定位置index前插入元素object

代码语言:javascript
复制
strs = ['a','b','m','s']
strs.insert(3,'h')
print(strs)  # ['a', 'b', 'm', 'h', 's']
extend

通过extend可以将另一个集合中的元素逐一添加到列表中

代码语言:javascript
复制
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中的+号可以用在那些方面

  1. 数字:n = 1 + 2
  2. 字符串:s = ‘aa’ + ‘bb’
  3. 列表:list = [1,2,3] + [‘a’,’b’,’c’] = [1,2,3,’a’,’b’,’c’]

二、修改元素

我们是通过指定下标来访问列表元素,因此修改元素的时候,为指定的列表下标赋值即可。

代码语言:javascript
复制
#定义变量A,默认有3个元素
A = ['xiaoWang','xiaoZhang','xiaoHua']

print("-----修改之前,列表A的数据-----A=%s" % A)

#修改元素
A[1] = 'xiaoLu'

print("-----修改之后,列表A的数据-----A=%s" % A)

三、查找元素

所谓的查找,就是看看指定的元素是否存在,以及查看元素所在的位置,主要包含一下几个方法:

  • in 和 not in
  • index 和 count
in, not in

python中查找的常用方法为:

  • in(存在),如果存在那么结果为true,否则为false
  • not in(不存在),如果不存在那么结果为true,否则false
代码语言:javascript
复制
#待查找的列表
nameList = ['xiaoWang','xiaoZhang','xiaoHua']

#获取用户要查找的名字
findName = input('请输入要查找的姓名:')

#查找是否存在
if findName in nameList:
    print('在列表中找到了相同的名字')
else:

结果1:(找到)

结果2:(没有找到)

说明:

in的方法只要会用了,那么not in也是同样的用法,只不过not in判断的是不存在

index, count

index用来查找元素所在的位置,如果未找到则会报错;count用来计算某个元素出现的次数。它们的使用和字符串里的使用效果一致。

代码语言:javascript
复制
>>> 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

四、删除元素

类比现实生活中,如果某位同学调班了,那么就应该把这个条走后的学生的姓名删除掉;在开发中经常会用到删除这种功能。

列表元素的常用删除方法有:

  • del:根据下标进行删除
  • pop:删除最后一个元素:从后往前删除
  • remove:根据元素的值进行删除
del
代码语言:javascript
复制
movieName = ['加勒比海盗','骇客帝国','第一滴血','指环王','霍比特人','速度与激情']
print('------删除之前------movieName=%s' % movieName)
del movieName[2]
print('------删除之后------movieName=%s' % movieName)
pop
代码语言:javascript
复制
movieName = ['加勒比海盗','骇客帝国','第一滴血','指环王','霍比特人','速度与激情']
print('------删除之前------movieName=%s' % movieName)
movieName.pop()
print('------删除之后------movieName=%s' % movieName)

movieName.pop(-1)#['加勒比海盗','骇客帝国','第一滴血','指环王','霍比特人']
movieName.pop(8)#报错
remove
代码语言:javascript
复制
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)

  1. 如果参数是不存在的一个字符串会报错。所以remove使用前可以先用in或者not in判断。
  2. 如果列表中中有多个同名的元素,remove只会删除第一个。如果想都删掉则需要循环删除。

五、排序(sort, reverse)

sort方法是将list按特定顺序重新排列,默认为由小到大,参数reverse=True可改为倒序,由大到小。

reverse方法是将list逆置。

代码语言:javascript
复制
>>> 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'] 里所有的空字符串。

列表的遍历

1. 使用while循环

为了更有效率的输出列表的每个数据,可以使用循环来完成

代码语言:javascript
复制
namesList = ['xiaoWang','xiaoZhang','xiaoHua']
length = len(namesList)  # 获取列表长度
i = 0
while i<length:
    print(namesList[i])
    i+=1
Copy

结果:

代码语言:javascript
复制
xiaoWang
xiaoZhang
xiaoHua
Copy

2. 使用for循环

while 循环是一种基本的遍历列表数据的方式,但是最常用也是最简单的方式是使用 for 循环

代码语言:javascript
复制
namesList = ['xiaoWang','xiaoZhang','xiaoHua']
for name in namesList:
    print(name)
Copy

结果:

代码语言:javascript
复制
xiaoWang
xiaoZhang
xiaoHua
Copy

3. 交换2个变量的值

代码语言:javascript
复制
# 使用中间变量
a = 4
b = 5
c = 0

c = a
a = b
b = c

print(a)
print(b)
Copy

练习

手动实现冒泡排序(难)

代码语言:javascript
复制
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循环的嵌套,列表也是支持嵌套的

一个列表中的元素又是一个列表,那么这就是列表的嵌套

此处重点掌握怎么操作被嵌套的列表

代码语言:javascript
复制
>>> 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位老师等待工位的分配,请编写程序,完成随机的分配

代码语言:javascript
复制
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

运行结果如下:

image
image

列表推导式

所谓的列表推导式,就是指的轻量级循环创建列表

基本方式

image
image
代码语言:javascript
复制
list1 = [i*2 for i in range(1,10,2)]

在循环的过程中使用if

image
image
image
image

2个for循环

image
image

3个for循环

image
image

列表的复制

查看以下代码,说出打印的结果。

代码语言:javascript
复制
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

思考:

  1. 为什么修改了 nums2里的数据,nums1的数据也会改变?

Python中的赋值运算都是引用(即内存地址)的传递。对于可变类型来说,修改原数据的值,会改变赋值对象的值。

  1. 怎样nums1和nums2变成两个相互独立不受影响的列表?

使用列表的 copy 方法,或者 copy 模块就可以赋值一个列表。

列表的copy方法

使用列表的copy方法,可以直接将原来的列表进行复制,变成一个新的列表,这种复制方式是浅复制。

代码语言:javascript
复制
nums1 = [1, 5, 8, 9, 10, 12]
nums2 = nums1.copy()  # 调用列表的copy方法,可以复制出一个新的列表

nums2[0] = 100

# 修改新列表里的数据,不会影响到原有列表里的数据
print(nums2)
print(nums1)
Copy

copy模块的使用

除了使用列表的copy方法以外,Python还提供了copy模块来复制一个对象。copy模块提供了浅复制和深复制两种方式,它们的使用方式相同,但是执行的效果有一定的差异。

浅拷贝

浅拷贝是对于一个对象的顶层拷贝,通俗的理解是:拷贝了引用,并没有拷贝内容。

代码语言:javascript
复制
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
深拷贝

深拷贝是对于一个对象所有层次的递归拷贝。

代码语言:javascript
复制
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
切片

列表和字符串一样,也支持切片,切片其实就是一种浅拷贝。

代码语言:javascript
复制
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的元组与列表类似,不同之处在于元组的元素不能修改。元组使用小括号,列表使用方括号。

代码语言:javascript
复制
>>> aTuple = ('et',77,99.9)
>>> aTuple
('et',77,99.9)
Copy

一、访问元组

image
image

二、修改元组

image
image

说明: python中不允许修改元组的数据,包括不能删除其中的元素。

三、count, index

index和count与字符串和列表中的用法相同

代码语言:javascript
复制
>>> 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

四、定义只有一个数据的元组

定义只有一个元素的元组,需要在唯一的元素后写一个逗号

代码语言:javascript
复制
>>> a = (11)
>>> a
11
>>> type(a)
int
>>> a = (11,)  # 只有一个元素的元组,必须要在元素后写一个逗号
>>> a
(11,)
>>> type(a)
tuple
Copy

五、交换两个变量的值

代码语言:javascript
复制
# 第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)

字典

列表的缺点

当存储的数据要动态添加、删除的时候,我们一般使用列表,但是列表有时会遇到一些麻烦。

代码语言:javascript
复制
# 定义一个列表保存,姓名、性别、职业
nameList = ['xiaoZhang', '男', '木匠'];

# 当修改职业的时候,需要记忆元素的下标
nameList[2] = '铁匠'  

# 如果列表的顺序发生了变化,添加年龄
nameList = ['xiaoWang', 18, '男',  '铁匠']

# 此时就需要记忆新的下标,才能完成名字的修改
nameList[3] = 'xiaoxiaoWang'
Copy

有没有方法,既能存储多个数据,还能在访问元素的很方便就能够定位到需要的那个元素呢?

答:

字典

字典的使用

定义字典的格式:**{键1:值1, 键2:值2, 键3:值3, …, 键n:值n}**

变量info为字典类型:

代码语言:javascript
复制
info = {'name':'班长', 'id':100, 'sex':'f', 'address':'地球亚洲中国上海'}
info['name']  # 字典使用键来获取对应的值

Python中的字典是一种可变的数据结构,可以用于存储键值对。下面是一些常用的字典操作:

1.创建字典

使用大括号{}或者dict()函数创建一个空的字典:

代码语言:javascript
复制
my_dict = {}
my_dict = dict()

使用大括号{}创建包含键值对的字典:

代码语言:javascript
复制
my_dict = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}

2.添加或修改键值对

通过指定键来添加或修改对应的值:

代码语言:javascript
复制
my_dict['key4'] = 'value4'
my_dict['key1'] = 'new_value1'

PS:setdefault()函数

字典的setdefault()函数是一种用于向字典中添加键值对的方法。如果字典中已经存在指定的键,则setdefault()函数会返回该键的值,否则会将键值对添加到字典中。

setdefault()函数的语法如下:

代码语言:javascript
复制
dict.setdefault(key, default_value)

其中,key是要查找或添加的键,default_value是可选参数,表示如果指定的键不存在时要添加的默认值。如果没有指定default_value,则默认为None。

setdefault()函数的作用如下:

  • 如果字典中已经存在指定的键,则返回该键对应的值。
  • 如果字典中不存在指定的键,则向字典中添加一个新的键值对,并将默认值作为该键的值返回。

例如,以下代码演示了如何使用setdefault()函数:

代码语言:javascript
复制
>>> 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关键字删除指定的键值对:

代码语言:javascript
复制
del my_dict['key3']

使用pop()方法删除指定的键值对,并返回对应的值:

代码语言:javascript
复制
value = my_dict.pop('key2')

使用popitem()方法从后往前删除一个键值对,并返回对应的键和值(返回的是一个元组):

代码语言:javascript
复制
key, value = my_dict.popitem()

4.访问字典中的值

通过指定键来访问对应的值:

代码语言:javascript
复制
value = my_dict['key1']

使用get()方法访问指定键对应的值,如果键不存在则返回指定的默认值:

代码语言:javascript
复制
value = my_dict.get('key2', 'default_value')

使用keys()方法返回字典中所有的

代码语言:javascript
复制
keys = my_dict.keys()#dict_keys(['name', 'id', 'sex', 'address'])

使用values()方法返回字典中所有的

代码语言:javascript
复制
values = my_dict.values()#dict_values(['班长', 100, 'f', '地球亚洲中国上海'])

使用items()方法返回字典中所有的键值对

代码语言:javascript
复制
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()函数获取字典中键值对的数量:

代码语言:javascript
复制
length = len(my_dict)

2.使用in关键字检查指定的键是否存在于字典中:

代码语言:javascript
复制
if 'key1' in my_dict:
    print('Key exists in the dictionary')

3.使用clear()方法清空字典中所有的键值对:

代码语言:javascript
复制
my_dict.clear()

4.使用update方法合并字典

它用于将一个字典中的键值对添加到另一个字典中。

update() 方法的语法如下:

代码语言:javascript
复制
dict.update([other])

其中,other 是要添加到当前字典中的字典或键值对序列。如果 other 是字典,则将其键值对添加到当前字典中。如果 other 是键值对序列,则将其作为一个列表添加到当前字典中。如果键已经存在,则将其对应的值更新为新的值。

例如,假设我们有两个字典 dict1dict2

代码语言:javascript
复制
dict1 = {"a": 1, "b": 2}
dict2 = {"b": 3, "c": 4}

现在我们想将 dict2 中的键值对添加到 dict1 中,可以使用 update() 方法:

代码语言:javascript
复制
dict1.update(dict2)
print(dict1)

输出结果为:

代码语言:javascript
复制
{'a': 1, 'b': 3, 'c': 4}

注意,update() 方法会修改原字典 dict1,并返回 None。如果要创建一个新的字典而不修改原字典,则可以使用以下语句:

代码语言:javascript
复制
new_dict = dict1.copy()
new_dict.update(dict2)

5.fromkeys方法

fromkeys()是一个dict类方法,用于创建一个新的字典,其中包含指定键列表的键和所有值为默认值None(或指定的默认值)。fromkeys()方法的语法如下:

代码语言:javascript
复制
dict.fromkeys(seq[, value])

其中,seq参数是指定的键列表,可以是一个可迭代对象,例如一个列表、元组或集合。value参数是可选的,它是指定的默认值。如果省略了value参数,则默认值为None

下面是一个示例:

代码语言:javascript
复制
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

说明:

  • 字典和列表一样,也能够存储多个数据
  • 列表中找某个元素时,是根据下标进行的;字典中找某个元素时,是根据’名字’(就是冒号:前面的那个值,例如上面代码中的’name’、’id’、’sex’)
  • 字典的每个元素由2部分组成,键:值。例如 ‘name’:’班长’ ,’name’为键,’班长’为值
  • 键可以使用数字、布尔值、布尔值、元组等不可变数据类型,但是一般习惯使用字符串
  • 每个字典里的key都是唯一的,如果出现了多个key,后面的value会覆盖前一个key对应的value.

在习惯上:

  • 列表更适合保存多个商品、多个姓名、多个时间,这样的相似数据;
  • 字典更适合保存一个商品的不同信息、一个人的不同信息,这样的不同数据。

字典的遍历

for循环遍历

您可以使用for循环遍历字典中的所有键值对。以下是一个示例代码,它使用for循环遍历字典,并打印出每个键值对:

代码语言:javascript
复制
my_dict = {"apple": 3, "banana": 5, "orange": 2}

for key, value in my_dict.items():
    print(key, value)

在上面的代码中,my_dict.items()方法返回一个包含字典中所有键值对的列表,然后for循环迭代该列表,并将每个键值对的键和值分别赋值给keyvalue变量。然后,print函数用于打印出每个键值对。

集合

set的使用

集合(set)是一个无序的不重复元素序列,可以使用大括号 { } 或者 set() 函数创建集合。

注意:创建一个空集合必须用 set() 而不是 { },因为 { } 是用来创建一个空字典。

创建格式:

代码语言:javascript
复制
parame = {value01,value02,...}
#或者
set(value)

添加元素

语法格式如下:

代码语言:javascript
复制
s.add(x)

将元素 x 添加到集合 s 中,如果元素已存在,则不进行任何操作。

代码语言:javascript
复制
>>>thisset = set(("Google", "Runoob", "Taobao"))
>>> thisset.add("Facebook")
>>> print(thisset)
{'Taobao', 'Facebook', 'Google', 'Runoob'}

还有一个方法,也可以添加元素,且参数可以是列表,元组,字典等,语法格式如下:

代码语言:javascript
复制
s.update( x )

x 可以有多个,用逗号分开。

代码语言:javascript
复制
>>>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'}

移除元素

语法格式如下:

代码语言:javascript
复制
s.remove( x )

将元素 x 从集合 s 中移除,如果元素不存在,则会发生错误。

代码语言:javascript
复制
>>>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'
>>>

此外还有一个方法也是移除集合中的元素,且如果元素不存在,不会发生错误。格式如下所示:

代码语言:javascript
复制
s.discard( x )
Copy
>>>thisset = set(("Google", "Runoob", "Taobao"))
>>> thisset.discard("Facebook")  # 不存在不会发生错误
>>> print(thisset)
{'Taobao', 'Google', 'Runoob'}

我们也可以设置随机删除集合中的一个元素,语法格式如下:

代码语言:javascript
复制
s.pop()
Copy
thisset = set(("Google", "Runoob", "Taobao", "Facebook"))
x = thisset.pop()

print(x)
print(thisset)

set常见方法列表

方法

描述

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],要求对这个列表里的元素去重,并进行降序排序。

代码语言:javascript
复制
# 方法一:调用列表的sort方法
nums2 = list(set(nums))
nums2.sort(reverse=True)
print(nums2)

# 方法二:使用sorted内置函数
print(sorted(list(set(nums)),reverse=True))

转换相关

执行字符串

使用Python内置的eval函数,可以执行字符串里的Python代码。使用这种方式,可以将字符串转换成为其他类型的数据。

代码语言:javascript
复制
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方法,可以将字典、列表或者元组转换成为字符串。

代码语言:javascript
复制
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方法,可以将格式正确的字符串转换成为字典、列表。

代码语言:javascript
复制
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

元素是否不存在

字符串、列表、元组、字典

加法运算符可以用于字符串、列表和元组,用来拼接多个可迭代对象,不能用于字典和集合(思考:为什么字典和集合不能使用)。

代码语言:javascript
复制
>>> "hello " + "world"
'hello world'
>>> [1, 2] + [3, 4]
[1, 2, 3, 4]
>>> ('a', 'b') + ('c', 'd')
('a', 'b', 'c', 'd')
-

减法只能用于集合里,用来求两个集合的差集。

代码语言:javascript
复制
>>> {1, 6, 9, 10, 12, 3} - {4, 8, 2, 1, 3}
{9, 10, 12, 6}
*

加法运算符可以用于字符串、列表和元组,用来将可迭代对象重复多次,同样不能用于字典和集合。

代码语言:javascript
复制
>>> '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

in和not in成员运算符可以用于所有的可迭代对象。但是需要注意的是,in 和 not in 在对字典进行判断时,是查看指定的key是否存在,而不是value.

代码语言:javascript
复制
>>> '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 … 我们可以遍历字符串、列表、元组、字典、集合等可迭代对象。

字符串遍历
代码语言:javascript
复制
>>> a_str = "hello world"
>>> for char in a_str:
...     print(char,end=' ')
...
h e l l o   w o r l d
列表遍历
代码语言:javascript
复制
>>> a_list = [1, 2, 3, 4, 5]
>>> for num in a_list:
...     print(num,end=' ')
...
1 2 3 4 5
元组遍历
代码语言:javascript
复制
>>> a_turple = (1, 2, 3, 4, 5)
>>> for num in a_turple:
...     print(num,end=" ")
1 2 3 4 5
带下标的遍历

可迭代对象都可以使用 enumerate 内置类进行包装成一个 enumerate 对象。对enumerate进行遍历,可以同时得到一个可迭代对象的下标和元素。

代码语言:javascript
复制
nums = [12, 9, 8, 5, 4, 7, 3, 6]

# 将列表 nums 包装成 enumerate 对象
for i, num in enumerate(nums): # i表示元素下标,num表示列表里的元素
    print('第%d个元素是%d' % (i, num))

垃圾回收机制

java,python的垃圾回收机制对比

Python使用引用计数来跟踪对象的引用,当一个对象的引用计数变为0时,Python会立即释放该对象的内存。但是,引用计数并不能解决循环引用的问题,Python还使用垃圾收集器(Garbage Collector)来处理循环引用。Python的垃圾收集器使用标记-清除算法(Mark and Sweep Algorithm)来找到和清除无法访问的循环引用对象。

C++没有内置的垃圾回收机制,程序员需要手动管理内存,例如使用new和delete操作符来分配和释放内存。虽然C++有一些第三方库来帮助程序员管理内存,但是这些库的性能和效率并不如手动管理内存。

Java也有垃圾回收机制,但与Python不同的是,Java使用的垃圾回收机制是自动的。Java垃圾回收器在应用程序运行时自动回收无用的对象,从而避免了内存泄漏和野指针问题。Java的垃圾回收器使用标记-清除算法和复制算法(Copying Algorithm)来处理垃圾对象。

Python的垃圾回收机制对比

Python的垃圾回收机制主要使用两种算法:引用计数算法和标记-清除算法。

1.引用计数算法

引用计数算法是Python最基本的垃圾回收算法。在Python中,每个对象都有一个引用计数器,记录有多少个变量引用了这个对象。当引用计数器为0时,说明没有任何变量指向该对象,Python就会回收该对象的内存。

举例来说:

代码语言:javascript
复制
a = [1, 2, 3]
b = a #a给b赋值是把a的引用地址给了b,python的变量类似与指针
c = b

在这个例子中,a、b、c都指向同一个列表对象[1, 2, 3],它的引用计数为3。如果我们执行以下代码:

代码语言:javascript
复制
del a
del b
del c

当没有变量引用这个对象时,那么这个列表对象的引用计数就变为了0,Python就会回收该对象的内存。

2.标记-清除算法

引用计数算法只能处理没有循环引用的对象,当对象之间存在循环引用时,引用计数算法就无法正常工作了。这时Python就需要使用标记-清除算法来处理垃圾对象。

标记-清除算法分为两个阶段:标记阶段和清除阶段。

在标记阶段,Python会从根对象开始,遍历所有可达对象,并给它们打上标记。根对象可以是Python程序中所有全局变量、局部变量、以及当前线程的调用栈中的变量。

在清除阶段,Python会遍历所有未标记的对象,将它们回收掉。这些未标记的对象就是垃圾对象,可以被回收掉。

举例来说:

代码语言:javascript
复制
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都会被保留下来,直到没有任何变量引用它们为止。

函数

  • 如果在开发程序时,需要某块代码多次执行。为了提高编写的效率以及更好的维护代码,需要把具有独立功能的代码块组织为一个小模块,这就是函数。

函数定义和调用

一、定义函数

定义函数的格式如下:

代码语言:javascript
复制
def 函数名():
    代码

示例:

代码语言:javascript
复制
# 定义一个函数,能够完成打印信息的功能
def printInfo():
    print('------------------------------------')
    print('         人生苦短,我用Python')
    print('------------------------------------')

print(printInfo)#打印函数名返回的是函数地址

二、调用函数

定义了函数之后,就相当于有了一个具有某些功能的代码,想要让这些代码能够执行,需要调用它

调用函数很简单的,通过 函数名() 即可完成调用

代码语言:javascript
复制
# 定义完函数后,函数是不会自动执行的,需要调用它才可以
printInfo()

三、注意:

  • 函数定义好以后,函数体里的代码并不会执行,如果想要执行函数体里的内容,需要手动的调用函数。
  • 每次调用函数时,函数都会从头开始执行,当这个函数中的代码执行完毕后,意味着调用结束了。
  • 当然了如果函数中执行到了return也会结束函数。

四、练一练

要求:定义一个函数,能够计算两个数字之和,并且调用这个函数让它执行

  • 使用def定义函数
  • 编写完函数之后,通过 函数名() 进行调用

函数参数

python函数传递参数与C++,java函数传递参数的区别

在Python、C++和Java中,函数传递参数的基本概念是相似的,都是将值或者引用传递给函数。但是,这些编程语言在实现参数传递时有一些区别:

1.参数传递方式

  • Python:默认情况下,Python 采用传递对象引用的方式传递参数,即实参传递的是对象的引用,函数形参接收的是该引用的副本。在函数内部修改形参,也会影响到实参。
  • C++:C++中有两种参数传递方式,分别是传值调用和引用调用。传值调用是将实参的值拷贝一份传递给函数,函数内部对形参的修改不会影响到实参;而引用调用则是将实参的地址传递给函数,函数内部对形参的修改会影响到实参。
  • Java:Java采用传值调用的方式传递参数。与C++的传值调用类似,Java中也是将实参的值拷贝一份传递给函数,函数内部对形参的修改不会影响到实参。

2.参数类型

  • Python:Python的参数类型是动态的,不需要在函数定义时声明参数类型。
  • C++:C++的参数类型是静态的,需要在函数定义时声明参数类型。
  • Java:Java的参数类型也是静态的,需要在函数定义时声明参数类型。

3.默认参数

  • Python:Python支持在函数定义时为参数设置默认值,调用函数时可以不传递该参数。如果调用函数时传递了该参数,则使用传递的值覆盖默认值。
  • C++:C++也支持默认参数,但是必须在函数声明或定义时设置默认值,调用函数时可以不传递该参数。
  • Java:Java不支持默认参数。

4.参数数量

  • Python:Python支持可变数量的参数,包括可变长参数列表和关键字参数列表。
  • C++:C++不支持可变数量的参数,必须在函数定义时确定参数数量和类型。
  • Java:Java也不支持可变数量的参数,必须在函数定义时确定参数数量和类型。

总的来说,Python、C++和Java在函数参数传递方面有一些区别,但基本概念是相似的。在选择使用哪种语言时,需要根据具体情况来决定。

定义、调用带有参数的函数

定义一个add2num(a, b)函数,来计算任意两个数字之和:

代码语言:javascript
复制
def add2num(a, b):
    c = a+b
    print c

add2num(11, 22) # 调用带有参数的函数时,需要在小括号中,传递数据

注意点:

  • 在定义函数的时候,小括号里写等待赋值的变量名
  • 在调用函数的时候,小括号里写真正要进行运算的数据

用函数时参数的顺序

代码语言:javascript
复制
>>> 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

函数参数是列表

代码语言:javascript
复制
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)

局部变量,全局变量

局部变量
  • 局部变量,就是在函数内部定义的变量
  • 其作用范围是这个函数内部,即只能在这个函数中使用,在函数的外部是不能使用的
  • 因为其作用范围只是在自己的函数内部,所以不同的函数可以定义相同名字的局部变量(打个比方,把你、我是当做成函数,把局部变量理解为每个人手里的手机,你可有个iPhone8,我当然也可以有个iPhone8了, 互不相关)
  • 局部变量的作用,为了临时保存数据需要在函数中定义变量来进行存储
  • 当函数调用时,局部变量被创建,当函数调用完成后这个变量就不能够使用了
全局变量

如果一个变量,既能在一个函数中使用,也能在其他的函数中使用,这样的变量就是全局变量

打个比方:有2个兄弟 各自都有手机,各自有自己的小秘密在手机里,不让另外一方使用(可以理解为局部变量);但是家里的电话是2个兄弟都可以随便使用的(可以理解为全局变量)

代码语言:javascript
复制
# 定义全局变量
a = 100

def test1():
    print(a)  # 虽然没有定义变量a但是依然可以获取其数据

def test2():
    print(a)  # 虽然没有定义变量a但是依然可以获取其数据

# 调用函数
test1()
test2()
Copy

运行结果:

代码语言:javascript
复制
100
100

总结1:

  • 在函数外边定义的变量叫做全局变量
  • 全局变量能够在所有的函数中进行访问
全局变量和局部变量名字相同问题

看如下代码:

image
image

总结2:

  • 当函数内出现局部变量和全局变量相同名字时,函数内部中的 变量名 = 数据 此时理解为定义了一个局部变量,而不是修改全局变量的值
修改全局变量

函数中进行使用时可否进行修改呢?

代码如下

总结3:

  • 如果在函数中出现global 全局变量的名字 那么这个函数中即使出现和全局变量名相同的变量名 = 数据 也理解为对全局变量进行修改,而不是定义局部变量
  • 如果在一个函数中需要对多个全局变量进行修改,那么可以一次性全部声明,也可以分开声明。
代码语言:javascript
复制
# 可以使用一次global对多个全局变量进行声明
global a, b
# 还可以用多次global声明都是可以的
# global a
# global b
查看所有的全局变量和局部变量

Python提供了两个内置函数globals()和locals()可以用来查看所有的全局变量和局部变量。

代码语言:javascript
复制
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}

函数参数详解

拆包和装包

问题:当需求比较大时,如下面无法满足要求

代码语言:javascript
复制
#求和
def get_sum(a,b):
    r = a + b
    print(r)

get_sum(1,2)#可行
get_sum(2,6,9)#不可行

*args

装包:

def demo(*args)此时装包

代码语言:javascript
复制
a,*b,c = 1,2,3,4,5,6
print(a)#1
print(b)#[2,3,4]
print(c)#5
代码语言:javascript
复制
def get_sum(*a):
    print(a)#
    
get_sum(1,2)#(1,2)
get_sum(2,6,9)#(2,6,9)

拆包:

调用的时候拆包

函数(*list),函数(*tuple),函数(*set)

代码语言:javascript
复制
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],)

image
image

解决方案:传参数时拆包后传入,即传入*re

代码语言:javascript
复制
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)#参数加上*就是拆包
image
image

**kwargs

关键字参数,在函数调用的时候必须传递关键字参数,才可以将其转换成key:value,转到字典中。

装包

代码语言:javascript
复制
def show_book(**kwargs):
    print(kwargs)
    
show_book()#{}
show_book(bookname='西游记',author='吴承恩',number=5)#{'bookname':'西游记','author':'吴承恩','number':5}

拆包

代码语言:javascript
复制
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
'''
代码语言:javascript
复制
def show_book(*args,**kwargs):
    print(args)
    print(kwargs)

book = {'bookname':'坏小孩','author':'zzz','number':5}
show_book('龙少','小芳',**book)
一、缺省参数

调用函数时,缺省参数的值如果没有传入,则取默认值。

下例会打印默认的age,如果age没有被传入:

代码语言:javascript
复制
def printinfo(name, age=35):
   # 打印任何传入的字符串
   print("name: %s" % name)
   print("age %d" % age)

# 调用printinfo函数
printinfo(name="miki")  # 在函数执行过程中 age取默认值35
printinfo(age=9 ,name="miki")

以上实例输出结果:

代码语言:javascript
复制
name: miki
age: 35
name: miki
age: 9

总结:

在形参中默认有值的参数,称之为缺省参数

注意:带有默认值的参数一定要位于参数列表的最后面

代码语言:javascript
复制
>>> def printinfo(name, age=35, sex):
...     print name
...
  File "<stdin>", line 1
SyntaxError: non-default argument follows default argument
二、不定长参数

有时可能需要一个函数能处理比当初声明时更多的参数, 这些参数叫做不定长参数,声明时不会命名。

基本语法如下:

代码语言:javascript
复制
def functionname([formal_args,] *args, **kwargs):
   """函数_文档字符串"""
   function_suite
   return [expression]

注意

  • 加了星号*的变量args会存放所有未命名的变量参数,args为元组
  • 而加**的变量kwargs会存放命名参数,即形如key=value的参数, kwargs为字典.
代码语言:javascript
复制
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={}
三、缺省参数在*args后面
代码语言:javascript
复制
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)

说明

  • 如果很多个值都是不定长参数,那么这种情况下,可以将缺省参数放到 *args的后面, 但如果有kwargs的话,kwargs必须是最后的
四、引用,函数参数引用

在 Python 中,引用是指将变量名和对象之间的关系。当您使用变量名引用对象时,实际上是引用该对象的内存地址,而不是对象本身。因此,当您将一个变量赋值给另一个变量时,两个变量都指向相同的内存地址,因此它们引用相同的对象。

例如,考虑以下代码:

代码语言:javascript
复制
a = [1, 2, 3]
b = a

在这里,变量 ab 都是对列表 [1, 2, 3] 的引用,它们指向相同的内存地址。如果您更改其中一个变量的值,则另一个变量也会受到影响,因为它们引用相同的对象:

代码语言:javascript
复制
a.append(4)
print(b)  # 输出 [1, 2, 3, 4]

因此,在 Python 中,理解引用非常重要,因为它们可以使您在代码中更有效地使用内存和处理对象。

如果函数传入的是可变类型则实参有可能改变也有可能不变,但是如果传入的是不可变对象则函数体内对形参的修改不会导致实参的变化。

在 Python 中,函数可以修改可变类型的实参,但无法修改不可变类型的实参。

可变类型的实参包括列表、字典和集合等,这些类型的对象是可以在原地进行修改的。因此,如果您将一个可变类型的实参传递给一个函数,并在函数中修改它,则这些更改也将影响原始对象。

例如,以下代码演示了如何在函数中修改列表类型的实参:

代码语言:javascript
复制
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 函数可以在原地修改它。

不可变类型的实参包括整数、字符串和元组等,这些类型的对象是无法在原地进行修改的。因此,如果您将一个不可变类型的实参传递给一个函数,并在函数中尝试修改它,则会引发一个错误。

例如,以下代码演示了试图修改字符串类型的实参将引发的错误:

代码语言:javascript
复制
def capitalize_string(s):
    s[0] = s[0].upper()

my_string = "hello"
capitalize_string(my_string)  # 引发 TypeError 错误

在这里,capitalize_string 函数试图将传递的字符串 s 的第一个字符大写化。但是,由于字符串是不可变的对象,因此无法在原地进行修改,这将导致 TypeError 错误。

总之,您可以在 Python 中通过修改可变类型的实参来更改函数外部的对象,但是如果您试图修改不可变类型的实参,则将引发一个错误。

可变、不可变类型

image
image

总结

  • 所谓可变类型与不可变类型是指:数据能够直接进行修改,如果能直接修改那么就是可变,否则是不可变
  • 可变类型(修改数据,内存地址不会发生变化)有: 列表、字典、集合
  • 不可变类型(修改数据,内存地址必定发生变化)有: 数字、字符串、元组

函数返回值

python函数返回值与C++,java函数返回值的区别

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

如下示例:

代码语言:javascript
复制
def add2num(a, b):
    c = a+b
    return c  # return 后可以写变量名

或者

代码语言:javascript
复制
def add2num(a, b):
    return a+b  # return 后可以写计算表达式

return后面可以是一个值,也可以是多个值,多个值时会将多个值封装到一个元组中,将元组作为整体返回。

代码语言:javascript
复制
def add2num(a, b):
    return a,b  # 返回一个元组(a,b)

a,b = add2num(1,2)
print(a,b)#1 2

三、保存函数的返回值

在本小节刚开始的时候,说过的“买冰淇淋”的例子中,最后儿子给你冰淇淋时,你一定是从儿子手中接过来 对么,程序也是如此,如果一个函数返回了一个数据,那么想要用这个数据,那么就需要保存

保存函数的返回值示例如下:

代码语言:javascript
复制
#定义函数
def add2num(a, b):
    return a+b

#调用函数,顺便保存函数的返回值
result = add2num(100,98)

#因为result已经保存了add2num的返回值,所以接下来就可以使用了
print(result)

结果:

代码语言:javascript
复制
198

函数的文档说明

1.基本使用

代码语言:javascript
复制
>>> 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个数求和

2.高级使用

代码语言:javascript
复制
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:使用循环来完成

代码语言:javascript
复制
def cal(num):
    result,i = 1,1
    while i <= num:
        result *= i
        i+= 1
    return result

print(cal(3))

看阶乘的规律

代码语言:javascript
复制
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:使用递归来实现

代码语言:javascript
复制
def factorial(num):
    result = 1
    if num == 1:
        return 1
    result = num * factorial(num -1)
    return result
print(cal(3))

原理

image
image
  • 练习:使用递归实现斐波那契数列。1、1、2、3、5、8、13、21、34、……

匿名函数

用lambda关键词能创建小型匿名函数。这种函数得名于省略了用def声明函数的标准步骤。

lambda函数的语法只包含一个语句,如下:

代码语言:javascript
复制
lambda 参数列表: 运算表达式

如下实例:

代码语言:javascript
复制
sum = lambda arg1, arg2: arg1 + arg2

# 调用sum函数
print("Value of total : %d" % sum( 10, 20 ))
print("Value of total : %d" % sum( 20, 20 ))

以上实例输出结果:

代码语言:javascript
复制
Value of total :  30
Value of total :  40

Lambda函数能接收任何数量的参数但只能返回一个表达式的值

匿名函数可以执行任意表达式(甚至print函数),但是一般认为表达式应该有一个计算结果供返回使用。

python在编写一些执行脚本的时候可以使用lambda,这样可以接受定义函数的过程,比如写一个简单的脚本管理服务器。

应用场合

函数作为参数传递

代码语言:javascript
复制
>>> 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

练习:

有一个列表

代码语言:javascript
复制
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中,函数其实也是一种数据类型。

代码语言:javascript
复制
def test():
    return 'hello world'
print(type(test))  # <class 'function'>

函数对应的数据类型是 function,可以把它当做是一种复杂的数据类型。

既然同样都是一种数据类型,我们就可以把它当做数字或者字符串来处理。

定义一个变量指向函数

在Python中,我们还可以定义一个变量,让它来指向一个函数,相当于给函数起了一个别名。

代码语言:javascript
复制
def test():
    return 'hello wrold'

fun = test   # 定义了一个变量fun,让它指向了 test 这个函数
print(fun())   # 使用fun()可以直接调用test这个函数

print(id(fun))  # 1819677672040
print(id(test))  # 1819677672040

注意:在定义一个变量表示一个函数时,函数后面不能加括号!加括号表示的是调用这个函数。

代码语言:javascript
复制
def test():
    return 'hello world'

result = test()   # 这种写法是调用test函数,并把函数的返回值赋值给result变量
print(result())   # 这里会报错  TypeError: 'str' object is not callable

fun = test   # 这种写法是给test函数起了一个别名,注意,这里的test后面不能加()
fun()        # 可以使用别名调用这个函数

高阶函数

既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,同样,我们还可以把一个函数当做另一个函数的返回值。这种函数的使用方式我们称之为高阶函数。

函数做为另一个函数的参数
代码语言:javascript
复制
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)  # 还可以不再定义一个新的变量,直接传入函数名
函数作为另一个函数的返回值
代码语言:javascript
复制
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函数里输入的内容

闭包

函数只是一段可执行代码,编译后就“固化”了,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数了。函数还可以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题。

函数嵌套

在函数里面还可以定义函数,可以嵌套多层,执行需要被调用。

代码语言:javascript
复制
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函数

内部函数可以使用外部函数的变量

举例:

代码语言:javascript
复制
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边可以修改外部环境的变量

什么是闭包

闭包在装饰器中用。

闭包特点:

  1. 嵌套函数
  2. 内部函数引用了外部函数的变量
  3. 返回值是内部函数

闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数块+引用环境)。

代码语言:javascript
复制
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).

修改外部变量的值

闭包里默认不能修改外部变量。

代码语言:javascript
复制
def outer(n):
    num = n
    def inner():
        num = num + 1
        return num
    return inner

print(outer(1)())

上述代码运行时会报错!

代码语言:javascript
复制
UnboundLocalError: local variable 'num' referenced before assignment
原因分析

在python里,只要看到了赋值语句,就会认为赋值语句的左边是一个局部变量。num = num + 1 这段代码里,num=的左边,python解析器会认为我们要修改inner函数里num这个局部变量,而这个变量使用之前是未声明的,所以会报错。

解决方案

我们分析过,报错的原因在于当我们在闭包内修改外部变量时,会被python解析器误会为内部函数的局部变量。所以,解决方案就在于,我们需要想办法,让解析器知道我们不是要修改局部变量,而是要修改外部变量。

  • 解决方法:使用 nonlocal 关键字
代码语言:javascript
复制
def outer(n):
    num = n
    def inner():
        nonlocal num  # 修改前使用nonlocal关键字对 num 变量进行说明
        num = num + 1
        return num
    return inner

print(outer(2)())

装饰器

引入

代码语言:javascript
复制
def foo():
    print('foo')
    
def func():
    print('func')

foo = func
foo()  # func

foo 表示是函数,函数可以理解为变量,存放的是函数的地址,foo = func表示更给了函数存放的地址。

代码语言:javascript
复制
#### 第一波 ####
def foo():
    print('foo')

foo  # 表示是函数
foo()  # 表示执行foo函数

#### 第二波 ####
def foo():
    print('foo')

foo = lambda x: x + 1

foo()  # 执行lambda表达式,而不再是原来的foo函数,因为foo这个名字被重新指向了另外一个匿名函数

函数名仅仅是个变量,只不过指向了定义的函数而已,所以才能通过 函数名()调用,如果 函数名=xxx被修改了,那么当在执行 函数名()时,调用的就不知之前的那个函数了

引入例子

初创公司有N个业务部门,基础平台部门负责提供底层的功能,如:数据库操作、redis调用、监控API等功能。业务部门使用基础功能时,只需调用基础平台提供的功能即可。如下:

代码语言:javascript
复制
############### 基础平台提供的功能如下 ###############

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()

目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先进行验证。

方案一:

跟每个业务部门交涉,每个业务部门自己写代码,调用基础平台的功能之前先验证。这样一来基础平台就不需要做任何修改了。

代码语言:javascript
复制
############### 基础平台提供的功能如下 ############### 

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()

代码量太多,冗余。

方案二:

只对基础平台的代码进行重构,其他业务部门无需做任何修改

代码语言:javascript
复制
############### 基础平台提供的功能如下 ############### 

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的内部进行修改代码。

方案三:

代码语言:javascript
复制
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为例

代码语言:javascript
复制
def w1(func):
    def inner():
        # 验证1
        # 验证2
        # 验证3
        func()
    return inner

@w1 #f1 = w1(f1)
def f1():
    print('f1')

python解释器就会从上到下解释代码,步骤如下:

  1. def w1(func): ==>将w1函数加载到内存
  2. @w1

没错, 从表面上看解释器仅仅会解释这两句代码,因为函数在 没有被调用之前其内部代码不会被执行。

从表面上看解释器着实会执行这两句,但是 @w1 这一句代码里却有大文章, @函数名 是python的一种语法糖。

上例@w1内部会执行一下操作:

执行w1函数

执行w1函数 ,并将 @w1 下面的函数作为w1函数的参数,即:**@w1 等价于 w1(f1)** 所以,内部就会去执行:

代码语言:javascript
复制
def inner(): 
    #验证 1
    #验证 2
    #验证 3
    f1()    # func是参数,此时 func 等于 f1 
return inner# 返回的 inner,inner代表的是函数,非执行函数 ,其实就是将原来的 f1 函数塞进另外一个函数中

w1的返回值

将执行完的w1函数返回值 赋值 给@w1下面的函数的函数名f1 即将w1的返回值再重新赋值给 f1,即:

代码语言:javascript
复制
f1 = def inner(): 
            #验证 1
            #验证 2
            #验证 3
            原来f1()
        return inner

所以,以后业务部门想要执行 f1 函数时,就会执行 新f1 函数,在新f1 函数内部先执行验证,再执行原来的f1函数,然后将原来f1 函数的返回值返回给了业务调用者。

如此一来, 即执行了验证的功能,又执行了原来f1函数的内容,并将原f1函数返回值 返回给业务调用者。

再议装饰器

代码语言:javascript
复制
# 定义函数:完成包裹数据
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())

运行结果:

代码语言:javascript
复制
<b>hello world-1</b>
<i>hello world-2</i>
<b><i>hello world-3</i></b>

装饰器(decorator)功能

  1. 引入日志
  2. 函数执行时间统计
  3. 执行函数前预备处理
  4. 执行函数后清理功能
  5. 权限校验等场景
  6. 缓存

装饰器示例

例1:无参数的函数
代码语言:javascript
复制
def check_time(action):
    def do_action():
        action()
    return do_action

@check_time
def go_to_bed():
     print('去睡觉')

go_to_bed()

上面代码理解装饰器执行行为可理解成

代码语言:javascript
复制
result = check_time(go_to_bed)  # 把go_to_bed 当做参数传入给 check_time函数,再定义一个变量用来保存check_time的运行结果
result()  # check_time 函数的返回值result是一个函数, result()再调用这个函数,让它再调用go_to_bed函数
例2:被装饰的函数有参数
代码语言:javascript
复制
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","床上")
例3:被装饰的函数有不定长参数
代码语言:javascript
复制
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)
例4:装饰器中的return
代码语言:javascript
复制
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

总结:

  • 一般情况下为了让装饰器更通用,可以有return
例5:装饰器带参数
代码语言:javascript
复制
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())
提高:使用装饰器实现权限验证

以下代码不要求掌握,如果能看懂最好,如果能自己手动写出来,那就太棒了!

代码语言:javascript
复制
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 的函数。

导入模块

导入模块有五种方式

  • import 模块名
  • from 模块名 import 功能名
  • from 模块名 import *
  • import 模块名 as 别名
  • from 模块名 import 功能名 as 别名

下面来挨个的看一下。

import

在Python中用关键字import来引入某个模块,比如要引入系统模块 math,就可以在文件最开始的地方用import math来引入。

语法:

代码语言:javascript
复制
import 模块1,模块2,...  # 导入方式

模块名.函数名()  # 使用模块里的函数
  • 想一想: 为什么必须加上模块名调用呢?
  • 答: 因为可能存在这样一种情况:在多个模块中含有相同名称的函数,此时如果只是通过函数名来调用,解释器无法知道到底要调用哪个函数。所以如果像上述这样引入模块的时候,调用函数必须加上模块名

示例:

代码语言:javascript
复制
import math

#这样才能正确输出结果
print math.sqrt(2)

#这样会报错
print(sqrt(2))

from…import

有时候我们只需要用到模块中的某个函数,只需要引入该函数即可,此时可以用下面方法实现:

代码语言:javascript
复制
from 模块名 import 函数名1,函数名2....

不仅可以引入函数,还可以引入一些全局变量、类等

  • 注意: 通过这种方式引入的时候,调用函数时只能给出函数名,不能给出模块名,但是当两个模块中含有相同名称函数的时候,后面一次引入会覆盖前一次引入。也就是说假如模块A中有函数function( ),在模块B中也有函数function( ),如果引入A中的function在先、B中的function在后,那么当调用function函数的时候,是去执行模块B中的function函数。

例如,要导入模块fib的fibonacci函数,使用如下语句:

代码语言:javascript
复制
from fib import fibonacci

注意

  • 不会把整个fib模块导入到当前的命名空间中,它只会将fib里的fibonacci单个函数引入

from … import *

把一个模块的所有内容全都导入到当前的命名空间也是可行的,只需使用如下声明:

代码语言:javascript
复制
from modname import *

注意

  • 这提供了一个简单的方法来导入一个模块中的所有项目。然而这种声明不该被过多地使用。

as 别名

代码语言:javascript
复制
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模块

OS全称OperationSystem,即操作系统模块,这个模块可以用来操作系统的功能,并且实现跨平台操作。

代码语言:javascript
复制
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

sys模块

该模块提供对解释器使用或维护的一些变量的访问,以及与解释器强烈交互的函数。

代码语言:javascript
复制
import sys
sys.path # 模块的查找路径
sys.argv # 传递给Python脚本的命令行参数列表
sys.exit(code) # 让程序以指定的退出码结束

sys.stdin # 标准输入。可以通过它来获取用户的输入
sys.stdout # 标准输出。可以通过修改它来百变默认输出
sys.stderr # 错误输出。可以通过修改它来改变错误删除
Copy

math模块

math模块保存了数学计算相关的方法,可以很方便的实现数学运算。

代码语言:javascript
复制
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模块

random 模块主要用于生成随机数或者从一个列表里随机获取数据。

代码语言:javascript
复制
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模块

datetime模块主要用来显示日期时间,这里主要涉及 date类,用来显示日期;time类,用来显示时间;dateteime类,用来显示日期时间;timedelta类用来计算时间。

代码语言:javascript
复制
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

time模块

除了使用datetime模块里的time类以外,Python还单独提供了另一个time模块,用来操作时间。time模块不仅可以用来显示时间,还可以控制程序,让程序暂停(使用sleep函数)

代码语言:javascript
复制
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模块用来显示一个日历,使用的不多,了解即可。

代码语言:javascript
复制
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模块

hashlib是一个提供字符加密功能的模块,包含MD5和SHA的加密算法,具体支持md5,sha1, sha224, sha256, sha384, sha512等算法。 该模块在用户登录认证方面应用广泛,对文本加密也很常见。

代码语言:javascript
复制
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算法也是一种一种单项加密算法,并且它是基于上面各种哈希算法/散列算法的,只是它可以在运算过程中使用一个密钥来增增强安全性。hmac模块实现了HAMC算法,提供了相应的函数和方法,且与hashlib提供的api基本一致。

代码语言:javascript
复制
h = hmac.new('h'.encode(),'你好'.encode())
result = h.hexdigest()
print(result)  # 获取加密后的结果
Copy

copy模块

copy模块里有copy和deepcopy两个函数,分别用来对数据进行深复制和浅复制。

代码语言:javascript
复制
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模块

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方法就已经够用了,使用方法如下:

代码语言:javascript
复制
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'))

pip命令的使用

在安装Python时,同时还会安装pip软件,它是Python的包管理工具,可以用来查找、下载、安装和卸载Python的第三方资源包。

配置pip

可以直接在终端中输入pip命令,如果出错,可能会有两个原因:

  1. pip安装成功以后没有正确配置
  2. 安装Python时,没有自动安装pip(很少见)

配置pip

和运行Python命令一样,如果想要运行 pip 命令同样也需要将pip命令的安装目录添加到环境变量中。

管理第三方包

对第三方包的管理主要包含查找、安装和卸载三个部分的操作。

安装

使用 pip install <包名>命令可以安装指定的第三方资源包。

代码语言:javascript
复制
pip install ipython # 安装ipython包
Copy

使用 install 命令下载第三方资源包时,默认是从 pythonhosted下载,由于各种原因,在国内下载速度相对来说比较慢,在某些时候甚至会出现连接超时的情况,我们可以使用国内镜像来提高下载速度。

临时修改

如果只是想临时修改某个第三方资源包的下载地址,在第三方包名后面添加 -i 参数,再指定下载路径即可,格式为pip install <包名> -i <国内镜像路径>

代码语言:javascript
复制
pip install ipython -i https://pypi.douban.com/simple
Copy
永久修改

除了临时修改pip的下载源以外,我们还能永久改变pip的默认下载路径。

在当前用户目录下创建一个pip的文件夹,然后再在文件夹里创建pip.ini文件并输入一下内容:

代码语言:javascript
复制
[global]
index-url=https://pypi.douban.com/simple
[install]
trusted-host=pypi.douban.com
Copy
常见国内镜像

卸载

使用 pip install <包名>命令可以用来卸载指定的第三方资源包。

代码语言:javascript
复制
pip uninstall ipython # 卸载ipython包
Copy

查找

使用pip list 或者 pip freeze命令可以来管理第三方资源包。这两个命令的功能一致,都是用来显示当前环境里已经安装的包,区别在于pip list会列出所有的包,包括一些无法uninstall的包;而pip freeze只会列出我们安装的第三方包。

总结

开发中,我们通常会使用很多第三方的资源包,我们在将程序部署到服务器的时候,不仅要把代码上传到服务器,同时还需要把代码里用到的第三方资源包告诉服务器。那么这里就有两个问题:

  1. 当我们电脑上运行很多个项目,每个项目使用的第三方资源包又不一致时,怎样将代码和它使用到的第三方资源包放在一起呢?答:虚拟环境
  2. 怎样将自己代码使用到的第三方资源包告诉给服务器?
    1. 使用 pip freeze > requires.txt 命令,将代码里使用到的第三方资源包以及版本号写入到 requirements.txt 文件,在部署时,同时将 requirements.txt 文件上传到服务器。
    2. 服务器在拿到代码以后,首先运行 pip install -r requirements.txt 命令,将文件里列出的所有第三方框架先安装到服务器,然后才能运行代码。

使用pycharm管理第三方包

除了使用pip 命令管理第三方资源包以外,我们还能使用pycharm来对第三方包进行管理。

自定义模块

除了使用系统提供的内置模块以外,我们还能自己写一个模块供自己的程序使用。一个py文件就是一个模块,所以,自定义模块很简单,基本上相当于创建一个py文件。但是,需要注意的是,如果一个py文件要作为一个模块被别的代码使用,这个py文件的名字一定要遵守标识符的命名规则。

模块的查找路径

创建一个模块非常简单,安装标识符的命名规则创建一个py文件就是一个模块。但是问题是,我们需要把创建好的这个py文件放在哪个位置,在代码中使用 import语句才能找到这个模块呢?

Python内置sys模块的path属性,列出了程序运行时查找模块的目录,只需要把我们创建好的模块放到这些任意的一个目录里即可。

代码语言:javascript
复制
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__属性

代码语言:javascript
复制
a = 'hello'
def fn():
    print('我是test1模块里的fn函数')
Copy

test2.py:模块里有__all__属性

代码语言:javascript
复制
x = '你好'
y = 'good'
def foo():
    print('我是test2模块里的foo函数')
__all__ = ('x','foo')
Copy

test3.py:模块里有以_开头的属性

代码语言:javascript
复制
m = '早上好'
_n = '下午好'
def _bar():
    print('我是test3里的bar函数')
Copy

demo.py

代码语言:javascript
复制
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

代码语言:javascript
复制
def add(a,b):
    return a+b

# 这段代码应该只有直接运行这个文件进行测试时才要执行
# 如果别的代码导入本模块,这段代码不应该被执行
ret = add(12,22)
print('测试的结果是',ret)
Copy

demo.py

代码语言:javascript
复制
import test1.py   # 只要导入了tets1.py,就会立刻执行 test1.py 代码,打印测试内容
Copy

为了解决这个问题,python在执行一个文件时有个变量__name__.在Python中,当直接运行一个py文件时,这个py文件里的__name__值是__main__,据此可以判断一个一个py文件是被直接执行还是以模块的形式被导入。

代码语言:javascript
复制
def add(a,b):
    return a+b

if __name__ == '__main__':  # 只有直接执行这个py文件时,__name__的值才是 __main__
    # 以下代码只有直接运行这个文件才会执行,如果是文件被别的代码导入,下面的代码不会执行
    ret = add(12,22)
    print('测试的结果是',ret)
Copy

注意事项

在自定义模块时,需要注意一点,自定义模块名不要和系统的模块名重名,否则会出现问题!

包的使用

一个模块就是一个 py 文件,在 Python 里为了对模块分类管理,就需要划分不同的文件夹。多个有联系的模块可以将其放到同一个文件夹下,为了称呼方便,一般把 Python 里的一个代码文件夹称为一个包。

1. 导入包的方式

现有以下包newmsg,包里由两个模块,分别是sendmsg.pyrecvmsg.py文件。在包的上级文件夹里,有一个test.py文件,目标是在test.py文件里引入newmsg的两个模块。

目录结构如下图所示:

image
image

sendmsg.py文件里的内容如下:

代码语言:javascript
复制
def send_msg():
  print('------sendmsg方法被调用了-------')
Copy

recvmsg.py文件里的内容如下:

代码语言:javascript
复制
def recv_msg():
  print('-----recvmsg方法被调用了--------')
Copy

可以使用以下几种方式来导入模块,使用模块里的方法。

1>. 直接使用包名.模块模块名导入指定的模块。

image
image

2>. 使用from xxx import xxx 方式导入指定模块。

image
image

3>. 使用__init__.py文件,导入包里的指定模块。

可以在newmsg里创建__init__.py文件,在该文件里导入指定的内容。

image
image

__init__.py文件里编写代码:

代码语言:javascript
复制
from . import sendmsg  # 导入指定的模块    . 代表的是当前文件夹
Copy

test.py文件里的代码

代码语言:javascript
复制
import newmsg # 导入时,只需要输入包名即可。在包名的__init__.py文件里,导入了指定模块
newmsg.sendmsg.sendm_msg()  # 可以直接调用对应的方法
# newmsg.recvmsg.recv_msg()   不可以使用 recvmsg 模块,因为 __init__.py文件里没有导入这个模块
Copy

4.> 使用__init__.py文件,结合__all__属性,导入包里的所有模块。

newmsg包里的__init__.py文件里编写代码:

代码语言:javascript
复制
__all__ = ["sendmsg","recvmsg"]  # 指定导入的内容
Copy

test.py文件代码:

代码语言:javascript
复制
from newmsg import *  # 将newmsg里的__inint__.py文件里,__all__属性对应的所有模块都导入
sendmsg.sendmsg()
recvmsg.recvmsg()
Copy

总结

  • 包将有联系的模块组织在一起,即放到同一个文件夹下,并且在这个文件夹创建一个名字为__init__.py 文件,那么这个文件夹就称之为
  • 有效避免模块名称冲突问题,让应用组织结构更加清晰

2. __init__.py文件有什么用

__init__.py 控制着包的导入行为。__init__.py为空仅仅是把这个包导入,不会导入包中的模块。可以在__init__.py文件中编写内容。

newmsg/__init__.py文件:

代码语言:javascript
复制
print('hello world')
Copy

别的模块在引入这个包的时候,会自动调用这段代码。

image
image

3. __all__

__init__.py文件中,定义一个__all__变量,它控制着 from 包名 import *时导入的模块。

newmsg/__init__.py文件:

代码语言:javascript
复制
__all__ = ['sendmsg','recvmsg']

面向对象

python,java,C++的面向对象的区别

Python、Java 和 C++ 都是面向对象编程语言,但它们之间还是有一些区别的。

1.语法风格:

  • Python 对于面向对象编程的支持比较灵活,支持动态绑定,不需要显式地声明变量的类型。Python 的语法风格更加简洁易懂,适合快速开发,但是在大规模程序中,Python 的代码可读性可能会受到一定的影响。
  • Java 的面向对象语法比 Python 略微复杂一些,需要显式地声明变量类型,但是它的类型检查可以帮助开发人员更容易地发现程序中的错误。Java 的代码比较规范,适合大型项目开发。
  • C++ 是一门支持多种编程范式的语言,其面向对象语法相对来说较为复杂,需要程序员手动管理内存,但是它的性能较好,可以用来开发一些对性能要求较高的项目。

2.继承:

  • Python 和 Java 都支持单继承和多继承,但是 Python 的多继承有些不同寻常,有时需要格外小心使用。
  • C++ 支持单继承和多继承,但是它的继承机制比较复杂,需要程序员有更深的理解和掌握。

3.多态性:

  • Python 和 Java 都支持运行时多态性(动态绑定),使得子类能够在父类的接口上进行扩展。
  • C++ 支持编译时多态性(静态绑定),通过虚函数实现多态性。

4.内存管理:

  • Python 和 Java 采用垃圾回收机制,开发人员不需要手动管理内存。
  • C++ 需要程序员手动管理内存,需要注意内存泄漏和悬垂指针等问题。

Python面向对象学习目标

  • 说出面向对象和面向过程的特点
  • 能够使用dir内置函数查看对象的方法
  • 能够使用类创建对象
  • 能够说出self的含义
  • 能够说出魔法方法什么时候调用
  • 能够说出内置属性的作用
  • 能够说出实例属性和类属性的区别
  • 能够定义和操作私有属性

基础

面向对象与面向过程

  • 面向过程:根据业务逻辑从上到下写代码。
  • 面向对象:将变量与函数绑定到一起,分类进行封装,每个程序只要负责分配给自己的分类,这样能够更快速的开发程序,减少了重复代码。

面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程的思路是将数据与函数按照执行的逻辑顺序组织在一起,数据与函数分开考虑,面向过程基本是由函数组成的。

面向过程编程

面向过程编程的关注点在于怎么做

  • 把完成某一个需求的 所有步骤 从头到尾 逐步实现
  • 根据开发需求,将某些 功能独立 的代码 封装 成一个又一个 函数
  • 最后完成的代码,就是顺序地调用 不同的函数

特点:

  • 注重步骤与过程,不注重职责分工
  • 如果需求复杂,代码会变得很复杂
  • 开发复杂项目,没有固定的套路,开发难度很大!
面向对象基本概念

面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)和面相过程编程,是两种不同的编程方式。

面向对象编程的关注点在于谁来做

相比较函数,面向对象是更大的封装,根据职责在 一个对象中封装多个方法

  • 在完成某一个需求前,首先确定职责 —— 要做的事情(方法)
  • 根据 职责 确定不同的 对象,在对象内部封装不同的方法(多个)
  • 最后完成的代码,就是顺序地调用不同对象的相应方法。

特点:

  • 注重 对象和职责,不同的对象承担不同的职责。
  • 更加适合应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路。
  • 需要在面向过程基础上,再学习一些面向对象的语法。

类和对象

对象是面向对象编程的两个核心概念。

类是对一群具有相同特征或者行为 的事物的一个统称,是抽象的,不能直接使用

  • 特征其实就是一个变量,在类里我们称之为属性。
  • 行为其实就是一个函数,在类里我们称之为方法。
  • 类其实就是由 属性方法 组成的一个抽象概念。

类就相当于制造飞机时的图纸,是一个模板。这个模板只规定了飞机的某些特征(例如大小,颜色,型号等等)和行为(例如起飞,降落,飞行等等),它并不是一个具体的飞机,而是对飞机的一个抽象概念。它出现的目的,是为了让我们的创建飞机对象。

对象

对象是由类创建出来的一个具体存在,可以直接使用。由哪一个类创建出来的 对象,就拥有在哪一个类中定义的属性和方法。 对象 就相当于用图纸制造的飞机。在开发中,应该先有类,在类里定义好属性和行为,再根据类来创建对象。

类和对象的关系
  • 类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象。
  • 使用同一个类,能够创建出很多对象。
  • 类中定义了什么属性和方法,对象中就有什么属性和方法。
  • 不同对象对应的属性值也会不同。

例如:定义了一个狗类,这个狗类有以下属性:

  • 品种
  • 颜色
  • 性别
  • 名字

现在根据这个类创建出了两条狗,这两条狗分别是 哈士奇、灰色、母、二哈中华田园犬、黄色、公、旺财。我们发现,这两条狗都具有 品种、颜色、性别和名字这些属性,但是每条狗对应的属性值却不一样。

类的设计

在程序开发中,要设计一个类,通常需要满足一下三个要素:

  1. 类名 这类事物的名字,安照大驼峰命名法(每个单词的首字母大写)起名。
  2. 属性 这类事物具有什么样的特征。
  3. 方法 这类事物具有什么样的行为。
定义类名

名词提炼法:分析整个业务流程,出现的名词,通常就是找到的类。

属性和方法的确定
  • 对对象的特征描述,可以定义成属性
  • 对象具有的行为(动词)可以定义成方法

面向对象基本语法

在Python中,对象几乎是无处不在的,我们可以使用dir内置函数来查看这个对象里的方法。

定义简单的类(只包含方法)

面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法了!

定义类

在Python中要定义一个只包含方法的类,语法格式如下:

代码语言:javascript
复制
class 类名:
    def 方法1(self,参数列表):
        pass
    def 方法2(self,参数列表):
        pass
Copy

  1. 方法的定义格式和之前学习过的函数一样
  2. 方法里的第一个参数必须是self,大家暂时先记住,稍后介绍 self.
  3. 类名要遵守大驼峰命名法。
创建实例对象

当一个类定义完成之后,要使用这个类来创建对象,语法格式如下:

代码语言:javascript
复制
对象变量名 = 类名()
Copy
第一个面向对象代码

需求

  • 小猫 爱 吃 鱼,小猫 要 喝 水

分析

  • 定义一个猫类 Cat
  • 定义两个方法 eat 和 drink
  • 按照需求 —— 不需要定义属性
代码语言:javascript
复制
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

思考:tomhello_kitty 是同一个对象吗?

self的使用
给对象添加属性

python支持动态属性,当一个对象创建好了以后,直接使用 对象.属性名 = 属性值 就可以很方便的给对象添加一个属性。

代码语言:javascript
复制
tom = Cat()
tom.name = 'Tom'  # 可以直接给 tom 对象添加一个 name 属性
Copy

这种方法很方便,但是,不建议使用这种方式给对象添加属性。

self的概念

哪个对象调用了方法,方法里的self指的就是谁。 通过 self.属性名 可以访问到这个对象的属性;通过 self.方法名() 可以调用这个对象的方法。

代码语言:javascript
复制
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 属性,但是这样做会有问题。

代码语言:javascript
复制
tom = Cat()
tom.eat()
tom.anme = "Tom"
Copy

程序运行时会报错:

代码语言:javascript
复制
AttributeError: 'Cat' object has no attribute 'name'
错误提示:'Cat'对象没有 'name' 属性
Copy

在日常开发中,不推荐在类的外部直接给对象添加属性这种方式。对象应该具有哪些属性,我们应该封装在类的内部。

魔法方法

Python 里有一种方法,叫做魔法方法。Python 的类里提供的,两个下划线开始,两个下划线结束的方法,就是魔法方法,魔法方法在恰当的时候就会被激活,自动执行。 魔法方法的两个特点:

  • 两侧各有两个下划线;
  • “咒语”名字已经由 Python 官方定义好,我们不能乱写。
1.__init__方法

__init__()方法,在创建一个对象时默认被调用,不需要手动调用。在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对 __init__ 方法进行改造。

代码语言:javascript
复制
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爱吃鱼

注意:

  1. __init__()方法在创建对象时,会默认被调用,不需要手动的调用这个方法。
  2. __init__()方法里的self参数,在创建对象时不需要传递参数,python解释器会把创建好的对象引用直接赋值给self
  3. 在类的内部,可以使用self来使用属性和调用方法;在类的外部,需要使用对象名来使用属性和调用方法。
  4. 如果有多个对象,每个对象的属性是各自保存的,都有各自独立的地址。
  5. 方法是所有对象共享的,只占用一份内存空间,方法被调用时会通过self来判断是哪个对象调用了实例方法。
2.__del__方法

创建对象后,python解释器默认调用__init__()方法;

而当删除对象时,python解释器也会默认调用一个方法,这个方法为__del__()方法。

代码语言:javascript
复制
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:

代码语言:javascript
复制
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()

3.__str__方法

__str__方法返回对象的描述信息,使用print()函数打印对象时,其实调用的就是这个对象的__str__方法。

代码语言:javascript
复制
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__ 方法。

代码语言:javascript
复制
class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def __str__(self):
        return '哈哈'

p  = Person('张三',18)
print(p)   # 哈哈  打印对象时,会自动调用对象的 __str__ 方法
Copy

一般情况下,我们在打印一个对象时,可能需要列出这个对象的所有属性。

代码语言:javascript
复制
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
4. __repr__方法

__repr__方法和__str__方法功能类似,都是用来修改一个对象的默认打印内容。在打印一个对象时,如果没有重写__str__方法,它会自动来查找__repr__方法。如果这两个方法都没有,会直接打印这个对象的内存地址。

代码语言:javascript
复制
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
5. __call__方法

对象后面加括号,触发执行。

代码语言:javascript
复制
class Foo:
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):
        print('__call__')


obj = Foo()  # 执行 __init__
obj()  # 执行 __call__
Copy
总结
  1. 当创建一个对象时,会自动调用__init__方法,当删除一个对象时,会自动调用__del__方法。
  2. 使用__str____repr__方法,都会修改一个对象转换成为字符串的结果。一般来说,__str__方法的结果更加在意可读性,而__repr__方法的结果更加在意正确性(例如:datetime模块里的datetime类)

运算相关的魔法方法

思考:

代码语言:javascript
复制
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?==到底比较的是什么?

比较运算符相关魔法方法
代码语言:javascript
复制
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
算数运算符相关魔法方法
代码语言:javascript
复制
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
类型转换相关魔法方法
代码语言:javascript
复制
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中存在着很多的内置属性。

slots

Python中支持动态属性,可以直接通过点语法直接给一个对象添加属性,代码更加的灵活。但是在某些情况下,我们可能需要对属性进行控制,此时,就剋使用__slots__实现。

代码语言:javascript
复制
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
doc

表示类的描述信息。

代码语言:javascript
复制
class Foo:
    """ 描述类信息,这是用于看片的神奇 """
    def func(self):
        pass

print(Foo.__doc__)
#输出:类的描述信息
Copy
moduleclass

module 表示当前操作的对象在那个模块;class 表示当前操作的对象的类是什么。

代码语言:javascript
复制
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
dict

以字典的形式,显示对象所有的属性和方法。

代码语言:javascript
复制
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__方法

这三个方法,是将对象当做字典一样进行操作。

代码语言:javascript
复制
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__

实例属性、类属性

在面向对象开发中,使用类创建出来的实例是一个对象,那么,类是否是一个对象呢?

实例属性

通过类创建的对象被称为 实例对象,对象属性又称为实例属性,记录对象各自的数据,不同对象的同名实例属性,记录的数据各自独立,互不干扰。

代码语言:javascript
复制
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
类属性

类属性就是类对象所拥有的属性,它被该类的所有实例对象所共有,类属性可以通过类对象或者实例对象访问。

代码语言:javascript
复制
class Dog:
    type = "狗"  # 类属性

dog1 = Dog()
dog2 = Dog()

# 不管是dog1、dog2还是Dog类,都可以访问到type属性
print(Dog.type)  # 结果:狗
print(dog1.type)  # 结果:狗
print(dog2.type)  # 结果:狗
Copy
使用场景:
  1. 类的实例记录的某项数据始终保持一致时,则定义类属性。
  2. /实例属性要求每个对象为其单独开辟一份内存空间来记录数据,而类属性为全类所共有 ,仅占用一份内存,更加节省内存空间。
注意点:

1> 尽量避免类属性和实例属性同名。如果有同名实例属性,实例对象会优先访问实例属性

代码语言:javascript
复制
class Dog(object):
    type = "狗"  # 类属性

    def __init__(self):
        self.type = "dog"  # 对象属性

# 创建对象
dog1 = Dog()

print(dog1.type)     # 结果为 “dog”   类属性和实例属性同名,使用 实例对象 访问的是 实例属性
Copy

2> 类属性只能通过类对象修改,不能通过实例对象修改

代码语言:javascript
复制
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> 类属性也可以设置为私有,前边添加两个下划线。 如:

代码语言:javascript
复制
class Dog(object):
    count = 0  # 公有的类属性
    __type = "狗"  # 私有的类属性

print(Dog.count)       # 正确
print(Dog.__type)      # 错误,私有属性,外部无法访问。

私有属性和方法

在实际开发中,对象的某些属性或者方法可能只希望在对象的内部别使用,而不希望在外部被访问到,这时就可以定义私有属性和私有方法。

定义方法

在定义属性或方法时,在属性名或者方法名前增加两个下划线__,定义的就是私有属性或方法。

代码语言:javascript
复制
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
访问私有属性和方法

私有属性不能直接使用,私有方法不能直接调用。但是,通过一些代码,我们也可以在外部访问一个对象的私有属性和方法。

直接访问

使用方式:在私有属性名或方法名前添加 _类名

代码语言:javascript
复制
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

注意:在开发中,我们强烈不建议使用 对象名._类名__私有属性名 的方式来访问对象的私有属性!

定义方法访问私有变量

在实际开发中,如果对象的变量使用了__ 来修饰,就说明它是一个私有变量,不建议外部直接使用和修改。如果硬要修改这个属性,可以使用定义getset方法这种方式来实现。

代码语言:javascript
复制
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())

进阶

类方法、静态方法

1. 类方法
  • 第一个形参是类对象的方法
  • 需要用装饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数。
代码语言:javascript
复制
class Dog(object):
    __type = "狗"

    # 类方法,用classmethod来进行修饰
    @classmethod
    def get_type(cls):
        return cls.__type
print(Dog.get_type())
Copy

使用场景:

  • 当方法中 需要使用类对象 (如访问私有类属性等)时,定义类方法
  • 类方法一般和类属性配合使用
2. 静态方法
  • 需要通过装饰器@staticmethod来进行修饰
  • 静态方法既不需要传递类对象也不需要传递实例对象(形参没有self/cls)
  • 静态方法 也能够通过 实例对象类对象 去访问。
  • 跟类方法一样,也只能访问类的属性和方法,对象的是无法访问的。
  • 创建时机:跟类方法一样,
代码语言:javascript
复制
class Dog(object):
    type = "狗"

    def __init__(self):
        name = None

    # 静态方法    
    @staticmethod
    def introduce():  # 静态方法不会自动传递实例对象和类对象
        print("犬科哺乳动物,属于食肉目..")

dog1 = Dog()
Dog.introduce()    # 可以用 实例对象 来调用 静态方法
dog1.introduce()    # 可以用 类对象 来调用 静态方法
Copy

使用场景:

  • 当方法中 既不需要使用实例对象(如实例对象,实例属性),也不需要使用类对象 (如类属性、类方法、创建实例等)时,定义静态方法
  • 取消不需要的参数传递,有利于 减少不必要的内存占用和性能消耗

注意点:

  • 类中定义了同名的方法时,调用方法会执行最后定义的方法
代码语言:javascript
复制
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面向对象中静态方法和类方法有什么区别

区别:

  1. 装饰器不同。
  2. 类方法是有参数的,静态方法是没参数的。

相同:

  1. 只能访问类的属性和方法,对象是无法访问的。
  2. 都可以通过类名来访问。
  3. 都可以在创建对象前使用。

PS:普通方法与两者的区别

不同:

  1. 没有装饰器
  2. 普通方法是永远依赖对象,因为每个普通方法都有一个self
  3. 只有创建了对象才能调用普通方法,否则都无法使用。

单列设计模式

__new__和__init__方法
代码语言:javascript
复制
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__不需要返回值
单例设计模式

举个常见的单例模式例子,我们日常使用的电脑上都有一个回收站,在整个操作系统中,回收站只能有一个实例,整个系统都使用这个唯一的实例,而且回收站自行提供自己的实例。因此回收站是单例模式的应用。

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,单例模式是一种对象创建型模式。

代码语言:javascript
复制
# 实例化一个单例
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)

继承的基本使用

在现实生活中,继承一般指的是子女继承父辈的财产,父辈有的财产,子女能够直接使用。

程序里的继承

继承是面向对象软件设计中的一个概念,与多态、封装共为面向对象的三个基本特征。继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。

image
image
  • 在程序中,继承描述的是多个类之间的所属关系。
  • 如果一个类A里面的属性和方法可以复用,则可以通过继承的方式,传递到类B里。
  • 那么类A就是基类,也叫做父类;类B就是派生类,也叫做子类。
代码语言:javascript
复制
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中,继承可以分为单继承、多继承和多层继承。

单继承:子类只继承一个父类

继承概念:子类用于父类的所有的方法和属性。

继承语法:

代码语言:javascript
复制
class 类名(父类名):
    pass
Copy
  • 子类继承自父类,可以享受父类中已经封装好的方法,不需要再次定义
  • 子类中应该根据职责,封装子类特有的属性和方法。
继承的传递性

Dog类继承自Animal,XiaoTianQuan又继承自Dog类,那么XiaoTianQuan类就具有了Animal类里的所有属性和方法。

子类拥有父类以及父类的父类中封装的所有属性和方法。

思考:

XiaoTianQuan能否调用Animal的run()方法? XiaoTianQUan能够调用Cat里的方法?

多继承

子类可以拥有多个父类,并且具有所有父类的属性和方法。

语法格式:

代码语言:javascript
复制
class 子类名(父类名1,父类名2...)
    pass
Copy
多继承的使用注意事项

思考:

如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪个父类的方法? 说明:开发中,应该尽量避免这种容易产生混淆的情况。如果多个父类之间存在同名的属性后者方法,应该尽量避免使用多继承。

Python中的MRO
  • Python中针对类提供了一个内置属性__mro__可以用来查看方法的搜索顺序。
  • MRO 是method resolution order的简称,主要用于在多继承时判断方法属性的调用顺序。
代码语言:javascript
复制
print(C.__mro__)

输出结果:

代码语言:javascript
复制
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
  • 在调用方法时,按照__mro__的输出结果从左至右的顺序查找。
  • 如果再当前类中找到方法,就直接执行,不再向下搜索。
  • 如果没有找到,就顺序查找下一个类中是否有对应的方法,如果找到,就直接执行,不再继续向下搜索。
  • 如果找到了最后一个类,依然没有找到方法,程序就会报错。
新式类和旧式(经典)类

object是Python中所有对象的基类,提供了一些内置的属性和方法,可以时候用dir函数查看。

  • 新式类:以object为基类的类,推荐使用
  • 经典类:不以object为基类的类,不推荐使用
  • 在 Python3.x 以后定义类时,如果没有指定父类,这个类会默认继承自 object,所以,python3.x版本定义的类都是新式类。
  • 在Python2.x中定义类时,如果没有指定父类,则不会继承自object.

为了保证代码在Python2.x和Python3.x中都能够运行,在定义类时,如果一个类没有父类,建议统一继承自’object’

代码语言:javascript
复制
class 类名(object):
    pass

对象相关的运算符和内置函数

Python中的身份运算符用来判断两个对象是否相等;isinstance用来判断对象和类之间的关系;issublcass用啊里判断类与类之间的关系。

身份运算符

身份运算符用来比较两个对象的内存地址,看这两个对象是否是同一个对象。

代码语言:javascript
复制
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
isinstance

instance内置函数,用来判断一个实例对象是否是由某一个类(或者它的子类)实例化创建出来的。

代码语言:javascript
复制
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

issubclass 用来判断两个类之间的继承关系。

代码语言:javascript
复制
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

多态

面向对象的三大特性:

  • 封装:这是定义类的准则,根据对象的特点,将行为和属性抽象出来,封装到一个类中。
  • 继承:这是设计类的技巧。父类与子类,主要体现在代码的重用,不需要大量的编写重复代码。
  • 多态:不同的子类调用相同的父类方法,产生不同的执行结果,可以增加代码的外部灵活度。多态是以继承和重写父类方法为前提的,它是一种调用方法的技巧,不会影响到类的内部设计。
场景
  • 提供三个类:缉毒犬、军犬、人
  • 缉毒犬–>追查毒品,军犬–>攻击假人,人–>让小狗干活
  • 设计类来完成功能。
image
image

代码实现:

代码语言:javascript
复制
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类里新建一个方法,让这个方法操作新的狗。

代码语言:javascript
复制
class XiaoTianDog(object):

    def eat_moon(self):
        print('哮天犬把月亮吃了')

class Person(object):

    def work_with_xiaotian(self, dog):  # 添加方法
        dog.eat_moon()

Person 类总是不断的添加新的功能,每次都需要改动Person类的源码,程序的扩展性太差了!

  • 最好是提供一个父类 Dog,具备 work 的功能,其他小狗继承它,这样只要是小狗类,则行为被统一起来了,我们人类完全可以保证,只要是小狗的子类,找它干活肯定不会有问题。
  • 这样人只要一个方法就能逗任意种类的狗玩,哪怕是添加新的狗,人的类都不需要修改。

代码实现:

代码语言:javascript
复制
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 产生了不同的执行效果
  • 最终效果
    • Person 类中只需要调用 Dog 对象 work() 方法,而不关心具体是 什么狗
    • work() 方法是在 Dog 父类中定义的,子类重写并处理不同方式的实现
    • 在程序执行时,传入不同的 Dog 对象作为实参,就会产生不同的执行效果
多态总结
  • 定义:多态是一种使用对象的方式,子类重写父类方法,调用不同子类对象的相同父类方法,可以产生不同的执行结果
  • 好处:调用灵活,有了多态,更容易编写出通用的代码,做出通用的编程,以适应需求的不断变化!
  • 实现步骤:
    • 定义父类,并提供公共方法
    • 定义子类,并重写父类方法
    • 传递子类对象给调用者,可以看到不同子类执行效果不同

文件操作

序列化反序列化和异常处理

迭代器生成器

网络编程

正则表达式

多任务

HTTP服务器

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-12-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 变量,输入输出语句
    • Java,C++,Python中的数组和变量存储位置相同吗
      • python,C++,java在格式化输出上的区别
        • PS:python字符串的format方法
    • 进制转换、数据类型详解、类型转换、运算符
      • python,C++,java在数据类型和类型转换上的区别
        • python,java,C++位运算的区别
        • 流程控制语句
          • C++,java,python的for循环的区别
            • C++的for循环
            • Java的for循环
            • Python的for循环
        • 字符串
          • C++,java,python的字符串常见操作对比
            • 字符串长度
            • 字符串比较
            • 字符串查找
            • 字符串切片
            • 字符串连接
            • 字符串分割
            • 字符串替换
            • 转换字符串
            • PS:Python中字符串的常见操作
          • 字符集
            • 字符和编码相互转换
            • 编码规则
        • 列表
          • 列表的基本使用
            • 一、列表的格式
            • 二、使用下标获取列表元素
          • 列表的增删改查
            • 一、添加元素
            • 二、修改元素
            • 三、查找元素
            • 四、删除元素
            • 五、排序(sort, reverse)
            • 练习
          • 列表的遍历
            • 1. 使用while循环
            • 2. 使用for循环
            • 3. 交换2个变量的值
            • 练习
          • 列表的嵌套
            • 列表嵌套
            • 应用
          • 列表推导式
            • 基本方式
            • 在循环的过程中使用if
            • 2个for循环
            • 3个for循环
          • 列表的复制
            • 列表的copy方法
            • copy模块的使用
        • 元组、字典、集合
          • 元组
            • 一、访问元组
            • 二、修改元组
            • 三、count, index
            • 四、定义只有一个数据的元组
            • 五、交换两个变量的值
          • 字典
            • 列表的缺点
            • 字典的使用
            • 字典的遍历
          • 集合
            • set的使用
            • 添加元素
            • 移除元素
            • set常见方法列表
            • 练习
          • 转换相关
            • 执行字符串
            • 转换成为字符串
          • 通用方法
            • 算数运算符
            • 遍历
        • 垃圾回收机制
          • java,python的垃圾回收机制对比
            • Python的垃圾回收机制对比
            • 函数
              • 函数定义和调用
                • 一、定义函数
                • 二、调用函数
                • 三、注意:
                • 四、练一练
              • 函数参数
                • python函数传递参数与C++,java函数传递参数的区别
                • 定义、调用带有参数的函数
                • 函数参数是列表
                • 局部变量,全局变量
                • 函数参数详解
                • 可变、不可变类型
              • 函数返回值
                • python函数返回值与C++,java函数返回值的区别
                • 一、“返回值”介绍
                • 二、带有返回值的函数
                • 三、保存函数的返回值
              • 函数的文档说明
                • 1.基本使用
                • 2.高级使用
            • 高阶函数、装饰器
              • 递归函数
                • 什么是递归函数
                • 递归函数的作用
              • 匿名函数
                • 应用场合
              • 高阶函数
                • 定义一个变量指向函数
                • 高阶函数
              • 闭包
                • 函数嵌套
                • 什么是闭包
                • 修改外部变量的值
              • 装饰器
                • 引入
                • 再议装饰器
                • 装饰器(decorator)功能
                • 装饰器示例
            • 模块和包
              • 导入模块
                • 导入模块有五种方式
                • import
                • from…import
                • from … import *
                • as 别名
              • 常见系统模块
                • OS模块
                • sys模块
                • math模块
                • random模块
                • datetime模块
                • time模块
                • calendar模块
                • hashlib模块
                • hmac模块
                • copy模块
                • uuid模块
              • pip命令的使用
                • 配置pip
                • 配置pip
              • 管理第三方包
                • 安装
                • 卸载
                • 查找
                • 总结
                • 使用pycharm管理第三方包
              • 自定义模块
                • 模块的查找路径
                • __all__的使用
                • 模块里的私有成员
                • 总结
                • __name__的使用
                • 注意事项
              • 包的使用
                • 1. 导入包的方式
                • 2. __init__.py文件有什么用
                • 3. __all__
            • 面向对象
              • python,java,C++的面向对象的区别
                • 基础
                  • 面向对象与面向过程
                  • 类和对象
                  • 面向对象基本语法
                  • 魔法方法
                  • 运算相关的魔法方法
                  • 内置属性
                  • 实例属性、类属性
                  • 私有属性和方法
                • 进阶
                  • 类方法、静态方法
                  • 单列设计模式
                  • 继承的基本使用
                  • 继承的注意使用
                  • 对象相关的运算符和内置函数
                  • 多态
              • 文件操作
              • 序列化反序列化和异常处理
              • 迭代器生成器
              • 网络编程
              • 正则表达式
              • 多任务
              • HTTP服务器
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档