OverIQ-中文系列教程-二-
OverIQ 中文系列教程(二)
C 程序:使用递归计算阶乘
原文:https://overiq.com/c-examples/c-program-to-calculate-factorial-using-recursion/
最后更新于 2020 年 9 月 24 日
下面是一个使用递归计算阶乘的 C 程序:
/**************************************************
Program to calculate Factorial using recursion
*
* Enter a number: 4
* 4! = 24
***************************************************/
#include<stdio.h> // include stdio.h library
long factorial(int num);
int main(void)
{
int n;
printf("Enter a number: ");
scanf("%d", &n);
printf("%d! = %ld", n, factorial(n));
return 0; // return 0 to operating system
}
long factorial(int num)
{
//base condition
if(num == 0)
{
return 1;
}
else
{
// recursive call
return num * factorial(num - 1);
}
}
预期输出:
第一次运行:
Enter a number: 0
0! = 1
第二次运行:
Enter a number: 5
5! = 120
它是如何工作的
下图演示了5!
的评估是如何进行的:
C 程序:使用递归计算幂
原文:https://overiq.com/c-examples/c-program-to-calculate-the-power-using-recursion/
最后更新于 2020 年 9 月 24 日
下面是一个用递归计算幂的 C 程序:
/***************************************************
Program to calculate the power using recursion
*
* Enter base: 2
* Enter exponent: 5
* 2^5 = 32
***************************************************/
#include<stdio.h> // include stdio.h library
int power(int, int);
int main(void)
{
int base, exponent;
printf("Enter base: ");
scanf("%d", &base);
printf("Enter exponent: ");
scanf("%d", &exponent);
printf("%d^%d = %d", base, exponent, power(base, exponent));
return 0; // return 0 to operating system
}
int power(int base, int exponent)
{
//base condition
if(exponent == 0)
{
return 1;
}
else
{
// recursive call
return base * power(base, exponent - 1);
}
}
**预期输出:**第一次运行:
Enter base: 2
Enter exponent: 0
2^0 = 1
第二次运行:
Enter base: 4
Enter exponent: 4
4^4 = 256
它是如何工作的
下图显示了(4^4\的递归计算是如何进行的。
推荐阅读:
C 程序:使用递归打印斐波那契数列
原文:https://overiq.com/c-examples/c-program-to-print-fibonacci-sequence-using-recursion/
最后更新于 2020 年 9 月 24 日
下面是一个用递归打印斐波那契数列的 C 程序:
/****************************************************
Program to print Fibonacci Sequence using recursion
*
* Enter terms: 10
* 0 1 1 2 3 5 8 13 21 34
****************************************************/
#include<stdio.h> // include stdio.h library
int fibonacci(int);
int main(void)
{
int terms;
printf("Enter terms: ");
scanf("%d", &terms);
for(int n = 0; n < terms; n++)
{
printf("%d ", fibonacci(n));
}
return 0; // return 0 to operating system
}
int fibonacci(int num)
{
//base condition
if(num == 0 || num == 1)
{
return num;
}
else
{
// recursive call
return fibonacci(num-1) + fibonacci(num-2);
}
}
预期输出:
Enter terms: 20
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
它是如何工作的
下图显示了fibonacci(3)
的评估是如何进行的:
推荐阅读:
C 程序:使用递归反转数字
原文:https://overiq.com/c-examples/c-program-to-reverse-the-digits-of-a-number-using-recursion/
最后更新于 2020 年 9 月 24 日
下面是一个使用递归反转数字的 C 程序:
/**********************************************************
Program to reverse the digits of a number using recursion
*
* Enter a number: 4321
* 1234
**********************************************************/
#include<stdio.h> // include stdio.h library
void reverse_num(int num);
int main(void)
{
int num;
printf("Enter a number: ");
scanf("%d", &num);
reverse_num(num);
return 0; // return 0 to operating system
}
void reverse_num(int num)
{
int rem;
// base condition
if (num == 0)
{
return;
}
else
{
rem = num % 10; // get the rightmost digit
printf("%d", rem);
reverse_num(num/10); // recursive call
}
}
**预期输出:**第一次运行:
Enter a number: 12345
54321
第二次运行:
Enter a number: 1331
1331
它是如何工作的
下图显示了reverse_num(123)
的评估是如何进行的:
推荐阅读:
C 程序:使用递归将十进制数转换成二进制、八进制和十六进制
最后更新于 2020 年 9 月 24 日
下面是一个使用递归将十进制数转换为二进制、八进制和十六进制的 C 程序:
/**********************************************************
Program to convert a decimal number to binary,
octal and hexadecimal using recursion
*
* Select conversion:
* 1\. Decimal to binary.
* 2\. Decimal to octal.
* 3\. Decimal to hexadecimal.
* 4\. Exit.
* Enter your choice: 1
* Enter a number: 4
* Result = 100
**********************************************************/
#include<stdio.h> // include stdio.h library
void convert_to_x_base(int, int);
int main(void)
{
int num, choice, base;
while(1)
{
printf("Select conversion: \n\n");
printf("1\. Decimal to binary. \n");
printf("2\. Decimal to octal. \n");
printf("3\. Decimal to hexadecimal. \n");
printf("4\. Exit. \n");
printf("\nEnter your choice: ");
scanf("%d", &choice);
switch(choice)
{
case 1:
base = 2;
break;
case 2:
base = 8;
break;
case 3:
base = 16;
break;
case 4:
printf("Exiting ...");
exit(1);
default:
printf("Invalid choice.\n\n");
continue;
}
printf("Enter a number: ");
scanf("%d", &num);
printf("Result = ");
convert_to_x_base(num, base);
printf("\n\n");
}
return 0; // return 0 to operating system
}
void convert_to_x_base(int num, int base)
{
int rem;
// base condition
if (num == 0)
{
return;
}
else
{
rem = num % base; // get the rightmost digit
convert_to_x_base(num/base, base); // recursive call
if(base == 16 && rem >= 10)
{
printf("%c", rem+55);
}
else
{
printf("%d", rem);
}
}
}
预期输出:
Select conversion:
1\. Decimal to binary.
2\. Decimal to octal.
3\. Decimal to hexadecimal.
4\. Exit.
Enter your choice: 1
Enter a number: 7
Result = 111
Select conversion:
1\. Decimal to binary.
2\. Decimal to octal.
3\. Decimal to hexadecimal.
4\. Exit.
Enter your choice: 2
Enter a number: 25
Result = 31
Select conversion:
1\. Decimal to binary.
2\. Decimal to octal.
3\. Decimal to hexadecimal.
4\. Exit.
Enter your choice: 4
Exiting ...
它是如何工作的
下图显示了convert_to_x_base(4, 2)
的评估是如何进行的:
推荐阅读:
C 程序:将十进制数转换成二进制数
原文:https://overiq.com/c-examples/c-program-to-convert-a-decimal-number-to-a-binary-number/
最后更新于 2020 年 9 月 24 日
下面是一个将十进制数转换成二进制数的 C 程序:
/*******************************************************
Program to convert a decimal number to a binary number
******************************************************/
#include<stdio.h> // include stdio.h library
#include<math.h> // include math.h library
int main(void)
{
long long num, bin = 0;
int i = 0, rem;
printf("Enter a decimal number: ");
scanf("%lld", &num);
while(num != 0)
{
rem = num % 2; // get the remainder
bin = rem * (long long)pow(10, i++) + bin;
num /= 2; // get the quotient
}
printf("%lld", bin);
return 0; // return 0 to operating system
}
预期输出: 1sr 运行:
Enter a decimal number: 4
100
第二次运行:
Enter a decimal number: 123456
11110001001000000
工作原理:
要将十进制数转换为二进制数,我们遵循以下步骤:
第一步:十进制数连续除以 2,余数在被除数右侧向右。我们重复这个过程,直到得到商 0。
第二步:从下往上写余数。
现在我们举几个例子。
例 1 :将十进制数5
转换为二进制数。
第一步:
商 | 剩余物 | |
---|---|---|
5/2 |
2 |
1 |
2/2 |
1 |
0 |
1/2 |
0 |
1 |
第二步:
(5_{10}) = (101_2)
例 2 :将十进制数123
转换为二进制数。
第一步:
商 | 剩余物 | |
---|---|---|
123/2 |
61 |
1 |
61/2 |
30 |
1 |
30/2 |
15 |
0 |
15/2 |
7 |
1 |
7/2 |
3 |
1 |
3/2 |
1 |
1 |
1/2 |
0 |
1 |
步骤 2: (123_{10}) = (1111011_2)
下表显示了循环每次迭代时发生的情况(假设num = 4
):
循环 | 雷姆 | 容器 | 数字 | 我 |
---|---|---|---|---|
第一次迭代后 | rem = 4 % 2 = 0 |
bin = 0 * (10^0) + 0 = 0 |
num = 4 / 2 = 2 |
i = 2 |
第二次迭代后 | rem = 2 % 2 = 0 |
bin = 0 * (10^1) + 0 = 0 |
num = 2 / 2 = 1 |
i = 3 |
第三次迭代后 | rem = 1 % 2 = 1 |
bin = 1 * (10^2) + 0 = 100 |
num = 1 / 2 = 0 |
i = 4 |
C 程序:将十进制数转换成十六进制数
原文:https://overiq.com/c-examples/c-program-to-convert-a-decimal-number-to-a-hexadecimal-number/
最后更新于 2020 年 9 月 24 日
下面是一个将十进制数转换成二进制数的 C 程序:
/************************************************************
Program to convert a decimal number to a hexadecimal number
************************************************************/
#include<stdio.h> // include stdio.h library
int main(void)
{
int num, bin = 0;
int i = 0, rem;
char hex_arr[50];
printf("Enter a decimal number: ");
scanf("%d", &num);
while(num != 0)
{
rem = num % 16; // get the right most digit
if (rem < 10)
{
hex_arr[i++] = 48 + rem;
}
else
{
hex_arr[i++] = 55 + rem;
}
num /= 16; // get the quotient
}
printf("0x");
for(int j = i - 1; j >= 0 ; j--) // print the hex_arr in reverse order
{
printf("%c", hex_arr[j]);
}
return 0; // return 0 to operating system
}
**预期输出:**第一次运行:
Enter a decimal number: 198
0xC6
第二次运行:
Enter a decimal number: 123456
0x1E240
它是如何工作的
要将十进制数转换为十六进制数,我们执行以下步骤:
第一步:十进制数连续除以 16,余数写在被除数的右边。我们重复这个过程,直到得到商 0。
步骤 2:如果余数大于 10,则用下表中给出的十六进制字符替换它:
小数 | 十六进制的 |
---|---|
10 |
A |
11 |
B |
12 |
C |
13 |
D |
14 |
E |
15 |
F |
第三步:从下往上写剩余部分。
让我们举一些例子:
例 1 :将十进制 210 转换为十六进制数。
第一步:
商 | 剩余物 | |
---|---|---|
210/16 |
13 |
2 |
13/16 |
0 |
13 |
第二步:
商 | 剩余物 | 十六进制的 | |
---|---|---|---|
210/16 |
13 |
2 |
2 |
13/16 |
0 |
13 |
D |
第三步:
(\mathtt{210_{10} = 0xD2_{16}})
例 2 :
将十进制 100 转换为十六进制数。
第一步:
商 | 剩余物 | |
---|---|---|
100/16 |
6 |
4 |
6/16 |
0 |
6 |
第二步:在这种情况下,两个余数都小于 10,所以我们不需要用十六进制字符替换它们。
第三步:
(\mathtt{100_{10} = 0x64_{16}})
下表显示了循环每次迭代时发生的情况(假设num = 198
):
循环 | 雷姆 | hex_arr | 数字 | 我 |
---|---|---|---|---|
第一次迭代后 | rem=198%16=6 |
hex_arr[0]=48+6=54 |
num=198/16=12 |
i=1 |
第二次迭代后 | rem=12%16=12 |
hex_arr[1]=55+12=67 |
num=12/16=0 |
i=2 |
推荐阅读
C 程序:将十进制数转换成八进制数
原文:https://overiq.com/c-examples/c-program-to-convert-a-decimal-number-to-an-octal-number/
最后更新于 2020 年 9 月 24 日
下面是一个将十进制数转换成八进制数的 C 程序:
/*******************************************************
Program to convert a decimal number to an octal number
******************************************************/
#include<stdio.h> // include stdio.h library
#include<math.h> // include math.h library
int main(void)
{
long long num, oct = 0;
int i = 0, rem;
printf("Enter a decimal number: ");
scanf("%lld", &num);
while(num != 0)
{
rem = num % 8; // get the last digit
oct = rem * (long long)pow(10, i++) + oct;
num /= 8; // get the quotient
}
printf("0o");
printf("%lld", oct);
return 0; // return 0 to operating system
}
**预期输出:**第一次运行:
Enter a decimal number: 74
0o112
第二次运行:
Enter a decimal number: 2545
0o4761
它是如何工作的
要将十进制数转换为八进制数,我们遵循以下步骤:
第一步:十进制数连续除以 8,余数写在被除数的右边。我们重复这个过程,直到得到商 0。
第二步:从下往上写剩余部分。
让我们举一些例子:
示例 1 :将十进制125
转换为八进制数:
第一步:
商 | 剩余物 | |
---|---|---|
125/8 |
15 |
5 |
15/8 |
1 |
7 |
1/2 |
0 |
1 |
第二步:
(\ matht { 125 _ { 10 } = 175 _ { 8 } } )
示例 2 :将十进制500
转换为八进制数:
第一步:
商 | 剩余物 | |
---|---|---|
500/8 |
62 |
4 |
62/8 |
7 |
6 |
1/2 |
0 |
7 |
第二步:
(\mathtt{500_{10} = 764_{8}})
下表显示了循环每次迭代时发生的情况(假设num = 74
):
循环 | 雷姆 | 容器 | 数字 | 我 |
---|---|---|---|---|
第一次迭代后 | rem=74%8=2 |
oct=2*(10^0)+0=2 |
num=74/8=9 |
i=2 |
第二次迭代后 | rem=9%8=1 |
oct=1*(10^1)+2=12 |
num=9/8=1 |
i=3 |
第三次迭代后 | rem=1%8=1 |
oct=1*(10^2)+12=112 |
num=1/8=0 |
i=4 |
推荐阅读
C 程序:将二进制数转换成十进制数
原文:https://overiq.com/c-examples/c-program-to-convert-a-binary-number-to-a-decimal-number/
最后更新于 2020 年 9 月 24 日
下面是一个将二进制数转换成十进制数的 C 程序。
/*******************************************************
* Program to convert a binary number to decimal number
********************************************************/
#include<stdio.h> // include stdio.h
#include<math.h> // include stdio.h
int main()
{
long int bin, remainder, decimal = 0, i = 0;
printf("Enter a binary number: ");
scanf("%d", &bin);
while(bin != 0)
{
remainder = bin % 10;
decimal += remainder * (int)pow(2, i++);
bin = bin / 10;
}
printf("Decimal: %d", decimal);
return 0;
}
**预期输出:**第一次运行:
Enter a binary number: 100
Decimal: 4
第二次运行:
Enter a binary number: 1000011
Decimal: 67
它是如何工作的
以下是将二进制数转换为十进制数的步骤:
例 1 :将二进制数100
转换为十进制等效值。
=> ( 1 * 2^2 ) + ( 0 * 2^1 ) + ( 0 * 2^0 )
=> 4 + 0 + 0
=> 4
例 2 :将二进制数1001
转换为其十进制等效值。
=> ( 1 * 2^3 ) + ( 0 * 2^2 ) + ( 0 * 2^1 ) + ( 1 * 2^0 )
=> 8 + 0 + 0 + 1
=> 9
下表显示了循环每次迭代时发生的情况(假设bin = 10101
):
循环 | 剩余物 | 小数 | 容器 |
---|---|---|---|
第一次迭代后 | remainder = 10101 % 10 = 1 |
decimal = 0 + 1 * (2^0) = 1 |
bin = 10101 / 10 = 1010 |
第二次迭代后 | remainder = 1010 % 10 = 0 |
decimal = 1 + 0 * (2^1) = 1 |
bin = 1010 / 10 = 101 |
第三次迭代后 | remainder = 101 % 10 = 1 |
decimal = 1 + 1 * (2^2) = 5 |
bin = 101 / 10 = 10 |
第四次迭代后 | remainder = 10 % 10 = 0 |
decimal = 5 + 0 * 2^3 = 5 |
bin = 10 / 10 = 1 |
第五次迭代后 | remainder = 1 % 10 = 1 |
decimal = 5 + 1 * (2^4) = 21 |
bin = 1 / 10 = 0 |
C 程序:将华氏温度转换为摄氏温度
原文:https://overiq.com/c-examples/c-program-to-convert-the-temperature-in-fahrenheit-to-celsius/
最后更新于 2020 年 9 月 24 日
要将华氏温度转换为摄氏温度,我们使用以下公式:
[
cel = \ frac { 5 } { 9 }+[fah-32]t1]]
以下是将华氏温度转换为摄氏温度的 C 程序:
/*****************************************************************
* C Program to convert the temperature in Fahrenheit to Celsius
*
* Formula used: c = (5/9) * (f - 32)
******************************************************************/
#include<stdio.h> // include stdio.h
int main()
{
float fah, cel;
printf("Enter a temp in fah: ");
scanf("%f", &fah);
cel = (5.0/9) * (fah - 32);
printf("%.2f°F is same as %.2f°C", fah, cel);
return 0;
}
**预期输出:**第一次运行:
Enter a temp in fah: 455
455.00°F is same as 235.00°C
第二次运行:
Enter a temp in fah: 32
32.00°F is same as 0.00°C
C 程序:将十进制数转换成罗马数字
原文:https://overiq.com/c-examples/c-program-to-convert-a-decimal-number-to-roman-numerals/
最后更新于 2020 年 9 月 24 日
下面是一个将十进制数转换成罗马数字的 C 程序:
/******************************************************
Program to convert a decimal number to roman numerals
*
* Enter a number: 1996
* Roman numerals: mmxii
*
******************************************************/
#include <stdio.h>
int main(void)
{
int num, rem;
printf("Enter a number: ");
scanf("%d", &num);
printf("Roman numerals: ");
while(num != 0)
{
if (num >= 1000) // 1000 - m
{
printf("m");
num -= 1000;
}
else if (num >= 900) // 900 - cm
{
printf("cm");
num -= 900;
}
else if (num >= 500) // 500 - d
{
printf("d");
num -= 500;
}
else if (num >= 400) // 400 - cd
{
printf("cd");
num -= 400;
}
else if (num >= 100) // 100 - c
{
printf("c");
num -= 100;
}
else if (num >= 90) // 90 - xc
{
printf("xc");
num -= 90;
}
else if (num >= 50) // 50 - l
{
printf("l");
num -= 50;
}
else if (num >= 40) // 40 - xl
{
printf("xl");
num -= 40;
}
else if (num >= 10) // 10 - x
{
printf("x");
num -= 10;
}
else if (num >= 9) // 9 - ix
{
printf("ix");
num -= 9;
}
else if (num >= 5) // 5 - v
{
printf("v");
num -= 5;
}
else if (num >= 4) // 4 - iv
{
printf("iv");
num -= 4;
}
else if (num >= 1) // 1 - i
{
printf("i");
num -= 1;
}
}
return 0;
}
**预期输出:**第一次运行:
Enter a number: 99
Roman numerals: xcix
第二次运行:
Enter a number: 2020
Roman numerals: mmxx
它是如何工作的
下表列出了一些十进制数字及其对应的罗马数字:
小数 | 罗马数字 |
---|---|
one | 我 |
four | 静脉的 |
five | V |
nine | 离子交换 |
Ten | X |
Forty | 特大号 |
Fifty | L |
Ninety | 容抗 |
One hundred | C |
four hundred | 激光唱片 |
Five hundred | D |
Nine hundred | 厘米 |
One thousand | M |
要将十进制数字num
转换为罗马数字,我们执行以下操作:
- 在上表中找出小于或等于小数
num
的最大小数r
。 - 写出十进制数字
r
对应的罗马数字。 - 从
num
中减去r
,并将其分配回num
,即num = num - r
。 - 重复步骤 1、2、3,直到
num
降为0
。
举个例子吧。
示例:将十进制 123 转换为罗马数字
循环 | 数字 | r | 罗马数字 | 数字的新值 |
---|---|---|---|---|
第一次迭代后 | 123 |
100 |
c |
num=123-100=23 |
第二次迭代后 | 23 |
10 |
x |
num=23-10=13 |
第三次迭代后 | 13 |
10 |
x |
num=13-10=3 |
第四次迭代后 | 3 |
1 |
i |
num=3-1=2 |
第五次迭代后 | 2 |
1 |
i |
num=2-1=1 |
第 6 次迭代后 | 1 |
1 |
i |
num=1-1=0 |
于是,123 = cxiii
。
C 程序:检查一年是否是闰年
原文:https://overiq.com/c-examples/c-program-to-check-whether-a-year-is-a-leap-year/
最后更新于 2020 年 9 月 24 日
下面是一个 C 程序,它检查输入的年份是否是闰年。
/******************************************************************
* Program to check whether the entered year is a leap year or not
******************************************************************/
#include<stdio.h> // include stdio.h
int main() {
int year;
printf("Enter a year: ");
scanf("%d", &year);
if( (year % 4 == 0 && year % 100 != 0 ) || (year % 400 == 0) )
{
printf("%d is a leap year", year);
}
else
{
printf("%d is not a leap year", year);
}
return 0;
}
**预期输出:**第一次运行:
Enter a year: 2000
2000 is a leap year
第二次运行:
Enter a year: 1900
1900 is not a leap year
它是如何工作的
为了确定一年是否是闰年,我们使用以下算法:
- 检查年份是否能被 4 整除。如果是,请转到步骤 2,否则,请转到步骤 3。
- 检查年份是否能被 100 整除。如果是,请转到第 3 步,否则,这一年是闰年
- 检查年份是否能被 400 整除。如果是,那么这一年就是闰年,否则,这一年就不是闰年。
C 程序:打印两个日期中较早的一个
原文:https://overiq.com/c-examples/c-program-to-print-the-earlier-of-the-two-dates/
最后更新于 2020 年 9 月 24 日
下面的 C 程序要求用户输入两个日期,并打印两个日期中较早的一个。
/**********************************************
Program to print the earlier of the two dates
*
* Enter first date (MM/DD/YYYY): 20/10/2020
* Enter second date (MM/DD/YYYY): 02/29/2001
*
* 02/29/2001 comes earlier than 20/10/2020
**********************************************/
#include<stdio.h> // include stdio.h library
int check_date(int date, int mon, int year);
int main(void)
{
int day1, mon1, year1,
day2, mon2, year2;
int is_leap = 0, is_valid = 1;
printf("Enter first date (MM/DD/YYYY): ");
scanf("%d/%d/%d", &mon1, &day1, &year1);
printf("Enter second date (MM/DD/YYYY): ");
scanf("%d/%d/%d", &mon2, &day2, &year2);
if(!check_date(day1, mon1, year1))
{
printf("First date is invalid.\n");
}
if(!check_date(day2, mon2, year2))
{
printf("Second date is invalid.\n");
exit(0);
}
if(year1 > year2)
{
printf("%02d/%02d/%d comes earlier than %02d/%02d/%d",
mon2, day2, year2, mon1, day1, year1);
}
else if (year1 < year2)
{
printf("%02d/%02d/%d comes earlier than %02d/%02d/%d",
mon1, day1, year1, mon2, day2, year2);
}
// year1 == year2
else
{
if (mon1 == mon2)
{
if (day1 == day2)
{
printf("Both dates are the same.");
}
else if(day1 > day2)
{
printf("%02d/%02d/%d comes earlier than %02d/%02d/%d",
mon2, day2, year2, mon1, day1, year1);
}
else
{
printf("%02d/%02d/%d comes earlier than %02d/%02d/%d",
mon1, day1, year1, mon2, day2, year2);
}
}
else if (mon1 > mon2)
{
printf("%02d/%02d/%d comes earlier than %02d/%02d/%d",
mon2, day2, year2, mon1, day1, year1);
}
else
{
printf("%02d/%02d/%d comes earlier than %02d/%02d/%d",
mon1, day1, year1, mon2, day2, year2);
}
}
return 0; // return 0 to operating system
}
// function to check whether a date is valid or not
int check_date(int day, int mon, int year)
{
int is_valid = 1, is_leap = 0;
if (year >= 1800 && year <= 9999)
{
// check whether year is a leap year
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
is_leap = 1;
}
// check whether mon is between 1 and 12
if(mon >= 1 && mon <= 12)
{
// check for days in feb
if (mon == 2)
{
if (is_leap && day == 29)
{
is_valid = 1;
}
else if(day > 28)
{
is_valid = 0;
}
}
// check for days in April, June, September and November
else if (mon == 4 || mon == 6 || mon == 9 || mon == 11)
{
if (day > 30)
{
is_valid = 0;
}
}
// check for days in rest of the months
// i.e Jan, Mar, May, July, Aug, Oct, Dec
else if(day > 31)
{
is_valid = 0;
}
}
else
{
is_valid = 0;
}
}
else
{
is_valid = 0;
}
return is_valid;
}
**预期输出:**第一次运行:
Enter the first date (MM/DD/YYYY): 05/10/2016
Enter the second date (MM/DD/YYYY): 04/09/2016
04/09/2016 comes earlier than 05/10/2016
第二次运行:
Enter the first date (MM/DD/YYYY): 02/01/2008
Enter the second date (MM/DD/YYYY): 02/01/2008
Both dates are the same.
它是如何工作的
程序首先要求用户输入两个日期。
在第 27 行和第 32 行,我们使用用户定义的check_date()
函数检查输入的日期是否有效。要了解更多关于check_date()
功能如何工作的信息,请访问此链接。
如果其中一个日期无效,程序将终止,并显示相应的错误消息。
在第 39-85 行,我们有一个 if-else 语句来比较这两个日期。比较日期的算法如下:
- 如果
year1 > year2
,第二次约会比第一次来得早。 - 如果
year1 < year2
,第一次约会比第二次来得早。 - 如果
year1 == year2
。以下情况是可能的:- 如果
mon1 == mon2
,可能出现以下情况:- 如果
day1 == day2
,两个日期相同。 - 如果
day1 > day2
,那么第二次约会比第一次来得早。 - 如果
day1 < day2
,那么第一次约会比第二次来得早。
- 如果
- 如果
mon1 > mon2
,第二次约会比第一次来得早。 - 否则,
mon1 < mon2
和第一个日期比第二个来得早。
- 如果
C 程序:打印两个日期中较早的一个
原文:https://overiq.com/c-examples/c-program-to-print-the-earlier-of-the-two-dates/
最后更新于 2020 年 9 月 24 日
下面的 C 程序要求用户输入两个日期,并打印两个日期中较早的一个。
/**********************************************
Program to print the earlier of the two dates
*
* Enter first date (MM/DD/YYYY): 20/10/2020
* Enter second date (MM/DD/YYYY): 02/29/2001
*
* 02/29/2001 comes earlier than 20/10/2020
**********************************************/
#include<stdio.h> // include stdio.h library
int check_date(int date, int mon, int year);
int main(void)
{
int day1, mon1, year1,
day2, mon2, year2;
int is_leap = 0, is_valid = 1;
printf("Enter first date (MM/DD/YYYY): ");
scanf("%d/%d/%d", &mon1, &day1, &year1);
printf("Enter second date (MM/DD/YYYY): ");
scanf("%d/%d/%d", &mon2, &day2, &year2);
if(!check_date(day1, mon1, year1))
{
printf("First date is invalid.\n");
}
if(!check_date(day2, mon2, year2))
{
printf("Second date is invalid.\n");
exit(0);
}
if(year1 > year2)
{
printf("%02d/%02d/%d comes earlier than %02d/%02d/%d",
mon2, day2, year2, mon1, day1, year1);
}
else if (year1 < year2)
{
printf("%02d/%02d/%d comes earlier than %02d/%02d/%d",
mon1, day1, year1, mon2, day2, year2);
}
// year1 == year2
else
{
if (mon1 == mon2)
{
if (day1 == day2)
{
printf("Both dates are the same.");
}
else if(day1 > day2)
{
printf("%02d/%02d/%d comes earlier than %02d/%02d/%d",
mon2, day2, year2, mon1, day1, year1);
}
else
{
printf("%02d/%02d/%d comes earlier than %02d/%02d/%d",
mon1, day1, year1, mon2, day2, year2);
}
}
else if (mon1 > mon2)
{
printf("%02d/%02d/%d comes earlier than %02d/%02d/%d",
mon2, day2, year2, mon1, day1, year1);
}
else
{
printf("%02d/%02d/%d comes earlier than %02d/%02d/%d",
mon1, day1, year1, mon2, day2, year2);
}
}
return 0; // return 0 to operating system
}
// function to check whether a date is valid or not
int check_date(int day, int mon, int year)
{
int is_valid = 1, is_leap = 0;
if (year >= 1800 && year <= 9999)
{
// check whether year is a leap year
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
is_leap = 1;
}
// check whether mon is between 1 and 12
if(mon >= 1 && mon <= 12)
{
// check for days in feb
if (mon == 2)
{
if (is_leap && day == 29)
{
is_valid = 1;
}
else if(day > 28)
{
is_valid = 0;
}
}
// check for days in April, June, September and November
else if (mon == 4 || mon == 6 || mon == 9 || mon == 11)
{
if (day > 30)
{
is_valid = 0;
}
}
// check for days in rest of the months
// i.e Jan, Mar, May, July, Aug, Oct, Dec
else if(day > 31)
{
is_valid = 0;
}
}
else
{
is_valid = 0;
}
}
else
{
is_valid = 0;
}
return is_valid;
}
**预期输出:**第一次运行:
Enter the first date (MM/DD/YYYY): 05/10/2016
Enter the second date (MM/DD/YYYY): 04/09/2016
04/09/2016 comes earlier than 05/10/2016
第二次运行:
Enter the first date (MM/DD/YYYY): 02/01/2008
Enter the second date (MM/DD/YYYY): 02/01/2008
Both dates are the same.
它是如何工作的
程序首先要求用户输入两个日期。
在第 27 行和第 32 行,我们使用用户定义的check_date()
函数检查输入的日期是否有效。要了解更多关于check_date()
功能如何工作的信息,请访问此链接。
如果其中一个日期无效,程序将终止,并显示相应的错误消息。
在第 39-85 行,我们有一个 if-else 语句来比较这两个日期。比较日期的算法如下:
- 如果
year1 > year2
,第二次约会比第一次来得早。 - 如果
year1 < year2
,第一次约会比第二次来得早。 - 如果
year1 == year2
。以下情况是可能的:- 如果
mon1 == mon2
,可能出现以下情况:- 如果
day1 == day2
,两个日期相同。 - 如果
day1 > day2
,那么第二次约会比第一次来得早。 - 如果
day1 < day2
,那么第一次约会比第二次来得早。
- 如果
- 如果
mon1 > mon2
,第二次约会比第一次来得早。 - 否则,
mon1 < mon2
和第一个日期比第二个来得早。
- 如果
C 程序:计算两个年月日的日期之差
最后更新于 2020 年 9 月 24 日
下面是一个计算年、月、日两个日期差的 C 程序。确保开始日期早于结束日期。
/*******************************************************************
Program to calculate the number of days, months and years
between two dates dates
*
* Enter first date (MM/DD/YYYY): 05/20/2004
* Enter second date (MM/DD/YYYY): 06/05/2008
*
* Difference: 4 years 00 months and 15 days.
*******************************************************************/
#include<stdio.h> // include stdio.h library
#include<stdlib.h> // include stdlib.h library
int valid_date(int date, int mon, int year);
int main(void)
{
int day1, mon1, year1,
day2, mon2, year2;
int day_diff, mon_diff, year_diff;
printf("Enter start date (MM/DD/YYYY): ");
scanf("%d/%d/%d", &mon1, &day1, &year1);
printf("Enter end date (MM/DD/YYYY): ");
scanf("%d/%d/%d", &mon2, &day2, &year2);
if(!valid_date(day1, mon1, year1))
{
printf("First date is invalid.\n");
}
if(!valid_date(day2, mon2, year2))
{
printf("Second date is invalid.\n");
exit(0);
}
if(day2 < day1)
{
// borrow days from february
if (mon2 == 3)
{
// check whether year is a leap year
if ((year2 % 4 == 0 && year2 % 100 != 0) || (year2 % 400 == 0))
{
day2 += 29;
}
else
{
day2 += 28;
}
}
// borrow days from April or June or September or November
else if (mon2 == 5 || mon2 == 7 || mon2 == 10 || mon2 == 12)
{
day2 += 30;
}
// borrow days from Jan or Mar or May or July or Aug or Oct or Dec
else
{
day2 += 31;
}
mon2 = mon2 - 1;
}
if (mon2 < mon1)
{
mon2 += 12;
year2 -= 1;
}
day_diff = day2 - day1;
mon_diff = mon2 - mon1;
year_diff = year2 - year1;
printf("Difference: %d years %02d months and %02d days.", year_diff, mon_diff, day_diff);
return 0; // return 0 to operating system
}
// function to check whether a date is valid or not
int valid_date(int day, int mon, int year)
{
int is_valid = 1, is_leap = 0;
if (year >= 1800 && year <= 9999)
{
// check whether year is a leap year
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
is_leap = 1;
}
// check whether mon is between 1 and 12
if(mon >= 1 && mon <= 12)
{
// check for days in feb
if (mon == 2)
{
if (is_leap && day == 29)
{
is_valid = 1;
}
else if(day > 28)
{
is_valid = 0;
}
}
// check for days in April, June, September and November
else if (mon == 4 || mon == 6 || mon == 9 || mon == 11)
{
if (day > 30)
{
is_valid = 0;
}
}
// check for days in rest of the months
// i.e Jan, Mar, May, July, Aug, Oct, Dec
else if(day > 31)
{
is_valid = 0;
}
}
else
{
is_valid = 0;
}
}
else
{
is_valid = 0;
}
return is_valid;
}
预期输出:
第一次运行:
Enter start date (MM/DD/YYYY): 08/05/2001
Enter end date (MM/DD/YYYY): 08/20/2001
Difference: 0 years 00 months and 15 days.
第二次运行:
Enter start date (MM/DD/YYYY): 10/11/2005
Enter end date (MM/DD/YYYY): 05/20/2016
Difference: 10 years 07 months and 09 days.
它是如何工作的
用年、月、日来计算两个日期之差的过程很简单。我们需要做的就是分别从结束日期的日、月、年中减去开始日期的日、月、年。
date_diff = day2 - day1
mon_diff = mon2 - mon1
year_diff = year2 - year1
请注意,这里我们假设开始日期小于结束日期,并且天数、月数和年数的差值将为正值。
然而,有一个问题。
如果天数和月数的差异不是正值呢?
例如,考虑以下两个日期:
08/25/2001 (25 Aug 2001)
05/05/2005 (05 May 2005)
在这种情况下,第一个日期小于第二个日期,但天数和月数的差异不是正的。
date_diff = day2 - day1
date_diff = 5 - 25
date_diff = -20
mon_diff = mon2 - mon1
mon_diff = 5 - 8
mon_diff = -3
为了处理这种情况,我们执行以下操作:
如果day2 < day1
,我们借用mon2
之前的一个月,将该月的天数加到day2
。比如mon2 == 09
即 9 月,那么我们借月 8 月,把 8 月的天数加到day2
上。
day2 = day2 + 30
。(8 月有 30 天)
此外,由于我们借了一个月,我们必须从mon2
中减去1
。
mon2 = m2 - 1
同样的,如果mon2 < mon1
,那么我们借一个 1 年,也就是 12 个月,再加上这个多月到mon2
。
mon2 = mon + 12
同样,就像月份一样,我们必须从year
中减去1
。
year2 = year2 - 1
C 程序:计算从日期开始的一年中的某一天
原文:https://overiq.com/c-examples/c-program-to-calculate-the-day-of-year-from-the-date/
最后更新于 2020 年 9 月 24 日
下面是一个从日期开始计算一年中某一天的 C 程序:
/*************************************************
Program to calculate day of year from the date
*
* Enter date (MM/DD/YYYY): 12/30/2006
* Day of year: 364
*
************************************************/
#include<stdio.h> // include stdio.h library
int main(void)
{
int day, mon, year, days_in_feb = 28,
doy; // day of year
printf("Enter date (MM/DD/YYYY): ");
scanf("%d/%d/%d", &mon, &day, &year);
doy = day;
// check for leap year
if( (year % 4 == 0 && year % 100 != 0 ) || (year % 400 == 0) )
{
days_in_feb = 29;
}
switch(mon)
{
case 2:
doy += 31;
break;
case 3:
doy += 31+days_in_feb;
break;
case 4:
doy += 31+days_in_feb+31;
break;
case 5:
doy += 31+days_in_feb+31+30;
break;
case 6:
doy += 31+days_in_feb+31+30+31;
break;
case 7:
doy += 31+days_in_feb+31+30+31+30;
break;
case 8:
doy += 31+days_in_feb+31+30+31+30+31;
break;
case 9:
doy += 31+days_in_feb+31+30+31+30+31+31;
break;
case 10:
doy += 31+days_in_feb+31+30+31+30+31+31+30;
break;
case 11:
doy += 31+days_in_feb+31+30+31+30+31+31+30+31;
break;
case 12:
doy += 31+days_in_feb+31+30+31+30+31+31+30+31+30;
break;
}
printf("Day of year: %d", doy);
return 0; // return 0 to operating system
}
**预期输出:**第一次运行:
Enter date (MM/DD/YYYY): 03/05/2000
Day of year: 65
第二次运行:
Enter date (MM/DD/YYYY): 12/25/2018
Day of year: 359
它是如何工作的
一年中的某一天是一个介于 1 和 365 之间的数字(如果是闰年,则为 366)。例如,1 月 1 日是第 1 天,2 月 5 日是第 36 天,以此类推。
为了计算一年中的某一天,我们只需将给定月份中的天数与前几个月中的天数相加。
推荐阅读:
C 程序:以有效形式打印日期
原文:https://overiq.com/c-examples/c-program-to-print-the-date-in-legal-form/
最后更新于 2020 年 9 月 24 日
下面是一个用有效格式打印日期的 C 程序:
/******************************************
Program to print the date in legal form
*
* Enter date(MM/DD/YYY): 05/25/2018
* 25th May, 2018
******************************************/
#include<stdio.h> // include stdio.h library
int main(void)
{
int day, mon, year;
char *months[] = {
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December",
};
printf("Enter date(MM/DD/YYY): ");
scanf("%d/%d/%d", &mon, &day, &year);
printf("%d", day);
// if day is 1 or 21 or 31, add the suffix "st"
if(day == 1 || day == 21 || day == 31)
{
printf("st ");
}
// if day is 2 or 22, add the suffix "nd"
else if(day == 2 || day == 22)
{
printf("nd ");
}
// if day is 3 or 23, add the suffix "rd"
else if(day == 3 || day == 23)
{
printf("rd ");
}
// for everything else add the suffix "th"
else
{
printf("th ");
}
printf("%s, %d", months[mon - 1], year);
return 0;
}
**预期输出:**第一次运行:
Enter date(MM/DD/YYY): 10/21/2005
21st October, 2005
第二次运行:
Enter date(MM/DD/YYY): 04/23/2012
23rd April, 2012
它是如何工作的
在非正式写作中,日期中的数字通常用/
或-
隔开。例如:
05/11/15
05-11-2015
然而,在法律和正式文件中,日期是完整的。例如:
(21^)2012 年 12 月
(21^),2012 年
以下是上述程序的工作原理:
- 程序主体首先定义三个变量:
day
、mon
和year
。 - 在第 16-20 行中,我们定义了一个字符指针数组,其中包含我们想要显示的月份名称。
- 接下来,我们要求用户输入日期。
- 第 27-49 行的 if-else 语句打印日后缀(即“st”、“nd”、“rd”或“th”)
- 最后,第 51 行的打印语句打印月份和年份。
推荐阅读:
C 程序:打印各种三角形图案
原文:https://overiq.com/c-examples/c-program-to-print-various-triangular-patterns/
最后更新于 2020 年 9 月 24 日
图案 1:使用*的半金字塔图案
*
* *
* * *
* * * *
* * * * *
* * * * * *
* * * * * * *
* * * * * * * *
* * * * * * * * *
* * * * * * * * * *
下面是一个使用*打印半金字塔图案的 C 程序:
/**************************************************
* C Program to print Half Pyramid pattern using *
**************************************************/
#include<stdio.h> // include stdio.h
int main()
{
int n;
printf("Enter number of lines: ");
scanf("%d", &n);
printf("\n");
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= i; j++)
{
printf("*");
}
printf("\n");
}
return 0;
}
预期输出:
Enter number of lines: 5
*
* *
* * *
* * * *
* * * * *
模式 2:使用数字的半金字塔模式
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
1 2 3 4 5 6
1 2 3 4 5 6 7
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9 10
下面是一个用数字打印半金字塔图案的 C 程序:
/********************************************************
* C Program to print Half Pyramid pattern using numbers
********************************************************/
#include<stdio.h> // include stdio.h
int main()
{
int n;
printf("Enter number of lines: ");
scanf("%d", &n);
printf("\n");
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= i; j++)
{
printf("%-5d ", j);
}
printf("\n");
}
return 0;
}
预期输出:
Enter number of lines: 5
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
模式 3:使用字母的半金字塔模式
A
B B
C C C
D D D D
E E E E E
F F F F F F
G G G G G G G
下面是一个用字母打印半金字塔图案的 C 程序:
/**********************************************************
* C Program to print Half Pyramid pattern using alphabets
**********************************************************/
#include<stdio.h> // include stdio.h
int main()
{
int n, ch = 'A';
printf("Enter number of lines: ");
scanf("%d", &n);
printf("\n");
// loop for line number of lines
for(int i = 1; i <= n; i++)
{
// loop to print alphabets
for(int j = 1; j <= i; j++)
{
printf(" %c", ch);
}
ch++;
printf("\n");
}
return 0;
}
预期输出:
Enter number of lines: 5
A
B B
C C C
D D D D
E E E E E
图案 4:使用*的倒直角三角形图案
* * * * * * * *
* * * * * * *
* * * * * *
* * * * *
* * * *
* * *
* *
*
下面是一个使用*
打印倒直角三角形的 C 程序:
/*****************************************************
* C Program to print inverted right triangle pattern
******************************************************/
#include<stdio.h> // include stdio.h
int main()
{
int n;
printf("Enter number of lines: ");
scanf("%d", &n);
printf("\n");
// loop for line number of lines
for(int i = n; i >= 1; i--)
{
// loop to print *
for(int j = i; j >= 1; j--)
{
printf("* ");
}
printf("\n");
}
return 0;
}
预期输出:
Enter number of lines: 5
* * * * *
* * * *
* * *
* *
*
模式 5:全金字塔模式使用*
.
*
* * *
* * * * *
* * * * * * *
* * * * * * * * *
* * * * * * * * * * *
* * * * * * * * * * * * *
* * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * *
下面是一个使用*
打印全金字塔图案的 C 程序:
/*******************************************
* C Program to print full pyramid using *
********************************************/
#include<stdio.h> // include stdio.h
int main()
{
int n;
printf("Enter number of lines: ");
scanf("%d", &n);
printf("\n");
// loop for line number of lines
for(int i = 1; i <= n; i++)
{
// loop to print leading spaces in each line
for(int space = 0; space <= n - i; space++)
{
printf(" ");
}
// loop to print *
for(int j = 1; j <= i * 2 - 1; j++)
{
printf(" * ");
}
printf("\n");
}
return 0;
}
预期输出:
Enter number of lines: 8
*
* * *
* * * * *
* * * * * * *
* * * * * * * * *
* * * * * * * * * * *
* * * * * * * * * * * * *
* * * * * * * * * * * * * * *
图案 6:使用*的全倒金字塔图案
* * * * * * * * * * * * * * *
* * * * * * * * * * * * *
* * * * * * * * * * *
* * * * * * * * *
* * * * * * *
* * * * *
* * *
*
下面是一个使用*
打印全倒金字塔图案的 C 程序:
/***************************************************
* C Program to print full inverted pyramid using *
****************************************************/
#include<stdio.h> // include stdio.h
int main()
{
int n;
printf("Enter number of lines: ");
scanf("%d", &n);
printf("\n");
// loop for line number of lines
for(int i = n; i >= 1; i--)
{
// loop to print leading spaces in each line
for(int space = n-i; space >= 1; space--)
{
printf(" ");
}
// loop to print *
for(int j = i * 2 - 1; j >= 1; j--)
{
printf(" * ");
}
printf("\n");
}
return 0;
}
预期输出:
Enter number of lines: 10
* * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * *
* * * * * * * * * * * * *
* * * * * * * * * * *
* * * * * * * * *
* * * * * * *
* * * * *
* * *
*
图案 7:使用*的空心直角三角形
*
* *
* *
* *
* *
* *
* *
* * * * * * * *
下面是一个使用*打印空心直角三角形的 C 程序:
/**********************************************************
* C Program to print hollow right angled triangle using *
***********************************************************/
#include<stdio.h> // include stdio.h
int main()
{
int n;
printf("Enter number of lines: ");
scanf("%d", &n);
printf("\n");
// loop for number of lines
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= i; j++)
{
// print * only on the first line, last column of every line and on the last line
if(j == 1 || j == i || i == n)
{
printf("* ");
}
else
{
printf(" ");
}
}
printf("\n");
}
return 0;
}
预期输出:
Enter number of lines: 10
*
* *
* *
* *
* *
* *
* *
* *
* *
* * * * * * * * * *
图案 8:使用*的倒置空心直角三角形
* * * * * * * *
* *
* *
* *
* *
* *
* *
*
下面是一个使用*
打印倒空心直角三角形的 C 程序:
/*******************************************************************
* C Program to print inverted hollow right angled triangle using *
********************************************************************/
#include<stdio.h> // include stdio.h
int main()
{
int n;
printf("Enter number of lines: ");
scanf("%d", &n);
printf("\n");
// loop for number of lines
for(int i = n; i >= 1; i--)
{
for(int j = i; j >= 1; j--)
{
// print * only on the first line, last line and last column of every line and on the
if(j == 1 || j == i || i == n)
{
printf("* ");
}
else
{
printf(" ");
}
}
printf("\n");
}
return 0;
}
预期输出:
Enter number of lines: 10
* * * * * * * * * *
* *
* *
* *
* *
* *
* *
* *
* *
*
图案 9:全空心金字塔使用*
.
*
* *
* *
* *
* *
* *
* *
* * * * * * * * * * * * * * *
下面是一个使用*
打印全空心金字塔的 C 程序:
/*************************************************
* C Program to print full hollow pyramid using *
*************************************************/
#include<stdio.h> // include stdio.h
int main()
{
int n;
printf("Enter number of lines: ");
scanf("%d", &n);
printf("\n");
// loop for line number of lines
for(int i = 1; i <= n; i++)
{
// loop to print leading spaces in each line
for(int space = 0; space <= n - i; space++)
{
printf(" ");
}
// loop to print *
for(int j = 1; j <= i * 2 - 1; j++)
{
if (j == 1 || (j == i * 2 - 1) || i == n )
{
printf(" * ");
}
else
{
printf(" ");
}
}
printf("\n");
}
return 0;
}
预期输出:
Enter number of lines: 10
*
* *
* *
* *
* *
* *
* *
* *
* *
* * * * * * * * * * * * * * * * * * *
图案 10:全倒置空心金字塔使用*
* * * * * * * * * * * * * * *
* *
* *
* *
* *
* *
* *
*
下面是一个使用*
打印全空心倒金字塔的 C 程序:
/**********************************************************
* C Program to print full inverted hollow pyramid using *
**********************************************************/
#include<stdio.h> // include stdio.h
int main() {
int n;
printf("Enter number of lines: ");
scanf("%d", &n);
printf("\n");
// loop for line number of lines
for (int i = n; i >= 1; i--) {
// loop to print leading spaces in each line
for (int space = n - i; space >= 1; space--) {
printf(" ");
}
// loop to print *
for (int j = i * 2 - 1; j >= 1; j--)
{
if (j == 1 || (j == i * 2 - 1) || i == n)
{
printf(" * ");
}
else
{
printf(" ");
}
}
printf("\n");
}
return 0;
}
预期输出:
Enter number of lines: 10
* * * * * * * * * * * * * * * * * * *
* *
* *
* *
* *
* *
* *
* *
* *
*
图案 11:菱形图案使用*
.
*
* * *
* * * * *
* * * * * * *
* * * * * * * * *
* * * * * * * * * * *
* * * * * * * * *
* * * * * * *
* * * * *
* * *
*
下面是一个使用*
打印钻石图案的 C 程序:
/*********************************************
* C Program to print Diamond pattern using *
**********************************************/
#include<stdio.h> // include stdio.h
int main()
{
int n;
printf("Enter number of lines: ");
scanf("%d", &n);
printf("\n");
// loop to print upper pyramid
for(int i = 1; i <= n; i++)
{
// loop to print leading spaces in each line
for(int space = 0; space <= n - i; space++)
{
printf(" ");
}
// loop to print *
for(int j = 1; j <= i * 2 - 1; j++)
{
printf(" * ");
}
printf("\n");
}
// loop to print lower pyramid
for(int i = n+1; i >= 1; i--)
{
// loop to print leading spaces in each line
for(int space = n-i; space >= 0; space--)
{
printf(" ");
}
// loop to print *
for(int j = i * 2 - 1; j >= 1; j--)
{
printf(" * ");
}
printf("\n");
}
return 0;
}
预期输出:
Enter number of lines: 8
*
* * *
* * * * *
* * * * * * *
* * * * * * * * *
* * * * * * * * * * *
* * * * * * * * * * * * *
* * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * *
* * * * * * * * * * * * *
* * * * * * * * * * *
* * * * * * * * *
* * * * * * *
* * * * *
* * *
*
推荐节目:
C 程序:打印帕斯卡三角形
原文:https://overiq.com/c-examples/c-program-to-print-pascal-triangle/
最后更新于 2020 年 9 月 24 日
什么是帕斯卡三角?
帕斯卡三角形是二项式系数的三角形阵列。看起来是这样的:
row -> 0 1
row -> 1 1 1
row -> 2 1 2 1
row -> 3 1 3 3 1
row -> 4 1 4 6 4 1
row -> 5 1 5 10 10 5 1
以下是计算三角形中任何给定位置的值的公式:
[
\ begin n \ \ k \ end = \ frac { n!}{k!(n-k)!}
]
其中n
代表行号,k
代表列号。请注意,这些行从0
开始,最左边的一列是0
。因此,为了找出第 4 行第 2 列的值,我们这样做:
[
\ begin 4 \ \ 2 \ end = \ frac { 4!}{2!(4-2)!} = \frac{4!}{2!2 号!} = \frac{43}{21} = 6
]
下面是一个 C 程序,它根据用户输入的行数打印帕斯卡三角形:
/**************************************
* C Program to print Pascal Triangle
***************************************/
#include<stdio.h> // include stdio.h
unsigned long int factorial(unsigned long int);
int main()
{
int n;
printf("Enter number of rows: ");
scanf("%d", &n);
printf("\n");
// loop for number of rows
for(int i = 0; i <= n; i++)
{
// loop to print leading spaces at each line
for(int space = 0; space < n - i; space++)
{
printf(" ");
}
// loop to print numbers in each row
for(int j = 0; j <= i; j++)
{
printf("%-5d ", factorial(i) / (factorial(j) * factorial(i-j) ) );
}
// print newline character
printf("\n");
}
return 0;
}
unsigned long int factorial(unsigned long int n)
{
unsigned long int f = 1;
while(n > 0)
{
f = f * n;
n--;
}
return f;
}
预期输出:
Enter number of rows: 10
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1
1 10 45 120 210 252 210 120 45 10 1
推荐阅读:
C 程序:打印弗洛伊德三角形
原文:https://overiq.com/c-examples/c-program-to-print-floyds-triangle/
最后更新于 2020 年 9 月 24 日
弗洛伊德的三角形是这样的:
1
2 3
4 5 6
7 8 9 10
11 12 13 14 15
16 17 18 19 20 21
下面是一个打印弗洛伊德三角形的 C 程序:
/***************************************
* C Program to print Floyd's Triangle
***************************************/
#include<stdio.h> // include stdio.h
int main()
{
int n, k = 1;
printf("Enter number of lines: ");
scanf("%d", &n);
printf("\n");
// loop for number of lines
for(int i = 1; i <= n; i++)
{
//loop to print numbers in each line
for(int j = 1; j <= i; j++)
{
printf("%-5d ", k++);
}
printf("\n");
}
return 0;
}
预期输出:
Enter number of lines: 5
1
2 3
4 5 6
7 8 9 10
11 12 13 14 15
推荐阅读:
Python 教程
Python 入门
最后更新于 2020 年 9 月 6 日
Python 是由 Guido Van Rossum 创建的高级通用编程语言。它于 1991 年公开发行。所谓高级,我们指的是一种对程序员隐藏细节的语言。此外,它还用来指人类容易理解的计算机语言。Python 以其简单性和可读性而闻名。
Python 的特性
Python 很简单
Python 是最容易上手的语言之一。用 Python 编写的程序看起来非常像英语。由于其简单性,大多数入门级编程课程都使用 Python 向学生介绍编程概念。
Python 是可移植的/独立于平台的
Python 是可移植的,这意味着我们可以在各种不同的操作系统中运行 Python 程序,而无需任何更改。
Python 是一种解释语言
Python 是一种解释语言。像 C、C++这样的语言是编译语言的例子。
用高级语言编写的程序称为源代码或源程序,源代码中的命令称为语句。一台计算机不能执行用高级语言编写的程序,它只能理解仅由 0 和 1 组成的机器语言。
有两种类型的程序可供我们将高级语言翻译成机器语言:
- 编译程序
- 解释者
编译程序
编译器一次将整个源代码翻译成机器语言,然后执行机器语言。
解释者
另一方面,解释器将高级语言逐行翻译成机器语言,然后执行。Python 解释器从文件顶部开始,将第一行翻译成机器语言,然后执行。这个过程一直重复,直到文件结束。
像 C、C++这样的编译语言使用编译器将高级代码翻译成机器语言,而像 Python 这样的解释语言使用解释器将高级代码翻译成机器语言。
编译语言和解释语言的另一个重要区别是,编译语言的性能略好于使用解释语言编写的程序。然而,我确实想指出编译语言的这种优势正在慢慢消失。
Python 是强类型的
强类型语言不会自动将数据从一种类型转换为另一种类型。像 JavaScript 和 PHP 这样的语言被称为松散类型语言,因为它们可以自由地将数据从一种类型转换为另一种类型。考虑以下 JavaScript 代码:
price = 12
str = "The total price = " + 12
console.log(str)
输出:
The total price = 12
在这种情况下,在将12
添加到字符串之前;JavaScript 首先将数字12
转换为字符串"12"
,然后将其附加到字符串的末尾。
然而,在 Python 中,像str = "The total price = " + 12
这样的语句会产生错误,因为 Python 不会自动将数字12
转换为字符串。
一大套图书馆
Python 有大量的库,这使得添加新功能变得容易,而无需重新发明轮子。我们可以在 https://pypi.python.org/pypi 访问这些图书馆。
下面是我从初级 Python 程序员那里听到的两个常见问题。
我可以使用 Python 创建什么类型的应用?
我们可以使用 Python 创建以下类型的应用:
- Web 应用
- 安卓应用
- 图形用户界面应用
- 比赛
- 科学应用
- 系统管理应用
- 控制台应用
名单还在继续...
谁用 Python?
以下是使用 Python 的知名公司的小列表:
- 收纳盒
- 唱片公司
- Quora
- 浏览器名
- 谷歌
- 油管(国外视频网站)
我想这足以让你印象深刻。
在下一课中,我们将学习如何安装 Python。
**注:**本教程源代码可在https://github.com/overiq/intro-to-python获得。
安装 Python
最后更新于 2020 年 7 月 27 日
本课将指导您完成在 Windows、Ubuntu 和 Mac OS 中安装 Python 的过程。虽然整个教程是面向 Python 3.4 的,但是如果您选择使用高于 3.4 的 Python,那就完全没问题了。只是不要用 Python 2。
在 Windows 上安装 Python
要在 Windows 中安装 Python,请前往https://www.python.org/downloads/release/python-344/,向下滚动页面,直到看到“文件”标题。在那里你可以找到不同版本的 Python,你可以下载。
如果您使用的是 32 位版本的 Windows,请下载 Windows x86 安装程序,如果您使用的是 64 位版本的 Windows,请下载 Windows x86-64 安装程序。请注意,Windows x86 安装程序将在 32 位和 64 位上工作。
下载安装程序后,双击它开始安装过程。安装非常简单,只需接受所有默认值。安装过程中唯一关键的一步是当您被要求定制 Python 安装时。
当此窗口出现时,使用滚动条向下滚动列表。在最后一项,即“将 python.exe 添加到路径”前面,使用下拉菜单选择“将安装在本地硬盘上”。该选项允许我们从命令提示符下的任何工作目录调用python.exe
,而无需指定其完整路径。
要验证安装,请打开命令提示符并输入以下命令。
C:\Users\Q>python --version
Python 3.4.4
C:\Users\Q>
在 Ubuntu 中安装 Python
像大多数 Linux 发行版一样,Ubuntu 预装了 Python。事实上,现在的一些 Linux 发行版默认安装了 Python 2 和 Python 3。要测试机器上的 Python 版本,请执行以下命令。
q@vm:~$ python3 --version
Python 3.5.2
q@vm:~$
如您所见,我的 Ubuntu 安装有 Python 3.5,这意味着我可以开始了。但是,如果您得到如下输出:
q@vm:~$ python3 --version
python3: command not found
这意味着您的计算机上没有安装 Python 3。要安装 Python 3.4,请在终端中逐一执行以下命令。
q@vm:~$: sudo add-apt-repository ppa:fkrull/deadsnakes
q@vm:~$: sudo apt-get update
q@vm:~$: sudo apt-get install python3.4
执行完这些命令后,再次运行python3 --version
。这次您应该会得到以下输出:
q@vm:~$ python3 --version
Python 3.4.5
q@vm:~$
在苹果电脑上安装 Python
大多数苹果电脑已经安装了 Python 2。要在 Mac 上安装 Python 3.4,请访问https://www.python.org/downloads/release/python-344/并向下滚动到“文件”标题下的底部,选择与您的 Mac OS 版本相对应的安装程序。
如果您使用的是 Mac OS X 32 位系统,请下载 Mac OS X 32 位 i386/PPC 安装程序,如果您使用的是 Mac OS X 64 位,请下载 Mac OS X 64 位/32 位安装程序。在 64 位系统上,两个安装程序都可以工作。
双击下载的文件开始安装过程,并接受所有默认值。与 Windows 不同的是,Macs 的 Python 安装程序会自动将python
添加到 PATH 环境变量中,因此您不需要做任何事情。要验证安装,请执行以下命令。
Qs-Mac:~ q$ python3 --version
Python 3.4.4
Qs-Mac:~ q$
IDLE 和 Python Shell
最后更新于 2020 年 9 月 6 日
在本课程中,我们将使用 Python 3.4,但是如果您选择使用 Python 3.4 或更高版本,那就没问题了。
在最后一章,我们已经安装了 Python 解释器。解释器是一个把你的代码翻译成机器语言,然后一行一行执行的程序。
我们可以在两种模式下使用 Python 解释器:
- 互动模式。
- 脚本模式。
在交互模式下,Python 解释器等待您输入命令。当您键入命令时,Python 解释器会继续执行该命令,然后它会再次等待您的下一个命令。
在脚本模式下,Python 解释器从源文件运行程序。
对话方式
交互模式下的 Python 解释器俗称 Python Shell。要启动 Python Shell,请在终端或命令提示符下输入以下命令:
C:\Users\Q>python
Python 3.4.4 (v3.4.4:737efcadf5a6, Dec 20 2015, 20:20:57) [MSC v.1600 64 bit (AM
D64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
例如,如果您的系统同时安装了 Python 2 和 Python 3,Ubuntu 默认情况下会安装 Python 2 和 3。要启动 Python 3 Shell,请输入python3
而不仅仅是python
。
q@vm:~$ python3
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
你现在看到的叫做 Python Shell。>>>
被称为提示字符串,它只是意味着 Python shell 已经准备好接受你的命令了。Python shell 允许您键入 Python 代码,并立即看到结果。在技术术语中,这也被称为 REPL,是读-评估-打印-循环的缩写。每当你听到 REPL 的话,你就会想到一个环境,它可以让你快速测试代码片段,并立即看到结果,就像一个计算器一样。在 Python shell 中,逐个输入以下计算,然后按 enter 键即可获得结果。
>>>
>>> 88 + 4
92
>>>
>>> 45 * 4
180
>>>
>>> 17 / 3
5.666666666666667
>>>
>>> 78 - 42
36
>>>
在 Python 中,我们使用print()
函数在屏幕上打印一些东西。
在 Python shell 中键入print("Big Python")
并点击回车:
>>>
>>> print("Big Python")
Big Python
>>>
我们刚刚使用了两个重要的编程构造——一个函数和一个字符串。
print()
是一个函数——编程中的函数是一堆代码,做一些非常具体的事情。在我们的例子中,print()
函数打印它被给予控制台的参数(即"Big Python"
)。字符串只是括在单引号或双引号内的一系列字符串。例如:
"olleh"
、'print it'
是弦而1
、3
不是。
别担心,我们不指望你在这一点上理解这些事情。在接下来的课程中,我们将详细讨论这些概念。
像17 / 3
、print("Big Python")
这样的命令在编程中被称为语句。语句只是 Python 解释器要执行的指令。正如我们将看到的,语句有不同的类型。程序通常由一系列语句组成。
要退出 Windows 中的 Python shell,请按 Ctrl+Z 后按回车键,在 Linux 或 Mac 上,请按 Ctrl+D 后按回车键。
脚本模式
Python shell 非常适合测试小块代码,但是有一个问题——您在 Python Shell 中输入的语句不会保存在任何地方。
如果您想多次执行同一组语句,最好将整个代码保存在一个文件中。然后,在脚本模式下使用 Python 解释器执行文件中的代码。
创建一个名为python101
的新目录,你可以在任何你想创建的地方创建这个目录,只要记住位置就可以了,因为我们会用这个目录来存储我们整个课程的所有程序。在python101
内创建另一个名为Chapter-03
的目录来存储本章的源文件。
要创建可以使用任何文本编辑器的程序,只需确保将文件保存为纯文本即可。然而,如果你拼命寻找推荐,去崇高的文本。
在Chapter-03
目录中创建一个名为hello.py
的新文件,并添加以下代码:
蟒蛇 101/章-03/hello.py
print("Woods are lovely dark and deep")
print("but I have promises to keep")
print("and miles to go before I sleep")
按照惯例,所有 Python 程序都有.py
扩展名。文件hello.py
称为源代码或源文件或脚本文件或模块。要执行程序,打开终端或命令提示符,使用cd
命令将当前工作目录更改为python101
,然后键入以下命令:
Woods are lovely dark and deep
but I have promises to keep
and miles to go before I sleep
注意:在 Windows 上使用python hello.py
执行程序。
该命令以脚本模式启动 Python 解释器,并执行hello.py
文件中的语句。
我们知道,在 Python Shell 中,如果您键入任何表达式并按 enter 键,Python 解释器会计算该表达式并显示结果。
>>>
>>> 12+8
20
>>> 75/2
37.5
>>> 100*2
200
>>> 100-24
76
>>>
但是,如果您在文件中键入这些语句并运行该文件,您将根本得不到任何输出。在Chapter-03
目录中创建新的名为no_output.py
的文件,并向其中添加以下代码。
蟒蛇 101/第-03 章/no_output.py
12+8
75/2
100*2
100-24
要运行文件,请输入以下命令。
q@vm:~/python101/Chapter-03$ python3 no_output.py
q@vm:~/python101/Chapter-03$
如您所见,程序没有输出任何内容。
要从 Python 脚本打印值,您必须显式使用print()
函数。用以下代码创建一个名为no_output2.py
的新文件:
蟒蛇 101/章-03/no_output2.py
print(12+8)
print(75/2)
print(100*2)
print(100-24)
输出:
20
37.5
200
76
闲置的
除了安装 Python 解释器,Mac 和 Windows 的 Python 安装程序还安装了一个轻量级的集成开发环境,简称 IDLE。
那么什么是 IDLE?
IDLE 允许在同一屋檐下创建/读取/编辑和执行您的程序,而无需接触命令行。也可以从 IDLE 访问 Python Shell。
要在 Windows 上启动 IDLE,请单击开始菜单并搜索“IDLE”或“IDLE”。如图所示,单击 IDLE,您将看到一个类似如下的窗口:
这还是 Python Shell,只需键入命令,按 enter 键,它就会显示结果。
>>>
>>> print("Big Python")
Big Python
>>>
>>> 12 + 774
786
>>> 89 * 321
28569
>>>
要在 Mac 上启动 IDLE,只需在终端中键入idle3
。
Ubuntu 没有安装 IDLE。无论您使用的是 Ubuntu 预装的 Python,还是您自己通过在课程安装 Python 中输入命令安装的 Python,都是如此。
要安装 IDLE,在终端中发出以下命令。
q@vm:~/python101/Chapter-03$ sudo apt-get install idle3
要启动 IDLE,输入idle3
并在终端中输入:
q@vm:~/python101/Chapter-03$ idle3
现在让我们使用 IDLE 创建一个 Python 程序。IDLE 还有一个内置的文本编辑器来编写 Python 程序。要创建新程序,请转到文件>新建文件。将会打开一个新的无标题窗口。该窗口是一个文本编辑器,您可以在其中编写程序。
在编辑器中键入以下代码:
print("Big Python")
点击 Ctrl + S 或转到文件>另存为。将文件保存为Chapter-03
目录中的from_idle.py
。要运行程序,请转到运行>运行模块或点击 F5。
您的编辑器窗口将移动到后台,Python Shell 将变为活动状态,您将看到您的from_idle.py
程序的输出,如下所示:
错误的类型
在编程中错误是不可避免的,迟早你会遇到一个。编程错误主要分为三种类型:
- 语法错误。
- 运行时错误。
- 逻辑错误。
句法误差
语法是正确编写计算机语言的一套规则。如果不遵守语法规则,就会出现语法错误。它们通常出现在你的代码中,是因为一个错别字。当程序中遇到语法错误时,程序的执行会暂停,Python 解释器会显示一条错误消息,解释错误的可能原因。当语句被翻译成机器语言时,但在执行之前,会出现语法错误。名为 parser 的解释器组件发现了这些错误。
以下是语法错误的一些常见原因:
- 拼错的关键字。
- 操作员使用不正确。
- 在函数调用中省略括号。
- 不要用单引号或双引号将字符串括起来。
等等。
以下 Python Shell 会话演示了一些语法错误:
例 1:
>>>
>>> 4 +
File "<stdin>", line 1
4 +
^
SyntaxError: invalid syntax
>>>
在上面的例子中,我们试图添加两个数字,但是我们忘记了包括第二个数字。这就是 Python 解释器报告SyntaxError
的原因。另外,请注意插入符号(^
)指向 Python 解释器认为是问题原因的位置。
例 2:
>>>
>>> print("Big Python)
File "<stdin>", line 1
print("Big Python)
^
SyntaxError: EOL while scanning string literal
>>>
在这种情况下,我们忘记了用双引号结束字符串,这就是在遇到语法错误的原因。再次注意,插入符号指向 Python 解释器认为是问题原因的位置。
修复语法错误相当容易,你只需要训练你的眼睛去寻找插入符号(^
)。
运行时错误
顾名思义,运行时错误是程序运行时发生的错误。就像语法错误一样,当遇到语法错误时,程序的执行会暂停,解释器会显示一条关于问题可能原因的消息。
它们通常发生在解释器处理无法执行的操作时。一个这样的操作是将一个数除以0
。因为用0
除一个数是没有定义的。当解释器遇到被0
操作除的情况时,它会如下提升ZeroDivisionError
:
>>>
>>> 99/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>>
以下是运行时错误的一些其他常见原因:
- 当您尝试创建新变量时,系统内存不足,可能会出现运行时错误。
- 试图使用尚未定义的函数或变量。
- 访问不存在的文件。
等等。
逻辑错误
当程序产生不正确的结果时,就会出现逻辑错误。例如,假设我们正在创建一个简单的程序,将温度从华氏温度转换为摄氏温度,如下所示:
蟒蛇 101/章节-03/fah_to_cel.py
print("20 degree Fahrenheit in degree Celsius is: ")
print(5 / 9 * 20 - 32)
输出:
20 degree Fahrenheit in degree Celsius is:
-20.88888888888889
以上程序输出-20.88888888888889
,有误。正确的数值是-6.666
。这些类型的错误被称为逻辑错误。要获得正确答案,请使用5 / 9 * (20 - 32)
代替5 / 9 * 20 - 32
。20 - 32
周围的括号强制 Python 在除法和乘法之前执行减法。我们将在 Python 中的运算符一课中了解这样做的原因。
Python 中的数据类型和变量
原文:https://overiq.com/python-101/data-types-and-variables-in-python/
最后更新于 2020 年 9 月 6 日
数据类型
数据类型只不过是不同类型数据的分类。数据类型定义了一组值以及可以对这些值执行的操作。我们在程序中使用的显式值被称为字面量。例如,10
、88.22
、'pypi'
称为文字。每个文字都有一个关联的类型。例如10
为int
型,88.22
为float
型,'pypi'
为str
型(或弦)。文字的类型决定了可以对其执行哪些类型的操作。下表显示了 Python 中的一些基本数据类型以及示例:
数据类型 | 在 Python 中,我们称之为 | 例子 |
---|---|---|
整数 | int |
12 、-999 、0 、900000 等 |
实数 | float |
4.5 、0.0003 、-90.5 、3.0 ;等等 |
特性 | str |
'hello' 、"100" 、"$$$" 、"" ;等等 |
Python 有一个名为type()
的内置函数,我们可以用它来确定文字的数据类型。
>>>
>>> type(54)
<class 'int'>
>>>
>>> type("a string")
<class 'str'>
>>>
>>> type(98.188)
<class 'float'>
>>>
>>> type("3.14")
<class 'str'>
>>>
>>> type("99")
<class 'str'>
>>>
<class 'int'>
表示54
的类型为int
。同样,<class 'str'>
、<class 'float'>
表示"a string"
、98.188
分别为str
、float
型。
一开始,你可能会认为"3.14"
是float
类型,但是因为3.14
是用双引号括起来的,所以它实际上是一个字符串。出于同样的原因"99"
也是一根弦。
Python 还有许多其他数据类型,我们将在后面讨论。
变量
变量用于在我们的程序中存储数据。我们还使用变量来访问数据和操作数据。变量之所以这样叫,是因为它的值可以改变。
创建变量
为了在 Python 中创建一个变量,我们使用赋值语句,其格式如下。
variable_name = expression
variable_name
是变量的名称,(=
)被称为赋值运算符,expression
只是值、变量和运算符的组合。这里有一个例子:
homerun = 6
该语句创建一个名为homerun
的变量,并为其赋值6
。当 Python 解释器遇到这样的语句时,它会在幕后做以下事情。
- 将变量
6
存储在内存的某个地方。 - 使变量
homerun
参考它。
这里需要理解的重要一点是,变量homerun
本身并不包含任何值,它只是指一个包含实际值的内存位置。
与 C、C++和 Java 等语言不同,在 Python 中,我们不会提前声明变量的类型。事实上,如果您尝试这样做,它是一个语法错误。
>>>
>>> int homerun
File "<stdin>", line 1
int homerun
^
SyntaxError: invalid syntax
>>>
为变量赋值时,始终将变量名写在赋值(=
)运算符的左侧。如果不这样做,您将得到如下语法错误:
>>>
>>> 100 = words
File "<stdin>", line 1
SyntaxError: can't assign to literal
>>>
Python 会根据变量包含的值的类型自动检测变量的类型以及可以对其执行的操作。在编程术语中,这种行为被称为动态类型。这意味着我们可以使用同一个变量来引用与它最初指向的完全不同类型的数据。例如:
>>>
>>> homerun = 6 # at this point, homerun contains an int or integer
>>> homerun
6
>>> homerun = "now" # now homerun contains a string
>>> homerun
'now'
>>>
>>> type(homerun)
<class 'str'>
>>>
>>> homerun = 2.3 # now homerun contains a float
>>> homerun
2.3
>>> type(homerun)
<class 'float'>
>>>
当我们给一个变量赋值时,对旧值的引用就会丢失。例如,当"now"
被分配给homerun
时,对值6
的引用丢失。此时,没有任何变量指向该内存位置。当这种情况发生时,Python 解释器会通过一个称为垃圾收集的过程自动从内存中移除该值。
如果在给变量赋值之前试图访问它。你会得到这样的NameError
错误:
>>>
>>> age
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'age' is not defined
>>>
我们也可以使用print()
函数来打印一个变量值,如下所示:
>>>
>>> age = 100
>>> print(age)
100
>>>
变量名
在 Python 中,我们有以下规则来创建有效的变量名。
只允许使用字母(
a-z
、A-Z
)、下划线(_
)和数字(0-9
)来创建变量名,其他不允许。它必须以下划线(
_
)或字母开头。您不能使用保留关键字来创建变量名称。(见下文)。
变量名可以是任意长度。
Python 是区分大小写的语言,这意味着HOME
和home
是两个不同的变量。
Python 关键字
Python 关键字是在 Python 语言中表示特定事物的单词。这就是为什么,我们不允许使用它们作为变量名称。下面是 Python 关键字的列表:
随着本课程的进行,我们将进一步了解这里提到的一些关键字。
以下是一些有效和无效的变量名:
home4you
-有效
after_you
-有效
_thatsall
-有效
all10
-有效
python_101
-有效
$money
-无效,变量不能以$
字符
hello pi
开头-无效,变量之间不能有空格
2001
-无效,变量不能以数字
break
开头-无效,关键字不能是变量名
评论
注释用于给程序添加注释。在大型程序中,注释可能描述程序的目的及其工作方式。它们只针对那些试图理解程序源代码的人。它们不是编程语句,因此在执行程序时会被 Python 解释器忽略。
在 Python 中,从#
到行尾的所有内容都被视为注释。例如:
# This is a comment on a separate line
print("Testing comments") # This is a comment after print statement
命名常量
常量是在程序生命周期内其值不变的变量。不像 C 或 Java 这样的语言;Python 没有创建常量的特殊语法。我们像普通变量一样创建常量。但是,为了将它们与普通变量分开,我们使用了所有大写字母。
>>>
>>> MY_CONST = 100 # a constant
>>>
注意MY_CONST
只是一个变量,指的是int
类型的值。它没有其他特殊属性。
您甚至可以通过给MY_CONST
常量赋值来改变它的值,如下所示:
>>>
>>> MY_CONST = "new value"
>>>
我们将在程序中不时使用命名常量。
使用打印()功能显示多个项目
我们也可以使用print()
语句,通过用逗号(,
)分隔每个项目,在一次调用中打印多个项目。当多个参数被传递到print()
函数时,它们被打印到控制台,用空格隔开。例如:
>>>
>>> message = "item2"
>>> print("item1", message, "item3")
item1 item2 item3
>>>
同时分配
我们可以使用具有以下语法的同时赋值来同时为多个变量赋值:
var1, var2, var3, ... varN = exp1, exp2, exp3, ... expN
当 Python 遇到同时赋值语句时,它首先计算右侧的所有表达式,然后将它们的值赋给左侧相应的变量。例如:
>>>
>>> name, age, designation = "tom", 25, "Lead Developer"
>>>
>>> name
'tom'
>>>
>>> age
25
>>>
>>> designation
'Lead Developer'
>>>
>>>
在两个变量之间交换值是一种常见的编程操作。在像 C 这样的语言中,要执行交换,你必须创建一个额外的变量来临时存储数据。例如:
int i = 10, j = 20;
int tmp; // variable to store data temporary
tmp = i; // now tmp contains 10
i = j; // now i contains 20
j = tmp; // now j contains 10
在 Python 中,我们可以使用同时赋值来交换值,如下所示:
>>>
>>> x, y = 10, 20
>>> print(x, y)
10 20 # initial value
>>>
>>> x, y = y, x ## swapping values
>>> print(x, y)
20 10 # final value
>>>
Python 中的函数
函数是一段代码,它执行一些非常具体的任务。在这一点上,我们只讨论了print()
和type()
函数,但是 Python 标准库实际上有成千上万个执行各种操作的内置函数。
一个函数不能自己做任何事情,除非你调用它。要调用一个函数,请在括号内键入该函数的名称,后跟参数列表,即()
,如下所示:
function_name(arg1, arg2, arg3, arg4, ..., argN)
那么什么是论点呢?
参数是函数执行任务所需的附加数据。有些函数需要参数,有些则不需要。要调用不接受任何参数的函数,请键入函数名,后跟空括号,如下所示:
function_name()
参数通常也称为参数。
在语句中,print("Big Python")
、"Big Python"
是一个自变量。
当一个函数完成它的任务时,它通常会返回值。并非所有函数都返回值,有些函数返回值,而有些函数不返回值。如果一个函数没有显式返回任何值,那么它会返回一个名为None
的特殊值,这是 Python 中保留的关键字之一。这就是你现在需要知道的。我们将在第课中详细讨论 Python 中的函数。
Python 中的模块
Python 使用模块对相似的函数、类、变量等进行分组。例如,math
模块包含各种数学函数和常量,datetime
模块包含各种处理日期和时间等的类和函数。要使用模块中定义的函数、常量或类,我们首先必须使用import
语句导入它。import
语句的语法如下:
import module_name
要在 Python Shell 中导入math
模块,请键入以下代码:
>>>
>>> import math
>>>
要使用模块类型中的方法或常量,模块名称后跟点(.
)运算符,运算符后跟要访问的方法或变量的名称。例如,math
模块有sqrt()
函数,它的任务是返回一个数的平方根。要使用此函数,请在 Python shell 中键入以下代码。
>>>
>>> math.sqrt(441)
21.0
>>>
math
模块还有两个常用的数学常量pi
和e
。要访问它们,请键入以下代码:
>>>
>>> math.pi
3.141592653589793
>>>
>>> math.e
2.718281828459045
>>>
要查看由math
模块提供的函数和常量的完整列表,请查看math
模块的文档。
像print()
、type()
、input()
这样的内置功能(我们接下来讨论这个功能)属于一个叫做__builtin__
的特殊模块。但是为什么它是特别的呢?因为__builtin__
模块中的功能总是可以使用的,而无需显式导入__builtin__
模块。
从键盘读取输入
Python 有一个名为input()
的内置函数,用于读取键盘输入。它的语法如下:
var = input(prompt)
prompt
指的是指示用户输入的可选字符串。input()
功能从键盘读取输入数据,并将其作为字符串返回。然后将输入的数据分配给一个名为var
的变量进行进一步处理。
不管输入的数据是类型int
、float
还是其他什么,最终input()
函数会将数据转换为字符串。
当遇到input()
语句时,程序暂停,等待用户输入。完成输入后,按回车键提交输入。input()
函数然后将输入的数据作为字符串返回。
>>>
>>> name = input("Enter your name: ")
Enter your name: Tom
>>>
>>> name
'Tom'
>>>
>>> type(name)
<class 'str'>
>>>
>>>
使用帮助()函数获取帮助
文档是任何计算机语言的重要组成部分,作为一名初露头角的程序员,您应该知道如何访问 Python 的文档,以了解更多关于语言及其提供的各种功能的信息。
我们可以使用help()
命令来了解更多关于函数、类或模块的信息。要使用help()
函数,只需传递函数、类或模块的名称。例如,要了解input()
函数调用help()
函数如下:
>>>
>>> help(input)
Help on built-in function input in module builtins:
input(...)
input([prompt]) -> string
Read a string from standard input. The trailing newline is stripped.
If the user hits EOF (Unix: Ctl-D, Windows: Ctl-Z+Return), raise EOFError.
On Unix, GNU readline is used if enabled. The prompt string, if given,
is printed without a trailing newline before reading.
>>>
>>>
第input([prompt]) -> string
行称为函数签名。函数签名定义了它的参数和返回值。如果一个参数是可选的,那么它将被包装在方括号[]
中。
第input([prompt]) -> string
行表示input()
函数接受一个名为prompt
的可选参数,并返回一个字符串类型的值。
请注意,help()
功能不使用互联网连接来获取文档,而是使用存储在硬盘中的文档的离线副本。Python 文档也可以在线获得,要访问它,请访问https://docs.python.org/3/。
Python 中的数字
最后更新于 2020 年 9 月 17 日
Python 中的数字
在 Python 中,数字有 4 种类型:
- 整数。
- 浮点或实数。
- 复数。
- 布尔型。
整数或简称int
是没有小数点的数字。比如100
、77
、-992
是int
但是0.56
、-4.12
、2.0
不是。
浮点或实数或float
是有小数点的数字。比如1.2
、0.21
、-99.0
是浮动的而102
、-8
不是。我们也可以用科学记数法写浮点数。以a x 10^b
形式书写的数字被称为科学符号。科学符号对于写非常小或非常大的数字非常有用。例如,float 0.000000123
在科学记数法中可以简洁地写成1.23 x 10^-7
。Python 使用一种特殊的语法来用科学符号写数字。比如0.000000123
可以写成1.23E-7
。字母E
叫指数,用e
还是E
都无所谓。
复数是我们不能在一条数字线上表示的数字。复数的形式是a + ib
,其中a
是实部,bi
是虚部。比如2 + 3i
是复数。Python 对复数也使用特殊的语法。尾随的整数或浮点数j
在 Python 中被视为复数,因此10j
、9.12j
都是复数。
>>>
>>> type(5) # an integer
<class 'int'>
>>>
>>> type(3.4) # a float
<class 'float'>
>>>
>>> type(5j) # a complex number
<class 'complex'>
>>>
注意5j
只代表复数的虚部。要创建一个有实部和虚部的复数,只需在虚部上加上一个数字。比如复数2 + 3i
可以用 Python 写成2 + 3j
。
布尔类型将在本章后面讨论。
常见的数学函数
Python 提供了以下内置函数来帮助您完成常见的编程任务:
功能 | 它有什么作用? | 例子 |
---|---|---|
abs(number) |
返回数字的绝对值。换句话说,abs() 函数只是返回没有任何符号的数字。 |
abs(-12) 是12 ,abs(112.21) 是112.21 。 |
pow(a, b) |
返回a^b 。 |
pow(2, 3) 是8 ,pow(10, 3) 是1000 |
round(number) |
将数字舍入到最接近的整数。 | round(17.3) 是17 ,round(8.6) 是9 |
round(number, ndigits) |
小数点后将number 舍入到ndigits |
round(3.14159, 2) 是3.14 ,round(2.71828, 2) 是2.72 |
min(arg1, arg2, ... argN) |
返回arg1 、arg2 中最小的项目,...argN |
min(12, 2, 44, 199) 是2 ,min(4, -21, -99) 是-99 |
max(arg1, arg2, ... argN) |
返回arg1 、arg2 中最大的项目,...argN |
max(991, 22, 19) 是991 ,max(-2, -1, -5) 是-1 |
abs()功能
>>>
>>> abs(-100) # absolute value of -100
100
>>>
>>> abs(291.121) # absolute value of 291.121
291.121
>>>
>>> abs(88) # absolute value of 88
88
>>>
power()函数
>>>
>>> pow(3, 3) # calculate 3^3
27
>>>
>>> pow(0.35, 2) # calculate 0.35^2
0.12249999999999998
>>>
>>> pow(9, -2) # calculate 9^-2
0.012345679012345678
>>>
round()函数
>>>
>>> round(32.3) # round 32.3 to the nearest integer
32
>>> round(99.7) # round 99.7 to the nearest integer
100
>>> round(-5.23) # round -5.23 to the nearest integer
-5
>>>
>>> round(3.14159, 2) # round 3.14159 to 2 decimal places
3.14
>>>
>>> round(2.71828, 3) # round 2.71828 to 3 decimal places
2.718
>>>
最大()和最小()函数
>>>
>>> max(1, 4, 100) # Find the largest among 1, 4 and 100
100
>>>
>>> max(-21, 4.5, 91.12) # Find the largest among -21, 4.5 and 91.12
91.12
>>>
>>> max(-67, -17, 0) # Find the largest among -67, 417 and 0
0
>>>
>>> min(0, -1.23e10, -9921) # Find the smallest among 0, -1.23e10 and -9921
-12300000000.0
>>>
>>>
>>> min(92, 6, -102) # Find the largest among 92, 6, -102
-102
>>>
Python 的math
模块也提供了一些标准的数学函数和常量。回想一下,要使用数学模块,我们首先需要使用import
语句导入它,如下所示:
import math
下表列出了math
模块中的一些标准数学函数和常量。
功能 | 它有什么作用? | 例子 |
---|---|---|
math.pi |
返回pi 的值 |
math.pi 是3.141592653589793 |
math.e |
返回e 的值 |
math.e 是2.718281828459045 |
math.ceil(n) |
返回大于或等于n 的最小整数 |
math.ceil(3.621) 是4 |
math.floor(n) |
返回小于或等于n 的最大整数 |
math.floor(3.621) 是3 |
math.fabs(n) |
将x 的绝对值返回为float |
math.fabs(5) 是5.0 |
math.sqrt(n) |
以浮点形式返回 x 的平方根 | math.sqrt(225) 是15.0 |
math.log(n) |
将n 的自然对数返回到基数e |
math.log(2) 是0.6931 |
math.log(n, base) |
将n 的日志返回给定的基数 |
math.log(2, 2) 是1.0 |
math.sin(n) |
返回n 弧度的正弦值 |
math.sin(math.pi/2) 是1.0 |
math.cos(n) |
返回n 弧度的余弦值 |
math.cos(0) 是1.0 |
math.tan(n) |
返回n 弧度的正切值 |
math.tan(45) 是1.61 |
math.degrees(n) |
将角度从弧度转换为 | math.degrees(math.pi/2) 是90 |
math.radians() |
将角度从度转换为弧度 | math.radians(90) 是1.5707 |
数学π和数学 e 常量
>>>
>>> math.pi
3.141592653589793
>>>
>>> math.e
2.718281828459045
>>>
math.ceil()和 math.floor()函数
>>>
>>> math.ceil(3.5) # find the smallest integer greater than or equal to 3.5
4
>>> math.floor(3.5) # find the largest integer smaller than or equal to 3.5
3
>>>
math.fabs()和 math.sqrt()函数
>>>
>>> math.fabs(2) # absolute value of 2 in float
2.0
>>>
>>> math.fabs(-53.3) # absolute value of -53.3
53.3
>>>>
>>> math.sqrt(9801) # square root of 9801
99.0
>>>
>>> math.sqrt(4.3) # square root of 4.3
2.073644135332772
>>>
math.log()函数
>>>
>>> math.log(2) # find log of 2 to the base e
0.6931471805599453
>>>
>>> math.log(2, 5) # find log of 2 to the base 5
0.43067655807339306
>>>
math.sin(),math.cos()和 math.tan()函数
>>>
>>> math.sin(math.pi/2)
1.0
>>>
>>> math.cos(0)
1.0
>>>
>>> math.tan(45)
1.6197751905438615
>>>
math.degrees()和 math.radians()函数
>>>
>>> math.degrees(math.pi/2)
90.0
>>>
>>> math.radians(90)
1.5707963267948966
>>>
这只是数学模块中函数和常量的一个简短列表,要查看完整列表请访问https://docs.python.org/dev/library/math.html。
格式化数字
有时希望以特定的格式打印数字。考虑以下示例:
蟒蛇 101/第-05 章/简单 _ 兴趣 _ 计算器. py
# Program to calculate the Simple Interest
#
# The formula for Simple Interest is
# si = p * r * t
# where si is the simple interest
# p is principal
# r is interest rate
# t is number of years
p = 18819.99 # principal
r = 0.05 # rate of interest
t = 2 # years
si = p * r * t
print("Simple interest at the end of 2 years $", si)
输出:
Simple interest at the end of 2 years $ 1881.9990000000003
注意货币在输出中是如何显示的,它包含小数点后的13
位。当执行计算后打印浮点数时,这是一个非常常见的问题。由于金额是货币,将其格式化为两位小数是有意义的。我们可以使用round()
函数轻松地将数字四舍五入到小数点后 2 位,但是round()
函数并不总是给出正确的答案。考虑以下代码:
>>>
>>> round(1234.5012, 2)
1234.5
>>>
我们要输出1234.50
而不是1234.5
。我们可以使用format()
功能解决这个问题。下面是使用format()
方法的上述程序的修订版。
python 101/章节-05/simple _ interest _ calculator _ using _ format _ function . py
# Program to calculate the Simple Interest
#
# The formula for Simple Interest is
# si = p * r * t
# where si is the simple interest
# p is principal
# r is interest rate
# t is number of years
p = 18819.99 # principal
r = 0.05 # rate of interest
t = 2 # years
si = p * r * t
print("Simple interest at the end of 2 years $", format(si, "0.2f"))
输出:
Simple interest at the end of 2 years $ 1882.00
下一节将解释format()
功能。
格式()函数
format()
函数的语法如下:
format(value, format-specifier)
value
是我们要格式化的数据。
format-specifier
是一个字符串,它决定了如何格式化传递给format()
函数的值。
成功后format()
返回一个格式化的字符串。
格式化浮点数
为了格式化浮点数,我们使用下面的格式说明符。
width.precisionf
width
是为一个值保留的最小字符数,precision
是小数点后的字符数。width
包括小数点前后的数字和小数点字符本身。precision
后面的f
字符表示format()
函数将以浮点数的形式输出该值。字符f
也称为类型代码或说明符。正如我们将看到的,还有许多其他说明符。
默认情况下,所有类型的数字都右对齐。如果宽度大于值的长度,则数字以右对齐方式打印,前导空格由宽度减去值的长度确定。另一方面,如果宽度小于值的长度,则宽度的长度会自动增加以适应值的长度,并且不会添加前导空格
为了使一切具体化,让我们举几个例子:
例 1 :
>>>
>>> print(format(34.712, "9.2f"))
34.71
>>>
这里宽度为9
字符长,精度为2
。数字34.712
的长度是6
,但是由于精度是2
,所以数字会四舍五入到2
小数位。所以实际长度值为5
。这意味着宽度大于值的长度,因此值以4
( 9-5=4
)前导空格右对齐。
例 2 :
>>>
>>> print(format(567.123, "5.2f"))
567.12
>>>
这种情况下,宽度为5
,数值的实际长度为6
(因为数字会四舍五入到2
小数位)。因此,宽度小于值的长度,因此,宽度的长度会自动增加以适应值的长度,并且不会添加前导空格
我们也可以完全省略宽度,在这种情况下,它是由值的长度自动确定的。
>>>
>>> import math
>>> print(format(math.pi, ".2f"))
3.14
>>>
宽度通常用于整齐地排列列中的数据。
用科学符号格式化数字
要格式化科学记数法中的数字,只需将类型代码从f
替换为e
或E
。
>>>
>>> print(format(5482.52291, "10.2E"))
5.48E+03
>>>
>>>
>>> print(format(5482.52291, "5.2e"))
5.48e+03
>>>
>>>
>>> print(format(00000.212354, ".3E"))
2.124E-01
>>>
>>>
插入逗号
阅读大量数字可能很难读懂。我们可以用逗号(,
)来分隔它们,让它们更易读。要使用逗号分隔符,请在格式说明符的宽度之后或精度之前键入,
字符。
>>>
>>> print(format(98813343817.7129, "5,.2f"))
98,813,343,817.71
>>>
>>>
>>> print(format(98813343817.7129, ",.2f"))
98,813,343,817.71
>>>
如果您只想打印带逗号(,
)的浮点数,但不应用任何格式,请执行以下操作:
>>>
>>> print(format(98813343817.7129, ",f"))
98,813,343,817.712906
>>>
将数字格式化为百分比
我们可以使用%
类型代码将数字格式化为百分比。当在格式说明符中使用%
时,它将数字乘以100
,并将结果作为后跟%
符号的浮点数输出。我们也可以像往常一样指定宽度和精度。
>>>
>>> print(format(0.71981, "%"))
71.981000%
>>>
>>>
>>>
>>> print(format(0.71981, "10.2%"))
71.98%
>>>
>>>
>>>
>>> print(format(52, "%"))
5200.000000%
>>>
>>>
>>>
>>> print(format(95, ".2%"))
9500.00%
>>>
设置对齐方式
我们已经讨论过,默认情况下,数字是正确打印的。例如:
>>>
>>> import math
>>>
>>> print(format(math.pi, "10.2f"))
3.14
>>>
>>>
我们可以通过使用以下两个符号来更改默认对齐方式:
标志 | 描述 |
---|---|
> |
输出在指定宽度内右对齐的值 |
< |
输出在指定宽度内左对齐的值 |
对齐符号必须在指定宽度之前。
>>>
>>> print(format(math.pi, "<10.2f")) # output the value left justfied
3.14
>>>
这里我们打印的是左对齐的数字,结果添加了尾随空格而不是前导空格。
注意语句format(math.pi, ">10.2f")
和format(math.pi, "10.2f")
相同,右对齐是打印数字的默认格式。
格式化整数
我们也可以使用format()
函数来格式化整数。类型码d
、b
、o
、x
可分别格式化为十进制、二进制、八进制和十六进制。请记住,格式化整数时,只允许宽度,不允许精度。
>>>
>>> print(format(95, "5d"))
95
>>>
>>>
>>> print(format(4, "b")) # prints the binary equivalent of decimal 4
100
>>>
>>>
>>> print(format(255, "x")) # prints the hexadecimal equivalent of decimal 255
ff
>>>
>>>
>>> print(format(9, "o")) # prints the octal equivalent of decimal 9
11
>>>
>>>
>>> print(format(100, "<10d")) # Left align the number within specified width
100
>>>
Python 中的运算符
最后更新于 2020 年 9 月 17 日
在本课中,我们将学习 Python 提供的表达式和各种运算符。
操作符:操作符是指定特定动作的符号。
操作数:操作数是操作符作用的数据项。
一些运算符需要两个操作数,而另一些只需要一个。
表达式:表达式只不过是运算符、变量、常量和函数调用的组合,最终得到一个值。例如:
## some valid expressions
1 + 8
(3 * 9) / 5
a * b + c * 3
a + b * math.pi
d + e * math.sqrt(441)
让我们从算术运算符开始。
算术运算符
算术运算符通常用于执行数值计算。Python 有以下算术运算符。
操作员 | 描述 | 例子 |
---|---|---|
+ |
加法运算符 | 100 + 45 = 145 |
- |
减法运算符 | 500 - 65 = 435 |
* |
乘法运算符 | 25 * 4 = 100 |
/ |
浮点除法运算符 | 10 / 2 = 5.0 |
// |
整数除法运算符 | 10 / 2 = 5 |
** |
幂运运算符 | 5 ** 3 = 125 |
% |
余数运算符 | 10 % 3 = 1 |
我们在日常生活中使用+
、-
、*
运算符,不值得任何解释。但是,需要注意的重要一点是+
和-
运算符既可以是二元的,也可以是一元的。一元运算符只有一个操作数。我们可以使用-
运算符来否定任何正数。例如:-5
,在这种情况下-
运算符充当一元运算符,而在100 - 40
中,-
运算符充当二元运算符。同样,我们可以使用一元+
运算符。比如+4
。由于表达式4
和+4
相同,在表达式中应用一元+
运算符一般没有意义。
浮点除法运算符(/)
/
运算符执行浮点除法。简单的说就是/
返回一个浮点结果。例如:
>>>
>>> 6/3
2.0
>>>
>>> 3.14/45
0.06977777777777779
>>>
>>>
>>> 45/2.5
18.0
>>>
>>>
>>> -5/2
-2.5
>>>
整数除法运算符(//)
//
运算符的工作方式类似于/
运算符,但它返回的不是浮点值,而是整数。例如:
>>>
>>> 6//3
2
>>>
>>> 100//6
16
>>>
与/
运算符不同,当结果为负时,//
运算符将结果从零舍入到最接近的整数。
>>>
>>> -5//2
-3
>>>
>>> -5/2
-2.5
>>>
幂运运算符(**)
我们用**
运算符计算a^b
。例如:
>>>
>>> 21**2
441
>>>
>>> 5**2.2
34.493241536530384
>>>
余数运算符(%)
%
运算符返回左操作数除以右操作数后的余数。例如:
>>>
>>> 5%2
1
>>>
余数运算符%
是编程中非常有用的运算符。%
运算符的一个常见用法是判断一个数是否为偶数。
一个数即使被2
整除也是偶数。换句话说,一个数即使被2
除,剩下0
作为余数。我们将在第课中学习如何用 Python 编写这样的程序。
运算符优先级和结合性
考虑以下表达式:
10 * 5 + 9
它的结果会是什么?
如果在加法之前进行乘法运算,那么答案将是59
。另一方面,如果在乘法之前进行加法,那么答案将是140
。为了解决这个难题,我们使用了运算符优先级。
Python 中的运算符被组合在一起,并被赋予一个优先级。下表列出了运算符的优先级。
操作员 | 描述 | 结合性 |
---|---|---|
[ v1, … ] 、{ v1, …} 、{ k1: v1, …} 、(…) |
列表/集合/字典/生成器创建或理解,带圆括号的表达式 | 从左到右 |
seq [ n ] 、seq [ n : m ] 、func ( args… ) 、obj.attr |
索引、切片、函数调用、属性引用 | 从左到右 |
** |
幂运算 | 从右向左 |
+x 、-x 、~x |
正、负、按位非 | 从左到右 |
* 、/ 、// 、% |
乘法、浮点除法、整数除法、余数 | 从左到右 |
+ 、- |
加法、减法 | 从左到右 |
<< 、>> |
按位左移、右移 | 从左到右 |
& |
按位“与” | 从左到右 |
| | 按位“或” | 从左到右 |
in 、not in 、is 、is not 、< 、<= 、> 、>= 、!= 、== |
比较、成员资格和身份测试 | 从左到右 |
not x |
布尔非 | 从左到右 |
and |
布尔与 | 从左到右 |
or |
布尔或 | 从左到右 |
if-else |
条件表达式 | 从左到右 |
希腊字母的第 11 个 | λ表达式 | 从左到右 |
上面几行中的运算符优先级最高,随着我们向表格底部移动,优先级会降低。每当我们有一个表达式,其中涉及的运算符具有不同的优先级,具有较高优先级的运算符将首先被计算。因此,在表达式10 * 5 + 9
中,首先对*
运算符进行评估,然后对+
运算符进行评估。
=> 10 * 5 + 9 (multiplication takes place first)
=> 50 + 9 (followed by addition)
=> 59 (Ans)
运算符的结合性
在优先级表中,同一组中的运算符具有相同的优先级,例如,(*
、/
、//
、%
)具有相同的优先级。现在考虑以下表达式:
5 + 12 / 2 * 4
从优先级表中我们知道/
和*
的优先级都比+
高,但是/
和*
的优先级是一样的,那么你认为先评估哪个算符/
还是*
?
当运算符优先级相同时,为了确定求值顺序,我们使用运算符关联性。运算符关联性定义了计算具有相同优先级的运算符的方向,它可以是从左到右或从右到左。同一组中的运算符具有相同的关联性。从表中可以看出,/
和*
的关联性是从左到右。所以在表达中:
5 + 12 / 2 * 4
首先评估/
运算符,然后评估*
运算符。最后对+
运算符进行了评价。
=> 5 + 12 / 2 * 4 (/ operator is evaluated first)
=> 5 + 6 * 4 (then * operator is evaluated)
=> 5 + 24 (at last + operator is evaluated)
=> 29 (Ans)
以下是关于优先表需要记住的两点。
同一组中除幂运运算符(
**
)外,大多数运运算符的结合性都是从左到右。幂运运算符(**
)的结合性是从右到左。我们有时使用括号,即
()
来改变评估的顺序。例如:
2 + 10 * 4
在上面的表达式中*
将首先执行,然后是+
。我们可以通过在要首先求值的表达式或子表达式周围加上圆括号来轻松更改运算符优先级。例如:
(2 + 10) * 4
由于()
运算符的优先级高于*
运算符(见优先级表),将首先执行加法,然后是*
。
以下是一些表达式及其计算顺序:
例 1:
Expression: 10 * 3 + (10 % 2) ** 1
1st Step: 10 * 3 + 0 ** 1
2nd Step: 10 * 3 + 0
3rd Step: 30 + 0
4th Step: 30
例 2:
Expression: 45 % 2 - 5 / 2 + ( 9 * 3 - 1 )
1st Step: 45 % 2 - 5 / 2 + 26
2nd Step: 1 - 2.5 + 26
3rd Step: 24.5
复合赋值运算符
在编程中,增加或减少变量值,然后将该值重新分配回同一个变量是非常常见的。例如:
x = 10
x = x + 5
x
的初始值为10
。在第二个表达式中,我们将10
添加到 x 的现有值中,然后将新值重新分配回x
。所以现在x
的价值是15
。
第二个语句x = x + 5
可以使用复合赋值运算符以更简洁的方式编写,如下所示:
x += 5
这里+=
被称为复合赋值运算符。下表列出了 Python 中可用的其他复合赋值运算符。
操作员 | 例子 | 等效语句 |
---|---|---|
+= |
x += 4 |
x = x + 4 |
-= |
x -= 4 |
x = x - 4 |
*= |
x *= 4 |
x = x * 4 |
/= |
x /= 4 |
x = x / 4 |
//= |
x //= 4 |
x = x // 4 |
%= |
x %= 4 |
x = x % 4 |
**= |
x **= 4 |
x = x ** 4 |
不像其他基于 C 的语言,比如 Java、PHP、JavaScriptPython 没有递增运算符(++
)和递减运算符(--
)。在这些语言中,++
和--
运算符通常分别用于通过1
增加和减少变量值。例如,要在 JavaScript 中将变量值增加/减少1
,您可以这样做:
x = 10;
x++; // increment x by 1
console.log(x); // prints 11
x = 10;
x--; // decrement x by 1
console.log(x); // prints 9
我们可以很容易地使用复合赋值运算符来模拟这种行为,如下所示:
x = 10
x += 1
print(x) ## prints 11
x = 10
x -= 1
print(x) ## prints 9
类型变换
到目前为止,我们还没有考虑在 Python Shell 和我们的程序中的表达式中使用的数据类型。说到执行涉及不同类型数据的计算,Python 有以下规则:
当一个表达式中涉及的两个操作数都是
int
时,那么结果就是int
。当一个表达式中涉及的两个操作数都是
float
时,那么结果就是float
。当一个操作数为
float
类型,另一个操作数为int
类型时,结果将始终是一个float
值。在这种情况下,Python 解释器会自动将int
值临时转换为float
,然后执行计算。这个过程称为类型转换。
以下是一些例子:
>>>
>>> 45 * 3
135 # result is int because both operands are int
>>>
>>>
>>>
>>> 3.4 * 5.3
18.02 # result is float because both operands are float
>>>
>>>
>>>
>>> 88 * 4.3
378.4 # result is float because one operand is float
>>>
在最后一个表达式中,文字88
首先转换为88.0
,然后进行乘法运算。
有时,我们希望根据自己的意愿将数据从一种类型转换为另一种类型。为了处理这种情况,Python 为我们提供了以下功能:
函数名 | 描述 | 例子 |
---|---|---|
int() |
它接受字符串或数字,并返回类型为int 的值。 |
int(2.7) 返回2 ,int("30") 返回30 |
float() |
它接受一个字符串或数字,并返回一个类型为float 的值 |
float(42) 返回42.0 ,float("3.4") 返回3.4 |
str() |
它接受任何值并返回值类型str |
str(12) 返回"12" ,str(3.4) 返回"3.4" |
以下是一些例子:
int()函数
>>>
>>> int(2.7) # convert 2.7 to int
2
>>>
>>> int("30") # convert "30" to int
30
>>>
注意当int()
函数将一个浮点数转换为int
时,它只是去掉小数点后的数字。如果你想取整一个数字,使用round()
功能。
>>>
>>> int(44.55)
44
>>> round(44.55)
45
>>>
float()函数
>>>
>>> float(42) # convert 42 to float
42.0
>>>
>>> float("3.4") # convert "3.4" to float
3.4
>>>
str()函数
>>>
>>> str(12) # convert 12 to str
'12'
>>>
>>> str(3.4) # convert 3.4 to str
'3.4'
>>>
将语句分成多行
到目前为止,我们写的所有声明都仅限于一行。如果你的陈述变得太长了怎么办?
在一行中输入长语句在屏幕上和纸上都很难阅读。
Python 允许我们使用行延续符号(\
)将长表达式拆分成多行。\
符号告诉 Python 解释器语句在下一行继续。例如:
>>>
>>> 1111100 + 45 - (88 / 43) + 783 \
... + 10 - 33 * 1000 + \
... 88 + 3772
1082795.953488372
>>>
要将一条语句扩展为多行,请在要中断该语句的位置键入行延续符号(\
),然后按回车键。
当 Python Shell 遇到扩展到多行的语句时,会将提示字符串从>>>
改为...
。当你完成输入语句后,点击回车查看结果。
下面是另一个将print()
语句分成多行的例子:
>>>
>>> print("first line\
... second line\
... third line")
first line second line third line
>>>
>>>
以下示例显示了如何在 Python 脚本中编写多行语句。
python 101/章节-06/multiline_statements.py
result = 1111100 + 45 - (88 / 43) + 783 \
+ 10 - 33 * 1000 + \
88 + 3772
print(result)
print("first line\
second line\
third line")
输出:
1082795.953488372
first line second line third line
布尔类型
bool
数据类型代表两种状态,即真或假。Python 分别使用保留关键字True
和False
来定义值 true 和 false。bool
类型的变量只能包含这两个值中的一个。例如:
>>>
>>> var1 = True
>>> var2 = False
>>>
>>> type(var1)
<class 'bool'> # type of var1 is bool
>>>
>>> type(var2)
<class 'bool'> # type of var2 is bool
>>>
>>>
>>> type(True)
<class 'bool'> # type of True keyword is bool
>>>
>>> type(False)
<class 'bool'> # type of False keyword is bool
>>>
>>> var1
True
>>>
>>> var2
False
>>>
计算出bool
值True
or
False
的表达式称为布尔表达式。
我们通常使用bool
变量作为标志。标志只不过是一个变量,它表示程序中的某种情况。如果标志变量设置为假,则意味着条件不为真。另一方面,如果它是真的,那么它意味着条件是真的。
在内部,Python 使用1
和0
分别表示True
和False
。我们可以通过使用True
和False
关键字上的int()
功能来验证这个事实,如下所示:
>>>
>>> int(True) # convert keyword True to int
1
>>>
>>> int(False) # convert keyword False to int
0
>>>
真理和谬误的价值
真值:相当于真值的值称为真值。
虚假值:相当于布尔值 False 的值称为虚假值。
在 Python 中,以下值被认为是错误的。
None
False
- 零,即
0
、0.0
- 空序列,例如,
''
、[]
、()
- 空字典即
{}
**注:**顺序和字典在后面章节讨论。
其他一切都被认为是真实的。我们也可以使用bool()
函数来测试一个值是真还是假。如果值为真,则bool()
函数返回真,否则返回False
。以下是一些例子:
>>>
>>> bool("") # an empty string is falsy value
False
>>>
>>> bool(12) # int 12 is a truthy value
True
>>>
>>> bool(0) # int 0 is falsy a value
False
>>>
>>> bool([]) # an empty list is a falsy value
False
>>>
>>> bool(()) # an empty tuple is a falsy value
False
>>>
>>> bool(0.2) # float 0.2 is truthy a value
True
>>>
>>> bool("boolean") # string "boolean" is a truthy value
True
>>>
在接下来的课程中,真理和谬误价值观的重要性将变得更加明显。
关系运算符
为了比较值,我们使用关系运算符。包含关系运算符的表达式称为关系表达式。如果表达式为真,则返回布尔值True
,如果表达式为假,则返回布尔值False
。关系运算符是二元运算符。下表列出了 Python 中可用的关系运算符。
操作员 | 描述 | 例子 | 返回值 |
---|---|---|---|
< |
小于 | 3 < 4 |
True |
> |
大于 | 90 > 450 |
False |
<= |
小于或等于 | 10 <= 11 |
True |
>= |
大于或等于 | 31 >= 40 |
False |
!= |
不等于 | 100 != 101 |
True |
== |
等于 | 50==50 |
True |
>>>
>>> 3 < 4 # Is 3 is smaller than 4 ? Yes
True
>>>
>>> 90 > 450 # Is 90 is greater than 450 ? No
False
>>>
>>> 10 <= 11 # Is 10 is smaller than or equal to 11 ? Yes
True
>>>
>>> 31 >= 40 # Is 31 is greater than or equal to 40 ? No
False
>>>
>>> 100 != 101 # Is 100 is not equal to 101 ? Yes
True
>>>
>>> 50 == 50 # Is 50 is equal to 50 ? Yes
True
>>>
初学者经常混淆==
和=
运算符。永远记住=
是赋值运算符,用于给变量赋值。另一方面,==
是一个相等运算符,用于测试两个值是否相等。
逻辑运算符
逻辑运算符用于组合两个或多个布尔表达式,并测试它们是真还是假。包含逻辑运算符的表达式称为逻辑表达式。下表列出了 Python 中可用的逻辑运算符。
操作员 | 描述 |
---|---|
and |
逻辑积算符 |
or |
或运算符 |
not |
“非”算符 |
and
和or
是二元运算符,not
是一元运算符。
逻辑积算符
如果两个操作数都为真,and
运算符将返回一个bool
值True
。否则返回False
。
语法: operand_1 and operand_2
and
运算符的真值表如下:
操作数 _1 | 操作数 _2 | 结果 |
---|---|---|
False |
False |
False |
False |
True |
False |
True |
False |
False |
True |
True |
True |
以下是一些例子:
表示 | 中间表达 | 结果 |
---|---|---|
(10>3) and (15>6) |
True and True |
True |
(1>5) and (43==6) |
False and False |
False |
(1==1) and (2!=2) |
True and False |
False |
>>>
>>> (10>3) and (15>6) # both conditions are true so, the result is true
True
>>>
>>> (1>5) and (43==6) # both conditions are false so, the result is false
False
>>>
>>> (1==1) and (2!=2) # one condition is false(right operand) so, the result is false
False
>>>
关系运算符(即>
、>=
、<
、<=
、==
和!=
)的优先级大于and
运算符,因此以上表达式中的括号不是必须的,这里添加它只是为了使代码更易读。例如:
>>>
>>> (10>3) and (15>6)
True
>>>
>>> 10 > 3 and 15 > 6 # this expression is same as above
True
>>>
可以看到(10>3) and (15>6)
这个表达比10 > 3 and 15 > 6
要清晰的多。
在and
运算符中,如果第一个操作数被求值为False
,那么第二个操作数根本不会被求值。例如:
>>>
>>> (10>20) and (4==4)
False
>>>
在这种情况下,(10>20)
为 False,整个逻辑表达式也是如此。因此,无需评估表达式(4==4)
。
或运算符
当两个操作数都为False
时,or
运算符返回False
。否则返回True
。它的语法是:
语法 : operand_1 or operand_2
or
运算符的真值表如下:
操作数 _1 | 操作数 _2 | 结果 |
---|---|---|
False |
False |
False |
False |
True |
True |
True |
False |
True |
True |
True |
True |
以下是一些例子:
表示 | 中间表达 | 结果 |
---|---|---|
(100<200) or (55<6) |
True or False |
True |
(11>55) or (6==6) |
False or True |
True |
(1>12) or (2==3) |
False or False |
False |
(10<22) or (20>3) |
True or True |
True |
>>>
>>> (100<200) or (55<6)
True
>>>
>>> (11>55) or (6==6)
True
>>>
>>> (1>12) or (2==3)
False
>>>
>>> (10<22) or (20>3)
True
>>>
在or
运算符中,如果第一个操作数被求值为True
,那么第二个操作数根本不会被求值。例如:
>>>
>>> (100>20) or (90<30)
True
>>>
在这种情况下,(100>20)
就是True
,整个逻辑表达式也是如此。因此没有必要评价(90<30)
这个表达。
or
运算符的优先级低于and
运算符。
“非”算符
not
运算符否定表达式的值。换句话说,如果表达式为True
,则 not 运算符返回False
,如果表达式为False
,则返回True
。与其他两个逻辑运算符不同,not 是一元运算符。not
运算符的优先级高于and
运算符和or
运算符。它的语法是:
语法 : not operand
not
运算符的真值表如下:
操作数 | 结果 |
---|---|
True |
False |
False |
True |
以下是一些例子:
表示 | 中间表达 | 结果 |
---|---|---|
not (200==200) |
not True |
False |
not (10<=5) |
not False |
True |
>>>
>>> not (200==200)
False
>>>
>>> not (10<=5)
True
>>>
Python 中的字符串
最后更新于 2020 年 9 月 21 日
字符串是用单引号(''
)或双引号(""
)括起来的字符序列。下面是如何在 python 中创建字符串。
>>>
>>> s1 = 'String in single quotes'
>>> s1
'String in single quotes'
>>>
>>> s2 = "String in double quotes"
>>> s2
'String in double quotes'
>>>
>>>
>>> 'hello everyone'
'hello everyone'
>>>
>>> "hello everyone"
'hello everyone'
>>>
在 Python Shell 或 IDLE 中,字符串总是用单引号显示。但是,如果使用print()
功能,则仅显示字符串的内容。
>>>
>>> print(s1)
String in single quotes
>>> print(s2)
String in double quotes
>>>
有些语言像 C、C++、Java 把单个字符当作一种特殊类型叫做char
,但是在 Python 中单个字符也是一个字符串。
>>>
>>> achar = 'a' # string containing a single character
>>> type(achar)
<class 'str'>
>>>
>>> type("a string") # string containing multiple characters
<class 'str'>
>>>
使用 len()函数计算字符数
len()
内置函数统计字符串中的字符数。
>>>
>>> len("a string")
8
>>>
>>> s = "a long string"
>>>
>>> len(s)
13
>>>
>>> len("")
0
>>>
创建空字符串
>>>
>>> s3 = '' # empty string using single quotes
>>> s3
''
>>>
>>> s4 = "" # empty string using double quotes
>>> s4
''
>>>
虽然变量s3
和s4
不包含任何字符,但它们仍然是有效的字符串。您可以使用type()
功能来验证这一事实。
>>>
>>> type(s3)
<class 'str'>
>>>
>>> type(s4)
<class 'str'>
>>>
那么在创建字符串时,我应该使用单引号还是双引号呢?
当字符串中有单引号时,双引号就派上用场了。例如:
>>>
>>> print("I'm learning python")
I'm learning python
>>>
如果我们使用单引号,我们会得到以下错误:
>>>
>>> print('I'm learning python')
File "<stdin>", line 1
print('I'm learning python')
^
SyntaxError: invalid syntax
>>>
这里的问题是 Python 解释器认为第二个引号,就在字符I
之后,标志着字符串的结束,不知道如何处理剩下的字符。
同样,如果要在字符串中打印双引号,只需将整个字符串用单引号而不是双引号括起来。
>>>
>>> print('John says "Hello there !"')
John says "Hello there !"
>>>
还有一种使用转义序列在字符串中嵌入单引号或双引号的方法,这将在下面讨论。
转义序列
转义序列是一组特殊字符,用于打印无法使用键盘直接键入的字符。每个转义序列都以反斜杠(\
)字符开始。
下表列出了一些常见的转义序列。
换码顺序 | 意义 |
---|---|
\n |
换行符-打印换行符 |
\t |
制表符-打印制表符 |
\\ |
反斜杠-打印反斜杠(\ )字符 |
\' |
单引号-打印单引号 |
\" |
双引号-打印双引号 |
当转义序列在字符串中使用时,Python 将它们视为特殊命令。例如,字符串中的\t
字符打印一个制表符(一个制表符等于打印四个空格)。例如:
>>>
>>> s = "Name\tAge\tMarks"
>>> s
'Name\tAge\t\\Marks'
>>> print(s)
Name Age Marks
>>>
类似地,字符串中的\n
字符打印一个换行符。换行符不会显示在屏幕上,而是会使光标从下一行的开头开始打印后续字符。例如:
>>>
>>> s2 = 'One\nTwo\nThree'
>>> s2
'One\nTwo\nThree'
>>> print(s2)
One
Two
Three
>>>
也可以使用(\'
)和(\"
)转义序列在字符串中打印单引号或双引号。例如:
>>>
>>> print('I\'m learning python')
I'm learning python
>>>
>>> print("John says \"Hello there !\"")
John says "Hello there !"
>>>
当我们使用转义序列来打印单引号或双引号时,字符串是否包装在单引号或双引号中并不重要。
同样,要打印单个反斜杠字符(\
)请使用(\\
)转义序列。
>>>
>>> s3 = 'C:\\Users\\Q'
>>> s3
'C:\\Users\\Q'
>>> print(s3)
C:\Users\Q
>>>
串并置
字符串串联意味着将一个或多个字符串连接在一起。为了在 Python 中连接字符串,我们使用+
运算符。
>>>
>>> s1 = "This is " + "one complete string"
>>> print(s1)
This is one complete string
>>>
>>> s2 = "One " + "really really " + "long string"
>>> print(s2)
One really really long string
>>>
请注意,+
运算符在与两个数字一起使用时会执行数学加法。但是,当与字符串一起使用时,它会将它们连接起来。
>>>
>>> 98+57 # when used with numbers + operators adds them
155
>>>
如果其中一个操作数不是字符串,会发生什么?例如:
>>>
>>> s = "Python" + 101
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't convert 'int' object to str implicitly
>>>
这里 wear 试图连接字符串"Python"
和数字101
,但是 Python 报告了以下错误:
TypeError: Can't convert 'int' object to str implicitly
由于 Python 是一种强类型语言,它不能自动将一种类型的数据转换成完全不同的类型。
那么解决办法是什么呢?
解决方法是使用str()
函数将整数转换为字符串,如下所示:
>>>
>>> s = str(100)
>>> s
'100'
>>> type(s)
<class 'str'>
>>>
>>>
>>> s = "Python" + str(101)
>>> s
'Python101'
>>>
字符串重复运算符(*)
就像数字一样,我们也可以使用带字符串的*
运算符。当与琴弦一起使用时*
操作者重复琴弦n
的次数。它的一般格式是:
string * n
其中n
是一个数字类型int
。
>>>
>>> s = "www " * 5 # repeat "www " 5 times
>>> s
'www www www www www '
>>>
>>>
>>>
>>> print("We have got some", "spam" * 5)
We have got some spamspamspamspamspam
>>>
注意5 * "www "
和"www " * 5
产生相同的结果。
n
必须是int
。否则,你会得到一个错误。例如:
>>> "www" * "e" # n is a string
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'str'
>>>
>>>
>>> "www" * 1.5 # n is a float
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'float'
>>>
请注意,错误消息清楚地告诉我们,字符串不能乘以非 int 类型。
成员资格运算符-在和不在
in
或not in
运算符用于检查一个字符串是否存在于另一个字符串中。例如:
>>>
>>> s1 = "object oriented"
>>>
>>> "ted" in s1 # Does "ted" exists in s1 ?
True
>>>
>>>
>>> "eject" in s1 # Does "eject" exists in s1 ?
False
>>>
>>>
>>> "orion" not in s1 # Doesn't "orion" exists in s1 ?
True
>>>
>>> "en" not in s1 # Doesn't "en" exists in s1 ?
False
>>>
>>>
访问字符串中的单个字符
在 Python 中,字符串中的字符是按顺序存储的。我们可以通过使用索引来访问字符串中的单个字符。索引是指字符串中字符的位置。在 Python 中,字符串被0
索引,这意味着第一个字符在索引0
处,第二个字符在索引1
处,以此类推。最后一个字符的索引位置比字符串长度少一个字符。
要访问字符串中的单个字符,我们键入变量的名称,后跟方括号[]
中字符的索引号。
>>>
>>> s1 = "hello"
>>>
>>> s1[0] # get the first character
'h'
>>> s1[1] # get the second character
'e'
>>> s1[2] # get the third character
'l'
>>> s1[3] # get the fourth character
'l'
>>> s1[4] # get the fifth character
'o'
>>>
字符串s1
的最后一个有效索引是4
,如果您试图访问最后一个有效索引之外的字符,您将获得如下的IndexError
:
>>>
>>> s1[5] # get the sixth character
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range
>>>
不用手动统计字符串中最后一个字符的索引位置,我们可以使用len()
函数计算字符串,然后从中减去1
得到最后一个字符的索引位置。
>>>
>>> quote = "The best is the enemy of the good"
>>>
>>> quote[len(quote)-1]
'd'
>>>
我们也可以使用负指数。负索引允许我们从字符串的末尾访问字符。负索引从-1
开始,所以最后一个字符的索引位置是-1
,第二个最后一个字符是-2
等等。
>>>
>>> s = "markdown"
>>>
>>> s[-1] # get the last character
'n'
>>>
>>> s[-2] # get the second last character
'w'
>>>
>>> s[-8] # get the first character
'm'
>>>
>>> s[-len(s)] # get the first character
'm'
>>>
如果负指数小于最后一个有效指数(-8
),那么IndexError
将出现如下情况:
>>>
>>> s[-9]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range
>>>
切割字符串
字符串切片允许我们从字符串中获取一部分字符。为了获得字符串的切片,我们使用切片操作符([start_index:end_index]
)。它的语法是:
str_name[start_index:end_index]
str_name[start_index:end_index]
从索引start_index
到end_index
返回一段字符串。end_index
处的字符将不包含在切片中。考虑以下示例:
>>>
>>> s = "markdown"
>>>
>>>
>>> s[0:3] # get a slice of string starting from index 0 to 3, not including the character at index 3
'mar'
>>>
>>>
>>> s[2:5] # get a slice of string starting from index 2 to 5, not including the character at index 5
'rkd'
>>>
如果end_index
大于字符串的长度,则切片操作符返回从start_index
开始到字符串末尾的字符串切片。
>>>
>>> s[2:len(s)+200]
'rkdown'
>>>
start_index
和end_index
可选。如果未指定start_index
,则切片从字符串的开头开始,如果未指定end_index
,则切片继续到字符串的结尾。例如:
>>>
>>> s[:4] # start slicing from the beginning
'mark'
>>>
在上面的表达式中,切片从字符串的开头开始,所以上面的表达式与s[0:4]
相同。
>>>
>>> s[5:]
'own'
>>>
在这种情况下,结果省略了end_index
,切片继续到字符串的末尾,因此s[5:]
与s[5:len(s)]
相同。
>>>
>>> s[:]
'markdown'
>>>
这里我们省略了start_index
和end_index
,因此切片将从开始开始,一直到字符串的结尾。换句话说,s[:]
和s[0:len(s)]
是一样的
我们也可以在字符串切片中使用负索引。
因此s[1:-1]
将从索引1
开始返回一个切片到-1
,不包括索引-1
处的字符。
>>>
>>> s[1:-1]
'arkdow'
>>>
Python 中的一切都是对象
在 Python 中,所有数据都是对象。这意味着一个数字,一个字符串,其他类型的数据实际上是一个对象。为了确定物体的类型,我们使用type()
功能。
但是什么是对象呢?
类和对象-第一印象
在我们学习对象之前,我们必须先学习类。类只是一个定义数据和方法的模板。类内定义的函数称为方法。
class our_class
data
methods
当我们定义一个新的类时,我们实际上创建了一个新的数据类型。要使用我们的新类或数据类型,我们必须创建该类的对象。请注意,定义一个类不会占用任何内存空间。只有当我们基于该类创建对象时,才会分配内存。
根据这些新获得的知识,让我们看看当我们给一个变量赋值int
时会发生什么。
>>> num = 100
在上面的陈述中,我们给变量num
赋值100
。用面向对象的术语来说,我们刚刚创建了一个对象。要了解对象的类别或类型的更多信息,请使用如下的type()
方法:
>>>
>>> type(num)
<class 'int'>
>>>
<class 'int'>
表示num
变量是类int
的对象。同样,每根弦和float
分别是str
和float
类的对象。
内置类或类型,如int
、float
、str
;定义了许多有用的方法。要调用这些方法,我们使用以下语法:
object.method_name()
这里有一个例子:
str
类提供了像upper()
和lower()
这样的方法,分别将字符串转换成大写和小写后返回字符串。
>>>
>>> s1 = "A String"
>>>
>>> s2 = s.lower()
>>> s2
'a string'
>>>
>>> s3 = s1.upper()
>>> s3
'A STRING'
>>>
>>> s3
>>>
这些方法不会改变原始对象(s1
)的值。这就是为什么在调用lower()
和upper()
变量s1
后仍然指向"A String"
字符串对象的原因。
>>>
>>> s1
'A String'
>>>
>>>
要知道对象的内存地址,我们可以使用id()
函数,如下所示:
>>>
>>> id(s1)
15601373811
>>>
注意15601373811
是'A String'
字符串对象的地址,不是s1
变量的地址。在程序执行过程中,对象的内存地址不会改变。但是,它可能会在您每次运行程序时发生变化。如果两个对象相同,那么将具有相同的 id(或地址)。
ASCII 字符
在计算机中,一切都以 0 和 1 的序列存储。存储数字很容易,只要把它们转换成二进制就可以了。
但是字符是如何存储在内存中的呢?
电脑不能直接在内存中存储'a'
、'b'
、'1'
、'$'
等字符串。相反,它们存储的是代表字符的数字代码。字符及其数字代码的映射称为 ASCII(美国信息交换标准代码)字符集。ASCII 字符集有 128 个字符。
除了在美国键盘中找到的字符,ASCII 集还定义了一些控制字符。控制字符用于发出命令,它们是不可打印的字符。
控制字符的一个例子是 Ctrl+D,它通常用于终止 Shell 窗口。ASCII 表中的这个字符用EOT
(传输结束)表示,ASCII 值为 4。
下表显示了 ASCII 字符集的所有 128 个字符。
这里有几件事需要注意:
- 从
A
到Z
的所有大写字母都有从65
到90
的 ASCII 值。 - 从
'a'
到'z'
的所有小写字母都有从97
到122
的 ASCII 值。 - 当我们在字符串中使用数字(
0
-9
)时,它们是用从48
到57
的 ASCII 值来表示的。
order()和 chr()函数
ord()
函数返回一个字符的 ASCII 值,chr()
函数返回由 ASCII 值表示的字符。
order()函数
>>>
>>> ord("a") # print the ASCII value of character a
97
>>>
>>> ord("5") # print the ASCII value of character 5
53
>>>
chr()函数
>>>
>>> chr(97) # print the character represented by ASCII value 97
'a'
>>>
>>> chr(53) # print the character represented by ASCII value 53
'5'
>>>
print()函数中抑制换行符
默认情况下,print()
函数打印给定的参数,后跟一个换行符(\n
)。例如:
蟒蛇 101/章节-07/换行符 _ at _ end . py
print("first line")
print("second line")
输出:
first line
second line
请注意,字符串"second line"
打印在下一行的开头,这是因为第一个print()
调用打印的换行符(\n
)导致输出从下一行开始。
我们可以通过传递一个名为end
的特殊参数来改变print()
函数的这种行为。假设我们想在输出的末尾打印$
字符,而不是换行符(\n
)。为此调用print()
函数如下:
蟒蛇 101/章-07/dollor _ at _ end . py
print("first string", end="$")
print("second string", end="$")
输出:
first string$second string$
请注意两个字符串末尾的'$'
字符。由于第一条语句没有在输出的末尾打印一个换行符,所以第二条print()
的输出从同一行开始。
如果您不想在输出结束时打印任何内容,请将end=""
传递到print()
功能,如下所示:
蟒蛇 101/第-07 章/无 _at_end.py
print("first", end="")
print("second", end="")
print("third")
输出:
firstsecondthird
在这种情况下,前两个语句在输出的末尾打印一个空字符串(""
),但是最后一个语句打印"third"
,后跟一个换行符('\n'
)。
在打印中指定分隔符()函数
我们已经在第课中讨论了 Python 中的数据类型和变量,当我们将多个参数传递给print()
函数时,它们被打印到控制台上,用空格隔开。
>>>
>>> print("first", "second", "third")
first second third
>>>
为了覆盖这个行为,我们使用了另一个特殊的参数sep
作为分隔符的缩写。假设我们想通过#
将每个项目分开。为此,调用print()
函数如下:
>>>
>>> print("first", "second", "third", sep="#")
first#second#third
>>>
字符串比较
就像数字一样,我们也可以使用关系运算符来比较字符串。但是,与数字不同,字符串比较稍微复杂一些。Python 中的字符串使用其对应字符的 ASCII 值进行比较。比较从比较两个字符串的第一个字符开始。如果它们不同,则比较相应字符的 ASCII 值以确定比较结果。另一方面,如果它们相等,则比较下两个字符。这个过程一直持续到任何一个字符串用完。如果一个短字符串出现在另一个长字符串的开头,那么短字符串就是较小的字符串。在技术术语中,这种类型的比较被称为词典式比较。
让我们举一些例子:
例 1:
>>>
>>> "linker" > "linquish"
False
>>>
以下是评估上述表达式的步骤:
第一步:"link"
的"l"
与"linq"
的"l"
比较。由于它们相等,接下来的两个字符将被比较。
第二步:"link"
的"i"
与"linq"
的"i"
进行对比。同样,它们是相等的,接下来的两个字符进行比较。
第三步:"link"
的"n"
与"linq"
的"n"
进行对比。同样,它们是相等的,接下来的两个字符进行比较。
第四步:将"link"
的"k"
与"linq"
的"q"
进行比较。因为对应的字符不相同,所以比较在这一步停止。k
的 ASCII 值是107
,q
的 ASCII 值是113
,也就是说"k"
比"q"
小。因此弦"linker"
比"linquish"
小。因此"linker" > "linquish"
这个表达是错误的。
例 2:
>>>
>>> "qwerty" > "abc"
True
>>>
"qwerty"
的"q"
与"abc"
的"a"
相比较。此时,比较停止,因为对应的字符不相同。由于"q"
的 ASCII 值为113
,"a"
的 ASCII 值为97
,所以"q"
大于"a"
。因此弦"qwerty"
大于"abc"
。
例 3:
>>>
>>> "ab" > "abc"
False
>>>
这里短弦"ab"
出现在另一个长弦"abc"
的开始处。因此"ab"
是较小的一个。
更多示例:
>>>
>>> "tree" == "tree"
True
>>>
>>> "pass" != "password"
True
>>>
>>> "@@@" <= "123"
False
>>>
>>> "" <= "123"
True
>>>
字符串比较是编程中常见的操作。字符串比较的一个实际用途是按升序或降序对字符串进行排序。
字符串是不可变的
字符串对象是不可变的。这意味着我们不能在字符串对象创建后更改其内容。考虑以下示例:
>>>
>>> s = "hello"
>>>
>>> id(s)
35145912
>>>
这里我们已经创建了一个新的字符串对象,然后我们使用id()
函数来知道我们的字符串对象的地址。
让我们看看如果我们试图通过在字符串对象的末尾添加" world"
来修改现有的字符串对象s
会发生什么。
>>>
>>> s += " world"
>>> s
'hello world'
>>>
>>> id(s)
35150448
>>>
请注意,变量s
现在指向一个全新的地址,这是因为每次我们修改一个字符串对象,我们都会在这个过程中创建新的字符串对象。这证明了字符串对象是不可变的。
形式variable[index]
的表达式就像一个变量。因此,它们也可以出现在赋值运算符的左侧。例如:
>>>
>>> s[0] = 'y'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>>
这里,我们试图通过为索引0
处的元素分配一个新字符串来更新它。操作失败,因为字符串对象是不可变的。如果字符串是可变的,上述操作就会成功。
使用 format()函数格式化字符串
就像数字一样,我们也可以使用format()
函数来格式化字符串。要格式化字符串,我们使用类型代码s
和指定的宽度,例如:
>>>
>>> print(format("Python", "10s"))
Python
>>>
与数字不同,字符串默认为左对齐。这意味着,当宽度大于值的长度时,该值将左对齐打印,用尾随空格代替前导空格。
如果字符串的长度大于指定的宽度,则宽度会自动增加以匹配字符串的长度。
>>>
>>> print(format("Python", "4s"))
Python
>>>
要右对齐一个字符串,请使用如下符号>
:
>>>
>>> print(format("Python", ">10s"))
Python
>>>
>>>
语句print(format("Python", "<10s"))
与print(format("Python", "10s"))
相同,因为默认情况下字符串是左对齐打印的。
Python 中的字符串方法
最后更新于 2020 年 9 月 21 日
字符串类,即str
提供了许多有用的方法来操作字符串。具体来说,我们将讨论执行以下操作的方法。
- 在字符串中搜索子字符串。
- 测试字符串
- 格式化字符串
- 转换字符串。
回想一下前一章,方法是属于对象的函数。但是,与函数不同,方法总是使用以下符号在对象上调用。
object.method_name(arg1, arg2, arg3, ...., argN)
好了,我们开始吧。
测试字符串
str
类的以下方法测试字符串中的各种类型的字符。
方法 | 描述 |
---|---|
str.isalnum() |
如果字符串中的所有字符都是字母数字(包含数字或字母或两者的字符串),则返回True 。否则False 。 |
str.isalpha() |
如果字符串中的所有字符都是字母,则返回True 。否则False 。 |
str.isdigit() |
如果字符串中的所有字符都是数字,则返回True 。否则False 。 |
str.islower() |
如果字符串中的所有字符都是小写,则返回True 。否则False 。 |
str.isupper() |
如果字符串中的所有字符都是大写的,则返回True 。否则False 。 |
str.isspace() |
如果字符串中的所有字符都是空白字符,则返回True 。否则False 。 |
以下是一些例子:
isalnum()方法
>>>
>>> s = "A bite of python"
>>> s.isalnum()
False
>>>
>>> "123".isalnum()
True
>>> "abc".isalnum()
True
>>> "abc123".isalnum()
True
>>>
isalpha()方法
>>>
>>> "123".isalpha()
False
>>>
>>> "zyz".isalpha()
True
>>>
>>> "$$$$".isalpha()
False
>>>
>>> "abc233".isalpha()
False
>>>
isdigit()方法
>>>
>>>
>>> "name101".isdigit()
False
>>>
>>> "101".isdigit()
True
>>>
>>> "101 ".isdigit()
False
>>>
>>> "101.129".isdigit()
False
>>>
islower()和 isupper()方法
>>>
>>> s
'A bite of python'
>>>
>>> s.islower()
False
>>>
>>> "abc".islower()
True
>>>
>>> s.isupper()
False
>>>
>>>
>>> "ABC".isupper()
True
>>>
isspace()方法
>>>
>>> "\n\t".isspace()
True
>>>
>>> " \n\t".isspace()
True
>>>
>>> "@ \n\t".isspace()
False
>>> "123".isspace()
False
搜索和替换字符串
str
类有以下方法允许您搜索字符串中的子字符串。
方法 | 描述 |
---|---|
endswith(sub) |
如果字符串以子字符串sub 结尾,则返回True 。否则False 。 |
startswith(sub) |
如果字符串以子字符串sub 开头,则返回True 。否则False 。 |
find(sub) |
返回找到子字符串sub 的字符串的最低索引。如果未找到子字符串sub ,则返回-1 。 |
rfind(sub) |
返回找到子字符串sub 的字符串的最高索引。如果未找到子字符串sub ,则返回-1 。 |
count(sub) |
它返回在字符串中找到的子字符串sub 的出现次数。如果没有发现事件0 被返回。 |
replace(old, new) |
用new 替换old 子串后,返回一个新的字符串。请注意,它不会更改调用它的对象。 |
一些例子:
>>>
>>> s = "abc"
>>> s.endswith("bc")
True
>>>
>>> "python".startswith("py")
True
>>>
>>> "Learning Python".find("n")
4
>>>
>>> "Learning Python".rfind("n")
14
>>>
>>> "Learning Python".find("at")
-1
>>>
>>>
>>> "procrastination is the thief of time".count("ti")
3
>>>
>>>
>>>
>>> s1 = "Learning C" # old string
>>> id(s1)
49447664 # id of s1
>>>
>>> s2 = s.replace("C", "Python") # replace() creates a new string and assigns it to s2
>>> s2
'Learning Python'
>>>
>>> id(s1)
49447664 # notice that s1 object is not changed at all
>>>
>>>
转换字符串
以下方法通常用于返回字符串的修改版本。
方法 | 描述 |
---|---|
lower() |
将字符串的所有字符转换为小写后,返回字符串的新副本。 |
upper() |
将字符串的所有字符转换为大写后,返回字符串的新副本。 |
capitalize() |
仅将字符串中的第一个字母大写后,返回字符串的新副本。 |
title() |
将每个单词的第一个字母大写后,返回字符串的新副本。 |
swapcase() |
将小写字母转换为大写字母后返回新副本,反之亦然。 |
strip() |
移除所有前导和尾随空白字符后,返回字符串的新副本。 |
strip(chars) |
从字符串的开头和结尾删除chars 后,返回字符串的新副本。 |
请始终记住,这些方法会返回一个新字符串,并且不会以任何方式修改调用它们的对象。
以下是一些例子:
下()方法
>>>
>>> "abcDEF".lower()
'abcdef'
>>>
>>> "abc".lower()
'abc'
>>>
上限()方法
>>>
>>> "ABCdef".upper()
'ABCDEF'
>>>
>>> "ABC".upper()
'ABC'
>>>
大写()和 title()方法
>>>
>>> "a long string".capitalize()
'A long string'
>>>
>>>
>>> "a long string".title()
'A Long String'
>>>
>>>
swapcase()方法
>>>
>>> "ABCdef".swapcase()
'abcDEF'
>>>
>>> "def".swapcase()
'DEF'
>>>
条带()方法
>>>
>>> s1 = "\n\tName\tAge"
>>> print(s1)
Name Age
>>>
>>>
>>> s2 = s1.strip()
>>> s2
'Name\tAge'
>>> print(s2)
Name Age
>>>
>>>
>>> s = "--Name\tAge--"
>>>
>>> s.strip("-") # return a new copy of string after removing - characters from beginning and end of the string
'Name\tAge'
>>>
>>>
格式化方法
下表列出了str
类的一些格式化方法。
方法 | 描述 |
---|---|
center(width) |
在长度和宽度字段中居中后,返回字符串的新副本。 |
ljust(width) |
返回长度和宽度字段中向左对齐的字符串的新副本。 |
rjust(width) |
在长度和宽度字段中返回向右对齐的字符串的新副本。 |
center()方法
>>>
>>> "NAME".center(20)
' NAME '
>>>
>>>
ljust()方法
>>>
>>> "NAME".ljust(10)
'NAME '
>>> "NAME".ljust(4)
'NAME'
>>> "NAME".ljust(5)
'NAME '
>>>
rjust()方法
>>>
>>> "NAME".rjust(10)
' NAME'
>>>
>>> "NAME".rjust(4)
'NAME'
>>>
>>> "NAME".rjust(5)
' NAME'
>>>
>>>
Python 中的if-else
语句
原文:https://overiq.com/python-101/if-else-statements-in-python/
最后更新于 2020 年 9 月 21 日
到目前为止,我们编写的程序执行得非常有序。现实世界中的程序不是这样运行的。有时,我们只希望在满足特定条件时执行一组语句。为了处理这些情况,编程语言提供了一些称为控制语句的特殊语句。除了控制程序的流程,当某些条件为真时,我们还使用控制语句来循环或跳过语句。
控制语句有以下几种类型:
- 如果语句
- if-else 语句
- if-elif-else 语句
- while 循环
- for 循环
- break 语句
- 连续语句
在本课中,我们将讨论前三个控制语句。
如果语句
if 语句的语法如下:
语法:
if condition:
<indented statement 1>
<indented statement 2>
<non-indented statement>
语句的第一行即if condition:
被称为 if 子句,condition
是一个布尔表达式,其计算结果为True
或False
。在下一行中,我们有一组语句。一个块只是一个或多个语句的集合。当一个语句块后跟 if 子句时,它被称为 if 块。请注意,if 块中的每个语句在 if 关键字右侧缩进相同的量。许多语言,如 C、C++、Java、PHP,使用花括号({}
)来确定块的开始和结束,但是 Python 使用缩进。if 块中的每个语句都必须缩进相同数量的空格。否则,您会得到语法错误。Python 文档建议将块中的每个语句缩进 4 个空格。在本课程的所有课程中,我们都使用这一建议。
工作原理:
if 语句执行时,测试条件。如果条件为真,则执行 If 块中的所有语句。另一方面,如果条件为假,则跳过 if 块中的所有语句。
没有缩进的 if 子句后面的语句不属于 if 块。例如,<non-indented statement>
不是 if 块的一部分,因此,不管 if 子句中的条件是真还是假,它都会一直执行。
这里有一个例子:
蟒蛇 101/章节-09/if_statement.py
number = int(input("Enter a number: "))
if number > 10:
print("Number is greater than 10")
首次运行输出:
Enter a number: 100
Number is greater than 10
第二次运行输出:
Enter a number: 5
请注意,在条件失败时的第二次运行中,if 块中的语句被跳过。在这个例子中,if 块只包含一个语句,但是您可以有任意数量的语句,只要记住适当地缩进它们。
现在考虑以下代码:
python 101/章节-09/if_statement_block.py
number = int(input("Enter a number: "))
if number > 10:
print("statement 1")
print("statement 2")
print("statement 3")
print("Executes every time you run the program")
print("Program ends here")
首次运行输出:
Enter a number: 45
statement 1
statement 2
statement 3
Executes every time you run the program
Program ends here
第二次运行输出:
Enter a number: 4
Executes every time you run the program
Program ends here
在这个程序中需要注意的重要一点是,只有第 3、4 和 5 行的语句属于 if 块。因此,第 3、4 和 5 行中的语句只有在 if 条件为真时才会执行,而第 6 和 7 行中的语句无论如何都会执行。
当您在 Python shell 中键入控制语句时,它的响应有些不同。回想一下,要将一条语句拆分成多行,我们使用行延续运算符(\
)。控制语句不是这种情况,Python 解释器会在您按下 enter 键后立即自动将您置于多行模式。考虑以下示例:
>>>
>>> n = 100
>>> if n > 10:
...
请注意,在按回车键后跟 if 子句后,Shell 提示从>>>
变为...
。Python shell 显示...
对于多行语句,它只是意味着您开始的语句尚未完成。
要完成 if 语句,请向 if 块添加如下语句:
>>>
>>> n = 100
>>> if n > 10:
... print("n is greater than 10")
...
Python shell 不会自动缩进您的代码,您必须自己缩进。当您在块中键入完语句后,按两次回车键执行该语句,您将返回到正常的提示字符串。
>>>
>>> n = 100
>>> if n > 10:
... print("n is greater than 10")
...
n is greater than 10
>>>
如果条件为假,我们到目前为止编写的程序会突然结束,而不会向用户显示任何响应。大多数情况下,即使条件为假,我们也希望向用户显示响应。我们可以使用 if-else 语句轻松做到这一点
if-else 语句
if-else 语句在条件为真时执行一组语句,在条件为假时执行另一组语句。这样,if-else 语句允许我们遵循两个行动过程。if-else 语句的语法如下:
语法:
if condition:
# if block
statement 1
statement 2
and so on
else:
# else block
statement 3
statement 4
and so on:
工作原理:
当 if-else 语句执行时,如果条件被评估为True
,则测试条件,然后执行 if 块中的语句。但是,如果条件是False
,则执行 else 块中的语句。
这里有一个例子:
例 1 :计算圆的面积和周长的程序
蟒蛇 101/第-09 章/计算 _ 圆 _ 面积 _ 周长. py
radius = int(input("Enter radius: "))
if radius >= 0:
print("Circumference = ", 2 * 3.14 * radius)
print("Area = ", 3.14 * radius ** 2 )
else:
print("Please enter a positive number")
首次运行输出:
Enter radius: 4
Circumference = 25.12
Area = 50.24
第二次运行输出:
Enter radius: -12
Please enter a positive number
现在我们的程序向用户显示了一个适当的响应,即使条件是假的,这也是我们想要的。
在 if-else 语句中,始终确保 if 和 else 子句正确对齐。否则将会引发语法错误。例如:
python 101/章节-09/if _ and _ else _ not _ aligned . py
radius = int(input("Enter radius: "))
if radius >= 0:
print("Circumference = ", 2 * 3.14 * radius)
print("Area = ", 3.14 * radius ** 2 )
else:
print("Please enter a positive number")
尝试运行上面的程序,您会得到如下语法错误:
q@vm:~/python101/Chapter-09$ python3 if_and_else_not_aligned.py
File "if_and_else_not_aligned.py", line 6
else:
^
SyntaxError: invalid syntax
q@vm:~/python101/Chapter-06$
要解决该问题,请将 if 和 else 子句垂直对齐,如下所示:
radius = int(input("Enter radius: "))
if radius >= 0:
print("Circumference = ", 2 * 3.14 * radius)
print("Area = ", 3.14 * radius ** 2 )
else:
print("Please enter a positive number")
下面是另一个例子:
例 2 :检查用户输入的密码的程序
蟒蛇 101/第-09 章/秘密 _ 世界. py
password = input("Enter a password: ")
if password == "sshh":
print("Welcome to the Secret World")
else:
print("You are not allowed here")
首次运行输出:
Enter a password: sshh
Welcome to the Secret World
第二次运行输出:
Enter a password: ww
You are not allowed here
嵌套的 if 和 if-else 语句
我们还可以在另一个 if 或 if-else 语句中编写 if 或 if-else 语句。这可以通过一些例子更好地解释:
另一个 if 语句中的 if 语句。
例 1: 检查学生是否符合贷款条件的程序
python 101/章节-09/nested_if_statement.py
gre_score = int(input("Enter your GRE score: "))
per_grad = int(input("Enter percent secured in graduation: "))
if per_grad > 70:
# outer if block
if gre_score > 150:
# inner if block
print("Congratulations you are eligible for loan")
else:
print("Sorry, you are not eligible for loan")
这里我们在另一个 if 语句中写了一个 if 语句。内部 if 语句被称为嵌套 if 语句。在这种情况下,内部 if 语句属于外部 if 块,而内部 if 块只有一个打印"Congratulations you are eligible for loan"
的语句。
工作原理:
首先评估外中频条件,即per_grad > 70
,如果是True
,那么程序的控制进入外中频模块。在外如果块gre_score > 150
条件被测试。如果是True
,则"Congratulations you are eligible for loan"
被打印到控制台。如果是False
,则控制从 if-else 语句中出来,执行其后的语句,并且不打印任何内容。
另一方面,如果外部 if 条件被评估为False
,则跳过外部 if 块内部语句的执行,控制跳转到 else(第 9 行)块以执行其内部语句。
首次运行输出:
Enter your GRE score: 160
Enter percent secured in graduation: 75
Congratulations you are eligible for loan
第二次运行输出:
Enter your GRE score: 160
Enter percent secured in graduation: 60
Sorry, you are not eligible for loan
这个程序有一个小问题。再次运行程序,输入小于 T1 的 T0 和大于 T3 的 T2,如下所示:
输出:
Enter your GRE score: 140
Enter percent secured in graduation: 80
如您所见,程序不输出任何内容。这是因为我们的嵌套 if 语句没有 else 子句。在下一个示例中,我们将在嵌套的 if 语句中添加一个 else 子句。
例 2: 另一个 if 语句内部的 if-else 语句。
python 101/章节-09/nested _ if _ else _ statement . py
gre_score = int(input("Enter your GRE score: "))
per_grad = int(input("Enter percent secured in graduation: "))
if per_grad > 70:
if gre_score > 150:
print("Congratulations you are eligible for loan.")
else:
print("Your GRE score is no good enough. You should retake the exam.")
else:
print("Sorry, you are not eligible for loan.")
输出:
Enter your GRE score: 140
Enter percent secured in graduation: 80
Your GRE score is no good enough. You should retake the exam.
工作原理:
这个程序的工作原理和上面完全一样,唯一的区别是我们在这里添加了一个 else 子句,对应于嵌套的 if 语句。因此,如果你输入的 GRE 分数小于 150,程序会打印“你的 GRE 分数不够好。你应该重考。”而不是什么都不打印。
编写嵌套的 if 或 if-else 语句时,请始终记住正确缩进所有语句。否则你会得到一个语法错误。
else 子句中的 if-else 语句。
示例 3: 根据输入的分数确定学生成绩的程序。
蟒蛇 101/第-09 章/测试 _ 多重 _ 条件. py
score = int(input("Enter your marks: "))
if score >= 90:
print("Excellent ! Your grade is A")
else:
if score >= 80:
print("Great ! Your grade is B")
else:
if score >= 70:
print("Good ! Your grade is C")
else:
if score >= 60:
print("Your grade is D. You should work hard on you subjects.")
else:
print("You failed in the exam")
首次运行输出:
Enter your marks: 92
Excellent ! Your grade is A
第二次运行输出:
Enter your marks: 75
Good ! Your grade is C
第三次运行输出:
Enter your marks: 56
You failed in the exam
工作原理:
当程序控制进入 if-else 语句时,测试第 3 行的条件(score >= 90)
。如果条件为True
,控制台上会打印"Excellent ! Your grade is A"
。如果条件为假,控制跳转到第 5 行的 else 子句,然后测试条件score >= 80
(第 6 行)。如果是真的,那么"Great ! Your grade is B"
被打印到控制台。否则,程序控制跳转到第 8 行的 else 子句。同样,我们有一个带有嵌套 if-else 语句的 else 块。测试条件(score >= 70)
。如果为真,则"Good ! Your grade is C"
被打印到控制台。否则,控制再次跳到第 11 行的 else 块。最后,测试条件(score >= 60)
,如果是True
,则"Your grade is D. You should work hard on you subjects."
被打印到控制台。另一方面,如果为假,则将"You failed the exam"
打印到控制台。此时,控件从外部 if-else 语句中出来,执行其后的语句。
虽然嵌套的 if-else 语句允许我们测试多个条件,但是它们的读写相当复杂。我们可以使用 if-elif-else 语句使上面的程序更加易读和简单。
if-elif-else 语句
If-elif-else 语句是 if-else 语句的另一种变体,它允许我们轻松地测试多个条件,而不是编写嵌套的 if-else 语句。if-elif-else 语句的语法如下:
语法:
if condition_1:
# if block
statement
statement
more statement
elif condition_2:
# 1st elif block
statement
statement
more statement
elif condition_3:
# 2nd elif block
statement
statement
more statement
...
else
statement
statement
more statement
...
表示你,表示你可以根据需要写任意多的 elif 子句。
工作原理:
当 if-elif-else 语句执行时,首先测试condition_1
。如果条件为真,则执行 If 块中的语句。结构的其余部分中的条件和语句被跳过,控制从 if-elif-else 语句中出来,执行其后的语句。
如果condition_1
为假,程序控制跳转到下一个 elif 子句,并测试condition_2
。如果condition_2
为真,则执行第一个 elif 块中的语句。if-elif-else 结构的其余部分中的条件和语句被跳过,控制权从 if-elif-语句中出来,执行其后的语句。
这个过程一直重复,直到发现 elif 子句为真。如果没有发现 elif 子句为真,那么最后执行 else 块中的语句块。
让我们使用 if-elif-语句重写上面的程序。
蟒蛇 101/章节-09/if _ elif _ else _ statement . py
score = int(input("Enter your marks: "))
if score >= 90:
print("Excellent! Your grade is A")
elif score >= 80:
print("Great! Your grade is B")
elif score >= 70:
print("Good! Your grade is C")
elif score >= 60:
print("Your grade is D. You should work hard on you studies.")
else:
print("You failed in the exam")
首次运行输出:
Enter your marks: 78
Good ! Your grade is C
第二次运行输出:
Enter your marks: 91
Excellent! Your grade is A
第三次运行输出:
Enter your marks: 55
You failed in the exam
正如你所看到的,上面的程序很容易阅读和理解,不像它的嵌套等价物。
Python 中的循环
最后更新于 2020 年 9 月 21 日
循环允许我们多次执行某组语句。
考虑以下小问题:
假设我们要将字符串"Today is Sunday"
打印 100 次到控制台。实现这一点的一种方法是创建一个 Python 脚本并调用print()
函数 100 次,如下所示:
print("Today is Sunday") # 1st time
print("Today is Sunday") # 2nd time
...
...
print("Today is Sunday") # 100th time
正如你可能已经猜到的,这是一种非常低效的解决问题的方法。这个程序也不能很好地扩展。如果我们决定将"Today is Sunday"
打印 1000 次,那么我们将不得不将相同的语句多写 900 次。
我们可以使用 while 循环重写上述程序,如下所示:
i = 1
while i <= 100:
print("Today is Sunday")
i += 1
以后,如果我们决定打印"Today is Sunday"
1000 次,我们需要做的就是用 1000 替换 100,我们就完成了。
不要太担心 while 循环的语法,在下一节中,我们将深入讨论所有内容。
Python 提供了两种类型的循环:
- while 循环
- for 循环
while 循环
while 循环是一个有条件控制的循环,只要条件为真,它就会执行语句。while 循环的语法是:
while condition:
<indented statement 1>
<indented statement 2>
...
<indented statement n>
<non-indented statement 1>
<non-indented statement 2>
第一行称为 while 子句。就像 if 语句一样,condition 是一个布尔表达式,其计算结果为布尔True
或False
。缩进的语句组称为 while 块或循环体。通常需要缩进,它告诉 Python 块的开始和结束位置。
以下是它的工作原理:
首先评估条件。如果条件为真,则执行 while 块中的语句。在 while 块中执行语句后,再次检查条件,如果条件仍然为真,则再次执行 while 块中的语句。while 块中的语句将继续执行,直到条件为真。循环体的每次执行都被称为迭代。当条件变为假时,循环终止,程序控制从 while 循环中出来,开始执行后面的语句。
现在我们知道 while 循环是如何工作的了。让我们好好看看我们用来打印"Today is Sunday"
到控制台的程序。
蟒蛇 101/第 10 章/a_while_loop.py
i = 1
while i <= 100:
print("Today is Sunday")
i += 1
在第 1 行,我们为变量i
赋值1
。当控制进入 while 循环时,测试 while 条件i <= 100
。如果为真,则执行 while 块中的语句。while 块中的第一个语句向控制台打印“今天是星期天”。第二条语句增加变量i
的值。在 while 块中执行语句后,将再次测试该条件。如果仍然被发现为真,那么 while 块中的语句将被再次执行。这个过程一直重复,直到i
小于或等于100
。当i
变为101
时,while 循环终止,程序控制从 while 循环中出来,执行后面的语句。
示例 2: 下面的程序使用 while 循环来计算从 0 到 10 的数字总和
蟒蛇 101/第 10 章/sum_from_0_to_10.py
i = 1
sum = 0
while i < 11:
sum += i # same as sum = sum + i
i += 1
print("sum is", sum) # print the sum
输出:
sum is 55
这个 while 循环一直执行到i < 11
。变量sum
用于累加从0
到10
的数字总和。在每次迭代中,值i
被添加到变量sum
中,i
增加1
。当i
变为11
时,循环终止,程序控制从 while 循环中出来,执行第 7 行的print()
功能。
在 while 循环的主体中,您应该包含一个改变条件值的语句,这样条件最终会在某个时候变为假。如果你不这样做,那么你会遇到一个无限循环——一个永不终止的循环。
假设我们错误地将上述程序编写为:
蟒蛇 101/第 10 章/无限循环
i = 1
sum = 0
while i < 11:
print(i)
sum += i
i += 1
print("sum is", sum)
这里的语句i += 1
在循环体之外,这意味着i
的值永远不会增加。因此,循环是无限的,因为条件i < 11
永远是真的。试着执行上面的程序,你会发现它会无限期地保持打印i
的值。
输出:
1
1
1
1
1
...
当系统内存不足时,无限循环通常会结束。要手动停止无限循环,请按 Ctrl + C。
然而,这并不意味着无限循环没有用。有一些编程任务,无限循环真的很出色。考虑以下示例:
例 3 :从华氏到摄氏计算温度的程序。
蟒蛇 101/章节-10/华氏 _ 至 _ 摄氏. py
keep_calculating = 'y'
while keep_calculating == 'y':
fah = int(input("Enter temperature in Fahrenheit: "))
cel = (fah - 32) * 5/9
print(format(fah, "0.2f"), "°F is same as", format(cel, "0.2f"), "°C")
keep_calculating = input("\nWant to calculate more: ? Press y for Yes, n for N: ")
print("Program Ends")
当你运行程序时,它会要求你输入华氏温度。然后,它将华氏温度转换为摄氏温度,并显示结果。此时,程序会问你"Want to calculate more: ?"
。如果输入y
,会再次提示输入华氏温度。然而,如果您按下n
或任何其他字符,循环终止,程序控制从 while 循环中出来并打印"Program Ends"
到控制台。
输出:
Enter temperature in Fahrenheit: 43
43.00 °F is same as 6.11 °C
Want to calculate more: ? Press y for Yes, n for N: y
Enter temperature in Fahrenheit: 54
54.00 °F is same as 12.22 °C
Want to calculate more: ? Press y for Yes, n for N: n
Program Ends
for 循环
在 Python 中,我们使用 for 循环遍历序列。
那么什么是序列呢?
序列是一个通用术语,指以下类型的数据:
- 线
- 目录
- 元组
- 词典
- 设置
注:这里我们只介绍了字符串,其余的类型后面会讨论。
for 循环的语法如下:
for i in sequence:
# loop body
<indented statement 1>
<indented statement 2>
...
<indented statement n>
语法中的第一行即for i in sequence:
被称为 for 子句。在下一行中,我们有缩进的语句块,它在每次循环迭代时执行。
假设序列是一个列表[11, 44, 77, 33, 199]
,那么遍历列表的代码可以写成:
for i in [11, 44, 77, 33, 199]:
# loop body
<indented statement 1>
<indented statement 2>
...
<indented statement n>
注:在 Python 中,方括号[]
内逗号分隔的值列表称为列表。我们将在第课详细讨论 Python 中的列表。
以下是它的工作原理:
在第一次迭代中,列表中的值11
被分配给变量i
,然后执行循环体内部的语句。这就完成了循环的第一次迭代。在第二次迭代中,列表中的下一个值,即44
被分配给变量i
,然后再次执行循环体中的语句。对下一个值重复相同的过程。当列表中的最后一个值199
被分配给变量i
并执行循环体时,循环终止。然后程序控制从循环中出来,开始执行跟随它的语句。
以下是一些例子:
示例 1 :用于遍历列表中元素的循环
>>>
>>> for i in [11, 44, 77, 33, 199]:
... print(i)
...
11
44
77
33
199
>>>
示例 2 :用于循环遍历字符串中的字符
>>>
>>> for i in "astro":
... print(i)
...
a
s
t
r
o
>>>
对于带 range()函数的循环
除了迭代序列,我们还使用 for 循环,当我们事先知道循环体需要执行多少次时。这种循环被称为计数控制循环。Python 提供了一个名为range()
的函数,它简化了创建计数控制循环的过程。range()
函数返回一个被称为可迭代的特殊类型的对象。就像序列一样,当我们迭代一个可迭代对象时,它会从一个期望的序列中返回一个连续的项。你可以把可迭代对象想象成列表,但它不是一个真实的列表。
最简单地说,range()
函数的语法是:
range(a)
其中a
是一个整数。
range(a)
返回从0
到a-1
的整数序列。
下面是创建一个 for 循环的代码,它执行循环体 5 次:
>>>
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
>>>
在上面的代码中,range(5)
返回一个从0
到4
的数字序列。在第一次迭代中range(5)
函数返回0
,然后将其分配给变量i
。然后执行循环体。在第二次迭代中range(5)
返回1
,然后将其分配给变量i
。循环体被再次执行。当4
被分配给变量i
并执行循环体时,循环终止。
上述代码相当于:
>>>
>>> for i in [0, 1, 2, 3, 4]:
... print(i)
...
0
1
2
3
4
>>>
range()
功能还有以下两个版本:
range(a, b)
range(a, b, c)
范围(a,b)
range(a, b)
返回一系列的形式为a
、a+1
、a+2
,...b-2
、b-1
。
例如:range(90, 100)
返回一个序列,一个从90
到99
的数字序列。
范围(a、b、c)
默认情况下,range()
函数返回一个数字序列,其中每个连续的数字都比它的前身大1
。如果你想改变这一点,把第三个参数传递给range()
函数。第三个参数称为步长值。例如:
range(0, 50, 5)
返回以下数字序列:
0, 5, 10, 15, 20, 25, 30, 35, 40, 45
请注意,每个数字之间用5
隔开。
以下是一些例子:
例 1:
>>>
>>> for i in range(10, 20):
... print(i)
...
10
11
12
13
14
15
16
17
18
19
>>>
例 2:
>>>
>>> for i in range(10, 20, 3): # step value is 3
... print(i)
...
10
13
16
19
>>>
我们还可以指定负步长值:
>>>
>>> for i in range(50, 0, -5):
... print(i)
...
50
45
40
35
30
25
20
15
10
5
>>>
以下程序使用 for 循环生成从1
到20
的数字的平方:
蟒蛇 101/第 10 章/方块 _ 从 _ 1 _ 到 _20.py
print("Number\t | Square")
print("--------------------")
for num in range(1, 21):
print(num, "\t\t | ", num*num)
输出:
Number | Square
--------------------
1 | 1
2 | 4
3 | 9
4 | 16
5 | 25
6 | 36
7 | 49
8 | 64
...
17 | 289
18 | 324
19 | 361
Python 中的break
和continue
语句
原文:https://overiq.com/python-101/break-and-continue-statement-in-python/
最后更新于 2020 年 9 月 21 日
break 语句
break
语句用于在满足特定条件时提前终止循环。当在循环体内部遇到break
语句时,当前迭代停止,程序控制立即跳转到循环后的语句。break
的说法可以写成如下:
break
以下示例演示了 break 语句的作用。
例 1:
蟒蛇 101/第 11 章/break_demo.py
for i in range(1, 10):
if i == 5: # when i is 5 exit the loop
break
print("i =", i)
print("break out")
输出:
i = 1
i = 2
i = 3
i = 4
break out
一旦i
的值为5
,条件i == 5
变为真,break
语句导致循环终止,程序控制跳转到 for 循环之后的语句。执行第 6 行的 print 语句,程序结束。
例 2:
以下程序提示用户输入一个数字,并确定输入的数字是否为质数。
蟒蛇 101/第 11 章/prime_or_not.py
num = int(input("Enter a number: "))
is_prime = True
for i in range(2, num):
if num % i == 0:
is_prime = False # number is not prime
break # exit from for loop
if is_prime:
print(num, "is prime")
else:
print(num, "is not a prime")
首次运行输出:
Enter a number: 11
11 is prime
第二次运行输出:
Enter a number: 23
23 is prime
第三次运行输出:
Enter a number: 6
6 is not a prime
质数是只能被1
或自身整除的数。以下是质数的一些例子:
1, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 33, 37, 41
如果一个数n
不能被从2
到n-1
的任何数整除,那么它就是质数。请考虑以下示例:
例 1:
5 是素数吗?
以下是判断数字 5 是否是质数的步骤。
问题 | 编程语句 | 结果 |
---|---|---|
5 能被 2 整除吗? | 5 % 2 == 0 |
False |
5 能被 3 整除吗? | 5 % 3 == 0 |
False |
5 能被 4 整除吗? | 5 % 4 == 0 |
False |
结果:5 是质数
例 2:
9 是素数吗?
以下是判断数字 9 是否是质数的步骤。
问题 | 编程语句 | 结果 |
---|---|---|
9 能被 2 整除吗? | 9 % 2 == 0 |
False |
9 能被 3 整除吗? | 9 % 3 == 0 |
True |
第二步我们的测试9 % 3 == 0
通过了。换句话说,9
可以被3
整除,也就是说9
不是素数。在这一点上,用剩余的数字来检验9
的可分性是没有意义的。所以我们停下来。
这正是我们在上述程序中所做的。在第 1 行,我们要求用户输入一个数字。在第 3 行,我们已经用布尔值True
声明了一个变量is_prime
。最后,这个变量将决定用户输入的数字是否是质数。
for 循环迭代通过2
到num-1
。如果num
可以被该范围内的任意数字整除(第 6 行),我们将is_prime
设置为False
,并立即退出 for 循环。但是,如果条件n % i == 0
永远不满足break
语句将不会执行,is_prime
将保持设置为True
。在这种情况下num
将是一个质数。
嵌套循环中的 break 语句
在嵌套循环中,break
语句只终止它出现的循环。例如:
蟒蛇 101/第 11 章/break _ inside _ nested _ loop . py
for i in range(1, 5):
print("Outer loop i = ", i, end="\n\n")
for j in range (65, 75):
print("\tInner loop chr(j) =", chr(j))
if chr(j) == 'C':
print("\tbreaking out of inner for loop ...\n")
break
print('-------------------------------------------------')
输出:
Outer loop i = 1
Inner loop chr(j) = A
Inner loop chr(j) = B
Inner loop chr(j) = C
breaking out of inner for loop ...
-------------------------------------------------
Outer loop i = 2
Inner loop chr(j) = A
Inner loop chr(j) = B
Inner loop chr(j) = C
breaking out of inner for loop ...
-------------------------------------------------
Outer loop i = 3
Inner loop chr(j) = A
Inner loop chr(j) = B
Inner loop chr(j) = C
breaking out of inner for loop ...
-------------------------------------------------
Outer loop i = 4
Inner loop chr(j) = A
Inner loop chr(j) = B
Inner loop chr(j) = C
breaking out of inner for loop ...
-------------------------------------------------
对于外部循环的每次迭代,内部 For 循环执行三次。一旦条件chr(j) == 'C'
满足break
语句,就会立即退出内部 for 循环。然而,外部 for 循环将照常执行。
连续语句
continue
语句用于前进到下一个迭代,而不执行循环体中的剩余语句。就像break
语句一样,continue
语句一般与条件连用。continue
语句可以写成如下:
continue
这里有一个例子来演示continue
语句的工作原理:
蟒蛇 101/第 11 章/continue_demo.py
for i in range(1, 10):
if i % 2 != 0:
continue
print("i =", i)
输出:
i = 2
i = 4
i = 6
i = 8
在上述程序中,当条件i % 2 != 0
评估为True
时,执行continue
语句,省略循环体内print()
函数的执行,程序控制前进到循环的下一次迭代。
我们也可以在同一个循环中一起使用break
和continue
语句。例如:
蟒蛇 101/第 11 章/休息和继续
while True:
value = input("\nEnter a number: ")
if value == 'q': # if input is 'q' exit from the while loop
print("Exiting program (break statement executed)...")
break
if not value.isdigit(): # if input is not a digit move on to the next iteration
print("Enter digits only (continue statement executed)")
continue
value = int(value)
print("Cube of", value, "is", value**3) # everything is fine, just print the cube
输出:
Enter a number: 5
Cube of 5 is 125
Enter a number: 9
Cube of 9 is 729
Enter a number: @#
Enter digits only (continue statement executed)
Enter a number: 11
Cube of 11 is 1331
Enter a number: q
Exiting program (break statement executed)...
上面的程序要求用户输入一个数字并计算它的立方。如果输入了一个数字,程序将显示该数字的立方。如果用户输入非数字字符,则执行continue
语句,跳过循环主体中剩余语句的执行,程序再次要求用户输入。另一方面,如果用户输入q
,则执行循环体中的break
语句,while 循环终止。
Python 中的列表
最后更新于 2020 年 9 月 21 日
顺序
Sequences 是一个通用术语,用于指代可以保存多项数据的数据类型。在 Python 中,我们有几种类型的序列,以下三种是最重要的:
- 目录
- 元组
- 用线串
在本章中,我们将讨论列表类型。
什么是列表?
假设我们要计算一个班级 100 个学生的平均分数。为了完成这项任务,我们的第一步是通过创建 100 个变量来存储 100 名学生的分数。目前为止一切顺利。如果我们要计算 1000 名或以上学生的平均分数会怎样?我们应该创建 1000 个变量吗?不要。当然不是。这种解决问题的方法是非常不切实际的。我们需要的是一份清单。
列表是由多个项目组成的序列,用逗号分隔,用方括号括起来,即[]
。创建列表的语法如下:
variable = [item1, item2, item3, ..., itemN]
存储在列表中的每一项都称为列表元素或简称为元素。例如:
>>>
>>> numbers = [11, 99, 66, 22]
>>>
该语句创建一个包含 4 个元素的列表,并将其分配给变量numbers
。请注意,就像其他所有东西一样,列表也是一个对象,类型为list
。numbers
变量不存储列表的内容,它只存储对列表对象实际存储在内存中的地址的引用。
>>>
>>> type(numbers)
<class 'list'>
>>>
要在 Python shell 中打印列表内容,只需键入列表名称。
>>>
>>> numbers
[11, 99, 66, 22]
>>>
我们也可以使用print()
功能打印列表。
>>>
>>> print(numbers)
[11, 99, 66, 22]
>>>
列表可以包含相同或不同类型的元素。
>>>
>>> mixed = ["a string", 3.14, 199] # list where elements are of different types
>>>
>>> mixed
['a string', 3.14, 199]
>>>
要创建一个空列表,只需键入没有任何元素的方括号。
>>>
>>> empty_list = [] # an empty list
>>>
我们也可以使用list()
构造函数创建列表。
>>>
>>> list1 = list() # an empty list
>>> list2 = list([3.2, 4, 0.12]) # again elements in a list can be of different types
>>> list3 = list(["@@", "###", ">>>"]) # you can use symbols too
>>> list4 = list("1234") # creating list from string
>>>
>>>
>>> list1
[]
>>>
>>> list2
[3.2, 4, 0.12]
>>>
>>> list3
['@@', '###', '>>>']
>>>
>>> list4
['1', '2', '3', '4']
>>>
>>>
列表的元素也可以是列表。
>>>
>>> list5 = [
... [33, 55, 77], # first element
... [99, 31, 64] # second element
... ]
>>>
>>>
>>> list5
[[33, 55, 77], [99, 31, 64]]
>>>
>>>
list5
包含两种类型的元素list
。这种类型的列表被称为列表或嵌套列表或多维列表。
使用 range()函数创建列表
range()
功能也可以用来创建长列表。回想一下range()
函数返回一个 iterable 类型的对象(要了解更多信息请点击此处,我们只需要创建一个列表,将这个 iterable 对象传递给list()
构造函数,如下所示:
>>>
>>> list1 = list(range(5))
>>> list1
[0, 1, 2, 3, 4]
>>>
>>>
range(5)
生成以下序列:
0, 1, 2, 3, 4
list()
函数然后使用这个序列中的数字创建一个列表。
下面是更多的例子:
例 1:
>>>
>>> list2 = list(range(1, 101)) ## create a list of numbers from 1 to 100
>>> list2
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
>>>
>>>
例 2:
>>>
>>> list3 = list(range(0, 100, 10)) ## create a list of numbers from 0 to 100 with step value of 10
>>> list3
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>>
>>>
列出功能
下表列出了我们在处理列表时常用的一些函数。
功能 | 描述 |
---|---|
len() |
返回序列中元素的数量。 |
sum() |
返回序列中元素的总和。 |
max() |
返回序列中值最大的元素。 |
min() |
返回序列中值最小的元素。 |
>>>
>>>
>>> list1 = [1, 9, 4, 12, 82]
>>>
>>> len(list1) # find the length of the list
5
>>>
>>> sum(list1) # find the sum of the list
108
>>>
>>> max(list1) # find the greatest element in the list
82
>>>
>>> min(list1) # find the smallest element in the list
1
>>>
>>>
索引运算符
就像字符串一样,列表中的元素被0
索引,这意味着第一个元素在索引0
处,第二个在1
,第三个在2
处,以此类推。最后一个有效索引将比列表长度少一个。我们使用以下语法从列表中访问一个元素。
a_list[index]
其中index
必须是整数。
例如,下面的语句创建了一个包含 6 个元素的列表。
list1 = [88, 99, 4.12, 199, 993, 9999]
这里list1[0]
指的是元素88
,list1[1]
指的是99
,list[5]
指的是9999
。
>>>
>>> list1 = [88, 99, 4.12, 199, 993, 9999]
>>>
>>> list1[0] # get the first element
88
>>> list1[1] # get the second element
99
>>> list1[5] # get the sixth element
9999
>>>
我们还可以使用len()
函数计算列表的最后一个有效索引,如下所示:
>>>
>>> len(list1) - 1
5
>>>
>>> list1[len(list1) - 1] # get the last element
9999
>>>
试图访问超过最后一个有效索引的元素将导致IndexError
。
>>>
>>> list1[100] # get the element at index 100
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>>
>>>
就像字符串一样,负索引在这里仍然有效。事实上,我们可以在 Python 中的几乎所有类型的序列上使用 can 负索引。
从图中可以看到,最后一个元素在索引-1
处,第二个最后一个元素在-2
处,以此类推。第一个元素在索引-6
。
>>>
>>> list1
[88, 99, 4.12, 199, 993, 9999]
>>>
>>> list1[-1] # get the last element
9999
>>> list1[-2] # get the second last element
993
>>>
要计算第一个元素的指数,使用len()
函数,如下所示:
>>>
>>> list1[-len(list1)] # get the first element
88
>>>
列表是可变的
列表是可变的,这意味着我们可以在不创建新列表的情况下修改列表。考虑以下示例:
>>>
>>> list1 = ["str", "list", "int", "float"]
>>>
>>> id(list1) # address where list1 is stored
43223176
>>>
>>> list1[0] = "string" # Update element at index 0
>>>
>>> list1 # list1 is changed now
['string', 'list', 'int', 'float']
>>>
>>> id(list1) # notice that the id is still same
43223176
>>>
>>>
请注意,即使修改了列表,变量list1
的 id 也保持不变。这表明当我们在索引0
处分配新元素时,没有创建新的列表对象。
遍历列表中的元素
为了迭代一个列表,我们可以使用如下循环:
>>>
>>> marks = [122, 45, 23, 78, 65, 12]
>>> for m in marks:
... print(m)
...
122
45
23
78
65
12
>>>
在每次迭代中,从列表中为变量m
分配一个值。更改循环体中变量m
的值不会更新列表中的元素。因此,当我们不需要修改列表中的元素时,这个方法通常用于遍历列表。
要修改元素,我们可以结合range()
函数使用 for 循环,如下所示:
>>>
>>> marks = [122, 45, 23, 78, 65, 12]
>>>
>>> import random
>>>
>>> for i in range(len(marks)):
... marks[i] = random.randint(1, 100) # assign some random value between 1 to 100 to all elements
...
>>>
>>> marks
[59, 9, 59, 21, 75, 61]
>>>
>>>
虽然 for 循环是遍历列表的首选方式,但如果我们愿意,也可以使用 while 循环。例如:
>>>
>>> i = 0
>>>
>>> while i < len(marks):
... print(marks[i])
... i += 1
...
59
9
59
21
75
61
>>>
>>>
列表切片
我们在第课讨论的切片操作符 Python 中的字符串也可以在列表中找到。唯一的区别是,它不是返回一片字符串,而是返回一片列表。它的语法是:
list[start:end]
这将返回从索引start
到end - 1
的列表片段。以下是一些例子:
>>>
>>> list1 = [11, 33, 55, 22, 44, 89]
>>>
>>> list1[0:4]
[11, 33, 55, 22]
>>>
>>>
>>> list1[1:5]
[33, 55, 22, 44]
>>>
>>>
>>> list1[4:5]
[44]
>>>
start
和end
索引是可选的,如果没有指定,那么开始索引是0
,结束索引是列表的length
。例如:
>>>
>>> list1
[11, 33, 55, 22, 44, 89]
>>>
>>> list1[:2] # same as list1[0:2]
[11, 33]
>>>
>>> list1[2:] # same as list1[2:len(list1)]
[55, 22, 44, 89]
>>>
>>> list1[:] # same as list1[0:len(list1)]
[11, 33, 55, 22, 44, 89]
>>>
>>>
中的成员资格运算符,而不是中的
就像字符串一样,我们可以使用in
和not in
运算符来检查列表中是否存在某个元素。以下是一些例子:
>>>
>>> cards = ["club", "diamond", "heart", "spades"]
>>>
>>> "club" in cards
True
>>>
>>> "joker" in cards
False
>>>
>>> "pikes" not in cards
True
>>>
>>> "heart" not in cards
False
>>>
>>>
列表串联
使用+
操作符也可以加入列表。当两边的操作数都是列表时+
运算符通过组合两个列表中的元素来创建一个新列表。例如:
>>>
>>> list1 = [1,2,3] # create list1
>>> list2 = [11,22,33] # create list2
>>>
>>> id(list1) # address of list1
43223112
>>> id(list2) # address of list2
43223048
>>>
>>>
>>>
>>> list3 = list1 + list2 # concatenate list1 and list2 and create list3
>>> list3
[1, 2, 3, 11, 22, 33]
>>>
>>>
>>>
>>> id(list3) # address of the new list list3
43222920
>>>
>>>
>>> id(list1) # address of list1 is still same
43223112
>>> id(list2) # address of list2 is still same
43223048
>>>
>>>
请注意,串联并不影响list1
和list2
,它们的地址在串联前后保持不变。
连接列表的另一种方法是使用+=
运算符。+=
操作员修改列表,而不是创建新列表。这里有一个例子:
>>>
>>> list1
[1, 2, 3]
>>>
>>> id(list1)
43223112
>>>
>>> list1 += list2 # append list2 to list1
>>>
>>> list1
[1, 2, 3, 11, 22, 33]
>>>
>>>
>>> id(list1) # address is still same
43223112
>>>
语句list1 += list2
将list2
附加到list1
的末尾。注意list1
的地址还是没变。不像像 C、C++和 Java 这样的语言,数组是固定大小的。在 Python 中,列表是动态调整大小的。这意味着列表的大小会根据需要自动增长。
重复运算符
我们也可以对列表使用*
运算符。它的语法是:
sequence * n
*
操作符复制列表,然后加入它们。以下是一些例子:
>>>
>>> list1 = [1, 5]
>>>
>>>
>>> list2 = list1 * 4 # replicate list1 4 times and assign the result to list2
>>>
>>> list2
[1, 5, 1, 5, 1, 5, 1, 5]
>>>
>>>
*
运算符也可以用作复合赋值运算符*=
。唯一的区别是,它不是创建一个新的列表对象,而是更新现有的列表对象。
>>>
>>> action = ["eat", "sleep", "repeat"]
>>>
>>> id(action) # address of action list
32182472
>>>
>>> action *= 5
>>>
>>> action
['eat', 'sleep', 'repeat', 'eat', 'sleep', 'repeat', 'eat', 'sleep', 'repeat', '
eat', 'sleep', 'repeat', 'eat', 'sleep', 'repeat']
>>>
>>> id(action) # address is still the same
32182472
>>>
比较列表
就像字符串一样,我们可以使用关系运算符(>
、>=
、<
、<=
、!=
、==
)来比较列表。列表比较仅在涉及的操作数包含相同类型的元素时有效。该过程从比较两个列表中索引0
处的元素开始。只有当到达列表末尾或列表中对应的字符不同时,比较才会停止。
考虑以下示例:
>>>
>>> n1 = [1,2,3]
>>> n2 = [1,2,10]
>>>
>>>
>>> n1 > n2
False
>>>
以下是列表n1
和n2
比较涉及的步骤。
第一步:n1
的1
与n2
的1
比较。由于它们是相同的,接下来将对两个字符进行比较。
第二步:n2
的2
与n2
的2
进行对比。同样,它们是相同的,下面两个字符进行比较。
第三步:将n1
的3
与10
的10
进行比较。显然3
比10
小。所以比较n1 > n2
返回False
。
这里还有一个例子,其中的元素是字符串。
>>>
>>> word_list1 = ["pow", "exp"]
>>> word_list2 = ["power", "exponent"]
>>>
>>> word_list1 < word_list2
True
>>>
第一步:word_list1
的"pow"
与word_list2
的"power"
进行比较。显然"pow"
比"power"
小。此时,比较停止,因为我们发现列表中的元素不一样,所以比较word_list1 < word_list2
为真。
列表理解
通常,您需要创建列表,其中序列中的每个元素都是某些操作的结果,或者列表中的每个元素都满足某些条件。例如,创建一系列从50
到100
的数字立方体。我们在这种情况下使用列表理解。列表理解的语法是:
[ expression for item in iterable ]
以下是它的工作原理:
在每次迭代中item
从可迭代对象中被赋值,然后for
关键字之前的expression
被求值;expression
的结果随后用于产生列表的值。重复这个过程,直到没有更多的元素需要迭代。
这里有一个例子:
>>>
>>> cube_list = [ i**3 for i in range(50, 101) ]
>>>
>>> cube_list
[125000, 132651, 140608, 148877, 157464, 166375, 175616, 185193, 195112, 205379,
216000, 226981, 238328, 250047, 262144, 274625, 287496, 300763, 314432, 328509,
343000, 357911, 373248, 389017, 405224, 421875, 438976, 456533, 474552, 493039,
512000, 531441, 551368, 571787, 592704, 614125, 636056, 658503, 681472, 704969,
729000, 753571, 778688, 804357, 830584, 857375, 884736, 912673, 941192, 970299,
1000000]
>>>
我们还可以在列表理解中包含一个 if 条件,如下所示:
[ expression for item in iterable if condition ]
这和上面的完全一样,唯一的区别是for
关键字前的expression
只有在condition
为True
时才被评估。
这里有一个例子:
>>>
>>> even_list = [ i for i in range(1, 10) if i % 2 == 0 ]
>>>
>>> even_list
[2, 4, 6, 8]
>>>
列出方法
list
类有许多内置方法,允许我们添加元素、移除元素、更新元素等等。下表列出了list
类提供的一些操作列表的常用方法。
方法 | 描述 |
---|---|
appends(item) |
在列表末尾添加一个item 。 |
insert(index, item) |
在指定的index 处插入一个item 。如果指定的index 大于最后一个有效的index ,则item 被添加到列表的末尾。 |
index(item) |
返回指定item 第一次出现的索引。如果列表中不存在指定的item ,则会引发异常。 |
remove(item) |
从列表中删除指定item 的第一次出现。如果列表中不存在指定的item ,则会引发异常。 |
count(item) |
返回一个item 在列表中出现的次数。 |
clear() |
从列表中移除所有元素。 |
sort() |
按升序对列表进行排序。 |
reverse() |
颠倒列表中元素的顺序。 |
extend(sequence) |
将sequence 的元素追加到列表的末尾。 |
pop([index]) |
移除指定index 处的元素并返回该元素。如果未指定index ,则从列表中移除并返回最后一个元素。当index 无效时,会引发异常。 |
请注意,除了count()
之外,所有这些方法都会修改调用它的列表对象。
下面的 shell 会话演示了如何使用这些方法:
append()方法
>>>
>>> list1 = [1,2,3,4,5,6]
>>>
>>> id(list1)
45741512 # address of list1
>>>
>>> list1.append(10) # append 10 to list1
>>>
>>> list1
[1, 2, 3, 4, 5, 6, 10]
>>>
>>> id(list1) # address remains unchanged
45741512
>>>
insert()方法
>>>
>>> list1 = [1,2,3,4,5,6]
>>>
>>> id(list1)
45739272
>>>
>>> list1.insert(2, 1000) # insert item 1000 at index 2
>>>
>>> list1
[1, 2, 1000, 3, 4, 5, 6] # now the last valid index is 6
>>>
>>>
>>> list1.insert(6, 4000) # insert the item 4000 at index 6
>>>
>>> list1
[1, 2, 1000, 3, 4, 5, 4000, 6] # now the last valid index is 7
>>>
>>>
>>> list1.insert(8, 8000) # insert the item 8000 at index 8, which is beyond the last valid index
>>>
>>> list1
[1, 2, 1000, 3, 4, 5, 4000, 6, 8000]
>>> list1[8]
8000
>>>
index()方法
>>>
>>> list1 = [1, 2, 3, 4, 5, 6, 1, 2, 3]
>>>
>>> list1.index(1) # index of first occurrence of element 1
0
>>> list1.index(3) # index of first occurrence of element 3
2
>>> list1.index(90) # an exception is raised, as value 90 doesn't exists in the list
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: 90 is not in list
>>>
>>>
remove()方法
>>>
>>> list1 = [1, 2, 3, 4, 5, 6, 1, 2, 3]
>>>
>>> list1.remove(1) # remove first occurence of element 1
>>>
>>> list1
[2, 3, 4, 5, 6, 1, 2, 3]
>>>
>>> list1.remove(2) # remove first occurence of element 2
>>>
>>> list1
[3, 4, 5, 6, 1, 2, 3]
>>>
>>> list1.remove(100) # an exception is raised, as value 100 doesn't exists in the list
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
>>>
>>>
count()方法
>>>
>>> list1 = [1, 2, 3, 4, 5, 6, 1, 2, 3]
>>>
>>> list1.count(2) # count the appearance of element 2 in the list
2
>>> list1.count(100) # count the appearance of element 100 in the list
0
>>> list1.count(1) # count the appearance of element 1 in the list
2
>>>
clear()方法
>>>
>>>
>>> list1 = [1, 2, 3, 4, 5, 6, 1, 2, 3]
>>>
>>> id(list1)
45738248
>>>
>>> list1.clear() # clear all the elements in the list list1
>>>
>>> list1
[]
>>>
>>> id(list1)
45738248
>>>
>>>
sort()方法
>>>
>>> list1 = [12, -2, 3, 4, 100, 50]
>>>
>>> list1.sort() # sort the list in ascending order
>>>
>>> list1
[-2, 3, 4, 12, 50, 100]
>>>
>>> types = ["str", "float", "int", "list"]
>>>
>>> types.sort() # sort the list, strings are sorted based on the ASCII values
>>>
>>> types
['float', 'int', 'list', 'str']
>>>
>>>
反向()方法
>>>
>>> list1 = [12, -2, 3, 4, 100, 50]
>>>
>>>
>>> list1.sort() # sort the list in ascending order
>>>
>>> list1
[-2, 3, 4, 12, 50, 100]
>>>
>>> list1.reverse() # sort the list in descending order
>>>
>>> list1
[100, 50, 12, 4, 3, -2]
>>>
>>>
扩展()方法
>>>
>>> list1 = [1, 2, 3]
>>>
>>> id(list1)
45738248
>>>
>>> list1.extend([100, 200, 300]) # append elements of list [100, 200, 300] to list1
>>>
>>> list1
[1, 2, 3, 100, 200, 300]
>>>
>>> id(list1)
45738248
>>>
>>>
>>> list1.extend("str") # We can pass strings too
>>>
>>> list1
[1, 2, 3, 100, 200, 300, 's', 't', 'r']
>>>
>>>
>>> id(list1)
45738248
>>>
>>>
pop()方法
>>>
>>> list1 = [1, 2, 3, 4, 5, 6]
>>>
>>> list1.pop(4) # remove the element at index 4
5
>>> list1
[1, 2, 3, 4, 6]
>>>
>>> list1.pop() # remove the last element
6
>>>
>>> list1
[1, 2, 3, 4]
>>>
>>>
>>> list1.pop(10) # index specified is not valid
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: pop index out of range
>>>
>>>
Python 中的函数
最后更新于 2020 年 9 月 22 日
到目前为止,在本课程中,我们一直在使用 Python 附带的内置函数。在本课中,我们将学习如何创建自己的函数。但在此之前,让我们花点时间了解一下为什么我们一开始就需要它们。
假设您想创建一个程序,允许用户计算两个数字之间的和。在这一点上,你写这样的程序应该没有任何问题;无论如何,您的代码看起来可能是这样的:
sum = 0
start = 10
end = 30
for i in range(start, end+1):
sum += i
print("Sum is", sum)
该程序计算从10
到30
的所有数字的总和。如果我们想计算从100
到200
的数字总和,我们需要更新程序如下:
sum = 0
start = 100
end = 200
for i in range(start, end+1):
sum += i
print("Sum is", sum)
正如你所看到的,程序的两个版本几乎相同,唯一的区别在于start
和end
变量的值。所以每次我们想计算两个数的和时,我们都需要更新程序的源代码。如果我们能以某种方式重用整个代码而不做任何修改,那就太好了。我们可以用函数来实现。
什么是函数
一个函数被命名为一组执行特定任务的语句。定义函数的语法如下:
def function_name(arg1, arg2, arg3 ... argN):
# function body
<indented statement 1>
<non-indented statement 2>
...
<indented statement n>
<return statement>
函数由两部分组成:标题和正文。函数头以def
关键字开始,然后是函数名,接着是参数,最后是冒号(:
)。
def
是一个保留的关键字,所以你不应该在你的程序中使用它作为变量或函数名。function_name
可以是任何有效的标识符。在函数名之后,我们有一个用逗号(,
)分隔的括号内的参数列表。我们使用这些参数向函数传递必要的数据。一个函数可以接受任意数量的参数,也可以不接受任何参数。如果函数不接受任何参数,则圆括号为空。
在下一行,我们有一个语句块或函数体。函数体包含定义函数功能的语句。像往常一样,Python 使用语句缩进来确定块开始和结束的时间。函数主体中的所有语句必须同等缩进,否则会出现语法错误。
特别注意函数体中的最后一个语句,即<return statement>
。return
语句用于从函数返回值。return
语句不是强制性的,有些函数返回值,有些则不是。如果函数体中没有return
语句,则自动返回一个保留的关键字None
。None
实际上是一个内置类型的物体NoneType
。如果发现return
语句混乱,不用担心;他们不是,我们将在下一节详细讨论return
声明。
这里有一个小功能,可以打印当前日期和时间以及问候语:
蟒蛇 101/第 13 章/first_function.py
import datetime
def greet():
print("Hello !")
print("Today is", datetime.datetime.now())
greet()
函数不接受任何参数,这就是括号留空的原因。函数体包含两个print()
语句。这两个语句将在我们调用greet()
函数时执行。greet()
函数不返回值。
函数调用
函数定义本身没有任何作用。要使用一个函数,我们必须调用它。调用函数的语法如下:
function_name(arg1, arg2, arg3, ... argN)
如果函数不接受任何参数,则使用以下语法:
function_name()
以下代码调用greet()
函数:
greet() # calling greet() function
python 101/第 13 章/调用 _first_function.py
import datetime
def greet():
print("Hello !")
print("Today is", datetime.datetime.now())
greet()
输出:
Hello !
Today is 2017-06-20 13:20:45.281651
函数调用必须在定义函数后出现,否则会遇到NameError
异常。例如:
蟒蛇 101/第 13 章/call_before_definition.py
import datetime
greet() ## ERROR: trying to call greet() before its defined
def greet():
print("Hello !")
print("Today is", datetime.datetime.now())
输出:
q@vm:~/python101/Chapter-13$ python call_before_definition.py
Traceback (most recent call last):
File "call_before_definition.py", line 3, in <module>
greet() ## ERROR: trying to call greet() before its defined
NameError: name 'greet' is not defined
当一个函数被调用时,程序控制跳转到该函数定义并执行函数体内的语句。在执行函数体之后,程序控制跳回到调用函数的程序部分,并在该点恢复执行。
下面的示例演示了调用函数时会发生什么。
蟒蛇 101/第 13 章/控制权转移
import datetime
def greet():
print("Hello !")
print("Today is", datetime.datetime.now())
print("Before calling greet()")
greet()
print("After calling greet()")
输出:
Before calling greet()
Hello !
Today is 2017-06-20 13:20:45.281651
After calling greet()
在第 3-5 行,我们定义了一个greet()
语句。第 7 行的print()
语句将字符串"Before calling greet()"
打印到控制台。在第 8 行,我们正在调用greet()
函数。此时,调用greet()
后语句的执行停止,程序控制跳转到greet()
函数的定义。执行完greet()
功能程序的主体后,控制再次跳回到它停止的位置,并从那里继续执行。
我们之前的程序只有一个功能。程序拥有数百甚至数千个功能并不罕见。在 python 中,定义一个名为main()
的函数是一种常见的约定,当程序启动时会调用该函数。该main()
函数随后根据需要调用其他函数。下面的程序演示了当我们在一个程序中有两个功能时程序控制的流程。
蟒蛇 101/第-13 章/two _ func _ program _ control . py
import datetime
def greet(name):
print("Hello", name, "!")
print("Today is", datetime.datetime.now())
def main():
print("main() function called")
greet("Jon")
print("main() function finished")
main()
在第 3-5 行和第 8-11 行,我们定义了两个函数greet()
和main()
。greet()
函数现在被更新为接受一个名为name
的参数,然后在下一行中使用它来问候用户。
main()
函数不接受任何参数,体内有三个语句。
第 13 行的语句调用main()
函数。程序控制跳转到main()
功能的主体。main()
内的第一条语句将字符串"main() function called"
打印到控制台。第 10 行的语句调用带有参数"Jon"
的greet()
函数,该参数将被分配给函数头中的变量name
。此时,调用greet()
后的语句执行停止,程序控制跳转到greet()
函数的主体。在执行完greet()
函数的主体后,程序控制跳回到它停止的地方,并执行第 11 行的print()
语句。由于在main()
函数中没有剩余的要执行的语句,程序控制再次跳回到函数调用后停止执行语句的地方(第 13 行)。
局部变量、全局变量和范围
**变量作用域:**变量的作用域是指程序中可以被访问的部分。
我们在函数内部创建的变量称为局部变量。局部变量只能在定义它的函数体内访问。换句话说,局部变量的范围从它们被定义的点开始,一直持续到函数结束。函数一结束,局部变量就要进行垃圾收集。因此,试图访问其范围之外的局部变量将导致错误。
在光谱的另一端,我们有全局变量。全局变量是在任何函数之外定义的变量。全局变量的范围从它们被定义的点开始,一直持续到程序结束。
现在考虑以下示例:
例 1 :
python 101/第-13 章/variable_scope.py
global_var = 200 # a global variable
def func():
# local_var is a local variable
# and is only available inside func()
local_var = 100
print("Inside func() - local_var =", local_var)
# accessing a global variable inside a function
print("Inside func() - global_var =", global_var)
func()
print("Outside func() - global_var =", global_var)
# print("Outside func() - local_var =", local_var) # ERROR: local_var is not available here
输出:
Inside func() - local_var = 100
Inside func() - global_var = 200
Outside func() - global_var = 200
在第 1 行,我们创建了一个名为global_var
的全局变量。然后在第 12 行func()
函数内部和第 17 行函数外部访问。我们还在函数func()
中声明了一个名为local_var
的局部变量。然后在第 9 行的函数中访问它。
让我们看看如果我们试图访问函数外部的局部变量会发生什么。为此,取消第 19 行代码的注释,并再次运行程序。
输出:
Inside func() - local_var = 100
Inside func() - global_var = 200
Outside func() - global_var = 200
Traceback (most recent call last):
File "variable_scope.py", line 37, in <module>
print("Outside func() - local_var =", local_var)
NameError: name 'local_var' is not defined
NameError: name 'local_var' is not defined
告诉我们这个范围内不存在名为local_var
的变量。
如果我们有同名的局部变量和全局变量呢?考虑以下程序。
例 2:
蟒蛇 101/第 13 章/相同 _ 全局 _ 和 _ 局部. py
num = "global" # global num variable
def func():
num = "local" # local num variable is entirely different from global num variable
print(num)
func()
print(num)
输出:
local
global
这里我们在第 1 行有一个全局变量num
,在第 4 行的函数内部有一个同名的局部变量。每当函数内部的局部变量和全局变量之间发生冲突时,局部变量优先。这就是print()
函数(第 5 行)打印本地num
变量的原因。然而,函数外num
指的是全局num
变量。
我们也可以在不同的函数中使用相同的变量名,而不会相互冲突。
python 101/第-13 章/相同 _ 变量 _ 名称 _ in _ differential _ functions . py
def func_1():
x = 100 # this x is only visible inside func_1()
print("func_1(): x =", x)
x = 200
print("func_1(): x =", x)
def func_2():
x = "str" # this x is only visible inside func_2() and it entirely different from func1()'s variable x
print("func_2(): x =", x)
x = "complex"
print("func_2(): x =", x)
# x is not visible in here
func_1()
func_2()
输出:
func_1(): x = 100
func_1(): x = 200
func_2(): x = str
func_2(): x = complex
传递参数
参数只不过是调用函数时传递给函数的一段数据。如前所述,函数可以接受任意数量的参数,也可以不接受任何参数。例如,print()
函数接受一个或多个参数,而random.random()
函数不接受任何参数。
如果希望函数接收参数,在调用它时,我们必须首先定义一个或多个参数。参数或参数变量只是函数头中的一个变量,它在调用函数时接收一个参数。就像局部变量一样,参数变量的范围只限于函数体。下面是一个接受单个参数的函数示例:
def add_100(num):
print("Result =", num+100)
当用参数调用函数add_100()
时,参数的值被赋给变量num
,print()
语句在加上100
后打印出num
的值。
下面的程序演示了如何用参数调用函数。
python 101/第-13 章/function_argument.py
def add_100(num): # num is a parameter
print("Result =", num+100)
x = 100
add_100(x)
输出:
Result = 200
在第 6 行,函数add_100()
用参数100
调用。该参数的值随后被分配给参数变量num
。
例 2: 计算一个数的阶乘的函数。
蟒蛇 101/第 13 章/阶乘 py
def factorial(n):
f = 1
for i in range(n, 0, -1):
f *= n
n -= 1
print(f)
num = input("Enter a number: ")
factorial(int(num))
输出:
Enter a number: 4
24
数字n
的阶乘定义为从1
到n
的所有数字的乘积。
n! = 1 * 2 * 3 * ... * (n-1) * n
其中n!
表示n
的阶乘。以下是一些例子:
6! = 1 * 2 * 3 * 4 * 5 * 6
4! = 1 * 2 * 3 * 4
现在,让我们看看当n
的值为4
时,for 循环是如何工作的:
在强制循环开始之前 | i 未定义 |
f = 1 |
n = 4 |
---|---|---|---|
第一次迭代后 | i = 4 |
f = n * f = 4 * 1 = 4 |
n = 3 |
第二次迭代后 | i = 3 |
f = n * f = 3 * 4 = 12 |
n = 2 |
第三次迭代后 | i = 2 |
f = n * f = 2 * 12 = 24 |
n = 1 |
第四次迭代后 | i = 1 |
f = n * f = 1 * 24 = 24 |
n = 0 |
第四次迭代循环结束后,print()
函数输出该数的阶乘。
示例 3 :向函数传递多个参数
python 101/第-13 章/multiple_arguments.py
def calc(num1, num2):
print("Sum =", num1 + num2)
print("Difference =", num1 - num2)
print("Multiplication =", num1 * num2)
print("Division =", num1 / num2)
print() # prints a blank line
calc(10, 20)
输出:
Sum = 30
Difference = -10
Multiplication = 200
Division = 0.5
第 8 行调用calc()
函数时,参数10
传递给参数变量num1
,20
传递给参数变量num2
。
调用函数时传递的参数顺序必须与函数头中的参数顺序相匹配,否则,您可能会得到意外的结果。
按值传送
回想一下,Python 中的一切都是对象。所以一个对象的变量,实际上是对该对象的引用。换句话说,变量存储对象在内存中的存储地址。它不包含实际对象本身。
当用参数调用函数时,存储在参数中的对象地址被传递给参数变量。然而,为了简单起见,我们说参数的值在调用函数时被传递给参数。这种机制被称为按值传递。考虑以下示例:
def func(para1):
print("Address of para1:", id(para1))
print(para1)
arg1 = 100
print("Address of arg1:", id(arg1))
func(arg1)
输出:
Address of arg1: 1536218288
Address of para1: 1536218288
100
注意id
值是相同的。这意味着变量arg1
和para1
引用同一个对象。换句话说,arg1
和para1
都指向存储int
对象(100
)的同一存储位置。
这种行为有两个重要后果:
如果传递给函数的参数是不可变的,那么对参数变量所做的更改不会影响该参数。
但是,如果传递给函数的参数是可变的,那么对参数变量所做的更改将影响该参数。
让我们通过一些例子来检查这种行为:
示例 1 :将不可变对象传递给函数。
蟒蛇 101/第 13 章/传递 _ 不可变 _ 对象. py
def func(para1):
para1 += 100 # increment para1 by 100
print("Inside function call, para1 =", para1)
arg1 = 100
print("Before function call, arg1 =", arg1)
func(arg1)
print("After function call, arg1 =", arg1)
输出:
Before function call, arg1 = 100
Inside function call, para1 = 200
After function call, arg1 = 100
在第 7 行中,func()
被一个参数arg1
调用(它指向一个不可变的对象int
)。arg1
的值被传递给参数para1
。para1
的内函数值增加100
(第 3 行)。当该功能结束时,执行第 8 行的打印语句,并将字符串"After function call, arg1 = 100"
打印到控制台。这证明了一点,无论para1
做什么功能,arg1
的值不变。
仔细想想,这种行为完全有道理。回想一下,不可变对象的内容是不能改变的。因此,每当我们给一个变量分配一个新的整数值时,我们实际上是在创建一个完整的新int
对象,同时给这个变量分配新对象的引用。这正是func()
功能内部正在发生的事情。
示例 2 :将可变对象传递给函数
蟒蛇 101/第 13 章/传递 _ 可变 _ 对象. py
def func(para1):
para1.append(4)
print("Inside function call, para1 =", para1)
arg1 = [1,2,3]
print("Before function call, arg1 =", arg1)
func(arg1)
print("After function call, arg1 =", arg1)
输出:
Before function call, arg1 = [1, 2, 3]
Inside function call, para1 = [1, 2, 3, 4]
After function call, arg1 = [1, 2, 3, 4]
代码几乎是一样的,但是这里我们传递一个列表给函数,而不是一个整数。由于列表是一个可变对象,因此第 2 行的func()
函数所做的更改会影响变量arg1
所指向的对象。
位置和关键字参数
函数的参数可以通过两种方式传递:
- 位置论证。
- 关键字参数。
在第一种方法中,我们将参数传递给一个函数,传递顺序与函数头中它们各自的参数相同。我们一直在使用这个方法向我们的函数传递参数。例如:
勾股 101/第-13 章/勾股 _ 三胞胎. py
def is_pythagorean_triplet(base, height, perpendicular):
if base ** 2 + height ** 2 == perpendicular ** 2:
print("Numbers passed are Pythagorean Triplets")
else:
print("Numbers passed are not Pythagorean Triplets")
报表is_pythagorean_triplet(3, 4, 5)
传递3
到base
、4
到height
、5
到perpendicular
,打印"Numbers passed are Pythagorean Triplets"
。但是语句is_pythagorean_triplet(3, 5, 4)
,将3
传递给底座,5
传递给height
,4
传递给perpendicular
,打印"Numbers passed are not Pythagorean Triplets"
,是错误的。所以在使用位置参数时,一定要确保函数调用中参数的顺序和函数头中参数的顺序相匹配。否则,你可能会得到预期的结果。
向函数传递参数的另一种方法是使用关键字参数。在此方法中,我们以以下形式传递每个参数:
parameter_name = val
其中parameter_name
是函数头中参数变量的名称,val
是指要传递给参数变量的值。因为我们将参数名称与值相关联,所以函数调用中参数的顺序并不重要。
以下是我们可以使用关键字参数调用is_pythagorean_triplet()
函数的一些不同方式:
is_pythagorean_triplet(base=3, height=4, perpendicular=5)
is_pythagorean_triplet(base=3, perpendicular=5, height=4)
is_pythagorean_triplet(perpendicular=5, height=4, base=3)
关键字参数有点灵活,因为我们不必记住函数头中参数的顺序。
混合位置和关键字参数
我们还可以在函数调用中混合位置参数和关键字参数。这样做的唯一要求是位置参数必须出现在任何关键字参数之前。这意味着以下两个调用完全有效,因为在这两个调用中,位置参数都出现在关键字参数之前。
## valid
is_pythagorean_triplet(3, 4, perpendicular=5)
is_pythagorean_triplet(3, perpendicular=5, height=4)
但是,我们不能这样做:
# not valid
is_pythagorean_triplet(3, height=4, 5)
这里的问题是位置参数(5
)出现在关键字参数(height=4
)之后。试图以这种方式调用is_pythagorean_triplet()
会导致以下错误:
File "pythagorean_triplets.py", line 8
is_pythagorean_triplet(3, height=4, 5)
^
SyntaxError: positional argument follows keyword argument
返回值
到目前为止,我们一直在创建不返回值的函数,这种函数也被称为 void 函数。
要从函数中返回值,我们使用return
语句。它的语法是:
return [expression]
expression
周围的方括号([]
)表示可选。如果省略,则返回特殊值None
。
当在函数内部遇到return
语句时,函数终止,后面跟有return
关键字的expression
的值被发送回调用函数的程序部分。return
语句可以出现在函数体的任何地方。返回值的函数称为返回值函数。
这里有一个例子:
def add(num1, num2):
return num1 + num2
一个函数有两种调用方式,这取决于它们是否返回值。
如果一个函数返回值,那么对这个函数的调用可以用作程序中任何表达式的操作数。例如:
result = add(12, 10)
在上面的表达式中,我们首先调用add()
函数,然后将函数的返回值赋给result
变量。如果我们没有在add()
函数中使用return
语句,我们将无法编写这段代码。这里有一些我们可以称之为add()
函数的其他方法。
result = add(12, 10) * 10 # return value of add() is multiplied by 10 and then assigned to result
if add(12, 10) == 100: # return value of add() is compared with 100 in the if statement
print("It is True")
print(add(12, 10)) # return value of add() is printed
我们不一定要使用函数的返回值。如果我们不想使用返回值,只需将函数作为语句调用即可。例如:
add(12, 10)
在这种情况下,add()
的返回值被简单丢弃。
让我们重写阶乘程序来返回阶乘,而不是打印它。
蟒蛇 101/第 13 章/return_factorial.py
def factorial(n):
f = 1
for i in range(n, 0, -1):
f *= n
n -= 1
return f
print("Factorial of 4 is", factorial(4))
print("Factorial of 4 is", factorial(6))
输出:
Factorial of 4 is 24
Factorial of 4 is 720
在上面的例子中,我们从函数中返回了一个整数值,但是我们可以使用任何类型的数据int
、float
、str
、bool
;你说吧。以下程序演示了如何从函数中返回bool
类型:
蟒蛇 101/第 13 章/是偶数还是奇数
def is_even(number):
if number % 2 == 0:
return True
else:
return False
num = int(input("Enter a number: "))
if is_even(num):
print(num, "is even")
else:
print(num, "is odd")
第一次运行输出:
Enter a number: 13
13 is odd
第二次运行输出:
Enter a number: 456
456 is even
如果省略了后跟return
关键字的表达式,则返回一个特殊值None
。
蟒蛇 101/第 13 章/返回 _none.py
def foo():
return
result = foo()
print(result)
输出:
None
我们也可以在函数内部多次使用return
语句,但是一旦遇到第一个return
语句,函数就会终止,并且它后面的所有语句都不会执行。例如:
蟒蛇 101/第 13 章/等级 _ 计算器. py
def calculate_grade(marks):
if marks >= 90:
return 'A'
elif marks >= 80:
return 'B'
elif marks >= 70:
return 'C'
elif marks >= 60:
return 'D'
else:
return 'F'
m = int(input("Enter your marks: "))
print("Your grade is", calculate_grade(m))
首次运行输出:
Enter your marks: 68
Your grade is D
第二次运行输出:
Enter your marks: 91
Your grade is A
无效函数返回无。
在 Python 中,void 函数与 C、C++或 Java 中的函数略有不同。如果函数体没有任何return
语句,那么当函数终止时会返回一个特殊值None
。在 Python 中,None
是类型NoneType
的字面意思,用于表示缺少值。它通常被分配给一个变量,以指示该变量不指向任何对象。
下面的程序演示了 void 函数返回None
。
蟒蛇 101/第 13 章/void_function.py
def add(num1, num2):
print("Sum is", num1 + num2)
return_val = add(100, 200)
print(return_val)
输出:
Sum is 300
None
果然!add()
功能确实返回None
。所以我们可以说,在 Python 中,无论是否使用return
语句,所有函数都是返回值的。然而,这并不意味着您可以像使用返回值函数一样使用 void 函数。考虑以下示例:
python 101/第 13 章/使用 _ void _ function _ as _ non _ void _ function . py
def add(num1, num2):
print("Sum is", num1 + num2)
result = add(10, 200) + 100
输出:
Sum is 210
Traceback (most recent call last):
File "using_void_function_as_non_void_function.py", line 5, in <module>
result = add(10, 200) + 100
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
在第 5 行,我们试图将从add()
即None
返回的值添加到整数100
,但是操作失败,因为+
操作无法将NoneType
添加到int
。
这就是为什么 void 函数通常被调用为这样的语句:
add(100, 200)
返回多个值
要从一个函数返回多个值,只需在return
关键字后指定用逗号(,
)分隔的每个值。
return val1, val2, val3, ..., valN
调用返回多个值的函数时,=
运算符左侧的变量个数必须等于return
语句返回的值个数。因此,如果一个函数返回两个值,那么您必须在=
运算符的左侧使用两个变量。这里有一个例子:
python 101/第 13 章/返回 _multiple_values.py
def sort_two_num(num1, num2):
if num1 < num2:
return num1, num2
else:
return num2, num1
number1, number2 = sort_two_num(100, 15)
print("number1 is", number1)
print("number2 is", number2)
输出:
number1 is 15
number2 is 100
请注意在调用函数时如何赋值。该声明:
number1, number2 = sort_two_num(100, 15)
给变量number1
分配较小的数字,给变量number2
分配较大的数字。
默认参数
在 Python 中,我们可以定义一个带有默认参数值的函数,当一个函数在没有任何参数的情况下被调用时,将使用这个默认值。要指定参数的默认值,只需使用赋值运算符后跟参数名称来指定值。考虑以下示例:
python 101/第 13 章/setting_default_values.py
def calc_area(length=2, width=3):
print("length=", length, ", width = ", width)
print("Area of rectangle is", width * length)
print()
calc_area()
calc_area(4, 6)
calc_area(width=100, length=23)
calc_area(length=12)
输出:
length= 2 , width = 3
Area of rectangle is 6
length= 4 , width = 6
Area of rectangle is 24
length= 23 , width = 100
Area of rectangle is 2300
length= 12 , width = 3
Area of rectangle is 36
在第 7 行,我们在调用函数calc_area()
时没有任何参数,因此默认值2
和3
将分别分配给length
和width
参数。
在第 8 行,我们通过将4
传递到length
和将6
传递到width
来呼叫calc_area()
。由于两个参数的值都是在调用函数时提供的,因此在这种情况下将不使用默认值。第 9 行的calc_area()
调用也是如此,除了这里我们使用的是关键字参数。
在第 10 行中,我们只使用关键字参数向length
参数提供值,因此将使用width
参数的默认值。
Python 中的模块
最后更新于 2020 年 7 月 27 日
创建模块
不同的语言提供了不同的代码模块化方式。在 Python 中,我们使用模块来组织大型程序。Python 标准库广泛使用模块来组织相关的函数、类、变量和常量。我们已经使用了一些内置模块,如math
、datetime
、random
;在之前的课程中也有过几次。你不需要把你的程序组织成模块,但是如果你的程序有几页长或者你想重用你的代码,你肯定应该使用模块。
那么到底什么是模块呢?
模块是以.py
扩展名结尾的普通 Python 文件。我们可以在一个模块中定义一组类、函数、变量、常量等等。为了重用模块内部定义的类或函数,我们必须使用import
语句在程序中导入模块。import
语句的语法如下:
import module_name
其中module_name
是没有.py
扩展名的文件的名称
import
语句搜索模块,解析并执行其内容,并使其对客户端程序可用。客户端程序或简称客户端是一个程序,它使用模块中定义的类、函数或变量,而不知道实现细节。引用客户端程序中的模块类、函数或变量,并在前面加上模块名。例如,要在客户端程序中调用名为great_module
的模块中定义的名为timer()
的函数,请执行以下操作:
great_module.timer()
如果import
语句找不到模块,将产生ImportError
错误。
让我们举个例子:
创建一个名为my_module.py
的新文件,并向其中添加以下代码。
蟒蛇 101/第-14 章/my_module.py
A_CONSTANT = 100
name = 'a variable'
def great_printer():
print("first great_printer() definition")
def doo_hickey(arg):
print("doo_hickey() called with argument:", arg)
现在,我们在与my_module.py
相同的目录下创建一个名为test_module.py
的独立程序(或客户端),代码如下。
python 101/第 14 章/test_module.py
import my_module
my_module.doo_hickey('www')
my_module.great_printer()
print(my_module.A_CONSTANT)
print(my_module.name)
输出:
doo_hickey() called with argument: www
first great_printer() definition
100
a variable
在模块中,用于命名类、函数或变量等实体的标识符必须是唯一的。如果找到两个同名的实体,Python 将使用最后一个定义。创建一个名为reusing_identifiers.py
的新文件,并向其中添加以下代码:
蟒蛇 101/第 14 章/重用标识符. py
A_CONSTANT = 100
name = 'a variable'
def great_printer():
print("first great_printer() definition")
def doo_hickey(arg):
print("doo_hickey() called with argument:", arg)
def great_printer():
print("second great_printer() definition")
代码和my_module.py
完全一样,唯一的区别是这里我们在文件的末尾定义了另一个名为great_printer()
的函数。
现在我们有两个同名的函数(great_printer
)。因此,Python 将使用最后定义的great_printer()
函数。使用以下代码创建另一个名为test_module2.py
的客户端程序:
python 101/第 14 章/test_module2.py
import reusing_identifiers
reusing_identifiers.great_printer()
输出:
second great_printer() definition
导入选定的标识符
语句import my_module
将模块中的每个标识符导入客户端程序。在某些情况下,我们只想使用模块中的一些特定名称。假设从my_module.py
模块开始,我们只需要在客户端程序中导入doo_hickey()
函数和name
变量。对于这种情况,存在另一种形式的import
语句,它允许我们只从模块中导入特定的名称。它的语法是:
from module_name import name1, name2, ... nameN
其中name1
、name2
等是我们要在客户端程序中导入的实体的名称。该导入语句后的任何代码都可以使用name1
、name2
等,而无需在前面加上模块名称。
这里有一个程序,它只从模块my_module
中导入doo_hickey
和name
标识符。
python 101/第 14 章/导入 _ selected _ identifier . py
from my_module import doo_hickey, name
doo_hickey('x')
print(name)
输出:
doo_hickey() called with argument: x
a variable
如果您想将每个标识符导入您的客户端程序,请执行以下操作:
from module_name import *
这里*
表示所有标识符。这种说法在功能上等同于import module_name
。唯一的区别是前者允许在客户端程序中访问标识符,而不用在它前面加上模块名。
注意 : import module_name
不会导入以双下划线字符开头的名称。
Python 中的对象和类
原文:https://overiq.com/python-101/objects-and-classes-in-python/
最后更新于 2020 年 9 月 22 日
有两种常用的编程范例:
- 编程。
- 面向对象编程。
编程
编程使用一系列步骤来告诉计算机做什么。过程编程广泛使用过程,我们可以把过程看作是执行特定任务的函数,例如计算员工的激励、将数据保存到数据库、运行备份等等。过程编程背后的中心思想是创建可重用的函数来操作数据。这种方法没有错,但是随着程序的增长,它变得很难管理。到目前为止,我们编写的所有程序都是程序性的,因为它们广泛依赖程序/函数来执行各种任务。
面向对象编程
面向对象编程围绕着对象而不是过程。对象是包含数据以及对数据进行操作的过程的实体。对象内部的数据和过程分别称为属性和方法。在创建对象之前,我们首先必须定义一个类。类只是一个模板,我们在其中定义属性和方法。当我们定义一个类时,我们实际上创建了一个新的数据类型。需要注意的是,类只是一个蓝图,它自己什么也做不了。要使用一个类,我们必须创建基于该类的对象。对象也称为类的实例、类的实例或仅仅是实例。从类创建对象的过程称为实例化类,我们可以根据需要创建任意多的对象。
定义类
定义类的语法如下:
class class_name(parent_class_name):
<method_1_definition>
...
<method_n_definition>
类定义分为两部分:类头和类体。
类头以class
关键字开始,后跟类名,括号内是可选的parent_class_name
。class_name
和parent_class_name
可以是任何有效的标识符。
parent_class_name
类是指你要继承的类。这就是所谓的继承。如果您在定义类时没有指定父类名,它将被自动设置为object
。我们将在第课中更详细地讨论继承和 Python 中的多态。
在下一行,我们有一个类主体,这是我们定义对数据进行操作的方法的地方。方法定义必须同样缩进,否则会出现语法错误。
好了,说够了!现在让我们定义一个类。
在下面的列表中,我们定义了一个类来表示Circle
。一个Circle
类定义了一个名为radius
的属性和三个方法,即__init__()
、get_area()
和get_perimeter()
。
蟒蛇 101/第 15 章/圆圈. py
import math
class Circle:
def __init__(self, radius):
self.radius = radius
def get_area(self):
return math.pi * self.radius ** 2
def get_perimeter(self):
return 2 * math.pi * self.radius
让我们一行一行地浏览代码:
在第 1 行,我们正在导入math
模块,因为我们将在我们的方法中使用它的pi
常量。
在第 3 行,我们有一个类头,它以class
关键字开始,后跟类名,在本例中是Circle
,后跟冒号(:
)。
在下一行中,我们有一个类主体,其中定义了以下三种方法:
__init__()
方法。get_area()
方法。get_perimeter()
方法。
定义方法的语法与定义函数的语法完全相同。
请注意,每个方法都有一个名为self
的第一个参数。在 Python 中,每个方法都需要self
参数。self
参数是指调用该方法的对象。Python 使用self
参数来知道在类内部操作哪个对象。在调用方法时,不需要向self
参数传递任何值,当调用方法时,Python 解释器会自动将self
参数绑定到对象。
在第 5-6 行,我们定义了一个名为__init__
的方法。__init__()
是一种特殊的方法,称为初始化器或构造器。每次在内存中创建新对象后都会调用它。初始值设定项方法的目的是用一些初始值创建对象的属性。除了self
参数,__init__()
方法期望radius
参数为Circle
对象的radius
属性提供初始值。对象属性通常也称为实例变量。对实例变量进行操作的方法称为实例方法。我们的Circle
类有两个实例方法get_perimeter()
和get_area()
。每个对象都有自己的一组实例变量。这些变量存储对象的数据。实例变量和关键字self
的范围仅限于类的主体。在一个类中,我们使用self
来访问对象的属性和方法。例如,我们可以使用self.var
访问名为var
的实例变量,使用self.foo()
调用对象的foo()
实例方法。
定义构造函数方法(即__init__()
)不是必需的,如果不定义,Python 会自动提供一个空的__init__()
方法,该方法什么也不做。
在第 6 行,实例变量self.radius
被初始化为radius
变量的值。
self.radius = radius
换句话说,上面一行代码为刚刚创建的对象创建了一个名为radius
的属性,该属性具有初始值。
与实例变量radius
(左侧)不同的是,赋值运算符的right
手动端的radius
变量是一个局部变量,其范围仅限于__init__()
方法。
在第 8 行到第 9 行,我们定义了get_area()
实例方法,计算并返回圆的面积。
在第 11 行到第 12 行,我们定义了get_perimeter()
实例方法,该方法计算并返回圆的周长。
请注意,在get_area()
和get_perimeter()
中,我们使用self.radius
而不仅仅是radius
来访问实例变量radius
。
现在我们知道Circle
类是如何定义的了。让我们创建一些Circle
类的对象。
创建对象
我们可以通过调用类名从类中创建对象,就好像它是一个函数:
ClassName()
但是,如果您已经定义了__init__()
方法,那么您将需要使用如下参数调用类名:
ClassName(arguments)
参数必须与没有self
的__init__()
方法中的参数匹配。否则,你会得到一个错误。
这里有一个例子:
my_circle = Circle(5)
上面的陈述做了以下事情:
- 创建一个
Circle
类的对象。 - 调用
__init__()
方法,将这个新创建的圆形对象传递给self
,将另一个参数(即 5)传递给radius
变量。 - 为
self
引用的对象创建一个名为radius
的属性值。 - 返回
Circle
对象 - 将
Circle
对象的引用分配给变量my_circle
。
注意my_circle
只包含对象的引用(地址),不包含实际对象。
访问属性和方法
一旦我们有了一个类的对象,我们就可以使用以下语法使用它来访问对象的属性(或实例变量)和方法:
object.attribute # syntax to access attributes
object.method(arguments) # syntax to access instance methods
下面是我们如何访问Circle
对象的属性和方法。
蟒蛇 101/第 15 章/circle_client.py
from circle import *
my_circle = Circle(5)
print("Circle of radius is",my_circle.radius)
print("Area of circle:", format(my_circle.get_area(), ".2f"))
print("Area of perimeter of circle:", format(my_circle.get_perimeter(), ".2f"), end="\n\n")
输出:
Circle of radius is 5
Area of circle: 78.54
Area of perimeter of circle: 31.42
请注意,在调用实例方法时,我们没有向self
参数传递任何值,因为 Python 会自动将对用于调用方法的对象的引用传递给self
参数。因此在这种情况下,变量my_circle
引用的对象被传递给self
参数。但是,不允许在代码中将参数传递给self
参数。如果你试图这样做,你会出错。例如:
蟒蛇 101/第 15 章/传递参数给自己
from circle import *
my_circle = Circle(5)
my_circle.get_area(my_circle)
my_circle.get_perimeter(my_circle)
输出:
Traceback (most recent call last):
File "passing_argument_to_self.py", line 4, in <module>
my_circle.get_area(my_circle)
TypeError: get_area() takes 1 positional argument but 2 were given
我们还可以使用以下语法更改对象的属性:
object.attribute = new_val
以下代码将my_circle
对象的radius
属性的值从5
更改为10
。
my_circle.radius = 10
最后,我们可以创建任意多的对象。每个对象都有自己的一组属性。更改一个对象的属性不会影响其他对象的属性。例如:
蟒蛇 101/第 15 章/多重 _ 物体. py
from circle import *
my_circle1 = Circle(5)
my_circle2 = Circle(10)
my_circle3 = Circle(15)
print("Address of Circle objects")
print("my_circle1:", id(my_circle1)) # print the address of Circle object referenced by variable my_circle1
print("my_circle2:", id(my_circle2)) # print the address of Circle object referenced by variable my_circle2
print("my_circle3:", id(my_circle3)) # print the address of Circle object referenced by variable my_circle3
print()
print("Address of radius attribute")
print("my_circle1:", id(my_circle1.radius)) # print the address of my_circle1's radius attribute
print("my_circle2:", id(my_circle2.radius)) # print the address of my_circle2's radius attribute
print("my_circle3:", id(my_circle3.radius), end="\n\n") # print the address of my_circle3's radius attribute
print("Initial value of radius attribute: ")
print("my_circle1's radius:", my_circle1.radius)
print("my_circle2's radius:", my_circle2.radius)
print("my_circle3's radius:", my_circle3.radius, end="\n\n")
# changing radius attribute of circle objects
my_circle1.radius = 50
my_circle2.radius = 100
my_circle3.radius = 150
print("After changing radius attribute of circle objects", end="\n\n")
print("Final value of radius attribute: ")
print("my_circle1's radius:", my_circle1.radius)
print("my_circle2's radius:", my_circle2.radius)
print("my_circle3's radius:", my_circle3.radius)
输出:
Address of Circle objects
my_circle1: 5236440
my_circle2: 5236608
my_circle3: 32036008
Address of radius attribute
my_circle1: 1586284752
my_circle2: 1586284912
my_circle3: 1586285072
Initial value of radius attribute:
my_circle1's radius: 5
my_circle2's radius: 10
my_circle3's radius: 15
After changing radius attribute of circle objects
Final value of radius attribute:
my_circle1's radius: 50
my_circle2's radius: 100
my_circle3's radius: 150
my_circle1
、my_circle2
和my_circle3
是指存储在distinct
存储位置的三个不同的Circle
对象。除此之外,每个对象的属性也存储在不同的内存位置。
下面是另一个创建名为BankAccount
的类的例子。这个类的对象模拟一个银行帐户,允许用户检查余额,取款和存款。
蟒蛇 101/第 15 章/银行账户. py
class BankAccount:
def __init__(self, balance):
self.balance = balance
def make_deposit(self, amount):
self.balance += amount
def make_withdrawal(self, amount):
if self.balance < amount:
print("Error: Not enough funds")
else:
print("Successfully withdrawn $", amount, sep="")
self.balance -= amount
def get_balance(self):
return self.balance
my_account = BankAccount(5000) # Create my bank account with $5000
print("Current Balance: $", my_account.get_balance(), sep="")
print("Withdrawing $10000 ...")
my_account.make_withdrawal(10000)
print("Lets try withdrawing $1000 ...")
my_account.make_withdrawal(1000)
print("Now Current Balance: $", my_account.get_balance(), sep="")
print("Depositing $2000 ...")
my_account.make_deposit(2000)
print("Now Current Balance: $", my_account.get_balance(), sep="")
输出:
Current Balance: $5000
Withdrawing $10000 ...
Error: Not enough funds
Lets try withdrawing $1000 ...
Successfully withdrawn $1000
Now Current Balance: $4000
Depositing $2000 ...
Now Current Balance: $6000
隐藏对象的属性
默认情况下,对象的属性在类外部可见。这就是为什么我们能够分别在Circle
和BankAccount
类之外访问radius
和balance
属性的原因。大多数情况下,我们不会在类外访问对象的属性,因为这可能会导致属性数据的意外损坏。根据目前的情况,我们的两个程序(circle.py
和bank_account.py
)都允许在类外访问对象属性,因为我们的程序有以下限制:
BankAccount
对象中的balance
属性非常敏感,因为它直接改变了账户余额。理想情况下,我们希望只有当有人存款或取款时,余额attribute
才会发生变化。从目前的情况来看,任何人都可以增加或减少balance
属性,而无需存取任何资金。my_account = BankAccount(5000) # Initially account is created with $2000 balance my_account.balance = 0 # well now your balance is 0
像这样的错误肯定会导致使用这种程序的公司破产。
Circle
对象的radius
属性必须包含一个正数,但此时没有任何东西阻止我们在其中存储一个字符串或列表。my_circle = Circle(4) my_circle.radius = "www"
我们可以通过限制对类外对象属性的访问,以及实现访问器和变异器方法来防止这些问题。
Python 中的数据隐藏可以通过定义私有属性来实现。我们可以通过用两个下划线字符(__
)来定义私有属性的名称。因此,如果我们将self.balance
和self.radius
分别更改为self.__balance
和self.__radius
,那么我们将无法访问类外的radius
和balance
属性。同样,我们可以通过用两个前导下划线(__
)来定义一个私有方法。私有属性和方法只能在类内部访问。如果你试图在类外访问它们,你会得到一个错误。
为了使属性值可以在类外访问,我们使用访问器方法。存取方法只是一种返回对象属性值但不改变它的方法。它们也通常被称为吸气剂方法或简称吸气剂,通常以单词get
开头。下面是访问器方法的一般格式。
def get_attributeName(self):
return self.attributeName
同样,我们可以有变异方法。将值存储到对象属性的方法称为 mutator 方法。当我们需要改变或设置对象属性的值时,我们调用 mutator 方法。除了为对象的属性设置值之外,它们还可以提供额外的验证,以便在将数据分配给对象的属性之前对其进行验证。Mutator 方法通常也称为 setter 方法或 setter。它的一般格式是:
def set_attributeName(self, newvalue):
## add data validation here
self.attributeName = newvalue
这是对Circle
类的重写,它使radius
属性成为私有的,并且还为radius
属性实现了 getter 和 setter 方法。
蟒 101/第-15 章/改良 _ 圈. py
import math
class Circle:
def __init__(self, radius):
self.__radius = radius #
def get_area(self):
return math.pi * self.__radius ** 2
def get_perimeter(self):
return 2 * math.pi * self.__radius
# getter method for radius attribute
def get_radius(self):
return self.__radius
# setter method for radius attribute
def set_radius(self, radius):
if not isinstance(radius, int):
print("Error: ", radius, "is not an int")
return
self.__radius = radius
注意set_radius()
方法只能接受整数参数,如果传递任何其他类型的数据,都会报错。set_radius()
中的数据验证是使用isinstance()
功能实现的。isinstance()
功能用于测试给定对象的类型。它的语法是:
isinstance(object, class_name)
object
代表我们要测试的对象,class_name
代表类名。如果object
是class_name
的实例,那么isinstance()
返回True
。否则False
。
现在考虑下面的 shell 会话。
>>>
>>> from improved_circle import Circle
>>>
>>> c = Circle(6)
>>>
>>> c.__radius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Circle' object has no attribute '__radius'
>>>
>>> c.get_radius() # get the initial value of __radius attribute
6
>>>
>>> c.set_radius("a radius") # trying to set invalid value
Error: a radius is not an int
>>>
>>> c.get_radius() # value of __radius attribute is still same
6
>>>
>>> c.set_radius(10) # this time operation succeeds
>>>
>>> c.get_radius() # get the new value of __radius attribute
10
>>>
在第 6 行,我们试图访问Circle
对象的__radius
属性,但是我们得到AttributeError
错误,因为__radius
属性是私有的。要在类外获取或设置__radius
属性的值,请分别使用get_radius()
和set_radius()
。
在第 11 行,我们使用get_radius()
getter 方法来访问Circle
对象的__radius
属性的值。
在第 14 行,我们试图使用set_radius()
方法为__radius
属性设置一个无效值,这就是为什么我们得到一个错误。
在第 20 行,我们再次尝试使用set_radius()
方法为__radius
属性设置值,但这次操作成功了。
将对象作为参数传递给函数
就像内置对象一样,我们可以将用户定义类的对象传递给函数或方法。
以下程序显示了如何将类型为Circle
的对象传递给名为print_circle_info()
的函数。
python 101/第 15 章/objects_as_arguments.py
from improved_circle import Circle
c1 = Circle(5.4)
c2 = Circle(10.5)
def print_circle_info(circle_obj):
print("#########################")
print("Radius of circle", format(circle_obj.get_radius(), "0.2f"))
print("Perimeter of circle", format(circle_obj.get_perimeter(), "0.2f"))
print("Area of circle", format(circle_obj.get_area(), "0.2f"))
print("#########################", end="\n\n")
print_circle_info(c1) # passing circle object c1 to print_circle_info()
print_circle_info(c2) # passing circle object c2 to print_circle_info()
输出:
#########################
Radius of circle 5.40
Perimeter of circle 33.93
Area of circle 91.61
#########################
#########################
Radius of circle 10.50
Perimeter of circle 65.97
Area of circle 346.36
#########################
Python 中的继承和多态
原文:https://overiq.com/python-101/inheritance-and-polymorphism-in-python/
最后更新于 2020 年 9 月 22 日
继承是一种机制,它允许我们通过在现有类的基础上添加新的属性和方法来创建一个基于现有类的新类,即父类。这样做时,子类继承父类的属性和方法。
当您想要创建非常相似的类时,继承真的会发光。您所需要做的就是为它们在一个类(父类)中共有的东西编写代码。然后为不同类中非常具体的东西编写代码——子类。这样可以避免大量代码的重复。
让我们举一个更具体的例子来说明这个概念。
假设我们正在创建一个处理各种形状的程序。每个形状都有一些共同的属性。比如形状的颜色,是否填充等等。除此之外,还有一些不同形状的属性。例如,面积和周长。矩形面积为width * length
,圆形面积为πr²
。首先,为不同的形状创建类可能很有诱惑力,比如:
class Rectangle:
def __init__(self, color, filled, width, length):
self.__color = color
self.__filled = filled
self.__width = width
self.__length = length
def get_color(self):
return self.__color
def set_color(self, color):
self.__color = color
def is_filled(self):
return self.__filled
def set_filled(self, filled):
self.__filled = filled
def get_area(self):
return self.__width * self.__length
class Circle:
def __init__(self, color, filled, radius):
self.__color = color
self.__filled = filled
self.__radius = radius
def get_color(self):
return self.__color
def set_color(self, color):
self.__color = color
def is_filled(self):
return self.__filled
def set_filled(self, filled):
self.__filled = filled
def get_area(self):
return math.pi * self.__radius ** 2
你注意到我们正在编写的大量重复代码了吗?
两个类共享相同的__color
和__filled
属性以及它们的getter
和setter
方法。更糟糕的是,如果我们想要更新这些方法的工作方式,那么我们必须逐个访问每个类来进行必要的更改。通过使用继承,我们可以抽象出通用的Shape
类(父类)的公共属性,然后我们可以创建从Shape
类继承的子类,如Rectangle
、Triangle
和Circle
。子类类继承了父类的所有属性和方法,但是它也可以添加自己的属性和方法。
要基于父类创建子类,我们使用以下语法:
class ParentClass:
# body of ParentClass
# method1
# method2
class ChildClass(ParentClass):
# body of ChildClass
# method 1
# method 2
在面向对象的行话中,当类c2
从类c1
继承时,我们说类c2
扩展类c1
或者类c2
是从类c1
派生出来的。
下面的程序演示了继承的作用。它创建了一个名为Shape
的类,该类包含所有形状共有的属性和方法,然后它创建了两个子类Rectangle
和Triangle
,这两个子类只包含它们特有的属性和方法。
蟒蛇 101/第 16 章/继承. py
import math
class Shape:
def __init__(self, color='black', filled=False):
self.__color = color
self.__filled = filled
def get_color(self):
return self.__color
def set_color(self, color):
self.__color = color
def get_filled(self):
return self.__filled
def set_filled(self, filled):
self.__filled = filled
class Rectangle(Shape):
def __init__(self, length, breadth):
super().__init__()
self.__length = length
self.__breadth = breadth
def get_length(self):
return self.__length
def set_length(self, length):
self.__length = length
def get_breadth(self):
return self.__breadth
def set_breadth(self, breadth):
self.__breadth = breadth
def get_area(self):
return self.__length * self.__breadth
def get_perimeter(self):
return 2 * (self.__length + self.__breadth)
class Circle(Shape):
def __init__(self, radius):
super().__init__()
self.__radius = radius
def get_radius(self):
return self.__radius
def set_radius(self, radius):
self.__radius = radius
def get_area(self):
return math.pi * self.__radius ** 2
def get_perimeter(self):
return 2 * math.pi * self.__radius
r1 = Rectangle(10.5, 2.5)
print("Area of rectangle r1:", r1.get_area())
print("Perimeter of rectangle r1:", r1.get_perimeter())
print("Color of rectangle r1:", r1.get_color())
print("Is rectangle r1 filled ? ", r1.get_filled())
r1.set_filled(True)
print("Is rectangle r1 filled ? ", r1.get_filled())
r1.set_color("orange")
print("Color of rectangle r1:", r1.get_color())
c1 = Circle(12)
print("\nArea of circle c1:", format(c1.get_area(), "0.2f"))
print("Perimeter of circle c1:", format(c1.get_perimeter(), "0.2f"))
print("Color of circle c1:", c1.get_color())
print("Is circle c1 filled ? ", c1.get_filled())
c1.set_filled(True)
print("Is circle c1 filled ? ", c1.get_filled())
c1.set_color("blue")
print("Color of circle c1:", c1.get_color())
输出:
Area of rectagle r1: 26.25
Perimeter of rectagle r1: 26.0
Color of rectagle r1: black
Is rectagle r1 filled ? False
Is rectagle r1 filled ? True
Color of rectagle r1: orange
Area of circle c1: 452.39
Perimeter of circle c1: 75.40
Color of circle c1: black
Is circle c1 filled ? False
Is circle c1 filled ? True
Color of circle c1: blue
在第 3-19 行,我们定义了一个Shape
类。它是一个父类,只包含所有形状共有的属性和方法。这个类定义了两个私有属性__color
和__filled
,然后它为这些属性提供了 getter 和 setter 方法。
在第 22-45 行,我们定义了一个继承自Shape
类的Rectangle
类。请密切注意我们使用的语法。
这句台词告诉我们Rectangle
类扩展了Shape
类或者Rectangle
类是Shape
类的子类。因此Rectangle
类继承了Shape
类中定义的属性和方法。除此之外,Rectangle
类增加了两个私有属性,私有属性的 getter 和 setter 方法,以及计算矩形面积和周长的方法。
注意第 25 行的代码。
super().__init__()
在 Python 中,我们使用super()
函数来调用父类方法。所以上面的代码调用了Shape
类的__init__()
方法。这是在父类中设置属性值所必需的。否则,当您试图使用 getter 或 setter 方法访问父类中定义的属性值时,您将会得到一个错误。
类似地,在第 48-63 行,我们定义了一个Circle
类。就像Rectangle
一样,它扩展了Shape
类,并且几乎没有增加自己的属性和方法。
第 66-86 行代码创建Rectangle
和Circle
对象,然后在这些对象上逐个调用get_area()
、get_perimeter()
、get_filled()
、get_color()
、set_color()
和set_filled()
方法。注意我们如何能够调用在同一个类中定义的方法,以及在父类中定义的方法。
多重继承
Python 允许我们同时从几个类中派生一个类,这就是所谓的多重继承。它的一般格式是:
Class ParentClass_1:
# body of ParentClass_1
Class ParentClass_2:
# body of ParentClass_2
Class ParentClass_3:
# body of ParentClass_1
Class ChildClass(ParentClass_1, ParentClass_2, ParentClass_3):
# body of ChildClass
ChildClass
来源于三个类ParentClass_1
、ParentClass_2
、ParentClass_3
。因此,它将从所有三个类继承属性和方法。
以下程序演示了多重继承的作用:
蟒 101/第-16 章/多重 _ 继承. py
class A:
def explore(self):
print("explore() method called")
class B:
def search(self):
print("search() method called")
class C:
def discover(self):
print("discover() method called")
class D(A, B, C):
def test(self):
print("test() method called")
d_obj = D()
d_obj.explore()
d_obj.search()
d_obj.discover()
d_obj.test()
输出:
explore() method called
search() method called
discover() method called
test() method called
多态和方法覆盖
在字面意义上,多态意味着采取各种形式的能力。在 Python 中,多态允许我们在子类中定义与父类中定义的名称相同的方法。
正如我们所知,子类继承了父类的所有方法。但是,您会遇到从父类继承的方法不太适合子类的情况。在这种情况下,您必须在子类中重新实现方法。这个过程被称为方法覆盖。
如果在子类中重写了一个方法,那么该方法的版本将根据用于调用它的对象的类型来调用。如果子类对象用于调用被重写的方法,则调用该方法的子类版本。另一方面,如果父类对象用于调用被重写的方法,则调用该方法的父类版本。
以下程序演示了方法重写的操作:
蟒蛇 101/第 16 章/方法 _ 覆盖. py
class A:
def explore(self):
print("explore() method from class A")
class B(A):
def explore(self):
print("explore() method from class B")
b_obj = B()
a_obj = A()
b_obj.explore()
a_obj.explore()
输出:
explore() method from class B
explore() method from class A
这里b_obj
是类B
(子类)的一个对象,因此,类B
版本的explore()
方法被调用。但是,变量a_obj
是类A
(父类)的对象,因此调用了类A
版本的explore()
方法。
如果出于某种原因,您仍然想要访问子类中父类的被覆盖方法,您可以使用super()
函数调用它,如下所示:
蟒蛇 101/第 16 章/方法 _ 覆盖 _2.py
class A:
def explore(self):
print("explore() method from class A")
class B(A):
def explore(self):
super().explore() # calling the parent class explore() method
print("explore() method from class B")
b_obj = B()
b_obj.explore()
输出:
explore() method from class A
explore() method from class B
对象-基类
在 Python 中,所有类都隐式继承自object
类。这意味着以下两个类定义是等价的。
class MyClass:
pass
class MyClass(object):
pass
原来object
类提供了一些特殊的方法,有两个前导和尾随下划线,所有的类都继承了这两个下划线。以下是object
类提供的一些重要方法。
__new__()
__init__()
__str__()
__new__()
方法创建对象。创建对象后,它调用__init__()
方法初始化对象的属性。最后,它将新创建的对象返回给调用程序。通常情况下,我们不会覆盖__new__()
方法,但是,如果你想显著改变一个对象的创建方式,你绝对应该覆盖它。
__str__()
方法用于返回对象的格式良好的字符串表示。__str__()
方法的object
类版本返回一个包含类名称及其十六进制内存地址的字符串。例如:
蟒蛇 101/第 16 章/_ _ str _ _ 法. py
class Jester:
def laugh(self):
return print("laugh() called")
obj = Jester()
print(obj)
输出:
<__main__.Jester object at 0x0000000002167E80>
当然,这不是很有帮助。我们可以通过在Jester
类中定义一个名为__str__()
的方法来轻松覆盖这个方法,如下所示。
蟒蛇 101/第 16 章/覆盖 _ _ str _ _ 方法. py
class Jester:
def laugh(self):
return "laugh() called"
def __str__(self):
return "A more helpful description"
obj = Jester()
print(obj)
输出:
A more helpful description
Python 中的运算符重载
原文:https://overiq.com/python-101/operator-overloading-in-python/
最后更新于 2020 年 9 月 22 日
操作员超载
运算符重载使您可以根据您的类重新定义运算符的含义。正是运算符重载的魔力,我们能够使用+
运算符来添加两个数字对象,以及两个字符串对象。
>>>
>>> 10 + 400
410
>>>
>>> "ten" + "sor"
'tensor'
>>>
这里+
运算符有两种解释。与数字一起使用时,它被解释为加法运算符,而与字符串一起使用时,它被解释为串联运算符。换句话说,我们可以说int
类和str
类的+
操作符过载了。
那么,我们如何为特定的类重新定义或重载一个运算符呢?
运算符重载是通过在类定义中定义一个特殊方法来实现的。这些方法的名称以双下划线(__
)开始和结束。用来使+
操作员过载的特殊方法叫做__add__()
。int
类和str
类都执行__add__()
方法。__add__()
方法的int
类版本简单地添加了两个数字,而str
类版本连接了字符串。
如果表达式是针对表单x + y
的,Python 会将其解释为x.__add__(y)
。调用的__add__()
方法的版本取决于x
和y
的类型。如果x
和y
是int
对象,则调用__add__()
的int
类版本。另一方面,如果x
和y
是列表对象,则调用__add__()
方法的list
类版本。
>>>
>>> x, y = 10, 20
>>>
>>> x + y
30
>>> x.__add__(y) # same as x + y
30
>>>
>>> x, y = [11, 22], [1000, 2000]
>>>
>>> x.__add__(y) # same as x + y
[11, 22, 1000, 2000]
>>>
下表列出了运算符及其对应的特殊方法。回想一下第课 Python 中的对象和类有两个前导下划线的变量名称是私有变量,表中列出的特殊方法不是私有的,因为它们除了前导下划线之外,还有尾随下划线。
操作员 | 特殊方法 | 描述 |
---|---|---|
+ |
__add__(self, object) |
添加 |
- |
__sub__(self, object) |
减法 |
* |
__mul__(self, object) |
增加 |
** |
__pow__(self, object) |
幂运算 |
/ |
__truediv__(self, object) |
分开 |
// |
__floordiv__(self, object) |
整数除法 |
% |
__mod__(self, object) |
系数 |
== |
__eq__(self, object) |
等于 |
!= |
__ne__(self, object) |
不等于 |
> |
__gt__(self, object) |
大于 |
>= |
__ge__(self, object) |
大于或等于 |
< |
__lt__(self, object) |
不到 |
<= |
__le__(self, object) |
小于或等于 |
in |
__contains__(self, value) |
成员操作符 |
[index] |
__getitem__(self, index) |
索引处的项目 |
len() |
__len__(self) |
计算项目数 |
str() |
__str__(self) |
将对象转换为字符串 |
请注意,表中最后两项不是运算符,而是内置函数。但是如果你想在你的类中使用它们,你应该定义它们各自的特殊方法。
下面的程序首先使用运算符执行一个操作,然后使用相应的特殊方法执行相同的操作。
蟒蛇 101/第 17 章/特殊 _ 方法. py
x, y = 2, 4
print("x = ", x, ", y =", y)
print("\nx + y =", x + y)
print("x.__add__(y) =", x.__add__(y)) # same as x + y
print("\nx * y = ", x * y)
print("x.__mul__(y) = ", x.__mul__(y)) # same as x * y
print("\nx / y = ", x / y)
print("x.__truediv__(y) = ", x.__truediv__(y)) # same as x / y
print("\nx ** y = ", x ** y)
print("x.__pow__(y) = ", x.__pow__(y)) # same as x ** y
print("\nx % y = ", x % y)
print("x.__mod__(y) = ", x.__mod__(y)) # same as x % y
print("\nx == y = ", x == y)
print("x.__eq__(y) = ", x.__eq__(y)) # same as x == y
print("\nx != y = ", x != y)
print("x.__ne__(y) = ", x.__ne__(y)) # same as x != y
print("\nx >= y = ", x >= y)
print("x.__ge__(y) = ", x.__ge__(y)) # same as x >= y
print("\nx <= y = ", x <= y)
print("x.__le__(y) = ", x.__le__(y)) # same as x <= y
print("------------------------------------------")
str1 = "special methods"
print("\nstr1 =", str1)
print("\n'ods' in str1 =", "ods" in str1)
print("str1.__contains__('ods') =", str1.__contains__("ods")) # same as "ods" in str1
print("\nlen(str1) =", len(str1))
print("str1.__len__() =", str1.__len__()) # same as len(str1)
print("------------------------------------------")
list1 = [11,33, 55]
print("\nlist1 =", list1)
print("\nlist1[1] =", list1[1])
print("list1.__getitem(1) =", list1.__getitem__(1)) # same as list1[1]
print("str(list1) =",str(list1)) # same as list1.__str__()
输出:
x = 2 , y = 4
x + y = 6
x.__add__(y) = 6
x * y = 8
x.__mul__(y) = 8
x / y = 0.5
x.__truediv__(y) = 0.5
x ** y = 16
x.__pow__(y) = 16
x % y = 2
x.__mod__(y) = 2
x == y = False
x.__eq__(y) = False
x != y = True
x.__ne__(y) = True
x >= y = False
x.__ge__(y) = False
x <= y = True
x.__le__(y) = True
------------------------------------------
str1 = special methods
'ods' in str1 = True
str1.__contains__('ods') = True
len(str1) = 15
str1.__len__() = 15
------------------------------------------
list1 = [11, 33, 55]
list1[1] = 33
list1.__getitem(1) = 33
str(list1) = [11, 33, 55]
下面的程序演示了如何在类中重写运算符。
蟒蛇 101/第 17 章/点. py
import math
class Point:
def __init__(self, x=0, y=0):
self.__x = x
self.__y = y
# get the x coordinate
def get_x(self):
return self.__x
# set the x coordinate
def set_x(self, x):
self.__x = x
# get the y coordinate
def get_y(self):
return self.__y
# set the y coordinate
def set_y(self, y):
self.__y = y
# get the current position
def get_position(self):
return self.__x, self.__y
# change x and y coordinate by a and b
def move(self, a, b):
self.__x += a
self.__y += b
# overloading + operator
def __add__(self, point_obj):
return Point(self.__x + point_obj.__x, self.__y + point_obj.__y)
# overloading - operator
def __sub__(self, point_obj):
return Point(self.__x - point_obj.__x, self.__y - point_obj.__y)
# overloading < operator
def __lt__(self, point_obj):
return math.sqrt(self.__x ** 2 + self.__y ** 2) < math.sqrt(point_obj.__x ** 2 + point_obj.__y ** 2)
# overloading <= operator
def __le__(self, point_obj):
return math.sqrt(self.__x ** 2 + self.__y ** 2) <= math.sqrt(point_obj.__x ** 2 + point_obj.__y ** 2)
# overloading > operator
def __gt__(self, point_obj):
return math.sqrt(self.__x ** 2 + self.__y ** 2) > math.sqrt(point_obj.__x ** 2 + point_obj.__y ** 2)
# overloading >= operator
def __ge__(self, point_obj):
return math.sqrt(self.__x ** 2 + self.__y ** 2) >= math.sqrt(point_obj.__x ** 2 + point_obj.__y ** 2)
# overloading == operator
def __eq__(self, point_obj):
return math.sqrt(self.__x ** 2 + self.__y ** 2) == math.sqrt(point_obj.__x ** 2 + point_obj.__y ** 2)
## overriding __str__ function
def __str__(self):
return "Point object is at: (" + str(self.__x) + ", " + str(self.__y) + ")"
p1 = Point(4, 6)
p2 = Point(10, 6)
print("Is p1 < p2 ?", p1 < p2) # p1 < p2 is equivalent to p1.__lt__(p2)
print("Is p1 <= p2 ?", p1 <= p2) # p1 <= p2 is equivalent to p1.__le__(p2)
print("Is p1 > p2 ?", p1 > p2) # p1 > p2 is equivalent to p1.__gt__(p2)
print("Is p1 >= p2 ?", p1 >= p2) # p1 >= p2 is equivalent to p1.__ge__(p2)
print("Is p1 == p2 ?", p1 == p2) # p1 < p2 is equivalent to p1.__eq__(p2)
p3 = p1 + p2 # p1 + p2 is equivalent to p1.__add__(p2)
p4 = p1 - p2 # p1 - p2 is equivalent to p1.__sub__(p2)
print() # print an empty line
print(p1) # print(p1) is equivalent to print(p1.__str__())
print(p2) # print(p2) is equivalent to print(p2.__str__())
print(p3) # print(p3) is equivalent to print(p3.__str__())
print(p4) # print(p4) is equivalent to print(p4.__str__())
输出:
Is p1 < p2 ? True
Is p1 <= p2 ? True
Is p1 > p2 ? False
Is p1 >= p2 ? False
Is p1 == p2 ? False
Point object is at: (4, 6)
Point object is at: (10, 6)
Point object is at: (14, 12)
Point object is at: (-6, 0)
Point
类定义了两个私有属性__x
和__y
,表示平面中的x
和y
坐标。然后它为这些属性定义了 getter 和 setter 方法。还定义了,get_position()
和move()
两种方法分别获取当前位置和改变坐标。
在第 35 行,我们已经重载了Point
类的+
运算符。__add__()
方法通过将一个Point
对象的单独坐标添加到另一个点对象来创建一个新的Point
对象。最后,它将新创建的对象返回给它的调用者。这让我们可以写出这样的表达式:
p3 = p1 + p2
其中p1
、p2
和p3
是三个点对象。
Python 将上面的表达式解释为p3 = p1.__add__(p2)
,并调用__add__()
方法添加两个Point
对象。然后将__add__()
方法的返回值分配给p3
。需要注意的是,调用__add__()
方法时,p1
的值赋给self
参数,p2
的值赋给point_obj
参数。其余的特殊方法以类似的方式工作。
Python 中的文件处理
最后更新于 2020 年 7 月 27 日
到目前为止,在本课程中,我们一直使用变量来存储数据。这种方法的问题是,一旦程序结束,我们的数据就会丢失。永久存储数据的一种方法是将其放入文件中。本章讨论如何在文件中存储数据以及从文件中读取数据。
在 Python 中,文件处理包括以下三个步骤:
- 打开文件。
- 处理文件,即执行读或写操作。
- 关闭文件。
文件类型
有两种类型的文件:
- 文本文件
- 二进制文件
可以使用文本编辑器查看其内容的文件称为文本文件。文本文件只是一系列 ASCII 或 Unicode 字符。Python 程序、HTML 源代码都是一些文本文件的例子。
二进制文件存储数据的方式与存储在内存中的方式相同。mp3 文件、图像文件、word 文档是二进制文件的一些例子。您不能使用文本编辑器读取二进制文件。
在本课中,我们将讨论如何处理这两种类型的文件。
我们开始吧。
打开文件
在对文件执行任何操作之前,必须先将其打开。Python 提供了一个名为fopen()
的函数来打开文件。它的语法是:
fileobject = open(filename, mode)
filename
是文件的名称或路径。
mode
是一个字符串,它指定了您想要对文件执行的类型操作(即读、写、追加等)。下表列出了您可以使用的不同模式。
方式 | 描述 |
---|---|
"r" |
打开文件进行阅读。如果文件不存在,你将得到FileNotFoundError 错误。 |
"w" |
打开文件进行写入。在这种模式下,如果指定的文件不存在,将创建该文件。如果文件存在,那么它的数据就被销毁了。 |
"a" |
以追加模式打开文件。如果文件不存在,此模式将创建文件。如果文件已经存在,那么它会将新数据附加到文件的末尾,而不是像"w" 模式那样销毁数据。 |
我们还可以指定文件的类型(即文本文件或二进制文件。)我们希望在mode
字符串中通过追加't'
来处理文本文件,追加'b'
来处理二进制文件。但是由于文本模式是默认模式,所以在文本模式下打开文件时通常会省略。
成功后,open()
返回一个与调用时指定的filename
相关联的文件对象。
以下是一些如何打开文件的示例:
例 1:
f = open("employees.txt", "rt")
该语句打开文本文件employees.txt
进行阅读。由于文本模式是默认的,上面的语句也可以写成:
f = open("employees.txt", "r")
例 2:
f = open("teams.txt", "w")
该语句以写模式打开文本文件。
例 3:
f = open("teams.dat", "wb")
该语句以写模式打开二进制文件。
例 4:
f = open("teams.dat", "ab")
该语句以追加模式打开二进制文件。
除了使用相对文件路径,我们还可以使用绝对文件路径。例如:
f = open("/home/tom/documents/README.md", "w")
该语句以写模式打开位于/home/tom/documents/
目录中的文本文件README.md
。
在 Windows 中,使用绝对路径名时记得转义反斜杠,否则会出错。例如:
f = open("C:\\Users\\tom\\documents\\README.md", "w")
我们也可以通过在字符串前面指定r
字符来使用称为“原始字符串”的东西,如下所示:
f = open(r"C:\Users\tom\documents\README.md", "w")
r
字符使 Python 将字符串中的每个字符都视为文字字符。
关闭文件
一旦我们处理完文件或者我们想以其他模式打开文件,我们应该使用文件对象的close()
方法关闭文件,如下所示:
f.close()
关闭文件会释放宝贵的系统资源。万一忘记关闭文件,当程序结束或者程序中不再引用文件对象时,Python 会自动关闭文件。但是,如果您的程序很大,并且您正在读取或写入多个文件,这可能会占用系统上大量的资源。如果你一直不小心打开新文件,你可能会耗尽资源。所以,做一个好的程序员,一写完就关闭文件。
文本包装类
open()
函数返回的文件对象是_io.TextIOWrapper
类型的对象。类_io.TextIOWrapper
提供了帮助我们读写文件的方法和属性。下表列出了_io.TextIOWrapper
类的一些常用方法。
方法 | 描述 |
---|---|
read([num]) |
从文件中读取指定数量的字符,并将其作为字符串返回。如果省略num ,则读取整个文件。 |
readline() |
读取一行并将其作为字符串返回。 |
readlines() |
逐行读取文件内容,并将它们作为字符串列表返回。 |
write(str) |
将字符串参数写入文件,并返回写入文件的字符数。 |
seek(offset, origin) |
将文件指针从原点移动到给定的偏移量。 |
tell() |
返回文件指针的当前位置。 |
close() |
关闭文件 |
将数据写入文本文件
以下程序演示了如何将数据写入文件:
python 101/第 18 章/writing_to_file.py
f = open("readme.md", "w")
f.write("First Line\n")
f.write("Second Line\n")
f.write("Third Line\n")
f.close()
在第 1 行,我们使用open()
方法以写模式打开文本文件。如果readme.md
文件不存在,open()
方法将创建该文件。如果文件已经存在,那么它的数据将被覆盖。运行程序,然后打开readme.md
文件。应该是这样的:
蟒蛇 101/第 18 章/readme.md
First Line
Second Line
Third Line
让我们仔细看看write()
方法是如何将数据写入文件的。
文件中的所有读写操作都从文件指针开始。什么是文件指针?文件指针只是一个标记,用于记录文件中读取或写入的字节数。该指针在每次读或写操作后自动移动。
打开文件时,文件指针指向文件的开头。write()
功能从当前文件位置开始写入,然后递增文件指针。例如,下图显示了每次写入操作后文件指针的位置。
请注意,与print()
函数不同,write()
方法不会自动在字符串末尾打印换行符(\n
)。我们也可以使用print()
功能将数据写入文件。让我们使用help()
功能仔细看看print()
的签名。
>>>
>>> help(print)
Help on built-in function print in module builtins:
print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
>>>
注意函数签名中的第四个参数,即file
。默认情况下,file
指向标准输出意味着将数据打印到屏幕上。要向文件输出数据,只需指定文件对象。以下程序使用print()
功能代替write()
将数据写入文件。
python 101/第 18 章/writing _ data _ using _ print _ function . py
f = open("readme.md", "w")
print("First Line", file=f)
print("Second Line", file=f)
print("Third Line", file=f)
f.close()
这个程序产生和以前一样的输出,唯一的区别是,在这种情况下,换行符(\n
)是由print()
功能自动添加的。
从文本文件中读取数据
要读取文件,您必须在"r"
模式下打开它。除此之外,您还应该确保您想要读取的文件已经存在,因为在"r"
模式下,open()
函数在找不到文件时会抛出FileNotFoundError
错误。
要测试文件是否存在,我们可以从os.path
模块使用isfile()
功能。isfile()
的句法是:
isfile(path)
如果在给定路径isfile()
找到文件,则返回True
。否则False
。下面的 shell 会话演示了isfile()
功能的工作。
>>>
>>> import os
>>>
>>> os.path.isfile("/home/q/python101/Chapter-18/readme.md") # file exists
True
>>>
>>> os.path.isfile("/home/q/python101/Chapter-18/index.html") # file doesn't exists
False
>>>
以下程序演示了如何使用read()
、readline()
和readlines()
功能读取文件。
示例 1: 使用read()
方法立即读取数据。
蟒蛇 101/第 18 章/read_method_demo.py
f = open("readme.md", "r")
print(f.read()) # read all content at once
f.close()
输出:
First Line
Second Line
Third Line
示例 2 :使用read()
方法读取数据块。
蟒蛇 101/第 18 章/阅读 _in_chunks.py
f = open("readme.md", "r")
print("First chunk:", f.read(4), end="\n\n") # read the first 4 characters
print("Second chunk:", f.read(10), end="\n\n") # read the next 10 character
print("Third chunk:", f.read(), end="\n\n") # read the remaining characters
f.close()
输出:
First chunk: Firs
Second chunk: t Line
Sec
Third chunk: ond Line
Third Line
当文件以读取模式打开时,文件指针指向文件的开头。
读取前 4 个字符后,文件指针位于t
。
读取后面 10 个字符后,文件指针在字符o
处。
对read()
的第三次调用读取文件中剩余的字符,并将它们作为字符串返回。此时,文件位置指针指向文件的末尾。因此,对read()
方法的任何后续调用都会返回一个空字符串。
示例 3 :使用readline()
从文件中读取数据。
蟒蛇 101/第 18 章/readline_method_demo.py
f = open("readme.md", "r")
# read first line
print("Ist line:", f.readline())
# read the fist two characters in the second line
print("The first two characters in the 2nd line:", f.read(2), end="\n\n")
# read the remaining characters int the second line
print("Remaining characters in the 2nd line:", f.readline())
# read the next line
print("3rd line:", f.readline())
# end of the file reached, so readline returns an empty string ""
print("After end of file :", f.readline())
f.close()
输出:
Ist line: First Line
The first two characters in the 2nd line: Se
Remaining characters in the 2nd line: cond Line
3rd line: Third Line
After end of file :
打开文件时,文件指针通常指向文件的开头。
对readline()
方法的第一次调用将文件指针移动到下一行的开始。
read()
功能然后从文件中读取两个字符,这将文件指针移动到字符c
。
在第 10 行中,再次调用readline()
,但这次它开始从字符c
读取到行尾(包括换行符)。
在第 13 行中,再次调用readline()
,读取最后一行。此时,文件位置指针在文件的末尾。这就是为什么第 16 行的readline()
调用返回一个空字符串(""
)。
示例 4 :使用readlines()
从文件中读取数据。
蟒蛇 101/第 18 章/readline _ method _ demo . py
f = open("readme.md", "r")
# read all the line as return and them as a list of strings
print(f.readlines())
f.close()
输出:
['First Line\n', 'Second Line\n', 'Third Line\n']
读取大文件
read()
和readlines()
方法适用于小文件。但是如果你的文件有几千或几百万行呢?在这种情况下,使用read()
或readlines()
可能会导致记忆猪。更好的方法是使用循环,以小块读取文件数据。例如:
蟒蛇 101/第 18 章/reading _ large _ file _ demo 1 . py
f = open("readme.md", "r")
chunk = 10 # specify chunk size
data = ""
# keep looping until there is data in the file
while True:
data = f.read(chunk)
print(data, end="")
# if end of file is reached, break out of the while loop
if data == "":
break
f.close()
输出:
First Line
Second Line
Third Line
这里我们使用一个无限循环来迭代文件的内容。一旦到达文件末尾,read()
方法返回一个空字符串(""
),如果第 12 行的条件为真,break
语句导致循环终止。
Python 还允许我们使用 for 循环来循环使用文件对象的文件数据,如下所示:
python 101/第 18 章/reading _ large _ files _ using _ for _ loop . py
f = open("readme.md", "r")
for line in f:
print(line, end="")
f.close()
输出:
First Line
Second Line
Third Line
将数据追加到文本文件
我们可以使用"a"
模式将数据追加到文件末尾。下面的程序演示了如何将数据追加到文件的末尾。
python 101/第 18 章/append_data.py
f = open("readme.md", "a")
print("Appending data to the end of the file ...")
f.write("Fourth Line\n")
f.write("Fifth Line\n")
print("Done!")
f.close()
## open the file again
print("\nOpening the file again to read the data ...\n")
f = open("readme.md", "r")
for line in f:
print(line, end="")
f.close()
输出:
Appending data to the end of the file ...
Done!
Opening the file again to read the data ...
First Line
Second Line
Third Line
Fourth Line
Fifth Line
使用 with 语句处理文件
Python 还使用with
语句为文件处理提供了一个很好的快捷方式。以下是with
语句用于文件时的一般形式。
with open(filename, mode) as file_object:
# body of with statement
# perform the file operations here
这个快捷方式最大的好处是它可以自动关闭文件,而不需要你做任何工作。with 语句体中的语句必须同样缩进,否则会出错。file_object
变量的范围仅限于with
语句的正文。如果你试图在块外调用read()
或write()
方法,你会得到一个错误。
下面的例子展示了我们如何使用with
语句来读写文件中的数据。
例 1 :使用 for 循环逐行读取数据。
蟒蛇 101/第 18 章/with_statement.py
with open("readme.md", "r") as f:
for line in f:
print(line, end="")
输出:
First Line
Second Line
Third Line
Fourth Line
Fifth Line
例 2 :使用read()
方法一次读取所有数据。
蟒蛇 101/第 18 章/with_statement2.py
with open("readme.md", "r") as f:
print(f.read())
输出:
First Line
Second Line
Third Line
Fourth Line
Fifth Line
例 3 :小块读取大文件。
蟒蛇 101/第 18 章/with_statement3.py
with open("readme.md", "r") as f:
chunk = 10 # specify chunk size
data = ""
# keep looping until there is data in the file
while True:
data = f.read(chunk)
print(data, end="")
# if end of file is reached, break out of the while loop
if data == "":
break
输出:
First Line
Second Line
Third Line
Fourth Line
Fifth Line
示例 4 :使用write()
方法将数据写入文件
蟒蛇 101/第 18 章/with_statement4.py
with open("random.txt", "w") as f:
f.write("ONE D\n")
f.write("TWO D\n")
f.write("THREE D\n")
f.write("FOUR D\n")
读取和写入二进制数据
以下程序将二进制数据从源文件(source.jpg
)复制到目标文件(dest.jpg
)。
python 101/第 18 章/reading _ and _ writing _ binary _ data . py
f_source = open("source.jpg", "rb")
f_dest = open("dest.jpg", "wb")
char_count = 0
for line in f_source:
char_count += len(line)
f_dest.write(line)
print(char_count, "characters copied successfully")
f_source.close()
f_dest.close()
输出:
2115658 characters copied successfully
运行程序,它应该在与source.jpg
相同的目录下创建dest.jpg
文件。
Python 中的异常处理
原文:https://overiq.com/python-101/exception-handling-in-python/
最后更新于 2020 年 9 月 22 日
异常处理是一种机制,它允许我们在程序运行时优雅地处理错误,而不是突然终止程序执行。
运行时错误
运行时错误是程序运行时发生的错误。请注意,运行时错误并不表示程序的结构(或语法)有问题。当出现运行时错误时,Python 解释器完全理解您的语句,但它就是不能执行它。但是,由于程序结构不正确,会出现语法错误。这两种类型的错误一遇到就停止程序的执行,并显示一条错误消息(或追溯),解释问题的可能原因。
以下是运行时错误的一些示例。
例 1 :除以零。
>>>
>>> 2/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>>
例 2 :将字符串添加到整数中。
>>>
>>> 10 + "12"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
>>>
示例 3 :试图访问无效索引处的元素。
>>>
>>> list1 = [11, 3, 99, 15]
>>>
>>> list1[10]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>>
例 4 :以读取模式打开一个不存在的文件。
>>>
>>> f = open("filedoesntexists.txt", "r")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'filedoesntexists.txt'
>>>
>>>
再次注意,上述所有语句在语法上都是有效的,唯一的问题是当 Python 试图执行它们时,它们进入了无效状态。
程序运行时出现的错误称为异常。当这种情况发生时,我们说 Python 已经引发了一个异常或者抛出了一个异常。每当这种错误发生时,Python 都会创建一个特殊类型的对象,其中包含关于刚刚发生的错误的所有相关信息。例如,它包含发生错误的行号、错误消息(记住这叫做回溯)等等。默认情况下,这些错误只是暂停程序的执行。异常处理机制允许我们在不停止程序的情况下优雅地处理这样的错误。
try-except 语句
在 Python 中,我们使用try-except
语句进行异常处理。它的语法如下:
try:
# try block
# write code that might raise an exception here
<statement_1>
<statement_2>
except ExceptiomType:
# except block
# handle exception here
<handler>
代码以单词try
开头,这是一个保留的关键字,后面跟一个冒号(:
)。在下一行中,我们有 try block。try 块包含可能引发异常的代码。之后,我们有一个以单词except
开头的except
子句,它也是一个保留关键字,后面是一个异常类型和一个冒号(:
)。在下一行,我们有一个例外块。异常块包含处理异常的代码。像往常一样,try and except 块中的代码必须正确缩进,否则会出错。
以下是try-except
语句的执行方式:
当 try 块中出现异常时,会跳过 try 块中其余语句的执行。如果引发的异常与except
子句中的异常类型匹配,则执行相应的处理程序。
如果在 try 块中引发的异常与except
子句中指定的异常类型不匹配,程序将停止并进行回溯。
另一方面,如果在尝试块中没有引发异常,则跳过除clause
之外的部分。
让我们举个例子:
蟒蛇 101/第 19 章/异常处理. py
try:
num = int(input("Enter a number: "))
result = 10/num
print("Result: ", result)
except ZeroDivisionError:
print("Exception Handler for ZeroDivisionError")
print("We cant divide a number by 0")
运行程序并输入 0。
首次运行输出:
Enter a number: 0
Exception Handler for ZeroDivisionError
We cant divide a number by 0
在本例中,第 3 行中的 try 块会引发ZeroDivisionError
。当异常发生时,Python 会查找具有匹配异常类型的except
子句。在这种情况下,它会找到一个并在该块中运行异常处理程序代码。请注意,因为在第 3 行中引发了异常,所以会跳过 try 块中语句重置的执行。
再次运行程序,但这次输入一个字符串而不是数字:
第二次运行输出:
Enter a number: str
Traceback (most recent call last):
File "exception_example.py", line 2, in <module>
num = int(input("Enter a number: "))
ValueError: invalid literal for int() with base 10: 'str'
这次我们的程序崩溃了,出现了ValueError
异常。问题是内置的int()
只适用于只包含数字的字符串,如果你传递一个包含非数字字符的字符串,它会抛出一个ValueError
异常。
>>>
>>> int("123") // that's fine because string only contains numbers
123
>>>
>>> int("str") // error can't converts characters to numbers
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'str'
>>>
由于我们没有带ValueError
异常的except
子句,我们的程序因ValueError
异常而崩溃。
再次运行程序,这次输入0
以外的整数。
第三次运行输出:
Enter a number: 4
Result: 2.5
在这种情况下,try 块中的语句执行时不会引发任何异常,因此except
子句被跳过。
处理多个异常
我们可以添加任意多的except
子句来处理不同类型的异常。这种try-except
语句的一般格式如下:
try:
# try block
# write code that might raise an exception here
<statement_1>
<statement_2>
except <ExceptiomType1>:
# except block
# handle ExceptiomType1 here
<handler>
except <ExceptiomType2>:
# except block
# handle ExceptiomType2 here
<handler>
except <ExceptiomType2>:
# except block
# handle ExceptiomType3 here
<handler>
except:
# handle any type of exception here
<handler>
以下是它的工作原理:
当异常发生时,Python 会按顺序匹配针对每个except
子句引发的异常。如果找到匹配项,则执行相应的except
子句中的处理程序,并跳过其余的except
子句。
如果引发的异常与最后一个except
子句之前的任何except
子句不匹配(第 18 行),则执行最后一个except
子句中的处理程序。请注意,最后一个except
子句前面没有任何异常类型,因此,它可以捕获任何类型的异常。当然,最后一个except
子句是完全可选的,但是我们通常将它作为捕捉意外错误的最后手段,从而防止程序崩溃。
下面的程序演示了如何使用多个except
子句。
python 101/第 19 章/处理多种类型的异常。py
try:
num1 = int(input("Enter a num1: "))
num2 = int(input("Enter a num2: "))
result = num1 / num2
print("Result: ", result)
except ZeroDivisionError:
print("\nException Handler for ZeroDivisionError")
print("We cant divide a number by 0")
except ValueError:
print("\nException Handler for ValueError")
print("Invalid input: Only integers are allowed")
except:
print("\nSome unexpected error occurred")
首次运行输出:
Enter a num1: 10
Enter a num2: 0
Exception Handler for ZeroDivisionError
We cant divide a number by 0
第二次运行输出:
Enter a num1: 100
Enter a num2: a13
Exception Handler for ValueError
Invalid input: Only integers are allowed
第三次运行输出:
Enter a num1: 5000
Enter a num2: 2
Result: 2500
下面是另一个示例程序,它要求用户输入文件名,然后将文件内容打印到控制台。
python 101/第 19 章/异常处理 _while_reading_file.py
filename = input("Enter file name: ")
try:
f = open(filename, "r")
for line in f:
print(line, end="")
f.close()
except FileNotFoundError:
print("File not found")
except PermissionError:
print("You don't have the permission to read the file")
except:
print("Unexpected error while reading the file")
运行程序并指定一个不存在的文件。
首次运行输出:
Enter file name: file_doesnt_exists.md
File not found
再次运行程序,这次指定一个您无权读取的文件。
第二次运行输出:
Enter file name: /etc/passwd
You don't have the permission to read the file
再次运行程序,但这次指定一个确实存在的文件,并且您有权限读取它。
第三次运行输出:
Enter file name: ../Chapter-18/readme.md
First Line
Second Line
Third Line
Fourth Line
Fifth Line
else and finally 子句
一个try-except
语句也可以有一个可选的else
子句,该子句只有在没有引发异常时才会被执行。带有else
条款的try-except
声明的一般格式如下:
try:
<statement_1>
<statement_2>
except <ExceptiomType1>:
<handler>
except <ExceptiomType2>:
<handler>
else:
# else block only gets executed
# when no exception is raised in the try block
<statement>
<statement>
下面是使用else
子句对上述程序的重写。
蟒蛇 101/第 19 章/else _ 子句 _demo.py
import os
filename = input("Enter file name: ")
try:
f = open(filename, "r")
for line in f:
print(line, end="")
f.close()
except FileNotFoundError:
print("File not found")
except PermissionError:
print("You don't have the permission to read the file")
except FileExistsError:
print("You don't have the permission to read the file")
except:
print("Unexpected error while reading the file")
else:
print("Program ran without any problem")
运行程序并输入一个不存在的文件。
首次运行输出:
Enter file name: terraform.txt
File not found
再次运行程序,但这次输入一个确实存在的文件,并且您有访问它的权限。
第二次运行输出:
Enter file name: ../Chapter-18/readme.md
First Line
Second Line
Third Line
Fourth Line
Fifth Line
Program ran without any problem
不出所料,else
子句中的语句这次被执行了。else
子句通常用于在 try 块中的代码成功运行后编写我们想要运行的代码。
同样,我们可以在所有except
子句之后有一个finally
子句。finally
条款下的声明将始终执行,无论是否提出例外。它的一般形式如下:
try:
<statement_1>
<statement_2>
except <ExceptiomType1>:
<handler>
except <ExceptiomType2>:
<handler>
finally:
# statements here will always
# execute no matter what
<statement>
<statement>
finally
条款通常用于定义在任何情况下都必须执行的清理动作。如果try-except
语句有一个else
子句,那么finally
子句必须出现在它之后。
以下程序显示了finally
子句的作用。
蟒蛇 101/第 19 章/finally _ 子句 _demo.py
import os
filename = input("Enter file name: ")
try:
f = open(filename, "r")
for line in f:
print(line, end="")
f.close()
except FileNotFoundError:
print("File not found")
except PermissionError:
print("You don't have the permission to read the file")
except FileExistsError:
print("You don't have the permission to read the file")
except:
print("Unexpected error while reading the file")
else:
print("\nProgram ran without any problem")
finally:
print("finally clause: This will always execute")
首次运行输出:
Enter file name: little.txt
File not found
finally clause: This will always execute
第二次运行输出:
Enter file name: readme.md
First Line
Second Line
Third Line
Fourth Line
Fifth Line
Program ran without any problem
finally clause: This will always execute
异常传播和引发异常
在前面几节中,我们已经学习了如何使用try-except
语句处理异常。在本节中,我们将讨论谁抛出异常、如何创建异常以及它们如何传播。
异常只是一个由函数引发的对象,它发出信号表示发生了函数本身无法处理的意外情况。函数通过从适当的类创建异常对象引发异常,然后使用raise
关键字将异常抛出到调用代码,如下所示:
raise SomeExceptionClas("Error message describing cause of the error")
我们可以通过创建一个RuntimeError()
的实例来从我们自己的函数中引发异常,如下所示:
raise RuntimeError("Someting went very wrong")
当一个异常在一个函数中被引发并且没有在那里被捕获时,它会自动传播到调用函数(以及堆栈中的任何函数),直到它被某个调用函数中的try-except
语句捕获。如果异常到达主模块但仍未处理,程序将终止并显示一条错误消息。
举个例子吧。
假设我们正在创建一个函数来计算一个数的阶乘。由于阶乘仅对正整数有效,传递任何其他类型的数据都会使函数无用。我们可以通过检查参数的类型并在参数不是正整数时引发异常来防止这种情况。这是完整的代码。
蟒蛇 101/第 19 章/阶乘 py
def factorial(n):
if not isinstance(n, int):
raise RuntimeError("Argument must be int")
if n < 0:
raise RuntimeError("Argument must be >= 0")
f = 1
for i in range(n):
f *= n
n -= 1
return f
try:
print("Factorial of 4 is:", factorial(4))
print("Factorial of 12 is:", factorial("12"))
except RuntimeError:
print("Invalid Input")
输出:
Factorial of 4 is: 24
Invalid Input
请注意,当使用字符串参数调用factorial()
函数时(第 17 行),第 3 行会引发运行时异常。由于factorial()
函数不处理异常,引发的异常被传播回main
模块,在那里被第 18 行的except
子句捕获。
请注意,在上面的例子中,我们已经在factorial()
函数之外对try-except
语句进行了编码,但是我们可以很容易地在factorial()
函数内部进行相同的编码,如下所示。
蟒蛇 101/第 19 章/处理 _ 异常 _ 内部 _ 阶乘 _ 函数 py
def factorial(n):
try:
if not isinstance(n, int):
raise RuntimeError("Argument must be int")
if n < 0:
raise RuntimeError("Argument must be >= 0")
f = 1
for i in range(n):
f *= n
n -= 1
return f
except RuntimeError:
return "Invalid Input"
print("Factorial of 4 is:", factorial(4))
print("Factorial of 12 is:", factorial("12"))
输出:
Factorial of 4 is: 24
Factorial of 12 is: Invalid Input
但是,不建议这样做。通常,被调用的函数会向调用方抛出一个异常,处理该异常是调用代码的职责。这种方法允许我们以不同的方式处理异常,例如,在一种情况下,我们向用户显示错误消息,而在另一种情况下,我们默默记录问题。如果我们在被调用的函数中处理异常,那么每次需要新的行为时,我们都必须更新函数。除此之外,Python 标准库中的所有函数也符合这一行为。库函数只检测问题并引发异常,客户端决定需要做什么来处理这些错误。
现在,让我们看看在深度嵌套的函数调用中引发异常时会发生什么。回想一下,如果在函数内部引发了异常,并且该异常没有被函数本身捕获,那么它将被传递给它的调用方。这个过程一直重复,直到被堆栈中的某个调用函数捕获。考虑下面的例子。
def function3():
try:
...
raise SomeException()
statement7
except ExceptionType4:
handler
statement8
def function2():
try:
...
function3()
statement5
except ExceptionType3:
handler
statement6
def function1():
try:
...
function2()
statement3
except ExceptionType2:
handler
statement4
def main():
try:
...
function1()
statement1
except ExceptionType1:
handler
statement2
main()
程序执行从调用main()
函数开始。main()
功能调用function1()
、function1()
调用function2()
,最后function2()
调用function3()
。让我们假设function3()
可以引发不同类型的异常。现在考虑以下情况。
如果异常类型为
ExceptionType4
,则跳过statement7
,第 7 行的except
子句捕捉到它。function3()
的执行照常进行,statement8
被执行。如果异常类型为
ExceptionType3
,则function3()
的执行被中止(因为没有匹配的except
子句来处理引发的异常),并且控制被转移到调用方,即function2()
,其中ExceptionType3
由第 17 行的except
子句处理。跳过function2()
中的statement5
,执行statement6
。如果异常类型为
ExceptionType2
,则function3()
中止,控制转移至function2()
。由于function2()
没有任何匹配的except
子句来捕获异常,因此其执行被中止,并且控制被转移到function1()
,在那里异常被第 26 行的except
子句捕获。跳过function1()
中的statement3
,执行statement4
。如果异常是类型
ExceptionType1
,那么控制转移到功能main()
(因为function3()
、function2()
和function1()
没有匹配的except
子句来处理异常),其中异常由第 35 行的except
子句处理。跳过main()
中的statement1
,执行statement2
。如果异常类型为
ExceptionType0
。由于没有一个可用的函数能够处理这个异常,程序会以错误消息结束。
访问异常对象
现在我们知道如何处理异常,以及在需要的时候抛出异常。我们还没有涉及到的一件事是如何访问函数抛出的异常对象。我们可以使用以下形式的 except 子句来访问异常对象。
except ExceptionType as e
从现在开始,每当except
子句捕捉到类型为ExceptionType
的异常时,它会将异常对象分配给变量e
。
下面的示例演示如何访问异常对象:
蟒蛇 101/第 19 章/访问 _ 异常 _ 对象. py
def factorial(n):
if not isinstance(n, int):
raise RuntimeError("Argument must be int")
if n < 0:
raise RuntimeError("Argument must be >= 0")
f = 1
for i in range(n):
f *= n
n -= 1
return f
try:
print("Factorial of 4 is:", factorial(4))
print("Factorial of 12 is:", factorial("12"))
except RuntimeError as e:
print("Error:", e)
输出:
Factorial of 4 is: 24
Error: Argument must be int
请注意,异常对象(第 21 行)打印的错误消息与我们在创建RuntimeError
对象(第 3 行)时传递的消息相同。
创建您自己的例外
到目前为止,在本章中,我们一直在使用内置的异常类,如ZeroDivisionError
、ValueError
、TypeError
、RuntimeError
等。Python 还允许您创建新的异常类来满足您自己的特定需求。BaseException
类是 Python 中所有异常类的根。下图显示了 Python 中的异常类层次结构。
我们可以通过从Exception
内置类中派生出自己的异常类来创建。以下示例显示了如何创建自定义异常。
蟒蛇 101/第 19 章/无效因子参数异常. py
class InvalidFactorialArgumentException(Exception):
def __init__(self, message):
super().__init__()
self.message = message
def __str__(self):
return self.message
python 101/第 19 章/阶乘 WithCustomException.py
from InvalidFactorialArgumentException import *
def factorial(n):
if not isinstance(n, int):
raise InvalidFactorialArgumentException("Argument must be int")
if n < 0:
raise InvalidFactorialArgumentException("Argument must be >= 0")
f = 1
for i in range(n):
f *= n
n -= 1
return f
try:
print("Factorial of 4 is:", factorial(4))
print("Factorial of 12 is:", factorial("12"))
except InvalidFactorialArgumentException as e:
print("Error:", e)
输出:
Factorial of 4 is: 24
Error: Argument must be int
Python 中的元组
最后更新于 2020 年 9 月 22 日
元组
元组是像列表一样工作的序列,唯一的区别是它们是不可变的,这意味着一旦它被创建,我们就不能添加、移除或修改它的元素。与列表相比,这也使它们超级高效。元组用于存储不变的项目列表。
创建元组
元组可以通过在一对括号内列出用逗号(,
)分隔的元素来创建,即()
。以下是一些例子:
>>>
>>> t = ("alpha", "delta", "omega")
>>>
>>> t
('alpha', 'delta', 'omega')
>>>
>>> type(t)
<class 'tuple'>
>>>
下面是我们如何创建一个空元组:
>>>
>>> et = () # an empty tuple
>>> et
()
>>>
>>> type(et)
<class 'tuple'>
>>>
我们也可以使用构造函数即tuple()
,它接受任何类型的序列或可迭代对象。
>>>
>>> t1 = tuple("abcd") # tuple from string
>>>
>>> t1
('a', 'b', 'c', 'd')
>>>
>>>
>>> t2 = tuple(range(1, 10)) # tuple from range
>>>
>>> t2
(1, 2, 3, 4, 5, 6, 7, 8, 9)
>>>
>>>
>>> t3 = tuple([1,2,3,4]) # tuple from list
>>> t3
(1, 2, 3, 4)
>>>
列表理解也可以用来创建元组。
>>>
>>> tlc = tuple([x * 2 for x in range(1, 10)])
>>> tlc
(2, 4, 6, 8, 10, 12, 14, 16, 18)
>>>
要创建一个只有一个元素的元组,必须在值后键入尾随逗号,否则它就不是元组。例如:
>>>
>>> t1 = (1,) # this is a tuple
>>> type(t1)
<class 'tuple'>
>>>
>>>
>>> t2 = (1) # this is not
>>> type(t2)
<class 'int'>
>>>
使用括号括住元组的元素是可选的。这意味着我们也可以创建这样的元组:
>>>
>>> t1 = "alpha", "delta", "omega" # t1 is a tuple of three elements
>>> t1
('alpha', 'delta', 'omega')
>>> type(t1)
<class 'tuple'>
>>>
>>>
>>> t2 = 1, 'one', 2, 'two', 3, 'three' # t2 is a tuple of six elements
>>> t2
(1, 'one', 2, 'two', 3, 'three')
>>> type(t2)
<class 'tuple'>
>>>
请注意,虽然我们在创建元组时没有使用括号,但是 Python 在打印元组时使用了它们。
和以前一样,要创建一个元组,需要在值后有一个元素尾随逗号(,
)。
>>>
>>> t1 = 1,
>>> t1
(1,)
>>> type(t1)
<class 'tuple'>
>>>
>>>
>>> t2 = 1
>>> t2
1
>>> type(t2)
<class 'int'>
>>>
我们将在创建元组时显式使用括号。然而,另一种形式也有它的用途,最明显的是当从函数返回多个值时。
元组解包
元组允许您一次为多个变量赋值。例如:
>>>
>>> first, second, third = ("alpha", "beta", "gamma")
>>>
>>> first
'alpha'
>>> second
'beta'
>>> third
'gamma'
>>>
这就是所谓的元组解包。变量的数量(在左边)和元组中元素的数量(在右边)必须匹配,否则你会得到一个错误。
>>>
>>> first, second, third, fourth = ("alpha", "beta", "gamma")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: need more than 3 values to unpack
>>>
>>>
>>> first, second, third = ("alpha", "beta", "gamma", "phi")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 3)
>>>
因为括号是可选的,所以上面的代码也可以写成:
>>>
>>> first, second, third = "alpha", "beta", "gamma"
>>>
>>> first
'alpha'
>>> second
'beta'
>>> third
'gamma'
>>>
回想一下,我们已经在 Python 中的数据类型和变量一课中,在标题同时赋值下了解到这一点。好吧,现在你知道在幕后我们在不知不觉中使用元组了!
元组的运算
元组本质上是一个不可变的列表。因此,可以对列表执行的大多数操作对元组也是有效的。以下是此类操作的一些示例:
- 使用
[]
运算符访问单个元素或元素切片。 - 像
max()
、min()
、sum()
这样的内置函数对元组有效。 - 会员操作员
in
和not in
。 - 比较元组的比较运算符。
+
和*
操作员。- for 循环遍历元素。
等等。
每当您面临一个操作对元组是否有效的两难问题时,只需在 Python Shell 中尝试一下。
元组不支持的唯一操作类型是修改列表本身的操作。因此append()
、insert()
、remove()
、reverse()
、sort()
等方法不适用于元组。
下面的程序演示了可以对元组执行的一些常见操作。
蟒蛇 101/第-20 章/operations_on_tuple.py
tuple_a = ("alpha", "beta", "gamma")
print("tuple_a:", tuple_a)
print("Length of tuple_a:", len(tuple_a)) # len() function on tuple
tuple_b = tuple(range(1,20, 2)) # i.e tuple_b = (1, 3, 5, 7, 9, 11, 13, 15, 17, 19)
print("\ntuple_b:", tuple_b)
print("Highest value in tuple_b:", max(tuple_b)) # max() function on tuple
print("Lowest value in tuple_b:",min(tuple_b)) # min() function on tuple
print("Sum of elements in tuple_b:",sum(tuple_b)) # sum() function on tuple
print("\nIndex operator ([]) and Slicing operator ([:]) : ")
print("tuple_a[1]:", tuple_a[1])
print("tuple_b[len(tuple_b)-1]:", tuple_b[len(tuple_b)-1])
print("tuple_a[1:]:", tuple_a[1:])
print("\nMembership operators with tuples: ")
print("'kappa' in tuple_a: ",'kappa' in tuple_a)
print("'kappa' not in tuple_b: ",'kappa' not in tuple_b)
print("\nIterating though elements using for loop")
print("tuple_a: ", end="")
for i in tuple_a:
print(i, end=" ")
print("\ntuple_b: ", end="")
for i in tuple_b:
print(i, end=" ")
print("\n\nComparing tuples: ")
print("tuple_a == tuple_b:", tuple_a == tuple_b)
print("tuple_a != tuple_b:", tuple_a != tuple_b)
print("\nMultiplication and addition operators on tuples: ")
print("tuple * 2:", tuple_a * 2)
print("tuple_b + (10000, 20000): ", tuple_b + (10000, 20000))
输出:
tuple_a: ('alpha', 'beta', 'gamma')
Length of tuple_a: 3
tuple_b: (1, 3, 5, 7, 9, 11, 13, 15, 17, 19)
Highest value in tuple_b: 19
Lowest value in tuple_b: 1
Sum of elements in tuple_b: 100
Index operator ([]) and Slicing operator ([:]) :
tuple_a[1]: beta
tuple_b[len(tuple_b)-1]: 19
tuple_a[1:]: ('beta', 'gamma')
Membership operators with tuples:
'kappa' in tuple_a: False
'kappa' not in tuple_b: True
Iterating though elements using for loop
tuple_a: alpha beta gamma
tuple_b: 1 3 5 7 9 11 13 15 17 19
Comparing tuples:
tuple_a == tuple_b: False
tuple_a != tuple_b: True
Multiplication and addition operators on tuples:
tuple * 2: ('alpha', 'beta', 'gamma', 'alpha', 'beta', 'gamma')
tuple_b + (10000, 20000): (1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 10000, 20000)
Python 中的集合
最后更新于 2020 年 9 月 22 日
设置
集合是用于存储唯一项目集合的另一种可变序列类型。你可以把集合想象成一个列表。但是,它们在以下方面不同于列表:
- 集合中的每个元素必须是唯一的。
- 集合中的元素没有特定的存储顺序。
如果您的应用不关心元素的存储方式,请使用集合而不是列表,因为当涉及到元素的操作时,集合远比列表更有效。
创建集合
我们可以使用以下语法创建一个集合:
语法 : a_set = { item1, item2, item3 ..., itemN }
请注意,我们这里使用的是花括号({}
)。
>>>
>>> s1 = {3, 31, 12, 1} # creating a set s1
>>>
>>> type(s1)
<class 'set'> # The set class
>>>
>>> s1 # print set s1
{1, 3, 12, 31}
>>>
>>>
>>> s2 = {'one', 1, 'alpha', 3.14} # creating a set s2
>>>
>>> s2 # print set s2
{1, 3.14, 'one', 'alpha'}
>>>
我们也可以使用set()
构造函数来创建集合。以下是一些例子:
>>>
>>> s3 = set({77, 23, 91, 271})
>>> s3
{271, 91, 77, 23}
>>>
>>>
>>> s4 = set("123abc") # creating set from a string
>>> s4
{'b', '3', 'a', '1', '2', 'c'}
>>>
>>>
>>> s5 = set(['one', 'two', 'nine', 33, 13]) # creating set from a list
>>> s5
{33, 'two', 'one', 13, 'nine'}
>>>
>>>
>>> s6 = set(("alpha", "beta", "gamma")) # creating set from a tuple
>>> s6
{'beta', 'gamma', 'alpha'}
>>>
列表理解也可以用来创建集合。
>>>
>>> s7 = set([x*2+3 for x in range(0, 5)])
>>> s7
{9, 3, 11, 5, 7}
>>>
如前所述,集合只能包含唯一的值。如果您尝试创建具有重复值的集合,Python 会自动移除这些值。例如:
>>>
>>> sd1 = {1, 2, "one", 3, 1, "one"} # two duplicate elements 1 and "one"
>>> sd1
{1, 2, 3, 'one'}
>>>
>>>
>>> sd2 = set("abcdabc") # three duplicate elements a, b and c
>>> sd2
{'b', 'c', 'd', 'a'}
>>>
请注意,虽然我们在创建集合时使用了一些重复的元素,但这些值在打印集合时只会出现一次,因为集合不存储重复的元素。
获取集合的长度
使用len()
功能确定一组中的项目数量。
>>>
>>> colors = {"blue", "orange", "green"}
>>> len(colors)
3
>>>
最大(),最小(),求和()内置函数
与列表和元组一样,我们也可以将这些函数用于集合。
>>>
>>> s1 = {33,11,88,55}
>>> s1
{88, 33, 11, 55}
>>>
>>> max(s1) # get the largest element from the set
88
>>> min(s1) # get the smallest element from the set
11
>>> sum(s1) # get the sum of all the elements in a set
187
>>>
添加和删除元素
请记住,集合是可变的对象,因此我们可以从集合中添加或删除元素,而无需在过程中创建额外的集合对象。我们使用add()
和remove()
方法分别从集合中添加和移除元素。
>>>
>>> s1 = {4, 1, 9, 6}
>>> s1 # print the original set
{9, 1, 4, 6}
>>>
>>> id(s1) # address of s1
35927880
>>>
>>> s1.add(0) # add 0 to the set
>>> s1.add(10) # add 10 to the set
>>>
>>> s1 # print the modified set
{0, 1, 4, 6, 9, 10}
>>>
>>> id(s1) # as sets are mutable objects, the address of s1 remains the same
35927880
>>>
>>> s1.remove(0) # remove 0 from the set
>>> s1.remove(6) # remove 6 from the set
>>>
>>> s1 # print s1 again
{1, 4, 9, 10}
>>>
如果试图移除集合中不存在的元素,remove()
方法将抛出KeyError
异常。
>>>
>>> s1.remove(61)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 61
>>>
如果不希望出现这种情况,请使用discard()
方法。discard()
方法也将元素从集合中移除,但是如果找不到元素,它会默默忽略错误。
>>>
>>> s1.discard(61) # remove 61 from the set
>>>
我们也可以使用update()
方法一次向集合中添加多个元素。update()
方法接受可迭代类型的对象,如列表、元组、字符串等。
>>>
>>> s1 = {1, 4, 9, 10}
>>>
>>> s1.update([33, 44])
>>>
>>> s1
{1, 33, 4, 9, 10, 44}
>>>
>>> s1.update(('a', 'b'))
>>>
>>> s1
{'b', 1, 33, 4, 9, 10, 44, 'a'}
>>>
请注意,成为集合元素的是对象的单个元素,而不是对象本身。
要从集合中移除所有元素,请使用clear()
方法。
>>>
>>> s1.clear()
>>>
>>> s1
set()
>>>
循环遍历集合
就像其他序列类型一样,我们可以使用 for 循环来迭代集合的元素。
>>>
>>> a_set = {99, 33, 44, 124, 25}
>>>
>>> for i in a_set:
... print(i, end=" ")
...
25 33 99 124 44 >>>
>>>
>>>
中的成员资格运算符,而不是中的
像往常一样,我们可以使用in
和not in
运算符来查找集合中某个元素的存在。
>>>
>>> 100 in a_set
False
>>> 200 not in a_set
True
>>>
子集和超集
如果集合 A 中的所有元素也是集合 B 中的元素,则集合 A 是集合 B 的子集。例如:
A = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
B = {1, 2, 3}
a 是前 10 个自然数的集合,B 是前 3 个自然数的集合。集合 B 中的所有元素也是集合 B 中的元素。因此,B 是 A 的子集。换句话说,我们也可以说集合 A 是 B 的超集
我们可以使用issubset()
和issuperset()
方法测试一个集合是另一个集合的子集还是超集。
>>>
>>> A = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
>>> B = {1, 2, 3}
>>>
>>> A.issubset(B) # Is A subset of set B ?
False
>>>
>>> B.issubset(A) # Is B subset of set A ?
True
>>>
>>> B.issuperset(A) # Is B superset of set A ?
False
>>>
>>> A.issuperset(B) # Is A subset of set B ?
True
>>>
比较集合
我们还可以使用关系运算符来测试一个集合是另一个集合的子集还是超集。例如:
>>>
>>> A = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
>>> B = {1, 2, 3}
>>>
>>> B < A # Is B subset of A ?
True
>>>
>>> A < B # Is A subset of B ?
False
>>>
>>> B > A # Is B superset of A ?
False
>>>
>>> A > B # Is A superset of B ?
True
>>>
==
和!=
运算符可用于测试两组是否相同,即它们包含相同的元素。
>>>
>>> s1 = {1,3,2}
>>> s2 = {1,2,3}
>>>
>>> s1 == s2 # Is set s1 and s2 equal ?
True
>>> s1 != s2 # Isn't set s1 and s2 equal ?
False
>>>
>>> B >= {1,2,3} # Is B superset or equal to {1,2,3} ?
True
>>>
>>> A <= {1, 2} # Is A subset or equal to {1,2} ?
False
>>>
集合的并与交
集合的并集:假设我们有两个集合 A 和 B,那么 A 和 B 的并集就是由 A 的所有元素和 B 的所有元素组成的集合,重复的元素只包含一次。在数学中,我们用∪
符号来表示联合。象征性地,我们把 A 联合 B 写成A ∪ B
。
例如:
A = {10, 20, 30, 40}
B = {1000, 2000}
A ∪ B => {10, 20, 30, 40, 1000, 2000}
要在 Python 中执行并集操作,我们可以使用union()
方法或|
运算符。例如:
>>>
>>> n1 = {2, 3, 4, 10}
>>> n2 = {10, 2, 100, 2000}
>>>
>>> n3 = n1.union(n2)
>>> n3
{2, 3, 4, 100, 10, 2000}
>>>
>>> n4 = n1 | n2
>>> n4
{2, 3, 4, 100, 10, 2000}
>>>
集合的交集:集合 A 和集合 B 的交集是由 A 和 B 共有的所有元素组成的集合,∩
符号表示交集。象征性地,A 交点 B 写成A ∩ B
。例如:
A = {100, 200, 1, 2, 3, 4}
B = {100, 200}
A ∩ B => {100, 200}
在 Python 中进行交集运算,我们使用intersection()
方法或&
运算符。例如:
>>>
>>> s1 = {20, 40}
>>> s2 = {10, 20, 30, 40, 50}
>>>
>>> s3 = s1.intersection(s2)
>>>
>>> s3
{40, 20}
>>>
>>> s4 = s1 & s2
>>>
>>> s4
{40, 20}
>>>
集合的差和对称差
集合差:集合 A 和集合 B 的差是包含集合 A 中所有元素但不包含集合 B 中所有元素的集合,我们通常用-
符号表示差运算。象征性地,A 减 B 写成A - B
。例如:
s1 = {2, 4, 6, 8, 10}
s2 = {2, 3, 5, 8}
s1 - s2 = {4, 6, 10}
s2 - s1 = {3, 5}
在 Python 中,我们使用difference()
方法或-
运算符来执行设置差。
>>>
>>> s1 = {'a', 'e','i', 'o', 'u'}
>>> s2 = {'e', 'o'}
>>>
>>> s3 = s1.difference(s2)
>>> s3
{'i', 'u', 'a'}
>>>
>>> s4 = s1 - s2
>>> s4
{'i', 'u', 'a'}
>>>
集合的对称差:两个集合 A 和 B 的对称差是由一个集合中的元素而不是两个集合中的元素组成的集合。△
符号表示对称差。例如:
s1 = {20, 30, 40}
s2 = {30, 40, 200, 300}
s1 △ s2 = {200, 300, 20}
在 Python 中,我们使用symmetric_difference()
方法或^
运算符来执行此操作。
>>>
>>> s1 = {'a', 'e','i', 'o', 'u'}
>>> s2 = {'e', 'o'}
>>>
>>> s3 = s1 ^ s2
>>> s3
{'i', 'u', 'a'}
>>>
>>> s4 = s2 ^ s1
>>> s4
{'i', 'u', 'a'}
>>>
Python 中的字典
最后更新于 2020 年 9 月 23 日
字典是另一种内置的数据类型,它允许我们存储键值对的集合。
如图所示。字典中的每个元素都有两个部分:键和值。
把字典想象成一个列表,不像列表,然而,存储在字典中的元素没有特定的顺序,我们使用一个键来访问一个特定的值。大多数时候,键是一个字符串,但它可以是任何不可变的类型,如 int、float、tuple、string 等。每个键映射到一个值,所以我们不能有重复的键。字典是可变的对象,这意味着我们可以在创建之后添加、移除或更新元素。
创建词典
我们可以使用以下语法创建字典:
variable_name = {
'key1' : 'value1',
'key1' : 'value1',
...
'keyN' : 'valueN',
}
这里有一个例子:
>>>
>>> contacts = {
... "tom": "122-444-333",
... "jim": "412-1231-121",
... "ron": "8912-121-1212",
... "jake": "333-1881-121"
... }
>>>
>>> contacts
{'jim': '412-1231-121', 'jake': '333-1881-121', 'tom': '122-444-333', 'ron': '89
12-121-1212'}
>>>
这个语句创建了一个包含 4 个元素的字典。第一个元素中的键是"tom"
,对应的值是"122-444-333"
。其余元素的创建方式相同。虽然我在字典中缩进了每个元素,但这不是必需的。这里这么做只是为了可读性。
请注意,元素在控制台中的显示顺序与它们的创建顺序不同。这表明字典中的元素没有特定的存储顺序。
要创建空字典,请执行以下操作:
>>>
>>> empty_dict = {}
>>>
回想一下 Python 中的集合,我们也使用花括号({}
)来创建集合。当{}
创建一个空字典时,使用set()
构造函数创建一个空集合,如下所示:
>>>
>>> empty_set = set() # create an empty set
>>> type(empty_set)
<class 'set'>
>>>
我们也可以使用dict()
构造函数来创建一个字典对象,但是语法有点不同。dict()
函数要求您传递关键字参数,而不是用冒号(:
)分隔的键值对。下面是我们如何使用dict()
构造函数创建相同的contacts
字典。
>>>
>>> dict_contacts = dict(
... tom = "122-444-333",
... jim = "412-1231-121",
... ron = "8912-121-1212",
... jake = "333-1881-121"
... )
>>>
这个语句创建了完全相同的字典。有些人更喜欢dict()
函数而不是花括号{}
,因为dict()
函数不需要在按键周围加引号。这只是个人喜好的问题,你可以自由选择你想要的任何东西。在本指南中,我们将使用{}
语法来创建词典。
从字典中访问值
如前所述,字典中元素的顺序可能会有所不同。因此,我们不能使用元素的索引位置来访问该值。相反,我们使用一把钥匙。要从字典中访问值,我们使用以下语法:
dict_name[key]
>>>
>>> contacts = {
... "tom": "122-444-333",
... "jim": "412-1231-121",
... "ron": "8912-121-1212",
... "jake": "333-1881-121"
... }
>>>
>>> contacts['ron']
'8912-121-1212'
>>>
>>> contacts['tom']
'122-444-333'
>>>
如果指定的键不存在,将引发KeyError
异常。
>>>
>>> contacts['jane']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'jane'
>>>
>>>
添加和修改值
我们可以使用以下语法向字典中添加新元素:
dict_name[key] = value
这里有一个例子:
>>>
>>> contacts['jane'] = '443-123-991' # adding new element
>>>
>>> contacts
{
'jim': '412-1231-121',
'jake': '333-1881-121',
'tom': '122-444-333',
'jane': '443-123-991',
'ron': '8912-121-1212'
}
>>>
如果关键字已经存在于字典中,那么它的值将被更新。
>>>
>>> contacts['jane'] = '443-123-000' # update Jane's number
>>>
>>> contacts['jane']
'443-123-000'
>>>
>>>
删除元素
要从字典中删除一个元素,我们使用del
语句。del
语句的语法如下:
del dict_name[key]
这里有一个例子:
>>>
>>> del contacts['ron']
>>>
>>> contacts
{
'jim': '412-1231-121',
'jake': '333-1881-121',
'tom': '122-444-333',
'jane': '443-123-000'
}
>>>
如果关键字在字典中不存在,则会引发KeyError
异常。
>>>
>>> del contacts['ron']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'ron'
>>>
使用 len()获取字典长度
我们可以使用内置的len()
函数来统计字典中的元素数量。
>>>
>>> len(contacts)
4
>>>
>>> contacts
{
'jim': '412-1231-121',
'jake': '333-1881-121',
'tom': '122-444-333',
'jane': '443-123-000'
}
>>>
使用 for 循环迭代元素
我们可以使用 for 循环遍历字典中的所有键,如下所示:
>>>
>>> for key in contacts:
... print(key, end=" ")
...
jim jake tom jane >>>
>>>
一旦我们有了一个密钥,我们就可以访问它相应的值。
>>>
>>> for key in contacts:
... print(key, ":" , contacts[key])
...
jim : 412-1231-121
jake : 333-1881-121
tom : 122-444-333
jane : 443-123-000
>>>
带字典的成员运算符
in
和not in
运算符可以用来测试字典中某个键的存在。以下是一些例子:
>>>
>>> "jane" in contacts # Does key 'jane' exists in contacts ?
True
>>>
>>> "ron" in contacts # Does key 'ron' exists in contacts ?
False
>>>
>>> "tom" not in contacts # Doesn't key 'tom' exists in contacts
False
>>>
带字典的比较运算符
我们可以使用==
和!=
运算符来测试两个字典是否包含相同的元素。
>>>
>>> marks1 = { 'larry': 90, 'bill': 60}
>>> marks2 = { 'bill': 60, 'larry': 90}
>>>
>>> marks1 == marks2
True
>>>
>>> marks1 != marks2
False
>>>
剩余的比较运算符如<
、<=
、>
和>=
不能用于字典,因为字典中的元素没有特定的存储顺序。
词典方法
下表列出了我们可以对字典对象调用的一些常见方法。
方法 | 描述 |
---|---|
keys() |
返回仅包含字典中的键的序列。 |
values() |
返回仅包含字典中的值的序列。 |
items() |
返回元组序列,其中每个元组包含一个元素的键和值。 |
get(key, [default]) |
返回与key 相关的值。如果没有找到key ,则返回None 。我们还可以提供一个可选的默认值作为第二个参数,在这种情况下,如果没有找到key ,将返回默认值而不是None 。 |
pop(key) |
返回与key 相关的值,然后从字典中删除指定的key 及其对应的值。如果密钥不存在KeyError 将引发异常。 |
popitem() |
从字典中移除随机元素并将其作为元组返回。 |
copy() |
创建词典的新副本。 |
clear() |
从字典中移除所有元素。 |
keys()方法
>>>
>>> contacts.keys()
dict_keys(['jim', 'jane', 'tom', 'jake'])
>>>
>>> type(contacts.keys())
<class 'dict_keys'>
>>>
keys()
方法返回类型为dict_keys
的序列。把dict_keys
对象想象成一个不可变的列表,你可以在 for 循环中使用它,或者把它的结果传递给其他函数做进一步的处理。如果您想要一个列表或元组,只需将keys()
方法的结果传递给list()
或tuple()
构造函数,如下所示:
>>>
>>> list(contacts.keys())
['jim', 'jane', 'tom', 'jake']
>>>
>>> tuple(contacts.keys())
('jim', 'jane', 'tom', 'jake')
>>>
>>>
values()方法
>>>
>>> contacts.values()
dict_values(['412-1231-121', '443-123-000', '122-444-333', '333-1881-121'])
>>>
>>> type(contacts.values())
<class 'dict_values'>
>>>
>>> list(contacts.values())
['412-1231-121', '443-123-000', '122-444-333', '333-1881-121']
>>>
>>> tuple(contacts.values())
('412-1231-121', '443-123-000', '122-444-333', '333-1881-121')
>>>
items()方法
>>>
>>> contacts.items()
dict_items([
('jim', '412-1231-121'),
('jane', '443-123-000'),
('tom', '122-444-333'),
('jake', '333-1881-121')
])
>>>
>>>
>>> type(contacts.items())
<class 'dict_items'>
>>>
items()
方法返回一个元组序列,其中每个元组包含一个元素的键和值。我们可以使用 for 循环来循环元组,如下所示:
>>>
>>> for k, v in contacts.items():
... print(key, ":", value)
...
jim : 412-1231-121
jane : 443-123-000
tom : 122-444-333
jake : 333-1881-121
>>>
>>>
请注意,在 for 循环中,我们有两个目标变量key
和value
,因为元组有两个元素。for 循环对序列中的每个元组迭代一次。每次循环迭代时,第一个元素(它是一个键)被分配给变量k
,第二个元素(它是一个值)被分配给变量v
。这个过程一直持续到字典中有元素为止。
get()方法
>>>
>>> print(contacts.get('jake'))
333-1881-121
>>>
>>> print(contacts.get('paul'))
None
>>>
>>> print(contacts.get('paul', "Not found"))
Not found
>>>
pop()方法
>>>
>>> contacts
{
'jim': '412-1231-121',
'jane': '443-123-000',
'tom': '122-444-333',
'jake': '333-1881-121'
}
>>>
>>> contacts.pop('jim')
'412-1231-121'
>>>
>>> contacts
{
'jane': '443-123-000',
'tom': '122-444-333',
'jake': '333-1881-121'
}
>>>
>>> contacts.pop('jim')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'jim'
>>>
popitem()方法
>>>
>>> contacts.popitem()
('jane', '443-123-000')
>>>
>>> contacts
{'tom': '122-444-333', 'jake': '333-1881-121'}
>>>
copy()方法
>>>
>>> contacts
{'tom': '122-444-333', 'jake': '333-1881-121'}
>>>
>>> id(contacts) # address of contacts dictionary
4131208
>>>
>>> copy_contacts = contacts.copy() # create a copy of the contacts dictionary
>>>
>>> copy_contacts
{'jake': '333-1881-121', 'tom': '122-444-333'}
>>>
>>> id(copy_contacts) # address of copy_contacts dictionary
4131272
>>>
clear()方法
>>>
>>> contacts.clear() # delete all the elements of a dictionary
>>>
>>> contacts
{}
>>>
>>> copy_contacts # however, copy_contacts still contains a copy of the original dictionary object
{'jake': '333-1881-121', 'tom': '122-444-333'}
>>>
虽然,我们已经删除了原contacts
词典,copy_contacts
仍然包含它的副本。
Django 1.11 教程
Django 介绍
最后更新于 2020 年 7 月 27 日
Django 介绍
说到使用 Python 创建 web 应用,Django 是一个很大的名字。2003 年,阿德里安·霍洛瓦蒂和西蒙·威廉森在《劳伦斯日报-世界报》工作时创建了 Django 框架。在接下来的两年里,他们一直在开发这个框架。2005 年,他们决定将该框架作为开源软件发布,并将其命名为 Django。快进到 2018 年,Django 仍然是最受欢迎的 Python 网络框架之一。
什么是网络框架
web 框架只是一个软件包,它简化了创建 web 应用的过程。如果你从头开始建立网站,那么你可能知道大多数网络应用都有一套通用的功能(比如 CRUD、表单验证、身份验证、会话等)。
创建一个没有任何框架的 web 应用意味着每次在一个新的站点上工作时,都必须重写所有这些功能。
更好的方法是将可重用代码打包到单独的库中,然后在所有项目中使用它。如果你选择后者,你就有效地创建了一个个人网络框架。
一个 web 框架就是这样——一个被设计来协同工作的库的集合。一个库可以做简单的事情,从格式化时间到创建一个完整的管理界面。web 框架允许我们完成事情,而不需要一遍又一遍地重写相同的代码。
为什么要用 Django
以下是使用 Django 的一些原因:
BSD 许可证- Django 使用 BSD 许可证,这意味着您可以不受任何限制地以任何方式修改和使用它。
快速开发-在 Django 开发 web 应用花费的时间更少,因为该框架处理 web 开发的所有繁琐方面。Django 甚至有一个完整的管理网站,你可以用它来管理你的网络应用的内容。
不要重复自己(DRY)——这是软件开发中的一个原则,意思是不要一遍又一遍地写同样的东西。Django 坚持 DRY 原则,因此,框架内的每一个知识都必须放在一个地方。
灵活——Django 非常灵活。一旦您了解了基础知识,您就可以轻松地定制框架来满足您的特定需求。
大社区——Django 是一个成熟的框架,拥有一个优秀的社区。因此,你会发现很多优秀的第三方软件包,它们都有很棒的文档。拥有一个活跃的社区也意味着 bug 和问题会很快得到解决。
可伸缩性——Django 是可伸缩的。互联网上一些最大的名字使用 Django。例如:
- 照片墙
- 比特桶
- 浏览器名
- 国家航空与航天局
- 唱片公司
- 洋葱
增强的安全性——即使对于有经验的开发人员来说,保护 web 应用也是一项艰巨的任务。Django 是一个很好的框架,处理所有常见的安全问题,如 SQL 注入、CSRF(跨站点请求伪造)等。
先决条件
要轻松阅读本教程,您应该对 Python 有一个基本的了解。如果你的 Python 有点模糊,那么这个 python 教程将是一个很好的读物。
开始之前
这是 Django 1.11 的一个循序渐进的教程。为了充分利用本教程,请按顺序阅读章节,因为每一章都建立在前一章的基础上。
本教程中的所有示例都在 Python 3.5 上进行了测试。虽然,Django 1.11 也可以和 Python 2.7、3.4、3.6 一起使用。您可以自由使用前面提到的任何 Python 版本。但是,我不建议使用 Python 2,因为它很快就要退役了。
安装 Django
最后更新于 2020 年 7 月 27 日
安装 Django
要创建新的 Django 应用,您必须在计算机上安装以下内容:
- 计算机编程语言
- 虚拟环境
- Django
**注意:**在整个教程中,针对 Ubuntu、Mac 和 Windows 给出了说明。无论您使用哪种操作系统,大多数命令都是相同的。然而,有几个命令因系统而异。如果是这样的话,我已经明确提到了它,并提供了特定于系统的命令。
我们先从 Windows 上安装 Python 开始。
在 Windows 上安装 Python
从这里下载 Python 3.5 安装程序。一旦下载完成;双击启动安装程序,您将看到如下屏幕:
勾选“将 Python 3.5 添加到路径”选项。这允许我们从目录结构中的任何地方执行 Python,而无需指定 Python 可执行文件的绝对路径。要开始安装,请单击“立即安装”。然后,安装程序将在您的系统上安装 Python。完成后,单击“完成”按钮关闭安装程序。
在 Windows 上测试安装
要测试 Python 安装是否成功,请打开命令提示符并键入以下命令:
C:\Users\Win>python --version
Python 3.5.4
C:\Users\Win>
Python 包管理器或画中画
在 Python 中,我们使用pip
(Python 包索引)来安装和管理在https://pypi.python.org/pypi提供的不同包(或库)。需要注意的是pip
本身是一个包,用于安装其他包。Windows 的 Python 安装程序会自动安装pip
,所以你不需要做其他事情。要检查系统上安装的pip
版本,请执行以下命令。
C:\Users\Win>pip3 --version
pip 9.0.1 from c:\users\win\appdata\local\programs\python\python35\lib\site-packages (python 3.5)
在 Linux 上安装 Python
现在几乎所有的现代 Linux 发行版都安装了 Python 3.5 或更高版本。要检查您的发行版上的 Python 版本,请键入以下命令:
$ python3 --version
Python 3.5.2
如您所见,我已经安装了 Python 3.5,这意味着我已经为下一步做好了准备。如果你的系统有 Python 3.4 或 3.6,你可以很好地遵循,而不必安装 Python 3.5。请记住,Django 1.11 可以与 Python 2.7、3.4、3.5 和 3.6 一起使用。
如果由于某种原因,您的发行版没有安装 Python 或者没有任何旧版本的 Python,那么您将不得不手动安装 Python。要在像 Ubuntu 这样的基于 Debian 的系统上安装 Python 3.5,请键入以下命令:
$ sudo add-apt-repository ppa:fkrull/deadsnakes
$ sudo apt-get update
$ sudo apt-get install python3.5
如果您使用的是基于红帽的系统,如 Fedora,请键入以下内容:
$ sudo dnf install python35
与 Windows 不同,在 Linux 中,你必须手动安装pip
。无论 Python 是预装在您的发行版中,还是您手动安装的,都是如此。要在基于 Debian 的系统上安装pip
,请键入以下命令:
$ sudo apt-get install python3-pip
要在基于红帽的系统上安装pip
,请键入以下命令:
$ sudo dnf install python3-pip
在 Linux 上测试安装
如果您已经手动安装了 Python,您可以使用以下命令来测试 Python 安装。
$ python3.5 --version
Python 3.5.2
$ pip3 --version
pip 8.1.1 from /usr/lib/python3/dist-packages (python 3.5)
在苹果操作系统上安装 Python
从这里下载 Python 安装程序。下载完成后,双击启动安装程序并完成常规安装步骤。与 Windows 不同,Mac OS 的 Python 安装程序不会提示您将 Python 可执行文件添加到PATH
环境变量中,而是会自动添加。除此之外,苹果的 Python 安装程序也安装了pip
,所以你不需要做其他任何事情。
在苹果操作系统上测试安装
$ python3 --version
Python 3.5.2
$
$ pip3 --version
pip 9.0.1 from /usr/local/bin/site-packages (python 3.5)
用 Virtualenv 创建虚拟环境。
使用mkdir
命令创建名为djangobin
的新目录,如下所示:
$ mkdir djangobin
我们将使用这个文件夹来存储我们的 Django 应用。我们的应用将是一个代码共享网站,允许用户创建、管理和搜索代码片段。你可以在https://djangosnippets.org看到这样一个网站的真实例子。
你可以在任何地方创建djangobin
目录,位置并不重要。我正在使用 Ubuntu,我已经在我的主目录中创建了这个目录。完成后,使用cd
命令将当前工作目录更改为djangobin
,如下所示:
$ cd djangobin
我们现在准备创建一个虚拟环境。
一个虚拟环境帮助我们在机器上运行 Python/Django 项目的孤立实例,而不会相互冲突。要理解虚拟环境背后的理念,请考虑以下示例:
假设我们正在为两个不同的客户开发两个项目,一个博客和一个论坛。我们的博客使用版本 2 的super_lib
库,而我们的论坛使用版本 1。在给定的时间点,我们的系统上只能安装一个版本的super_lib
。我们不能同时拥有两个版本。虚拟环境帮助我们解决这些问题。
将虚拟环境视为单独的 Python 安装。安装后,您使用pip
在那里安装的任何库都不会与系统范围的 Python 安装中可用的库冲突。
创建这些隔离环境所需的包称为virtualenv
。
要在 Ubuntu/Mac OS 上安装virtualenv
,请使用pip3
而不是pip
。
$ pip3 install virtualenv
要在 Windows 上安装virtualenv
,打开命令提示符,键入以下命令。
$ pip install virtualenv
要创建虚拟环境,请键入virtualenv
命令,后跟虚拟环境的名称。例如:
$ virtualenv env
该命令将在当前工作目录
( djangobin
)内创建一个名为env
的目录。env
的目录结构应该是这样的:
env/
├── bin/
├── include/
├── lib/
└── pip-selfcheck.json
env
目录包含一个单独的 Python 安装。您在此安装的任何库或包都将放在env/lib/python3.5/site-packages
目录中,而不是全局site-packages
目录中。您可以使用以下命令打印全局site-packages
的路径:
$ python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
默认情况下,virtualenv
使用安装它的 Python 版本创建虚拟环境。换句话说,如果您在 Python 3.5 下将virtualenv
作为一个包安装,那么这就是 Python 的版本,它将在虚拟环境中可用。
要指定 Python 的任何其他版本,请使用如下的-p
选项:
$ virtualenv env -p /usr/bin/python2.7
这将使用 Python 2.7 而不是 3.5 创建一个虚拟环境。
激活虚拟环境
在上一步中,我们已经创建了我们的虚拟环境。现在要使用它,我们首先要激活它。要在 Linux 或 Mac 操作系统中激活虚拟环境,请键入以下命令:
$ source env/bin/activate
(env) $
Windows 用户可以使用以下命令:
C:\Users\Win>env\Scripts\activate.bat
(end) C:\Users\Win>
注意提示字符串前面的(env)
,表示名为env
的虚拟环境已经启动运行。从这一点开始,您使用pip
添加或删除的任何包都只会影响该虚拟环境。您的系统级 Python 安装将保持不变。我们可以使用pip list
命令查看这个虚拟环境中安装的软件包。
(env) $ pip list
Package Version
---------- -------
pip 10.0.1
setuptools 39.2.0
wheel 0.31.1
(env) $:
这个虚拟环境安装了 3 个软件包。需要注意的是,一旦虚拟环境处于活动状态,您可以使用pip
或pip3
调用 pip。Window、Ubuntu 以及 Mac OS 都是如此。
停用虚拟环境
要停用虚拟环境,请键入以下命令:
(env) $ deactivate
这个命令对于 Windows、Ubuntu 和 Mac 都是一样的。现在我们脱离了虚拟环境。再次运行pip3 list
命令,但这一次它会显示您系统上安装的所有系统范围的软件包。
$ pip3 list
apt-clone (0.2.1)
apt-xapian-index (0.47)
apturl (0.5.2)
blinker (1.3)
Brlapi (0.6.4)
chardet (2.3.0)
command-not-found (0.3)
cryptography (1.2.3)
decorator (4.2.1)
defer (1.0.6)
dirspec (13.10)
httplib2 (0.9.1)
idna (2.0)
...
**注意:**省略号(...
)表示代码段被截断以节省空间。
在 Windows 上,可以用pip list
代替pip3 list
查看系统范围的包。
C:\Users\Win>pip list
certifi (2017.4.17)
chardet (3.0.4)
colorama (0.3.9)
decorator (4.0.11)
httpie (0.9.9)
idna (2.5)
ipython (6.1.0)
ipython-genutils (0.2.0)
...
所以这些包在我的系统级 Python 安装中是可用的。你的可能会不一样。
下一步,我们将安装 Django。
安装 Django
在本教程中,我们将使用 Django 1.11。要安装 Django,激活虚拟环境,然后键入以下命令。
$ pip3 install django==1.11
测试安装
在虚拟环境中,输入python3
启动 Python shell,如果你在 Windows 上,只需输入python
即可。
(env) $ python3
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
验证安装是否成功,导入django
包,调用get_version()
函数如下。
>>>
>>> import django
>>>
>>> django.get_version()
'1.11'
>>>
如果一切顺利,您应该安装 Django 的版本。如果您遇到任何错误,请完成以上所有步骤,或者在下面的框中发表评论,我们会找出问题所在。
要退出 Python shell,请在 Linux/Mac 中键入Ctrl+D
或在 Windows 中键入Ctrl+Z
或只需键入quit()
。
创建 Django 项目
最后更新于 2020 年 7 月 27 日
创建项目
要创建新的 Django 项目,请确保虚拟环境处于活动状态,并且您当前的工作目录设置为djangobin
,然后发出以下命令:
$ django-admin startproject django_project
该命令将在djangobin
目录内创建一个名为django_project
的新目录。django_project
目录也称为 Django 项目根目录或简称项目根目录。它的目录结构应该如下所示:
django_project/
├── django_project
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
1 directory, 5 files
在顶层,我们新创建的django_project
目录包含以下两项。
- 与项目根目录同名的目录,即
django_project
。 - 一个叫做
manage.py
的 Python 脚本。
目录django_project/django_project
被称为项目配置目录。在其中,您会发现以下四个 python 文件:
文件名 | 描述 |
---|---|
__init__.py |
一个特殊的空文件,告诉 Python 当前目录应该被视为 Python 包。 |
settings.py |
这个文件包含了 Django 项目的所有设置和配置。 |
urls.py |
一个 Python 脚本,用于存储 Django 项目的 URL 模式。 |
wsgi.py |
运行开发服务器并将项目部署到生产环境的 Python 脚本。 |
manage.py
-一个命令行工具,用于与您的 Django 项目进行交互或维护。我们可以通过简单地执行它来查看该文件提供的各种参数(或子命令)。
$ ./django_project/manage.py
Type 'manage.py help <subcommand>' for help on a specific subcommand.
Available subcommands:
[auth]
changepassword
createsuperuser
[contenttypes]
remove_stale_contenttypes
[django]
check
compilemessages
createcachetable
...
startapp
startproject
test
testserver
[sessions]
clearsessions
[staticfiles]
collectstatic
findstatic
runserver
如果您在 Windows 上,请始终在 Python 文件前面加上python
命令:
C:\Users\overiq\djangobin> python django_project/manage.py
最基本的子命令之一是runserver
(列表中最后一个),用于运行 Django 开发 web 服务器。让我们看看它是如何完成的。
首先使用cd
命令将当前工作目录更改为manage.py
所在的目录,然后执行runserver
子命令,如下所示:
$ cd django_project
$ ./manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
March 19, 2018 - 08:23:19
Django version 1.11, using settings 'django_project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
这里有几点需要注意。第一行:
System check identified no issues (0 silenced).
表示在您的 Django 项目中没有发现错误。如果有错误,那么./manage.py runserver
将无法启动服务器。这里需要注意的第二点如下:
You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
这条线告诉我们项目中有一些未应用的迁移。在 Django,我们使用迁移来改变数据库的状态。不要太担心什么是迁移,我们将在后面的章节中深入讨论。
第三点也是目前最重要的一点是 Django 开发服务器的地址,即http://127.0.0.1:8000/
。打开你喜欢的浏览器,访问http://127.0.0.1:8000/
。您应该会看到这样的页面:
停止开发服务器点击CTRL+C
。
默认情况下manage.py
总是在端口 8000 启动开发服务器,但是您可以使用以下命令将其更改为其他端口。
$ ./manage.py runserver <port>
例如./manage.py runserver 3000
将在 3000 号港口启动 Django 开发。如果您想打开特定 IP 地址的端口,可以使用以下命令轻松实现:
./manage.py runserver <ip-address>:<port>
例如,要在端口 4444 上的 localhost 运行 Django 开发服务器,请发出以下命令。
$ ./manage.py runserver 127.0.0.1:4444
每次修改 Python 代码后,Django 开发服务器都会自动重启。因此,您不需要每次都手动重新启动服务器来使更改生效。但是,有些操作,如添加文件,不会触发重启。在这种情况下,您必须手动重新启动服务器。
设置数据库
Django 可以与几乎所有流行的数据库一起使用,如 MySQL、Oracle、PostgreSQL(这是 Django 开发人员的首选)、SQLite 等。由于这是一个初学者教程,我们将使用 SQLite 数据库。但是为什么是 SQLite 呢?因为安装和配置 SQLite 数据库不需要额外的步骤。SQLite 已经与 Python 捆绑在一起,因此您不必配置任何东西来使用它。
第一次执行runserver
命令时,Django 会在项目根目录(即djangobin/django_project
)中自动创建一个名为db.sqlite3
的 SQLite 数据库。我们已经执行了一次runserver
命令,所以此时,您的项目根目录(djangobin/django_project
)中应该有一个名为db.sqlite3
的 SQLite 数据库文件。
django_project/
├── db.sqlite3 <-- SQLite database
├── django_project
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
如果我们没有使用 SQLite 数据库,那么我们可以很容易地在位于项目配置目录(djangobin/django_project/django_project
)中的settings.py
文件中指定数据库配置。如前所述,settings.py
文件包含与我们的 Django 项目相关的所有设置。
打开settings.py
文件,滚动到一半,直到找到
DATABASES
设置。DATABASES
变量包含所有数据库特定的设置。DATABASES
变量的默认值应该是这样的:
djangobin/django _ project/django _ project/settings . py
#...
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
#...
ENGINE
指定 Django 正在使用的数据库后端的类型。在我们的例子中,我们使用的是 SQLite,这就是为什么它被设置为django.db.backends.sqlite3
。以下是一些其他后端可供选择:
数据库ˌ资料库 | 发动机 |
---|---|
关系型数据库 | django.db.backends.mysql |
神谕 | django.db.backends.oracle |
一种数据库系统 | django.db.backends.postgresql |
NAME
指定我们正在使用的数据库的名称。由于 SQLite 数据库由单个文件组成,所以它当前指向db.sqlite3
文件的绝对路径。这就是我们使用 SQLite 数据库所需要的。
要使用像 MySQL 或 PostgreSQL 这样的数据库,我们需要添加一些额外的参数,如USER
、PASSWORD
、HOST
和PORT
。例如,下面是如何配置 Django 来使用 MySQL 数据库。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'database_name',
'USER': 'username',
'PASSWORD': 'password',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
Django 应用
在 Django,项目和应用意味着不同的东西。根据 Django 术语,Django 项目是配置和应用的集合,这些配置和应用共同组成了整个网络应用。换句话说,一个项目是一个完整的网络应用,而一个应用只是一个特性。例如,应用可以是博客、评论系统、论坛、聊天室,甚至是联系人表单。所有这些小应用和配置共同构成了一个 Django 项目。
Django 内置应用
Django 已经捆绑了几个内置应用。要查看这些内置应用,请查看settings.py
文件顶部的INSTALLED_APPS
设置。
djangobin/django _ project/django _ project/settings . py
#...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
#...
如您所见,默认安装了 6 个应用,它们都随 Django 一起提供。下面是每个应用的概要。
django.contrib.admin
-一个管理站点。django.contrib.auth
——用户管理和认证的框架。django.contrib.contenttypes
——内容类型的框架。django.contrib.sessions
-一个会话框架。django.contrib.messages
-一个消息传递框架。django.contrib.staticfiles
–管理静态文件的框架。
其中一些应用需要数据库表,而另一些则不需要。回想一下,当我们试图运行 Django 开发服务器时,我们得到了以下警告。
You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them
此警告告诉我们“未应用的迁移”。未应用的迁移意味着在所有应用成功运行之前,需要提交一些更改。迁移只是普通的 Python 文件,它描述了我们想要提交给数据库的更改。这些更改可能像重命名列一样微不足道,也可能像创建或更改数据库模式一样复杂。在任何时候,我们都可以使用manage.py
的showmigrations
子命令来获取已应用和未应用迁移的列表。
$ ./manage.py showmigrations
admin
[ ] 0001_initial
[ ] 0002_logentry_remove_auto_add
auth
[ ] 0001_initial
[ ] 0002_alter_permission_name_max_length
[ ] 0003_alter_user_email_max_length
[ ] 0004_alter_user_username_opts
[ ] 0005_alter_user_last_login_null
[ ] 0006_require_contenttypes_0002
[ ] 0007_alter_validators_add_error_messages
[ ] 0008_alter_user_username_max_length
contenttypes
[ ] 0001_initial
[ ] 0002_remove_content_type_name
sessions
[ ] 0001_initial
前面的输出显示了所有已安装应用下的未应用迁移文件列表(不带.py
扩展名)。我们知道这些迁移未被应用,因为迁移名称前面的方括号([]
)未被选中。如果它们被应用,我们会在迁移名称前面看到[x]
。从输出中我们还可以推断出django.contrib.admin
有 2 个未应用的迁移,django.contrib.auth
有 8 个,django.contrib.contenttypes
有 2 个,django.contrib.sessions
有 1 个未应用的迁移。为了应用这些迁移,我们使用migrate
子命令。
在终端或命令提示符下,输入以下命令以应用迁移。
$ ./manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying sessions.0001_initial... OK
再次运行./manage.py showmigrations
命令。这次您将在每个迁移文件前面看到[x]
,因为我们刚刚应用了挂起的迁移。
$ ./manage.py showmigrations
admin
[X] 0001_initial
[X] 0002_logentry_remove_auto_add
auth
[X] 0001_initial
[X] 0002_alter_permission_name_max_length
[X] 0003_alter_user_email_max_length
[X] 0004_alter_user_username_opts
[X] 0005_alter_user_last_login_null
[X] 0006_require_contenttypes_0002
[X] 0007_alter_validators_add_error_messages
[X] 0008_alter_user_username_max_length
contenttypes
[X] 0001_initial
[X] 0002_remove_content_type_name
sessions
[X] 0001_initial
$
在这一点上,我们并不期望您完全理解迁移。我们将在第课“T2 Django 的移民”中详细了解他们。现在,请记住这一点——在 Django,我们使用迁移来创建/更改/删除数据库中的表。我们将在下一节检查由migrate
子命令创建的表。
使用runserver
子命令再次运行开发服务器。这一次,您将看不到任何关于未应用迁移的警告。
$ ./manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
March 19, 2018 - 10:01:48
Django version 1.11, using settings 'django_project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[19/Mar/2018 10:01:56] "GET / HTTP/1.1" 200 1716
没有必要使用 Django 提供的所有应用。如果您不想使用任何特定的应用,只需将其从列表中删除即可。假设由于某种原因您不想使用django.contrib.staticfiles
应用,在移除django.contrib.staticfiles
后,INSTALLED_APPS
列表将如下所示:
djangobin/django _ project/django _ project/settings . py
#...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
]
#...
但是现在我们什么都需要,所以把django.contrib.staticfiles
加回INSTALLED_APPS
列表。最后,INSTALLED_APPS
的设定应该是这样的:
djangobin/django _ project/django _ project/settings . py
#...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
#...
查看数据库
在本教程中,我们将使用名为 Navicat 的图形数据库管理程序不时查看我们的 SQLite 数据库的当前状态。像 Navicat 这样的程序使查看表格、以图形方式创建/更改记录变得非常容易,而无需编写一行 SQL。
但是,如果你是一个 SQL 专业人员,并且熟记 SQL 命令,你可以自由地从命令提示符或终端打开db.sqlite3
文件,并从那里继续。
Navicat 不是免费软件,但是他们提供 30 天的试用期。还有许多其他的免费软件 Sqlite Browser,HeidiSQL,Sqlectron,Valentina Studio,DBeaver 等等,它们可以让你做或多或少相同的事情。
以下是使用 Navicat 打开 SQLite 数据库的说明,其他程序的说明大致相同。
启动 Navicat 高级版,然后转到连接> SQLite。
在新建连接窗口中,指定连接名称,选择“现有数据库文件”,浏览db.sqlite3
文件所在位置。跳过密码字段,单击窗口底部的“测试连接”按钮。如果连接成功,请关闭对话框并单击“确定”按钮保存连接。如果遇到任何错误,请检查 SQLite 数据库文件的位置,然后重试。
要查看表格,请单击左侧窗格中的连接名称,然后双击“main”。您应该会在窗口的右侧看到一个表格列表,如下所示:
如您所见,目前 SQLite 数据库中有 11 个表。
正在创建 Django 应用
要创建一个新的应用,首先要确保你当前的工作目录与manage.py
文件所在的目录相同。之后,执行以下命令。
$ ./manage.py startapp djangobin
该命令将在项目根目录(djangobin/django_project
)内创建一个名为djangobin
的新目录。我们新创建的目录的目录结构如下:
djangobin/
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
1 directory, 7 files
以下是每个文件和文件夹的摘要:
文件名 | 描述 |
---|---|
admin.py |
该文件包含将 djangobin 应用连接到 Django 提供的管理应用所需的所有配置。我们将在第课 Django 管理应用中详细学习如何操作。 |
apps.py |
该文件包含特定于应用的配置。 |
__init__.py |
__init__.py 文件只是 Python 把这个(djangobin )目录当作一个包的说法。 |
migrations |
这个目录将存储我们所有的迁移文件。换句话说,它会将我们所做的所有更改存储在数据库中。 |
migrations/__init__.py |
__init__.py 文件只是 Python 的说法,把这个(migrations )目录当成一个包。 |
models.py |
这个文件包含我们的应用模型。换句话说,这是我们定义表和它们之间关系的地方。 |
test.py |
这个文件包含对我们的应用进行单元测试的功能。 |
views.py |
该文件包含处理请求和返回响应的视图函数。 |
我们已经创建了一个新的应用djangobin
,现在我们必须通知我们的 Django 项目它的存在。为此,打开位于项目配置目录(djangobin/django_project/django_project
)中的settings.py
文件,并在INSTALLED_APPS
列表的末尾追加'djangobin',
,如下所示:
djangobin/django _ project/django _ project/settings . py
#...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'djangobin',
]
#...
为了验证 Django 是否获得了我们新的djangobin
应用,请重新启动服务器。如果您可以成功启动服务器而没有任何错误,那么这意味着您已经为下一步做好了准备。
MVC 模式与 Django
最后更新于 2020 年 7 月 27 日
MVC 模式
在 MVC 框架出现之前,web 编程将数据库代码与页面的服务器端代码混合在一起。如果你已经用像 PHP 这样的语言编程了一段时间,你已经在某种程度上做到了。这个问题并不是 PHP 特有的;事实上,在大多数服务器端语言中,代码至少在三种语言之间共享,例如 Python(或 PHP)、SQL、HTML。
创建 MVC 模式是为了将业务逻辑与表示分离开来。MVC 是当今最流行的架构。许多流行的框架,如 Ruby on Rails、Laravel、CodeIgniter 甚至 Django 都使用它。MVC 架构将应用分为以下三层:
- 模特。
- 查看。
- 控制器。
让我们分别讨论它们。
**模型:**模型表示数据在数据库中的组织方式。换句话说,在 MVC 模式中,我们使用模型来定义我们的数据库表以及它们之间的关系。
**视图:**视图是当你访问一个站点时看到的内容。例如,一篇博客文章,一个联系表等等,都是视图的例子。视图包含最终将发送给客户端的所有信息,即网络浏览器。通常,视图是 HTML 文档。
**控制器:**控制器控制信息流。当您请求一个页面时,该请求被传递给控制器,然后它使用编程逻辑来决定需要从数据库中提取什么信息以及应该将什么信息传递给视图。控制器是 MVC 架构的核心,因为它充当了模型和视图之间的粘合剂。
下面是一个 MVC 博客应用中涉及的步骤的概要。
- 网络浏览器或客户端将请求发送到网络服务器,要求服务器显示博客文章。
- 服务器接收的请求被传递给应用的控制器。
- 控制器要求模型获取博客文章。
- 模型将博客文章发送给控制器。
- 然后,控制器将博客文章数据传递给视图。
- 该视图使用博客文章数据来创建一个 HTML 页面。
- 最后,控制器将 HTML 内容返回给客户端。
MVC 模式不仅帮助我们创建和维护一个复杂的应用。当涉及到关注点的分离时,它真的会发光。例如,在一个网络开发公司中,有网络设计师,也有开发人员。网页设计师的工作是创建视图。开发人员采用这些视图,并将它们与模型和控制器结合在一起。
决哥 MTV
Django 非常接近 MVC 模式,但是它使用的术语略有不同。Django 本质上是一个 MTV(模型-模板-视图)框架。Django 使用术语“视图模板”和“控制器视图”。换句话说,在 Django 中,视图被称为模板,控制器被称为视图。因此,我们的 HTML 代码将在模板中,Python 代码将在视图和模型中。
Django 的视图和 URL 配置
原文:https://overiq.com/django-1-11/views-and-urlconfs-in-django/
最后更新于 2020 年 7 月 27 日
在前面的章节中,我们已经学习了如何设置 Django 项目和运行开发服务器。在本章中,我们将学习在 Django 中创建动态网页的基础知识。
创建第一个视图
让我们从简单开始。在本节中,我们将创建一个输出“你好,Django”的网页。为此,请在您最喜欢的文本编辑器中打开位于 djangobin 应用(即djangobin/django_project/djangobin
)中的views.py
。此时,views.py
应该是这样的:
djangobin/django_project/djangobin/views.py
from django.shortcuts import render
# Create your views here.
删除所有内容并输入以下代码。
djangobin/django_project/djangobin/views.py
from django.shortcuts import HttpResponse
def index(request):
return HttpResponse("<p>Hello Django</p>")
我们刚刚创建了一个简单的视图函数。
那么什么是视图函数呢?
一种功能,其工作是接受请求并返回正确的响应。
让我们一次添加一行代码。
- 在第 1 行,我们正在从
django.shortcuts
模块导入HttpResponse
类。 - 在第 4-5 行,我们定义了
index()
函数,该函数返回一个HttpResponse
对象的实例。
每个视图函数都有一个名为request
的参数,这是一个类型为HttpRequest
的对象。HttpRequest
对象包含触发此视图的当前 web 请求的信息。
视图函数必须返回一个HttpResponse
对象。要创建HttpResponse
对象,只需将表示页面内容的字符串传递给构造函数。
现在我们已经创建了一个简单的视图。要调用这个视图,您必须在 URL 配置中创建一个 URL 模式,简称为 URLconf。你可以把 URLconf 看作是 Django 支持的网络应用的目录。换句话说,URLconf 是 URL 和应该为这些 URL 调用的视图函数之间的映射。这是 Django 的说法,这个 URL 调用这个视图函数,那个 URL 调用那个视图函数,等等。
我们使用url()
函数创建网址模式。它接受两个参数,一个匹配网址的正则表达式和为该网址调用的视图函数的名称。
让我们创建一个网址模式。打开位于 Django 项目配置目录中的urls.py
文件(即djangobin/django_project/django_project
)。这个文件也被称为全网站urls.py
。urls.py
文件的内容应该是这样的。
djangobin/django _ project/django _ project/URLs . py
from django.conf.urls import url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
]
**注意:**为了节省空间,从上面的代码片段中删除了注释。
要将index()
视图函数绑定到 URL 模式,我们需要做两件事:
在导入列表的末尾添加
from djangobin import views
。通过在
urlpatterns
列表的开头添加以下一行来创建新的 URL 模式。url(r'^$', views.index),
站点范围内urls.py
文件的内容现在应该如下所示:
djangobin/django _ project/django _ project/URLs . py
from django.conf.urls import url
from django.contrib import admin
from djangobin import views
urlpatterns = [
url(r'^$', views.index),
url(r'^admin/', admin.site.urls),
]
如果尚未运行,使用./manage.py runserver
命令启动 Django 开发服务器,并访问http://127.0.0.1:8000/
。您应该会看到这样的页面:
传递给url()
函数的第一个参数是正则表达式字符串,第二个参数是视图函数的名称。正则表达式r'^$'
什么都不匹配。换句话说,r'^$'
指的是网站的根。如果我们的域是http://example.com/
,那么对http://example.com/
的请求将调用index()
查看功能。当用户请求http://example.com/
时,Django 使用urlpatterns
列表来决定调用哪个方法。当它找到匹配的网址模式时,它会调用与该模式相关联的视图函数。如果没有找到匹配的模式,将返回一个 HTTP 404 错误。
别忘了 Django 所说的视图实际上是一个控制器。
url()
函数接受许多其他可选参数,其中一个参数是name
关键字参数。name
关键字参数允许我们为 URL 模式指定一个唯一的名称。那么我们为什么要给我们的网址模式命名呢?定义名称允许我们在模板和视图中自动创建网址。我们将在第课中看到如何在 Django创建网址。现在,让我们给我们新创建的网址模式命名。
djangobin/django _ project/django _ project/URLs . py
from django.conf.urls import url
from django.contrib import admin
from djangobin import views
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^admin/', admin.site.urls),
]
Django 404 错误
我们的 URLconf 目前只包含两种 URL 模式——一种是由 Django 提供的,另一种是我们自己编写的。那么如果你请求一个不同的网址会发生什么呢?打开浏览器,尝试访问http://127.0.0.1:8000/django/
。
如果请求的网址与 URLconf 中任何现有的网址模式都不匹配,那么 Django 将抛出一个 404 错误。由于请求的网址没有在 URLconf 中定义,Django 将抛出一个 HTTP 404 未找到错误。以下是它的外观:
关于这个页面需要注意的重要一点是,它给出的信息比要求的要多。请注意,它确切地告诉了 Django 在抛出 HTTP 404 错误之前尝试了哪些 URL 模式。当然,这是敏感信息,应该只向参与开发网络应用的人披露。
那么,如果这个信息是敏感的,那么为什么 Django 首先要透露这一切呢?
因为默认情况下 Django 项目安装时将DEBUG
模式设置为True
。要查看此设置,请在位于djangobin/django_project/django_project
的 Django 项目配置目录中打开settings.py
。
djangobin/django _ project/django _ project/settings . py
#...
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '#=5=zv^cmqse-d=@#qp8f1bbto=235pz=we723*rt9is_$&hu)'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
#...
要关闭调试模式,请将DEBUG
的值设置为False
。当调试模式关闭时,Django 输出一个没有任何敏感信息的通用 HTTP 404 响应。由于我们目前处于开发阶段,请将DEBUG
设置为True
。
以正确的方式映射网址
目前,我们的 Django 项目中只有一个名为 djangobin 的应用。一般来说,一个项目至少由 3-4 个应用组成。如果我们继续为整个网站的urls.py
文件中的每个应用编写 URLconf,很快就会变得一团糟。因此,我们可以通过为每个应用创建“T2”来使我们的应用模块化,而不是直接将网址映射到整个站点urls.py
,这样我们就可以更有效、更容易地管理网址。为此,首先在 djangobin 应用中创建urls.py
文件,并向其中添加以下代码。
决哥/决哥 _ 项目/决哥/URL . py】
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
]
在第 1 行和第 2 行,我们正在导入必要的功能和模块。在第 4 行,我们正在为 djangobin 应用创建 URLconf。
下一步是将 djangobin 应用的 URLconf 通知给 Django 项目。为此,按如下方式修改站点范围内的urls.py
:
djangobin/django _ project/django _ project/URLs . py
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^index/', include('djangobin.urls')),
url(r'^admin/', admin.site.urls),
]
include()
功能告诉整个站点的urls.py
文件在 djangobin 应用中存在urls.py
。这里需要注意的重要一点是,在正则表达式中r'^index/'
没有尾随的$
字符,而是有一个尾随的斜线/
。这样做是因为每当 Django 遇到include()
函数时,它都会砍掉与该点匹配的网址部分,并将网址的剩余部分发送到包含的 URLconf 进行进一步处理。
但是,我们想在根网址http://127.0.0.1:8000/
而不是http://127.0.0.1:8000/index/
显示hello world
,为此,请将正则表达式r'^index/'
替换为r''
。
djangobin/django _ project/django _ project/URLs . py
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'', include('djangobin.urls')),
url(r'^admin/', admin.site.urls),
]
打开浏览器,导航至http://127.0.0.1:8000/
。Django 将以"Hello Django"
回应迎接您。
深入挖掘网址
让我们看看当你请求一个页面时会发生什么。
当你请求一个页面时,Django 做的第一件事就是删除 URL 的主机部分,例如,在 URL
https://overiq.com/contact/
中,主机部分是overiq.com
。然后,Django 从 Django 项目的
settings.py
文件中读取变量ROOT_URLCONF
的值。那么ROOT_URLCONF
有什么作用呢?
ROOT_URLCONF
包含必须首先加载的 URLconf。它也被称为根 URLconf 或站点范围 URLconf。在我们的例子中,它指向位于djangobin/django_project/django_project
目录中的urls.py
。
djangobin/django _ project/django _ project/settings . py
#...
ROOT_URLCONF = 'django_project.urls'
#...
读取
ROOT_URLCONF
后,Django 用请求的 URL 逐一检查根 URLconf 中的每个 URL 模式,直到找到匹配。如果没有找到模式,将返回 404 错误。另一方面,如果找到一个模式,那么 Django 调用相关的视图函数。如果
url()
函数的第二个参数包含对include()
函数的调用,那么 Django 会将网址中与该点匹配的部分去掉,并将网址的剩余部分发送到包含的 URLconf 进行进一步处理。
这里有一个例子:
假设请求的 URL 是http://example.com/time/
,Django 做的第一件事就是移除 URL 的主机部分。剥离宿主部分后http://example.com/time/
变成/time/
。然后对字符串/time/
逐个检查每个网址模式,直到找到匹配。如果找到匹配,则调用相应的视图函数。否则,将返回一个 HTTP 404 错误。
输出动态数据
我们在上面部分创建的视图函数非常简单,尽管如此,它还是向您介绍了一些基本概念,如视图函数、URL 模式以及 Django 如何在幕后处理 URL。在我们的下一个视图中,我们将输出一些动态内容。让我们创建一个简单的网页来输出当前的日期和时间。如果你做过一些 Python 编程,那么你可能已经知道 Python 有datetime
模块来处理日期和时间。以下是如何使用它的快速复习:
>>>
>>> import datetime
>>>
>>> current_datetime = datetime.datetime.now()
>>>
>>> current_datetime
datetime.datetime(2017, 1, 24, 13, 59, 42, 135163)
>>>
>>> print(current_datetime)
2017-01-24 13:59:42.135163
>>>
需要注意的是,上面的代码片段是纯 Python,与 Django 无关。要返回当前日期和时间,我们首先必须创建一个新视图。在 djangobin app 中打开views.py
,在index()
视图功能下方新增一个名为today_is()
的视图功能,如下所示:
djangobin/django_project/djangobin/views.py
from django.http import HttpResponse
import datetime
def index(request):
return HttpResponse("<p>Hello Django</p>")
def today_is(request):
now = datetime.datetime.now()
html = "<html><body>Current date and time: {0}</body></html>".format(now)
return HttpResponse(html)
让我们浏览一下在views.py
文件中所做的更改:
在第 2 行,我们添加了一个导入语句来导入
datetime
模块,这样我们就可以计算当前的日期和时间。在第 9-12 行,我们定义了
today_is()
函数。在第 10 行,我们通过调用now()
方法计算当前日期和时间,并将结果分配给now
变量。在第 11 行,我们使用字符串对象的format()
方法创建一个 HTML 响应。字符串中的{0}
只是当前日期和时间的占位符,将被变量now
的值替换。需要注意的是,变量now
代表的是一个datetime.datetime
对象,而不是一个常规的字符串,但是当now
的值代替{0}
打印在字符串内部时,datatime.datetime
对象的__str__()
方法将datatime.datetime
对象转换为字符串。最后,视图返回一个包含生成响应的HttpResponse()
对象。
有了视图功能,让我们创建一个新的 URL 模式来调用这个视图。打开 djangobin app 的urls.py
并添加以下 URL 模式调用today_is()
查看功能如下:
决哥/决哥 _ 项目/决哥/URL . py】
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^time/$', views.today_is, name='time'),
url(r'^$', views.index, name='index'),
]
我们添加了一个新的网址模式,将/time/
网址映射到today_is()
视图功能。你现在可能已经掌握了窍门。如果尚未运行,请启动服务器并访问http://127.0.0.1:8000/time/
。如果一切顺利,Django 会告诉你现在的日期和时间。
创建动态网址
动态网址是包含一个或多个影响网页输出的可变部分的网址。到目前为止,我们只创建了静态网址。
让我们创建另一个视图来显示用户配置文件。在views.py
文件中添加profile()
查看功能如下:
djangobin/django_project/djangobin/views.py
#...
def today_is(request):
#...
def profile(request):
return HttpResponse("<p>Profile page of user</p>")
如果用户访问网址路径/user/<username>/
,我们希望显示用户配置文件,其中<username>
是分配给每个用户的唯一字母数字标识符。
匹配字母数字字符的正则表达式是^[A-Za-z1-3]+$
。因此,网址模式^/user/[A-Za-z0-9]+/$
将匹配像/user/cs101/
、/user/james/
、/user/100ninja/
、/user/1000
等网址路径。但是不会匹配/user/string#$$$/
、/user/high five/
、/user/_10days/
等 URL 路径。
我们的网址模式^/user/[A-Za-z0-9]+/$
工作得很好,但是有一个问题。要显示用户配置文件,我们需要在查看功能的 URL 中访问用户名。有两种方法可以解决这个问题。第一种方法包含一点小技巧,第二种方法相当简单。
回想一下,视图函数的request
参数包含触发视图的当前 web 请求的所有信息。request
对象有一个名为path
的属性,它返回主机名后的网址部分。例如,如果用户请求http://127.0.0.1:8000/user/foo/
,那么request.path
将返回/user/foo/
。现在,你只需要去掉/user/
和尾部斜线,你就完成了。
第二种解决方案很简单,因此推荐使用。为了将 URL 参数传递给视图函数,我们使用了一种叫做正则表达式组或简称为组的东西。我们使用以下语法创建命名组:
(?P<name>pattern)
name
是 Python 标识符,用来指代匹配的子串,pattern
是实际的正则表达式。Django 在幕后所做的是,它接受命名组,并将其值作为关键字参数传递给视图函数。以下是我们应用命名组后的完整网址模式:
url(r'^user/(?P<username>[A-Za-z0-9]+)/$', views.profile, name='profile')
将此网址模式添加到urlpatterns
列表的末尾,如下所示:
决哥/决哥 _ 项目/决哥/URL . py】
#...
urlpatterns = [
url(r'^time/$', views.today_is, name='todays_time'),
url(r'^$', views.index, name='djangobin_index'),
url(r'^user/(?P<username>[A-Za-z0-9]+)/$', views.profile, name='profile'),
]
接下来,更新profile()
视图函数以接受名为username
的附加参数,如下所示:
djangobin/django_project/djangobin/views.py
#...
def profile(request, username):
return HttpResponse("<p>Profile page of #{}</p>".format(username))
从现在开始,像http://127.0.0.1:8000/user/foo/
这样的 URL 请求将调用profile()
视图函数,如下所示:
profile(request, username='foo')
访问http://localhost:8000/user/dragoon/
会出现如下用户档案页面:
需要注意的是,无论用于执行匹配的正则表达式是什么,由命名组匹配的子字符串总是作为字符串传递给视图函数。例如:
url(r'^user/(?P<user_id>[0-9]+)/$', views.profile),
在这个网址模式中,命名组只匹配整数。换句话说,网址模式将匹配类似/user/100/
、/user/9999/
、/user/1234567/
等网址路径。但是profile()
视图函数仍将使用字符串值调用:
profile(request, user_id='1000')
最后,这里有一些网址模式和它们匹配的网址:
网址模式 | 描述 | 示例网址 |
---|---|---|
url(r'^post/\d+/$', viewname) |
正则表达式\d+ 匹配一个或多个数字 |
该网址模式匹配类似/post/1/ 、/post/123/ 、/post/9999/ 等字符串。但是,与/post/a/ 、/post/123abc/ 、/post/!@#$% 等不匹配; |
url(r'^blog/(?P<slug>[\w-]+)/$', viewname) |
正则表达式(?P<slug>[\w-]+) 匹配一个或多个单词字符(字母数字或下划线)或破折号(-)。该正则表达式通常用于匹配 URL 中的 slug |
该网址模式匹配类似/blog/a-long-hyphenated-title/ 、/blog/mypost/ 、/blog/1111/ 等字符串。但与/blog/?search=query/ 、/blog/%3Apython/ 等不匹配; |
url(r'^archive/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/$', viewname) |
正则表达式(?P<year>\d{4}+)/(?P<month>\d{2}+)/(?P<day>\d{2}+)/ 匹配 ISO 8601 格式的日期,即 YYYY-MM-DD。 |
该网址模式匹配类似/archive/2010/01/01/ 、/archive/5000/30/30/ 、/archive/9999/99/99/ 等字符串;但不匹配/archive/2018/5/5/ 、/archive/11/11/2018/ 等字符串; |
url(r'^user/(?P<username>[\w.@+-]+)/$', viewname) |
正则表达式(?P<username>[\w.@+-]+) 匹配用户名和电子邮件。 |
该网址模式匹配类似/user/@draco/ 、/user/foobar007/ 、/user/tinker@mail.com/ 等字符串;但不匹配/user/ 、/user/$w00t/ 等字符串; |
将网址参数设为可选
偶尔,您会遇到希望将 URL 参数设为可选的情况。
假设我们想在 URL 路径/books/<category>/
显示一个图书列表,其中<category>
是图书类别的占位符。访问/books/self-help/
会显示自助类书籍的列表。然而,访问/books/
网址,会显示默认科幻类别的书籍。因此,网址路径/books/<category>/
中的<category>
是可选的。要完成此任务,请创建两个 URL 模式,一个不带可选参数,另一个带可选参数。
打开urls.py
文件,添加如下两个网址模式:
决哥/决哥 _ 项目/决哥/URL . py】
urlpatterns = [
url(r'^time/$', views.today_is, name='todays_time'),
url(r'^$', views.index, name='djangobin_index'),
url(r'^user/(?P<username>[A-Za-z0-9]+)/$', views.profile, name='profile'),
url(r'^books/$', views.book_category, name='book_category'),
url(r'^books/(?P<category>[\w-]+)/$', views.book_category, name='book_category'),
]
这里有两件事需要注意。第一,两种网址模式使用相同的视图功能。第二,两种网址模式的名称也是一样的。
接下来,在views.py
中添加book_category
查看功能如下:
djangobin/django_project/djangobin/views.py
#...
def profile(request, username):
return HttpResponse("<p>Profile page of #{}</p>".format(username))
def book_category(request, category='sci-fi'):
return HttpResponse("<p>Books in {} category</p>".format(category))
如果您现在访问http://localhost:8000/books/
,您将看到默认sci-fi
类别的书籍。
另一方面,如果您访问http://localhost:8000/books/action/
,您将被显示来自action
类别的书籍。
将额外的参数传递给视图函数
除了 URL 参数,我们还可以向视图函数传递额外的参数,如字典。url()
函数可以将可选的第三个参数作为字典,包含额外的关键字参数传递给视图函数。内置视图函数通常使用这种技术来定制默认行为(我们将在后面的章节中讨论)。
在urls.py
文件中,在urlpatterns
列表的末尾添加名为extra_args
的新网址模式:
决哥/决哥 _ 项目/决哥/URL . py】
urlpatterns = [
#...
url(r'^book/(?P<category>[\w-]+)/$', views.book_category, name='book_category'),
url(r'^extra/$', views.extra_args, {'arg1': 1, 'arg2': (10, 20, 30)}, name='extra_args'),
]
接下来,在views.py
文件的末尾添加一个名为extra_args
的视图函数:
djangobin/django_project/djangobin/views.py
#...
def book_category(request, category='sci-fi'):
return HttpResponse("<p>Books in {} category</p>".format(category))
def extra_args(request, arg1, arg2):
return HttpResponse("<p>arg1: {} <br> arg2: {} </p>".format(arg1, arg2))
如果您现在访问http://localhost:8000/extra/
,您将获得以下页面:
请注意,extra_args()
视图功能中的arg1
和arg2
参数是必需的。未能通过它们将导致这样的TypeError
:
我们可以通过提供一些默认值来使arg1
和arg2
成为可选的。修改extra_args()
接受默认值如下:
djangobin/django_project/djangobin/views.py
#...
def extra_args(request, arg1=None, arg2=None):
return HttpResponse("<p>arg1: {} <br> arg2: {} </p>".format(arg1, arg2))
从extra_args
网址模式中删除词典,访问http://localhost:8000/extra/
。你会得到这样的回答:
视图限制
到目前为止,我们已经能够使用音乐电视模式的视图部分完成一个基本的应用。但是,由于以下原因,我们的应用受到严重限制:
目前,我们正在视图中硬编码 HTML 代码。稍后,如果我们想修改我们的 HTML,逐个查看每个视图将是非常痛苦的。Django 附带了一个强大的模板系统,允许我们轻松创建复杂的 HTML 页面,而不是将它们硬编码在视图中。如果我们直接在视图中硬编码 HTML,我们将无法在 HTML 中使用 Django 模板系统提供的循环或条件语句(我们将在 Django 课程的模板标签中看到如何使用它们)。
在现实世界中,页面由许多动态组件组成。使用
format()
方法在大页面中嵌入动态内容非常容易出错且繁琐。在这一点上,我们还没有将数据库引入场景,因为这会造成更多的混乱。
在接下来的章节中,我们将看到将模型和模板与视图结合使用如何帮助我们大大简化所有这些问题。
创建网址和自定义响应
原文:https://overiq.com/django-1-11/creating-urls-and-custom-response/
最后更新于 2020 年 7 月 27 日
反转网址模式
从 URL 模式的名称创建 URL 的过程称为反转 URL。那我们为什么要这么做呢?为什么不只是硬编码的网址?
因为在以后,如果你想更新网址结构,你必须手动访问每个 Python 模块和模板来进行更改。另一方面,如果你已经通过反转网址模式创建了网址,你只需要更新urls.py
文件中的网址模式。模板和模块会自动选择变更。
反向()函数
要反转网址,我们使用reverse()
功能。它接受网址模式的名称,并返回不带主机部分的网址。当reverse()
无法反转网址时,会引发NoReverseMatch
异常。要使用这个功能,我们首先要从django.shortcuts
模块导入。
要查看动作中的reverse()
方法,通过发出以下命令打开交互式 Python shell。
$ ./manage.py shell
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
>>>
注意到这里有些不同了吗?好了,这里我们使用./manage.py shell
而不仅仅是python
来启动 Python shell。但是为什么呢?因为除了调用 Python 解释器之外./manage.py shell
还导入了最少的 Django 来使用。我们将这个 Shell 称为 Django Shell。
目前,我们的 djangobin 应用包含以下 6 种网址模式:
决哥/决哥 _ 项目/决哥/URL . py】
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^time/$', views.today_is, name='time'),
url(r'^$', views.index, name='index'),
url(r'^user/(?P<username>[A-Za-z0-9]+)/$', views.profile, name='profile'),
url(r'^books/$', views.book_category, name='book_category'),
url(r'^books/(?P<category>[\w-]+)/$', views.book_category, name='book_category'),
url(r'^extra/$', views.extra_args, name='extra_args'),
]
让我们尝试在 Django shell 中逐个反转每个 URL 模式。
>>>
>>> from django.shortcuts import reverse
>>>
>>>
>>> reverse("time")
'/time/'
>>>
>>>
>>> reverse("index")
'/'
>>>
>>>
>>> reverse("book_category")
'/books/'
>>>
>>>
>>> reverse("extra_args")
'/extra/'
>>>
>>>
我们已经成功反转了 4 个网址。剩下的两种 URL 模式呢?
剩下的两个 URL 模式中有一个或多个动态组件,因此,我们需要传递这些组件来成功地反转它们。试图在不传递动态组件的情况下在这样的 URL 模式上调用reverse()
会抛出NoReverseMatch
异常。
>>>
>>> reverse("profile")
...
django.urls.exceptions.NoReverseMatch: Reverse for 'profile' with no arguments not found. 1 pattern(s) tried: ['user/(?P<username>[A-Za-z0-9]+)/$']
>>>
>>>
Traceback (most recent call last):
还要注意,有两个同名的 URL 模式,book_category
,第一个没有动态组件,第二个有一个动态组件。当我们在前面的例子中调用reverse("book_category")
时,Django 足够聪明,可以推断出我们想要在没有动态组件的情况下反转 URL。
要反转动态网址模式,请将动态组件作为列表传递给args
关键字参数。
reverse('url_pattern', args=['arg1', 'arg2'])
以下是一些例子:
>>>
>>>
>>> reverse("book_category", args=['horror'])
'/books/horror/'
>>>
>>>
>>> reverse("profile", args=['foobar'])
'/user/foobar/'
>>>
>>>
我们还可以选择将动态组件作为字典传递给reverse()
:
reverse('url_pattern', kwargs={'arg1': 'val1', 'arg2': 'val2'})
以下是一些例子:
>>>
>>>
>>> reverse("book_category", kwargs={'category': 'crime'})
'/books/crime/'
>>>
>>>
>>> reverse("profile", kwargs={'username': 'tor'})
'/user/tor/'
>>>
>>>
使用kwargs
时,请记住密钥的名称必须与 URL 模式中的命名组的名称相同。否则会得到NoReverseMatch
异常。
自定义响应
默认情况下,HttpResponse
对象使用Content-Type:text/html
头和 HTTP 状态代码 200 创建。我们可以分别使用content_type
和status
关键字参数来更改内容类型标题和状态代码。
def custom_response(request):
return HttpResponse("<p>Custom response 1</p>", content_type='text/plain')
这将使用内容类型text/plain
向客户端发送响应。换句话说,客户端(浏览器)将按字面意思渲染文本<p>Custom response 1</p>
,而不是将其解释为 HTML。
在构建 API 时,您将需要以 JSON 的形式发送数据。这可以通过将content_type
标题设置为application/json
来轻松完成。例如:
def custom_response(request):
import json
data = {'name': 'john', 'age': 25}
return HttpResponse(json.dumps(data), content_type='application/json')
下面是我们如何更改默认状态代码:
def custom_response(request):
return HttpResponse("<h1>HTTP 404 Not Found</h1>", status=404)
该视图功能将返回 HTTP 404 未找到错误,内容为<h1>HTTP 404 Not Found</h1>
。
我们还可以通过将HttpResponse
实例视为字典来设置额外的头。
def custom_header(request):
res = HttpResponse(status=302)
res['location'] = 'http://example.com/'
return res
该视图功能将使用临时重定向(HTTP 302 重定向)将用户重定向至http://example.com/
。
下面是另一个例子:
def custom_header(request):
res = HttpResponse('some data')
res['content-disposition'] = 'attachment; filename=file.txt;'
return res
此查看功能将强制浏览器将响应视为文件附件。我们将使用完全相同的技术来下载代码片段。
HttpResponse 的常见子类
下表列出了HttpResponse
的一些常见子类:
ClassName | 描述 |
---|---|
HttpResponseRedirect |
它将重定向到路径作为第一个参数,并执行临时重定向(HTTP 状态代码 302)。路径可以是完全限定的(如http://example.com/contact )、绝对的(/books/crime/ )或相对的(search/ )。 |
HttpResponsePermanentRedirect |
与HttpResponseRedirect 相同,但执行永久重定向(HTTP 状态代码 301) |
HttpResponseNotFound |
与HttpResponse 相同,但返回状态码 404。 |
HttpResponseForbidden |
与HttpResponse 相同,但返回状态码 403。 |
HttpResponseBadRequest |
与HttpResponse 相同,但返回状态码 400。 |
HttpResponseServerError |
与HttpResponse 相同,但返回状态码 500。 |
这些班级都住在django.http
包里。但是也可以从django.shortcuts
模块导入
和HttpResponsePermanentRedirect
类。
下面的清单演示了如何使用这些类:
from django.http import (
HttpResponseRedirect, HttpResponsePermanentRedirect, HttpResponseNotFound,
HttpResponseBadRequest, HttpResponseForbidden, HttpResponseServerError)
def tem_redirect(request):
return HttpResponseRedirect("http://example.com")
def perm_redirect(request):
return HttpResponsePermanentRedirect("http://example.com")
def not_found(request):
return HttpResponseNotFound("Not Found")
def forbidden(request):
return HttpResponseForbidden("Request Forbidden - 403")
def bad_request(request):
return HttpResponseBadRequest("Bad Request - 400")
def server_error(request):
return HttpResponseServerError("Internal Server Error - 500")
触发 HTTP 错误的内置快捷方式
Django 还提供了一些从视图函数中显式触发 HTTP 错误的快捷方式。下表列出了一些异常及其触发的 HTTP 错误:
例外 | HTTP 状态代码 |
---|---|
django.http.Http404 或django.shortcuts.Http404 |
404 未找到 |
django.core.exceptions.SuspiciousOperation |
400 个错误请求 |
django.core.exceptions.PermissionDenied |
403 禁止 |
Exception |
500 内部服务器错误 |
下面的列表显示了如何使用这些异常:
from django.http import Http404
from django.core.exceptions import SuspiciousOperation, PermissionDenied
def view_func1(request):
if True: # some condition
raise Http404 # show 404 Not Found
else:
return HttpResponse("hello")
def view_func2(request):
if True: # some condition
raise SuspiciousOperation # show 400 Bad Request
else:
return HttpResponse("hello")
def view_func3(request):
if True: # some condition
raise PermissionDenied # show 403 Forbidden
else:
return HttpResponse("hello")
def view_func4(request):
if True: # some condition
raise Exception # show 500 Internal Server Error
else:
return HttpResponse("hello")
当DEBUG
为True
时,所有这些异常都会显示一个描述问题性质的详细页面。例如,Http404
异常将生成如下页面:
而SuspiciousOperation
异常会生成这样一个页面:
一旦我们进入生产(DEBUG
是False
)它们都会返回一个通用的错误页面,只包含错误的名称,没有任何敏感的细节。例如,Http404
异常将生成如下页面:
而PermissionDenied
异常会生成这样一个页面:
重定向()快捷方式
在前面几节中,我们已经看到了执行临时重定向(HTTP 状态代码 302)和永久重定向(HTTP 状态代码 301)的几种方法。redirect()
是将用户重定向到不同网址的快捷功能。它接受要重定向到的网址模式或完全限定网址的网址路径或名称。要使用它,首先从django.shortcuts
模块导入。
from django.shortcuts import redirect
以下查看功能将用户重定向至http://example.com
。
def view_func(request):
return redirect('http://example.com')
默认情况下,redirect()
执行临时重定向(HTTP 状态代码 302),以执行永久重定向通过permanent=True
。例如:
def view_func(request):
return redirect('http://example.com', permanent=True)
如果网址接受参数,将它们传递给网址模式名称后面的redirect()
。例如:
def view_func(request):
return redirect('profile', 'foobar', permanent=True)
该视图功能将使用永久重定向将用户重定向至/user/foobar/
。
命名空间 URL
随着项目的增长,您将很难防止 URL 模式之间的名称冲突。例如,假设我们有两个应用,一个论坛和一个博客。两者的urls.py
文件中都有一个名为index
的网址模式。那么,如果你试图使用reverse()
功能来反转index
的网址模式,会发生什么呢?
Django 将根据应用 URLConf 在全网站urls.py
文件中注册的顺序生成网址。位于列表末尾的 URLConf 将覆盖其上方 URLConf 中同名的中的 URL 模式。
我们可以通过在 URL 模式中添加一个名称空间来解决这个问题。命名空间网址允许我们使用一个唯一的标识符来引用一组网址。
有两种方法可以添加命名空间:
app_name
属性。include()
功能。
让我们探索这两种方法:
使用 app_name
从 djangobin 应用打开urls.py
,定义urlpatterns
列表正上方的app_name
变量如下:
决哥/决哥 _ 项目/决哥/URL . py】
from django.conf.urls import url
from . import views
app_name = 'djangobin'
urlpatterns = [
url(r'^time/$', views.today_is, name='time'),
url(r'^$', views.index, name='index'),
url(r'^user/(?P<username>[A-Za-z0-9]+)/$', views.profile, name='profile'),
url(r'^books/$', views.book_category, name='book_category'),
url(r'^books/(?P<category>[\w-]+)/$', views.book_category, name='book_category'),
url(r'^extra/$', views.extra_args, name='extra_args'),
]
从现在开始,要反转网址,你必须在网址模式的名称前面加上djangobin
,后面加上一个冒号(:
)。
djangobin:url_name
下面的 shell 会话演示了名称空间 URL 模式的反转:
>>>
>>> reverse("djangobin:time")
'/time/'
>>>
>>> reverse("djangobin:profile", args=['foobar'])
'/user/foobar/'
>>>
>>>
>>> reverse("djangobin:book_category", args=['biography'])
'/books/biography/'
>>>
>>>
>>> reverse("djangobin:index")
'/'
>>>
>>>
试图在不指定名称空间的情况下反转 URL 将导致NoReverseMatch
异常:
>>>
>>> reverse("index")
Traceback (most recent call last):
...
django.urls.exceptions.NoReverseMatch: Reverse for 'index' not found. 'index' is not a valid view function or pattern name.
>>>
>>>
>>> reverse("book_category", args=['action'])
Traceback (most recent call last):
...
django.urls.exceptions.NoReverseMatch: Reverse for 'book_category' not found. 'book_category' is not a valid view function or pattern name.
>>>
>>>
使用包含()函数
回想一下include()
函数用于包含来自指定路径下的应用的 URLConf。
创建名称空间的另一种方法是在定义名称空间的同时包含一个 URLConf。打开全网站urls.py
文件并修改include()
以包含名为namespace
的额外关键字参数,如下所示:
djangobin/django _ project/django _ project/URLs . py
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'', include('djangobin.urls', namespace='djangobin')),
url(r'^admin/', admin.site.urls),
]
在本教程中,我们将使用第二种方法来命名 URL,因此请注释掉我们在 djangobin 的urls.py
文件中使用app_name
变量定义命名空间的那一行。
这就是创建命名空间网址所需要的。
Django 模板基础
原文:https://overiq.com/django-1-11/basics-of-django-templates/
最后更新于 2020 年 7 月 27 日
什么是 Django 模板?
把 Django 模板想象成创建一个完整的 HTML 页面所需的脚手架。Django 模板只不过是一个包含静态内容的文本文件,以及一个指定一些逻辑、循环和数据显示的特殊动态标记。
Django 模板存放在哪里?
在我们开始构建模板之前,让我们先花一些时间来学习 Django 如何搜索模板。在位于djangobin/django_project/djangobin
的 djangobin 应用中创建一个名为templates
的新目录。但是为什么要templates
目录呢?因为默认情况下,Django 会在每个已安装应用的templates
目录中搜索模板。
如果出于某种原因,您想要关闭此行为,请打开settings.py
并将'APP_DIRS'
值设置为False
,如下所示:
djangobin/django _ project/django _ project/settings . py
#...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': False,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
#...
目前,我们没有任何合理的理由关闭此行为,因此再次将其切换回True
。
Django 将所有templates
目录视为一个目录。这意味着如果你有两个应用,博客和论坛分别有blog/templates/list.html
和forum/templates/list.html
模板,那么对于 Django 来说,这两个文件是相同的,它将使用它首先找到的模板(取决于INSTALLED_APPS
设置中列出的应用的顺序)。由于这种行为,Django 建议在templates
目录中用应用的名称创建一个子目录。在templates
目录下新建一个名为djangobin
的子目录。我们将在这里存储 djangobin 应用的所有模板。
一个简单的 Django 模板
为了让事情变得清晰,让我们以 Django 模板为例。下面的列表显示了一个简单的博客文章模板:
<!DOCTYPE html>
<html>
<head>
<title>Blog Post</title>
</head>
<body>
<h1>{{ post_title }}</h1>
<p>Published by <span>Author : {{ author|title }} </span></p>
<p>{{ post_content }}</p>
<p>Related Posts:</p>
<ul>
{% for item in item_list %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% if comments %}
<p>This post has some comments</p>
{% else %}
<p>This post has no comments</p>
{% endif %}
</body>
</html>
请注意,除了 HTML 代码之外,我们还使用特殊的标记来表示页面的动态部分。
让我们一步一步来看代码。
任何被双花括号(
{{ post_title }}
)包围的文本都是一个变量,例如,第 8 行(<h1>{{ post_title }}</h1>
)中的代码意味着输出变量post_title
在h1
标记中的值。所以如果post_title
的值是"Django Templates"
,那么<h1>Django Templates</h1>
就会被打印出来。你可能会说,但是我们如何指定变量值呢?别担心,我们稍后会谈到这一点。例如,第 16 行中由单个大括号(
{
)和百分号(%
)包围的文本,即{% for item in item_list %}
被称为模板标签。模板标签允许我们做一些非常具体的事情。在这种情况下,{% for %}
标签的工作原理非常像 Python 中的 for 循环,通常用于循环遍历列表或字典。就像我们可以使用
{% for %}
标签在模板中模拟一个循环一样。我们可以使用{% if %}
标签向我们的模板添加逻辑 if 语句。在第 21 行,我们有一个{% if %}
模板标签。{% if comments %} <p>This post has { comment.count } comments</p> {% else %} <p>This post has no comments</p> {% endif %}
以下是它的工作原理:
首先,评估变量
comments
的值。如果是真的,则打印<p>This post has some comments</p>
,否则打印<p>This post has no comments</p>
。最后,在第 10 行,我们使用了一个过滤器,即
{{ author|title }}
。过滤器是格式化变量输出的一种方式。我们使用管道字符(|
)后跟变量名后面的过滤器名称来指定过滤器。如果变量author
的值是"bob"
,那么由于title
的过滤"Bob"
将被打印而不是"bob"
。title
过滤器将字符串中每个单词的第一个字符转换为大写,其余字符转换为小写。
Django 模板可以访问许多其他过滤器;我们将在 Django 中的模板过滤器一课中讨论一些重要的内容。除此之外,您还可以创建自己的过滤器。
使用 Django 模板系统
模板引擎是将模板加载并渲染为标记的程序。每个模板引擎定义一种语言来创建模板。我们在前面的部分已经看到了这种语言的一些部分,例如,{{ post_title }}
、{% if comments %}
等。Django 提供的模板引擎简称为 Django 模板语言或 DTL 。在 Django 1.8 之前,DTL 是唯一的选择。但是对于 Django 1.8 和更高版本,我们可以使用像 Jinja2 这样的外部模板引擎。然而,如果你没有任何合理的理由使用 Jinja2,只要坚持 DTL,你就会没事。在本教程中,我们将只使用 DTL。
在我们开始在视图中使用模板之前,让我们深入了解一下它们的工作原理。以下是使用 Django 模板所涉及的基本工作流程:
- 通过将模板的内容作为原始字符串传递,创建一个新的
Template
对象。 - 用给定的一组变量调用模板对象上的
render()
方法。render()
方法在评估所有变量和模板标签后返回完全渲染的代码。
让我们尝试在 DjangoShell 中创建一些Template
对象。
$ ./manage.py shell
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
>>>
>>> from django import template
>>> t = template.Template("We are learning {{ name }}")
>>> c = template.Context({'name': 'Django'})
>>> print(t.render(c))
We are learning Django
>>>
以下是上述代码的工作原理。
在第 8 行,我们正在从
django
包导入template
模块。template
模块有Template
类。在第 9 行,我们通过将一个原始字符串传递给Template
类的构造函数来创建一个新的Template
对象。我们的模板字符串由一个变量组成。为了将值传递给这个变量,我们使用
Context
类。通过传递将变量名映射到值的字典来实例化Context
类。最后,渲染模板调用
Context
对象作为参数的render()
方法。
一旦加载了模板,就可以根据需要多次渲染,而无需重新加载。
>>>
>>> c = template.Context({'name': 'a new web framework'})
>>> print(t.render(c))
We are learning a new web framework
>>>
但是如果我们不向Context
构造函数提供任何值,会发生什么呢?在这种情况下,您将不会收到任何错误消息,而 Django 将不会打印任何内容。这在一开始听起来可能令人沮丧,但它非常有用,因为在现实世界的应用中,网站仅仅因为缺少一个值就开始抛出错误是不可接受的。
>>>
>>> c = template.Context()
>>> print(t.render(c))
We are learning
>>>
Context
字典中的键区分大小写,因此如果变量名与模板中预期的变量名略有不同,Django 将不会打印任何内容。
>>>
>>> c = template.Context({'Name': 'Django framework'})
>>> print(t.render(c))
We are learning
>>>
因此,确保上下文字典中键的名称与模板中变量的名称相同非常重要。
上下文变量查找
到目前为止,我们一直在向模板传递简单的数据,主要是字符串。然而,Django 模板系统可以轻松处理复杂的数据结构,如列表、字典甚至对象。访问模板中这些复杂数据结构的关键是使用点字符(.
)。这可以用一些例子来最好地描述。
通过字典
>>>
>>> framework = {
... 'first': 'Django',
... 'second': 'Laravel',
... 'third': 'Spring',
... 'fourth': 'CodeIgniter'
... }
>>>
>>> t = template.Template("We are learning {{ framework.first }}")
>>> c = template.Context({'framework': framework})
>>> print(t.render(c))
We are learning Django
>>>
同样,我们可以使用点运算符(.
)来访问对象属性。
传递对象
>>>
>>> import datetime
>>>
>>> now = datetime.datetime.now()
>>>
>>> now.day, now.month, now.year
(25, 1, 2017)
>>>
>>>
>>> t = template.Template("Date is {{ now.day }}-{{ now.month }}-{{ now.year }}")
>>> c = template.Context({'now':now})
>>> print(t.render(c))
Date is 25-1-2017
>>>
在上面的例子中,我们使用了一个内置的类。同样的行为也适用于我们创建的类。
>>>
>>> class Player:
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return self.name
...
>>>
>>> p1 = Player('bob')
>>>
>>> t = template.Template("The player name is {{ player }}")
>>>
>>> c = template.Context({'player': p1})
>>>
>>> t.render(c)
'The player name is bob'
>>>
>>>
调用方法
使用点运算符(.
)您也可以在模板中调用对象的方法,但是请记住,在这样做时,我们不包括括号()
。
>>>
>>> name = "django"
>>> name.capitalize()
'Django'
>>>
>>> name.upper()
'DJANGO'
>>> name
'django'
>>>
>>> t = template.Template("{{ var.capitalize }} learning {{ var.upper }}")
>>> c = template.Context({'var': name})
>>> print(t.render(c))
Django learning DJANGO
>>>
需要注意的是,我们只能在模板内部调用不需要任何参数的方法。
传递列表
我们还可以使用点(.
)运算符后跟索引位置来访问列表的元素。记住 python 中的列表是0
索引的。
>>>
>>> list = ['Ruby on Rails', 'Django', 'Laravel']
>>> t = template.Template("We are learning {{ list.1 }} ")
>>> c = template.Context({'list': list})
>>> print(t.render(c))
We are learning Django
>>>
虽然,我们可以在 Python 中使用负索引来访问列表的元素,但是这个功能在 Django 模板中是不可用的。
点查找的顺序
点(.
)字符在模板内部有特殊含义。变量名后面的点(.
)表示查找。当 Django 模板系统在变量名后面遇到一个点(.
)时,它会按照这个特定的顺序尝试执行查找。
- 字典查找-
var.['key']
- 属性查找-
var.key
- 方法调用查找-
var.key()
- 列表-索引查找-
var.1
模板系统使用第一个有效的查找。
我们也可以一个接一个地连锁查找。例如:
{{ person.first_name.capitalize }}`
这里首先评估person.first_name
,然后对结果应用capitalize()
方法。
Django 中的模板标签
最后更新于 2020 年 7 月 27 日
在前一章中,我们已经向您介绍了一些基本的模板标签和过滤器。在本课中,我们将详细讨论一些重要的内置模板标签。
if 标签
以下是{% if %}
标记的语法:
语法:
{% if condition %}
<p>Print this line</p>
{% endif %}
以下是它的工作原理:
{% if %}
标记计算condition
的值,如果它为真(Python 中的变量计算为真,如果它包含非空值或非False
布尔值),那么模板系统将显示{% if %}
和{% endif %}
之间的所有内容。例如:
{% if var %}
<p>Print this para</p>
{% endif %}
如果var
的值为10
,则打印<p>Print this para</p>
。另一方面,如果p
的值是[]
(空数组)或{}
(空字典)或0
(数字零)或False
(布尔假),则不会打印任何内容。
用相应的{% endif %}
关闭每个{% if %}
很重要。否则,Django 将抛出TemplateSyntaxError
异常。
您也可以在{% if %}
标签上添加一个可选的{% else %}
标签,如下所示:
{% if var %}
<p>Print this para</p>
{% else %}
<p>Else print the other para</p>
{% endif %}
以下是它的工作原理:
首先评估var
的值,如果为真,则打印<p>Print this para</p>
。否则,<p>Else print the other para</p>
将被印刷。
也可以增加一个或多个{% elif %}
子句,增加一些条件。例如:
{% if count < 10 %}
<p>Print this para</p>
{% elif count < 20 %}
<p>Otherwise print this</p>
{% elif count < 30 %}
<p>Let's try this</p>
{% else %}
<p>Okay everything failed print this now</p>
{% endif %}
以下是它的工作原理:
每个条件被依次评估,一旦一个条件被发现为真,相应块中的代码就被执行,所有其他条件的评估被跳过。
评论
Django 模板使用以下语法编写注释。
{# This is a comment #}
使用此语法编写的注释不会在 HTML 源代码中渲染。此外,您不能将此注释扩展到多行。
{# This is
not a comment #}
如果要多行写注释,使用{% comment %}
标记:
{% comment %}
This is a comment expanding to
multiple lines.
{% endcomment %}
使用逻辑运算符
也可以使用逻辑and
、or
、not
运算符测试多个条件。例如:
and
运算符
{% if palindrome and even %}
<p>Number is palindrome and even</p>
{% endif %}
如果两个变量评估为真,则打印<p>Number is palindrome and even</p>
。否则,将不会打印任何内容。
not
运算符
{% if not post_list %}
<p>There are no blog post</p>
{% endif %}
not
运算符否定该条件。因此,只有当post_list
为False
(或为空)时,上述代码才会打印<p>There are no blog posts</p>
。换句话说,如果没有博文打印<p>There are no blog posts</p>
。
or
运算符
{% if post_list or page_list %}
<p>The site has some blog post or pages</p>
{% endif %}
如果两个变量中的任何一个为真,那么将打印<p>The site has some blog post or pages</p>
。否则,将不会打印任何内容。
下面是更多的例子:
{% if not post_list or author_list %}
<p>There are no posts or there are some authors</p>
{% endif %}
这里not
运算符只求post_list
变量的反。所以字符串<p>There are no posts or there are some authors</p>
只有在没有帖子(即post_list
为空)或者有一些作者(author_list
不为空)时才会被打印出来。
也可以在同一个标签内使用and
和or
运算符。需要记住的重要一点是and
的优先级高于or
操作员。例如:
{% if post_list and page_list or author_list %}
<p>The site has either both posts and pages or only author</p>
{% endif %}
该代码将被解释为:
{% if (post_list and page_list) or author_list %}
<p>The site has either both posts and pages or only author</p>
{% endif %}
不过,我想澄清的是,不要动心用括号来分组条件。这是无效语法,会引发TemplateSyntaxError
异常。
您也可以将一个{% if %}
标签嵌套在另一个{% if %}
标签中。例如:
{% if num < 10 %}
{% if num > 5 %}
<p>The num is greater than 5 but less than 10</p>
{% endif %}
{% endif %}
使用关系运算符
您也可以在模板标签中使用关系运算符>
、<
、>=
、<=
、!=
、==
。
>
运算符
{% if num > 10 %}
<p>The num is greater than 10</p>
{% endif %}
如果num
大于10
,则打印<p>The num is greater than 10</p>
。
<
运算符
{% if num < 10 %}
<p>The num is lesser than 10</p>
{% endif %}
如果num
小于10
,则打印<p>The num is lesser than 10</p>
。
>=
运算符
{% if num >= 10 %}
<p>The num is greater than or equal to 10</p>
{% endif %}
如果num
大于或等于10
,则打印<p>The num is greater than or equal to 10</p>
。
<=
运算符
{% if num <= 10 %}
<p>The num is lesser than or equal to 10</p>
{% endif %}
如果num
小于或等于10
,则打印<p>The num is lesser than or equal to 10</p>
。
==
运算符
{% if num == 10 %}
<p>The num is equal to 10</p>
{% endif %}
如果num
等于10
,则打印<p>The num is equal to 10</p>
。
!=
运算符
{% if num != 10 %}
<p>The num is not equal to 10</p>
{% endif %}
如果num
不等于10
,则打印<p>The num is not equal to 10</p>
。
in
、not in
和is
运算符
in
运算符
{% if 10 in num_list %}
<p>Yes, the number 10 is in the num_list</p>
{% endif %}
如果num_list
中有数字 10,则打印<p>Yes, the number 10 is in the num_list</p>
。
not in
运算符
{% if 10 not in list %}
<p>Yes, the number 10 is not in the list</p>
{% endif %}
如果num_list
中不存在数字 10,则打印<p>Yes, the number 10 is in the num_list</p>
。
is
运算符
就像在 Python 代码中一样,模板中的is
运算符用于比较两个对象。如果两个物体相同,则is
操作员返回True
。否则False
。
{% if obj is user %}
<p>Yes obj is same as user</p>
{% endif %}
这里,如果变量obj
指向的对象与变量user
相同,则打印文本<p>Yes obj is same as user</p>
。
用于标记
一个{% for %}
标签允许我们用来循环一个序列(或集合)。我们可以使用{% for %}
标签对列表、元组、字典等内容进行迭代。以下是{% for %}
标签的语法:
{% for i in my_list %}
<p>The value of i is {{ i }}</p>
{% endfor %}
以下是它的工作原理:
当循环开始时,列表中的第一个值my_list
被分配给变量i
。然后模板引擎将渲染{% for %}
和{% endfor %}
之间的所有内容。这个过程一直重复,直到列表中没有更多元素需要迭代。
要以相反的顺序打印列表,请在列表后添加reversed
关键字,如下所示。
{% for i in my_list reversed %}
<p>The value of i is {{ i }}</p>
{% endfor %}
有时候在你的 Django 之旅中,你不得不处理列表中的列表。要访问列表,请将子列表的列表元素解包为单个变量。例如,假设我们的上下文中有以下列表。
list = [ ["uno", "one"], ["dos", "two"], ["tres", "three"], ["cuatro", "four"] ]
要在模板内循环列表,请执行以下操作:
{% for x, y in list %}
<p>{{ x }} : {{ y }}</p>
{% endfor %}
输出将是:
uno : one
dos : two
tres : three
cuatro : four
同样,我们可以访问字典的元素。假设我们的上下文变量包含一个名为dict
的字典。
dict = { 'uno': 'one', 'dos': 'two', 'tres': 'three', 'cuatro': 'four' }
要在模板中访问该字典,请使用以下代码:
{% for k, v in dict.items %}
<p>{{ k }} : {{ v }}</p>
{% endfor %}
输出如下所示:
cuatro : four
uno : one
tres : three
dos : two
请注意,字典中的元素没有特定的存储顺序。所以上面的输出可能会有所不同。
对于空标签
假设我们有一个名为post_list
的变量,它包含一个帖子对象列表。我们的工作是打印所有博客文章的列表。我们可以这样做,使用如下的for
标签:
{% for post in post_list %}
<h2>{{ post.title }}</h2>
{% endfor %}
但是有一个问题。我们还没有检查是否有任何博客文章存在。以下是更新后的代码:
{% if post_list %}
{% for post in post_list %}
<h2>{{ post.title }}</h2>
{% endfor %}
{% else %}
No post published yet.
{% endif %}
这种模式非常普遍,Django 为此提供了一个很好的捷径。for
标签可以附加一个{% empty %}
标签。这个标签让你定义在列表为空的情况下输出什么。例如:
{% for post in post_list %}
<h2>{{ post.title }}</h2>
{% empty %}
No post published yet.
{% endfor %}
就像嵌套的if
标签一样,我们可以有嵌套的for
标签。
{% for post in post_list %}
<p>{{ post.content }}</p>
<p>
<ul>
{% for tag in post.tags %}
<li>{{ tag }}</li>
{% endfor %}
</ul>
</p>
{% endfor %}
for
标签提供了一个名为forloop
的特殊变量。forloop
有几个属性可以用来跟踪循环的进度。
forloop.counter
-返回一个数字,表示循环的当前迭代。从1
开始。例如,假设我们的上下文包含一个名为list
的列表,定义如下:
list = [11,12,13]
{% for i in list %}
<p>{{ forloop.counter }} Iteration - {{ i }}</p>
{% endfor %}
那么上面的 for 循环将打印以下输出:
1 Iteration - 11
2 Iteration - 12
3 Iteration - 13
forloop.counter0
-工作方式与forloop.counter
相同,但以0
而非1
开头。
{% for i in list %}
<p>{{ forloop.counter0 }} Iteration - {{ i }}</p>
{% endfor %}
输出:
0 Iteration - 11
1 Iteration - 12
2 Iteration - 13
forloop.revcounter
-返回从循环结束开始的迭代次数。
{% for i in list %}
<p>{{ forloop.revcounter }} Iteration - {{ i }}</p>
{% endfor %}
输出:
3 Iteration - 11
2 Iteration - 12
1 Iteration - 13
forloop.revcounter0
-与forloop.revcounter
相同,但它是0
索引的。
{% for i in list %}
<p>{{ forloop.revcounter0 }} Iteration - {{ i }}</p>
{% endfor %}
输出:
2 Iteration - 11
1 Iteration - 12
0 Iteration - 13
forloop.first
-如果当前迭代是第一次迭代,则返回布尔值True
。否则False
。
{% for i in list %}
<p>{{ forloop.first }} Iteration - {{ i }}</p>
{% endfor %}
输出:
True Iteration - 11
False Iteration - 12
False Iteration - 13
forloop.last
-如果当前迭代是最后一次迭代,则返回布尔值True
。否则False
。
{% for i in list %}
<p>{{ forloop.last }} Iteration - {{ i }}</p>
{% endfor %}
输出:
False Iteration - 11
False Iteration - 12
True Iteration - 13
forloop.parentloop
-它在嵌套的for
循环中用于引用父for
循环中的forloop
变量。例如:
{% for i in list %}
<table>
{% for j in i %}
<tr>
<td>{{ forloop.parentloop.counter }} - {{ forloop.counter }} - {{ i }}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
标签 url
url
标签用于生成模板内部的网址。它具有以下语法:
{% url 'url_name' arg1 arg2 %}
其中url_name
是 URL 模式的名称。arg1
和arg2
指的是网址要求的参数。如果网址不接受任何参数,只需传递网址模式的名称。成功后,它将返回网址中没有主机部分的部分。如果不能创建网址NoReverseMatch
则抛出异常。
如果你有命名空间的网址,就像我们在第章【创建网址和自定义响应】中调用reverse()
函数时所做的那样,指定完全限定的名称:
{% url 'my_app:url_name' arg1 arg2 %}
以下是一些例子:
>>>
>>> from django import template
>>>
>>> t = template.Template("{% url 'djangobin:time' %}")
>>> t.render(template.Context())
'/time/'
>>>
>>>
>>> t = template.Template("{% url 'djangobin:index' %}")
>>> t.render(template.Context())
'/'
>>>
>>>
>>> t = template.Template("{% url 'djangobin:profile' 'foobar' %}")
>>> t.render(template.Context())
'/user/foobar/'
>>>
>>>
>>> t = template.Template("{% url 'djangobin:book_category' 'crime' %}")
>>> t.render(template.Context())
'/book/crime/'
>>>
>>>
>>> t = template.Template("{% url 'djangobin:contact' %}")
>>> t.render(template.Context())
Traceback (most recent call last):
...
django.urls.exceptions.NoReverseMatch: Reverse for 'contact' not found. 'contact' is not a valid view function or pattern name.
>>>
Django 的模板过滤器
原文:https://overiq.com/django-1-11/template-filters-in-django/
最后更新于 2020 年 7 月 27 日
Django 过滤器用于在变量渲染为 HTML 代码之前修改变量的值。要使用过滤器,请在变量名后键入管道字符(|
)后跟过滤器名称。
{{ variable|filter_name }}
下过滤器
当应用于变量时,lower
过滤器会将变量中的所有大写字符转换为等效的小写字符。例如:
<p>{{ name|lower }}</p>
如果name
变量的值是"Tom Sawyer"
,那么上面的代码将产生下面的 HTML。
<p>tom sawyer</p>
上部过滤器
upper
滤镜与lower
滤镜完全相反。它会将变量中的所有字符转换为大写等效字符。例如:
<p>{{ name|upper }}</p>
如果name
变量的值是"tom sawyer"
,那么上面的代码将产生下面的 HTML。
<p>TOM SAWYER</p>
capfirst 滤波器
capfirst
过滤器仅将变量中的第一个字符转换为其大写等效字符。例如:
<p>{{ name|capfirst }}</p>
如果变量name
包含"tom sawyer"
,那么上面的代码将产生下面的 HTML。
<p>Tom sawyer</p>
您也可以链接过滤器。例如:
<p>{{ name|lower|capfirst }}</p>
这里name
变量首先被转换成小写字符,然后对结果应用capfirst
过滤器。
标题过滤器
title
过滤器将每个单词的第一个字母大写。例如:
<p>{{ name|title }}</p>
如果变量name
包含"tom sawyer"
,那么上面的代码将产生下面的 HTML。
<p>Tom Sawyer</p>
长度过滤器
length
过滤器决定数值的长度。它可以处理字符串、列表、字典和元组。例如:
<p>The length variable name is {{ name|length }}</p>
如果变量name
包含"tom sawyer"
,那么上面的代码将产生下面的 HTML。
<p>The length variable name is 10</p>
截断词过滤器
truncatewords
过滤器在一定数量的单词后截断(缩短)字符串。截断字符串后,它将...
(称为省略号)附加到截断字符串的末尾。truncatewords
是你可以传递论点的过滤器之一。将参数传递给筛选器类型冒号(:
),后跟双引号内的参数(""
)。要将多个参数传递给过滤器,请使用逗号(,
)分隔它们。truncatewords
接受单个参数,表示后面要截断的字数。例如,要只输出博文的第一个10
字,请执行以下操作:
<p>{{ post|truncatewords:"10" }}</p>
如果post
变量定义为:
post = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eu lectus ut lacus posuere fringilla id eu turpis."
上面的代码将产生下面的 HTML。
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eu ...</p>
截断滤波器
truncatechars
过滤器类似于truncatewords
,但它不是按单词截断,而是按字符截断。如果字符串大于指定的字符数,它将截断字符串。就像truncatewords
过滤器一样,截断字符串后truncatechars
过滤器会将...
(省略号)追加到截断的字符串中。例如:
{{ long_text|truncatechars:"10" }}
如果字符串大于10
个字符,这将截断字符串并将...
追加到末尾。
如果long_text
包含"Lorem ipsum dolor sit amet, consectetur adipiscing elit"
,那么上面的代码会产生下面的 HTML。
<p>Lorem i...</p>
复数滤波器
pluralize
过滤器用于处理后缀。让我们举个例子:
<p>You have {{ num }} products in your cart</p>
如果n
是1
,那么我们要显示。
<p>You have 1 product in your cart</p>
另一方面,如果n
是10
,那么我们想要显示。
<p>You have 10 products in your cart</p>
注意字符串products
中的s
。
我们可以使用pluralize
过滤器轻松处理这些情况。
<p>You have {{ num }} product{{ num|pluralize }} in your cart</p>
现在,如果num
是1
,输出将是:
<p>You have 1 product in your cart</p>
如果num
的值为100
,则输出为:
<p>You have 100 products in your cart</p>
默认情况下pluralize
过滤器将"s"
追加到字符串中。然而,并不是所有的复数单词都以"s"
结尾,有些也像tomatoes
一样以"es"
结尾。例如:
<p>I have {{ num }} tomato{{ num|pluralize }}</p>
如果num
等于5
,则输出上述代码:
<p>I have 5 tomatos</p>
当然,这是错误的。要提供替代后缀,您可以将参数传递给pluralize
过滤器。例如:
<p>I have {{ num }} tomato{{ num|pluralize:"es" }}</p>
如果num
等于5
,输出将是:
<p>I have 5 tomatoes</p>
还有一些单词不是用简单的后缀来复数的,比如日记和日记,樱桃和樱桃等等。为了处理这种特殊情况,你必须提供单数和复数后缀作为pluralize
过滤器的参数。例如:
<p>I have {{ num }} diar{{ num|pluralize:"y,ies" }}</p>
如果num
为1
,输出将为:
<p>I have 1 diary</p>
如果num
为5
,输出将为:
<p>I have 5 diaries</p>
日期过滤器
我们使用date
过滤器来格式化datetime.date
和datetime.datetime
对象。date
过滤器使用一些特殊的格式字符来格式化datetime.date
和datetime.datetime
对象。要格式化日期,将一串格式字符作为参数传递给date
过滤器。例如,假设我们的上下文有一个名为now
的datetime.datetime
对象,其定义如下:
now = datetime.datetime.now()
我们的模板包含以下代码:
<p>Today is {{ now }}</p>
如果我们不使用date
过滤器,那么上面的代码会产生下面的 HTML。
<p>Today is Jan. 27, 2017, 4:28 p.m.</p>
以下是一些常用的格式字符串,可用于date
过滤器:
性格;角色;字母 | 它有什么作用? | 例子 |
---|---|---|
d |
使用两位数字打印一个月中的某一天 | 01 至31 |
D |
使用三个字母打印星期几 | Mon 为周一,Tue 为周二,以此类推 |
m |
使用两位数字打印月份 | 01 为 1 月,02 为 2 月,依此类推 |
M |
使用三个字母数字打印月份 | Jan 为 1 月,Feb 为 2 月,依此类推 |
i |
打印分钟 | 00 至59 |
h |
以 12 小时格式打印小时数 | 01 至12 |
H |
以 24 小时格式打印小时数 | 00 至23 |
s |
打印秒数 | 00 至59 |
a |
打印“上午”或“下午” | a.m. 、p.m. |
Y |
使用完整的 4 位数字打印年份 | 2001 、2014 等等 |
让我们向日期过滤器添加一些格式字符,如下所示:
<p>Today is {{ now|date:"D d M Y" }}</p>
这将输出如下内容:
<p>Today is Fri 27 Jan 2017</p>
换行过滤器
linebreaks
过滤器将字符串中的换行符转换为适当的 HTML。如果一个字符串有换行符,它将被转换为<br/>
,后面跟一个空行的换行符将被转换为</p>
。
{{ text|linebreak }}
请考虑以下示例:
例 1:
content = '''\
this is
a content
'''
{{ content|linebreaks }}
那么输出将是:
<p>this is<br />a test<br /></p>
在变量content
中有两个换行符,第一个出现在单词is
之后(第 2 行),第二个出现在单词content
之后(第 3 行)。
linebreaks
过滤器用<br />
标签替换这些换行符,并将整个字符串包装在<p>
标签中。
例 2:
content = '''\
this is
a test
'''
{{ content|linebreaks }}
那么输出将是:
<p>this is</p>
<p>a test<br /></p>
linebreaksbr 过滤器
linebreaksbr
过滤器仅将字符串中的换行符转换为<br>
标记。例如:
{{ text|linebreaksbr}}
现在如果text
变量的值是"Filter string\n using linebreaksbr"
,输出将是:
Filter string<br /> using linebreaksbr
标签自动抓取
出于安全考虑,Django 模板系统会自动为您进行转义。考虑以下示例:
假设变量my_code
包含"<p>this is short para </p>"
,模板中的代码为:
{{ my_code }}
上面的代码将渲染为以下 HTML:
<p>THIS IS TEST</p>
有两种方法可以关闭转义:
safe
过滤。autoescape
过滤。
安全过滤器
{{ my_code|safe }}
这段代码将产生以下 HTML 输出:
<p>this is short para </p>
safe
过滤器告诉 Django Template 系统my_code
变量是安全的,不需要转义。
关闭转义的另一种方法是使用autoescape
标记
标签自动抓取
autoescape
标签允许您在模板中转义/隐藏大块内容。它接受 on 或 off 作为参数,指示自动转义在块中是否有效。
例如:
{% autoescape on %}
{{ code_snippet }}
{% endautoescape %}
由于默认情况下 Django 模板系统会自动转义所有内容,因此您可能永远不需要使用上面的代码。相反,我们通常使用autoescape
标签来关闭大部分内容的转义。例如:
{% autoescape off %}
{{ heading }}
{{ main_content }}
{{ footer }}
{% endautoescape %}
逸出过滤器
escape
过滤器将以下字符转换为其 HTML 实体。
"
替换为"
;'
替换为'
;&
替换为&
;<
替换为<
;>
替换为>
;
由于 Django 模板系统会自动转义所有内容,您可能永远不会使用escape
过滤器:例如:
my_code = "<p>this is short para</p>"
{{ my_code }}
与...相同
{{ my_code|escape }}
当autoescape
关闭,我们想要逃离内容时,这个滤镜的效用就发挥出来了。例如:
{% autoescape off %}
{{ heading }}
{{ main_content }}
{{ footer }}
{{ comments }}
{% endautoescape %}
这里可以假设变量heading
、main_content
和footer
的内容是安全的。但是comments
变量不是这样。这就是为什么关闭comments
变量的转义不是一个好主意。要在模板中转义comments
变量,您可以执行以下操作:
{% autoescape off %}
{{ heading }}
{{ main_content }}
{{ footer }}
{{ comments|escape }}
{% endautoescape %}
首先,这是足够的过滤器。要查看过滤器的完整列表,请查看 Django 文档中的页面。
在 Django 中加载模板
原文:https://overiq.com/django-1-11/loading-templates-in-django/
最后更新于 2020 年 7 月 27 日
在最后几章中,我们已经了解了很多关于 Django 模板的知识。在这一课中,我们将把一些东西用到。从位于djangobin/django_project/djangobin
的 djangobin 应用打开views.py
。此时,文件应该如下所示:
djangobin/django_project/djangobin/views.py
from django.shortcuts import HttpResponse
import datetime
def index(request):
return HttpResponse("<p>Hello Django</p>")
def today_is(request, username):
now = datetime.datetime.now()
html = "<html><body>Current date and time: {0}</body></html>".format(now)
return HttpResponse(html)
def profile(request, username):
return HttpResponse("<p>Profile page of #{}</p>".format(username))
def book_category(request, category='sci-fi'):
return HttpResponse("<p>Books in {} category</p>".format(category))
def extra_args(request, arg1=None, arg2=None):
return HttpResponse("<p>arg1: {} <br> arg2: {} </p>".format(arg1, arg2))
让我们更新today_is()
视图以使用模板,如下所示:
djangobin/django_project/djangobin/views.py
from django.shortcuts import HttpResponse
import datetime
from django import template
#...
def today_is(request):
now = datetime.datetime.now()
t = template.Template("<html><body>Current date and time {{ now }}</body></html>")
c = template.Context({'now': now})
html = t.render(c)
return HttpResponse(html)
启动服务器,如果还没有运行,访问http://127.0.0.1:8000/time/
。你应该得到当前的日期和时间,就像以前一样。
当然,我们正在使用 Django 模板,但是我们仍然在视图中硬编码原始的 HTML。以这种方式为一个大的 HTML 页面创建模板将是非常麻烦和乏味的。如果我们能把 HTML 写在一个外部文件中,那就更好了。我们开始吧。
回想一下Django 模板的基础知识一课,默认情况下,Django 会在每个已安装应用的templates
目录中搜索模板。在djangobin/templates/djangobin
目录中创建一个名为datetime.html
的新文件,然后向其中添加以下代码。
决哥/决哥 _ 项目/决哥/样板/决哥/日期时间. html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Current Time</title>
</head>
<body>
{# This is a comment #}
{# check the existence of now variable in the template using if tag #}
{% if now %}
<p>Current date and time is {{ now }}</p>
{% else %}
<p>now variable is not available</p>
{% endif %}
</body>
</html>
好了,现在我们已经创建了一个模板,下一个问题是如何在我们的视图中加载这个模板。原来template
包提供了一个叫做loader
的模块,这个模块又提供了一个get_template()
函数来加载模板。get_template()
函数接受一个指示模板名称的字符串参数,计算出模板的位置,打开文件并返回一个Template
对象。如果get_template()
找不到模板,则引发TemplateDoesNotExist
异常。
打开views.py
并修改today_is()
查看功能如下:
djangobin/django_project/djangobin/views.py
#...
def today_is(request):
now = datetime.datetime.now()
t = template.loader.get_template('djangobin/datetime.html')
c = template.Context({'now': now})
html = t.render(c)
return HttpResponse(html)
请注意,在第 5 行中,我们只是将应用的名称,后跟斜杠(/
),后跟模板名称(即datetime.html
)传递给get_template()
函数,而不是传递给datetime.html
的完整路径,后者是djangobin/templates/djangobin/datetime.html
。这是因为我们使用的是存储模板的 Django 约定。因此,Django 会自动找出模板在哪里。
同样,如果我们有一个名为论坛的应用和一个名为forums/templates/forums/discussions.html
的模板。然后我们将使用forums/discussions.html
加载discussions.html
模板。
其余代码照常工作。要检查您是否做对了所有事情,请再次访问http://127.0.0.1:8000/time/
。你会得到这样的TypeError
异常:
不要害怕。问题是在 Django 1.11 中不推荐使用Context
类。要解决这个问题,只需将变量名到值的字典映射传递给render()
方法。但是,如果您手动创建django.template.Template
对象,那么您仍然需要将Context
实例传递给render()
方法(奇怪但真实)。这里是更新后的today_is()
查看功能。
djangobin/django_project/djangobin/views.py
#...
def today_is(request):
now = datetime.datetime.now()
t = template.loader.get_template('djangobin/datetime.html')
html = t.render({'now': now})
return HttpResponse(html)
再次访问http://127.0.0.1:8000/time/
,你会看到现在的日期和时间和以前一样。
如果get_template()
函数找不到模板,就会抛出TemplateDoesNotExist
异常。查看TemplateDoesNotExist
错误修改today_is()
查看功能如下:
djangobin/django_project/djangobin/views.py
#...
def today_is(request):
now = datetime.datetime.now()
t = template.loader.get_template('djangobin/datetimeeeeee.html')
html = t.render({'now': now})
return HttpResponse(html)
打开浏览器,访问http://127.0.0.1:8000/djangobin/time/
。您将获得如下TemplateDoesNotExist
异常:
在 Django 开发网络应用时,你会多次遇到类似的错误。这里需要注意的重要事情是“模板加载器事后分析”部分。这个部分告诉你一个目录列表,Django 模板系统在抛出TemplateDoesNotExist
异常之前试图在其中找到文件。在调试错误原因时,这些信息可能非常有价值。
在继续之前,让我们将djangobin/datetimeeeeee.html
更改为djangobin/datetime.html
。打开浏览器再次访问http://127.0.0.1:8000/djangobin/time/
,错误应该已经消失了。
使用 render_to_response()缩短代码
大多数情况下,视图会执行以下任务:
- 使用模型从数据库中提取数据(我们将在 Django 的模型基础中讨论模型)。
- 加载模板文件,创建
Template
对象。 - 调用
render()
方法渲染模板。 - 创建
HttpResponse()
对象并发送给客户端。
Django 提供了一个名为render_to_response()
的函数来完成从第 2 步到第 5 步提到的所有事情。它接受两个参数,模板名和字典(将用于创建Context
对象)。要使用render_to_response()
必须先从django.shortcuts
模块导入。
修改views.py
文件中的today_is()
查看功能,使用render_to_response()
方法如下:
djangobin/django_project/djangobin/views.py
from django.shortcuts import HttpResponse, render_to_response
import datetime
#...
def today_is(request):
now = datetime.datetime.now()
return render_to_response('djangobin/datetime.html', {'now': now })
在http://127.0.0.1:8000/time/
刷新页面,Django 会再次用当前日期和时间问候你。
render()函数
render()
函数的工作方式与render_to_response()
相似,但它在 Django 模板中自动提供了一些额外的变量。一个这样的变量是request
,它是一个类型为HttpRequest
的对象。回想一下,每个视图函数都接受request
对象作为第一个参数。如果要访问模板内的request
对象,必须使用render()
而不是render_to_response()
。在这一点上,我们不能用request
对象做任何有用的事情,但只是给你一个例子,我们将尝试获取用于访问网页的方案。打开datetime.html
模板,进行以下更改:
决哥/决哥 _ 项目/决哥/样板/决哥/日期时间. html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Current Time</title>
</head>
<body>
{# This is a comment #}
{# check the existence of now variable in the template using if tag #}
{% if now %}
<p>Current date and time is {{ now }}</p>
{% else %}
<p>now variable is not available</p>
{% endif %}
<p>Current scheme: {{ request.scheme }}</p>
</body>
</html>
request
对象有一个名为scheme
的属性,它返回请求的方案。换句话说,如果使用http
请求页面,那么scheme
就是http
。另一方面,如果使用https
请求,那么scheme
就是https
。
打开浏览器,访问http://127.0.0.1:8000/time/
会得到如下输出。
注意request.scheme
处没有打印任何内容,因为我们使用的是render_to_response()
。要使用render()
功能,首先从django.shortcuts
模块导入,然后更新today_is()
视图功能使用render()
功能,如下所示:
djangobin/django_project/djangobin/views.py
from django.shortcuts import HttpResponse, render
import datetime
#...
def today_is(request):
now = datetime.datetime.now()
return render(request, 'djangobin/datetime.html', {'now': now})
render()
函数接受一个名为request
的附加第一个参数。刷新页面,您将获得以下输出:
在你的代码中,你应该总是使用render()
而不是render_to_response()
,因为render_to_response()
有可能在未来被弃用。
设置内容类型和 HTTP 状态
默认情况下,render()
快捷方式创建一个Content-Type: text/html
和 HTTP 状态为 200 的HttpResponse
对象。
我们可以分别使用content_type
和status
关键字参数覆盖Content-Type
头和 HTTP 状态。例如:
def my_view(request):
return render(request,'myapp/markdown.md', context_dict, content_type=`text/markdown`)
这会返回一个内容类型为text/markdown
且默认 HTTP 状态为 200 OK 的HttpResponse
对象。
def my_view(request):
return render(request,'myapp/404.html', context_dict, status=404)
这将返回一个内容类型为text/html
且 HTTP 状态为 404“未找到”的HttpResponse
对象。
def my_view(request):
return render(request,'myapp/404.html', context_dict, content_type='application/json', status=405)
这将返回一个内容类型为application/json
且 HTTP 状态为 404“不允许方法”的HttpResponse
对象。
在最后几章中,我们已经了解了很多关于 Django 模板的知识。在这一课中,我们将把一些东西用到。从位于djangobin/django_project/djangobin
的 djangobin 应用打开views.py
。此时,文件应该如下所示:
djangobin/django_project/djangobin/views.py
from django.shortcuts import HttpResponse
import datetime
def index(request):
return HttpResponse("<p>Hello Django</p>")
def today_is(request, username):
now = datetime.datetime.now()
html = "<html><body>Current date and time: {0}</body></html>".format(now)
return HttpResponse(html)
def profile(request, username):
return HttpResponse("<p>Profile page of #{}</p>".format(username))
def book_category(request, category='sci-fi'):
return HttpResponse("<p>Books in {} category</p>".format(category))
def extra_args(request, arg1=None, arg2=None):
return HttpResponse("<p>arg1: {} <br> arg2: {} </p>".format(arg1, arg2))
让我们更新today_is()
视图以使用模板,如下所示:
djangobin/django_project/djangobin/views.py
from django.shortcuts import HttpResponse
import datetime
from django import template
#...
def today_is(request):
now = datetime.datetime.now()
t = template.Template("<html><body>Current date and time {{ now }}</body></html>")
c = template.Context({'now': now})
html = t.render(c)
return HttpResponse(html)
启动服务器,如果还没有运行,访问http://127.0.0.1:8000/time/
。你应该得到当前的日期和时间,就像以前一样。
当然,我们正在使用 Django 模板,但是我们仍然在视图中硬编码原始的 HTML。以这种方式为一个大的 HTML 页面创建模板将是非常麻烦和乏味的。如果我们能把 HTML 写在一个外部文件中,那就更好了。我们开始吧。
回想一下Django 模板的基础知识一课,默认情况下,Django 会在每个已安装应用的templates
目录中搜索模板。在djangobin/templates/djangobin
目录中创建一个名为datetime.html
的新文件,然后向其中添加以下代码。
决哥/决哥 _ 项目/决哥/样板/决哥/日期时间. html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Current Time</title>
</head>
<body>
{# This is a comment #}
{# check the existence of now variable in the template using if tag #}
{% if now %}
<p>Current date and time is {{ now }}</p>
{% else %}
<p>now variable is not available</p>
{% endif %}
</body>
</html>
好了,现在我们已经创建了一个模板,下一个问题是如何在我们的视图中加载这个模板。原来template
包提供了一个叫做loader
的模块,这个模块又提供了一个get_template()
函数来加载模板。get_template()
函数接受一个指示模板名称的字符串参数,计算出模板的位置,打开文件并返回一个Template
对象。如果get_template()
找不到模板,则引发TemplateDoesNotExist
异常。
打开views.py
并修改today_is()
查看功能如下:
djangobin/django_project/djangobin/views.py
#...
def today_is(request):
now = datetime.datetime.now()
t = template.loader.get_template('djangobin/datetime.html')
c = template.Context({'now': now})
html = t.render(c)
return HttpResponse(html)
请注意,在第 5 行中,我们只是将应用的名称,后跟斜杠(/
),后跟模板名称(即datetime.html
)传递给get_template()
函数,而不是传递给datetime.html
的完整路径,后者是djangobin/templates/djangobin/datetime.html
。这是因为我们使用的是存储模板的 Django 约定。因此,Django 会自动找出模板在哪里。
同样,如果我们有一个名为论坛的应用和一个名为forums/templates/forums/discussions.html
的模板。然后我们将使用forums/discussions.html
加载discussions.html
模板。
其余代码照常工作。要检查您是否做对了所有事情,请再次访问http://127.0.0.1:8000/time/
。你会得到这样的TypeError
异常:
不要害怕。问题是在 Django 1.11 中不推荐使用Context
类。要解决这个问题,只需将变量名到值的字典映射传递给render()
方法。但是,如果您手动创建django.template.Template
对象,那么您仍然需要将Context
实例传递给render()
方法(奇怪但真实)。这里是更新后的today_is()
查看功能。
djangobin/django_project/djangobin/views.py
#...
def today_is(request):
now = datetime.datetime.now()
t = template.loader.get_template('djangobin/datetime.html')
html = t.render({'now': now})
return HttpResponse(html)
再次访问http://127.0.0.1:8000/time/
,你会看到现在的日期和时间和以前一样。
如果get_template()
函数找不到模板,就会抛出TemplateDoesNotExist
异常。查看TemplateDoesNotExist
错误修改today_is()
查看功能如下:
djangobin/django_project/djangobin/views.py
#...
def today_is(request):
now = datetime.datetime.now()
t = template.loader.get_template('djangobin/datetimeeeeee.html')
html = t.render({'now': now})
return HttpResponse(html)
打开浏览器,访问http://127.0.0.1:8000/djangobin/time/
。您将获得如下TemplateDoesNotExist
异常:
在 Django 开发网络应用时,你会多次遇到类似的错误。这里需要注意的重要事情是“模板加载器事后分析”部分。这个部分告诉你一个目录列表,Django 模板系统在抛出TemplateDoesNotExist
异常之前试图在其中找到文件。在调试错误原因时,这些信息可能非常有价值。
在继续之前,让我们将djangobin/datetimeeeeee.html
更改为djangobin/datetime.html
。打开浏览器再次访问http://127.0.0.1:8000/djangobin/time/
,错误应该已经消失了。
使用 render_to_response()缩短代码
大多数情况下,视图会执行以下任务:
- 使用模型从数据库中提取数据(我们将在 Django 的模型基础中讨论模型)。
- 加载模板文件,创建
Template
对象。 - 调用
render()
方法渲染模板。 - 创建
HttpResponse()
对象并发送给客户端。
Django 提供了一个名为render_to_response()
的函数来完成从第 2 步到第 5 步提到的所有事情。它接受两个参数,模板名和字典(将用于创建Context
对象)。要使用render_to_response()
必须先从django.shortcuts
模块导入。
修改views.py
文件中的today_is()
查看功能,使用render_to_response()
方法如下:
djangobin/django_project/djangobin/views.py
from django.shortcuts import HttpResponse, render_to_response
import datetime
#...
def today_is(request):
now = datetime.datetime.now()
return render_to_response('djangobin/datetime.html', {'now': now })
在http://127.0.0.1:8000/time/
刷新页面,Django 会再次用当前日期和时间问候你。
render()函数
render()
函数的工作方式与render_to_response()
相似,但它在 Django 模板中自动提供了一些额外的变量。一个这样的变量是request
,它是一个类型为HttpRequest
的对象。回想一下,每个视图函数都接受request
对象作为第一个参数。如果要访问模板内的request
对象,必须使用render()
而不是render_to_response()
。在这一点上,我们不能用request
对象做任何有用的事情,但只是给你一个例子,我们将尝试获取用于访问网页的方案。打开datetime.html
模板,进行以下更改:
决哥/决哥 _ 项目/决哥/样板/决哥/日期时间. html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Current Time</title>
</head>
<body>
{# This is a comment #}
{# check the existence of now variable in the template using if tag #}
{% if now %}
<p>Current date and time is {{ now }}</p>
{% else %}
<p>now variable is not available</p>
{% endif %}
<p>Current scheme: {{ request.scheme }}</p>
</body>
</html>
request
对象有一个名为scheme
的属性,它返回请求的方案。换句话说,如果使用http
请求页面,那么scheme
就是http
。另一方面,如果使用https
请求,那么scheme
就是https
。
打开浏览器,访问http://127.0.0.1:8000/time/
会得到如下输出。
注意request.scheme
处没有打印任何内容,因为我们使用的是render_to_response()
。要使用render()
功能,首先从django.shortcuts
模块导入,然后更新today_is()
视图功能使用render()
功能,如下所示:
djangobin/django_project/djangobin/views.py
from django.shortcuts import HttpResponse, render
import datetime
#...
def today_is(request):
now = datetime.datetime.now()
return render(request, 'djangobin/datetime.html', {'now': now})
render()
函数接受一个名为request
的附加第一个参数。刷新页面,您将获得以下输出:
在你的代码中,你应该总是使用render()
而不是render_to_response()
,因为render_to_response()
有可能在未来被弃用。
设置内容类型和 HTTP 状态
默认情况下,render()
快捷方式创建一个Content-Type: text/html
和 HTTP 状态为 200 的HttpResponse
对象。
我们可以分别使用content_type
和status
关键字参数覆盖Content-Type
头和 HTTP 状态。例如:
def my_view(request):
return render(request,'myapp/markdown.md', context_dict, content_type=`text/markdown`)
这会返回一个内容类型为text/markdown
且默认 HTTP 状态为 200 OK 的HttpResponse
对象。
def my_view(request):
return render(request,'myapp/404.html', context_dict, status=404)
这将返回一个内容类型为text/html
且 HTTP 状态为 404“未找到”的HttpResponse
对象。
def my_view(request):
return render(request,'myapp/404.html', context_dict, content_type='application/json', status=405)
这将返回一个内容类型为application/json
且 HTTP 状态为 404“不允许方法”的HttpResponse
对象。
Django 的模板继承
原文:https://overiq.com/django-1-11/template-inheritance-in-django/
最后更新于 2020 年 7 月 27 日
使用包含标签包含模板
{% include %}
标签允许我们将一个模板的内容包含在另一个模板中。它的语法是:
{% include template_name %}
template_name
可以是字符串或变量。让我们举个例子:
在 djangobin 应用的templates
目录(即templates/djangobin
)中创建新的名为nav.html
的文件,代码如下:
djangobin/django _ project/djangobin/templates/djangobin/nav . html
<nav>
<a href="#">Home</a>
<a href="#">blog</a>
<a href="#">Contact</a>
<a href="#">Career</a>
</nav>
假设你想把nav.html
的内容包含在datetime.html
文件中。为此,在datetime.html
上添加{% include 'djangobin/nav.html' %}
标签,如下所示:
决哥/决哥 _ 项目/决哥/样板/决哥/日期时间. html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% include 'djangobin/nav.html' %}
{# This is a comment #}
{# check the existence of now variable in the template using if tag #}
{% if now %}
<p>Current date and time is {{ now }}</p>
{% else %}
<p>now variable is not available</p>
{% endif %}
<p>Current scheme: {{ request.scheme }}</p>
</body>
</html>
打开浏览器,访问http://127.0.0.1:8000/time/
,应该可以在页面顶部看到一个简单的导航栏,如下图:
请注意,在{% include %}
标签中,我们使用相同的约定来指定我们在get_template()
和render()
方法中使用的模板名称。我们也可以通过上下文传递模板名称,而不是直接在模板中硬编码。
打开 djangobin 应用内的views.py
文件。此时,today_is()
视图功能应该是这样的:
djangobin/django_project/djangobin/views.py
#...
def today_is(request):
now = datetime.datetime.now()
return render(request, 'djangobin/datetime.html', {'now': now })
修改如下:
djangobin/django_project/djangobin/views.py
#...
def today_is(request):
now = datetime.datetime.now()
return render(request, 'djangobin/datetime.html',
{'now': now, 'template_name': 'djangobin/nav.html'})
打开datetime.html
模板,用{% include template_name %}
替换{% include "djangobin/nav.html" %}
标签。
决哥/决哥 _ 项目/决哥/样板/决哥/日期时间. html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% include template_name %}
{# This is a comment #}
{# check the existence of now variable in the template using if tag #}
{% if now %}
<p>Current date and time is {{ now }}</p>
{% else %}
<p>now variable is not available</p>
{% endif %}
<p>Current scheme: {{ request.scheme }}</p>
</body>
</html>
在http://127.0.0.1:8000/time/
刷新页面,你应该会看到和之前一样的当前日期和时间。
include
标签找不到模板怎么办?
在这种情况下,Django 将做以下两件事之一:
- 如果
DEBUG
模式设置为True
,那么 Django 模板系统将抛出TemplateDoesNotExist
异常。 - 如果
DEBUG
被设置为False
,那么它将会失败,在include
标签处不显示任何内容。
模板继承
到目前为止,我们一直在创建非常简单的模板,但在现实世界中,情况很少如此。为了给网站的所有页面一个共同的外观和感觉,一些 HTML 代码被重复。通常,页眉、导航、页脚和侧边栏在整个网站中保持不变。当我们想要修改页面的某些部分时,问题就出现了。为了更好地理解这个问题,让我们举个例子。
假设我们有两个页面,即home.html
和contact.html
,代码如下:
home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home</title>
</head>
<body>
<nav>
<a href="#">Home</a>
<a href="#">blog</a>
<a href="#">Contact</a>
<a href="#">Career</a>
</nav>
<aside>
<h3>Ut enim ad minim veniam</h3>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo.
</p>
</aside>
<article>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam
</p>
</article>
<footer> © 2017\. All rights reserved </footer>
</body>
</html>
contact.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Contact</title>
</head>
<body>
<nav>
<a href="#">Home</a>
<a href="#">blog</a>
<a href="#">Contact</a>
<a href="#">Career</a>
</nav>
<aside>
<h3>Ut enim ad minim veniam</h3>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo.
</p>
</aside>
<article>
<p>
<form>
Name: <input type="text" name="name" required> <br>
Email: <input type="email" name="email" required> <br>
Query: <textarea name="message" required></textarea> <br>
<input type="submit" name="submit" value="Send">
</form>
</p>
</article>
<footer> © 2017\. All rights reserved </footer>
</body>
</html>
假设在部署时,公司决定将导航中某个链接的锚文本从“职业”更改为“工作”。为了做出这种改变,我们必须手动访问每一页。如果我们有 50 或 100 页呢?如今,网站拥有数百甚至数千个页面是非常常见的。如你所见,这种方法不太适用。
如果你正在仔细阅读这一页,你可能会说“嘿,我们为什么不使用
标签?”。是的,当然我们可以使用{% include %}
标签非常容易地解决这个特殊的问题,但是正如我们将看到的,模板继承提供了一个更强大和优雅的方法来解决这样的问题。
全网站模板
Django 模板,我们将在即将到来的部分创建,并不特定于任何应用。事实上,项目中任何已安装的应用都可以使用这些模板。如前所述,默认情况下,Django 会在每个已安装应用的templates
目录中查找模板。但是 Django 没有定义任何默认位置来寻找不特定于任何应用的模板。因此,我们几乎可以在任何地方存储这些全网站模板。
为了保持简单和有条理,我们将把我们的站点范围模板存储在项目根目录(djangobin/django_project/
)内的templates
目录中。在djangobin/django_project/
目录中创建一个名为templates
的新目录。
现在我们必须通知 Django 这个目录。打开位于djangobin/django_project/django_project/
的settings.py
文件,找到TEMPLATES
变量。该变量包含与模板相关的所有设置。如果你没有改变什么,应该是这样的:
djangobin/django _ project/django _ project/settings . py
#...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
#...
这里我们关注的设置是DIRS
键。目前,它被设置为空列表。DIRS
键指定模板引擎应该查找模板的路径列表。为了让 Django 在我们新创建的templates
目录中搜索整个网站的模板,我们必须提供DIRS
到它的绝对路径。
在我的电脑上,模板的绝对路径是/home/overiq/djangobin/django_project/templates
。更改DIRS
键,使其看起来像这样。
djangobin/django _ project/django _ project/settings . py
#...
'DIRS': [
'/home/overiq/djangobin/django_project/',
],
#...
你应该用你的用户名替换overiq
。
但是有一个问题。如果您将您的 Django 项目发送给其他人,除非您和那个人共享完全相同的用户名和目录结构,否则它将不起作用。这里的要点是,在您的 Django 项目中永远不要硬编码路径。硬编码路径使我们的项目不太便携——这意味着它可能无法在其他计算机上运行。
解决方案是使用动态路径。
注意settings.py
文件顶部的BASE_DIR
变量。
djangobin/django _ project/django _ project/settings . py
#...
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
#...
该变量存储 Django 项目根目录的路径。就我而言,它指向/home/overiq/djangobin/django_project/
。在你的机器上,可能会有所不同。现在我们可以以BASE_DIR
的形式访问 Django 项目根目录。因此,引用其他目录变得非常容易。我们的全网站templates
目录在BASE_DIR
指向的目录内。
django_project/
├── db.sqlite3
├── djangobin
├── django_project
├── manage.py
└── templates <--- sitewide templates directory
3 directories, 2 files
我们现在可以使用 Python 的os.path.join()
函数来连接多个路径。更新DIRS
值,使其看起来像这样:
djangobin/django _ project/django _ project/settings . py
#...
'DIRS': [
os.path.join(BASE_DIR, 'templates'),
],
#...
从现在开始,Django 将在djangobin/django_project/templates
目录中搜索模板。请记住,Django 首先在由DIRS
设置指定的目录中搜索模板,然后在相应应用的templates
目录中搜索模板(假设APP_DIRS
设置为True
)。这意味着,如果您在 sitewide templates
目录中的djangobin
目录中有一个名为datetime.html
的模板,那么today_is()
视图功能将使用 sitewide templates
目录中的datetime.html
而不是 djangobin 应用的templates
目录。
访问视图中的设置
在查看功能中,有时您需要从settings.py
文件访问设置。为此,您必须从django.conf
包中导入settings
变量。
在 djangobin app 中打开views.py
,修改文件如下。
djangobin/django_project/djangobin/views.py
from django.shortcuts import HttpResponse, render
import datetime
from django.conf import settings
#...
def today_is(request):
now = datetime.datetime.now()
return render(request, 'djangobin/datetime.html', {
'now': now,
'template_name': 'djangobin/nav.html' ,
'base_dir': settings.BASE_DIR }
)
打开datetime.html
并在文件末尾附加以下<p>
标记。
决哥/决哥 _ 项目/决哥/样板/决哥/日期时间. html
#...
<p>Current scheme: {{ request.scheme }}</p>
<p>BASE_DIR : {{ base_dir }}</p>
</body>
</html>
打开浏览器,访问http://127.0.0.1:8000/time/
。您应该看到您的 Django 项目根目录的路径,如下所示:
行动中的模板继承
要使用模板继承,我们必须首先创建一个基础模板。基础模板只是一个简单的框架,可以用作网站所有其他页面的基础。基本模板定义了可以插入内容的区域。我们使用{% block %}
标签定义这个区域。在全网站templates
目录中创建一个名为base.html
的新文件,并向其中添加以下代码。
djangobin/django _ project/templates/base . html
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Default Title{% endblock %}</title>
</head>
<body>
<nav>
<a href="#">Home</a>
<a href="#">blog</a>
<a href="#">Contact</a>
<a href="#">Career</a>
</nav>
{% block content %}
{% endblock %}
<hr>
{% block footer %}
<p>Creative Commons Attribution 4.0 International Licence</p>
{% endblock %}
</body>
</html>
这是我们的基础模板。这里我们使用了一个您还没有遇到的新模板标签。{% block %}
标签接受单个参数block_name
来指定块的名称。这里我们创建了三个区块,即title
、content
和footer
。{% block %}
标签定义了一个可以被子模板覆盖的块。什么是子模板?使用base.html
作为其基础的模板,或者更正式地说,子模板是扩展基础模板的模板。子模板可以覆盖、添加或保留父块的内容。
要给子模板提供一些默认内容,请在{% block %}
和{% endblock %}
之间添加内容。
要使用我们的基础模板,我们首先必须创建一个子模板。从博客 app 打开datetime.html
模板,删除其中的所有内容,然后添加以下代码。
决哥/决哥 _ 项目/决哥/样板/决哥/日期时间. html
{% extends "base.html" %}
{% block content %}
{% if now %}
<p>Current date and time is {{ now }}</p>
{% else %}
<p>now variable is not available</p>
{% endif %}
<p>Current scheme: {{ request.scheme }}</p>
<p>BASE_DIR : {{ base_dir }}</p>
{% endblock %}
以下是它的工作原理:
第 1 行,{% extends "base.html" %}
告诉 Django 模板引擎,当前模板是子模板,继承自base.html
。这必须是子模板中的第一行,否则它将不起作用。当模板引擎遇到这一行时,它会立即加载父模板(即base.html
),然后用子模板中定义的同名内容块替换父模板中的内容块。如果在子模板中找不到匹配名称的块,则使用父模板中的内容。请注意,在子模板中,我们没有覆盖title
和footer
块,因此渲染子模板时将使用父模板的默认内容。
如果尚未运行,请启动服务器并访问http://127.0.0.1:8000/time/
。您应该会得到以下输出:
要更改子模板中的标题,请在datatime.html
中添加标题栏,如下所示:
决哥/决哥 _ 项目/决哥/样板/决哥/日期时间. html
{% extends "base.html" %}
{% block title %}
Date & Time
{% endblock %}
{% block content %}
{% if now %}
<p>Current date and time is {{ now }}</p>
{% else %}
<p>now variable is not available</p>
{% endif %}
<p>Current scheme: {{ request.scheme }}</p>
<p>BASE_DIR : {{ base_dir }}</p>
{% endblock %}
</body>
</html>
刷新页面,您将获得以下输出:
假设您已经覆盖了子模板中的一个块,但是您仍然想要父模板中的默认内容。为此,请使用{{ block.super }}
。{{ block.super }}
允许我们在子模板中引用父模板的默认内容。
如下修改datatime.html
中的标题栏:
决哥/决哥 _ 项目/决哥/样板/决哥/日期时间. html
{% extends "base.html" %}
{% block title %}
Date & Time - {{ block.super }}
{% endblock %}
{% block content %}
{% if now %}
<p>Current date and time is {{ now }}</p>
{% else %}
<p>now variable is not available</p>
{% endif %}
<p>Current scheme: {{ request.scheme }}</p>
<p>BASE_DIR : {{ base_dir }}</p>
{% endblock %}
刷新页面,您将获得以下输出:
Django 模型基础
原文:https://overiq.com/django-1-11/basics-of-models-in-django/
最后更新于 2020 年 7 月 27 日
在最后几章中,我们学到了很多东西。此时,您应该对什么是模板和视图有了很好的了解。在本章中,我们将了解 Django MTV 架构的模型部分。
数据库和模型
如今,大多数现代 web 应用都是由数据库驱动的。SQL 是访问数据库的事实语言。如果你已经做过一些网络编程,那么你可能知道在像 PHP 这样的语言中,混合 PHP 代码和 SQL 代码是非常常见的。除了杂乱之外,这种方法不鼓励分离关注点。对于 Django,我们没有这样做,我们不是执行原始的 SQL 查询来访问数据库,而是使用 Django ORM ,但是在此之前,我们必须使用模型来定义我们的数据。
那么什么是模型呢?
模型定义了您正在存储的数据的字段和行为。Django 模型大致映射到一个数据库表。一旦模型到位,Django 就提供了一个自动生成的 API(也称为 Django ORM)来访问数据库。
不要担心如果这一切都发生在你的头上。在接下来的几节中,我们将深入讨论一切。
功能清单
如您所知,在本教程中,我们将构建一个名为 djangobin 的代码共享应用。你可以在https://djangosnippets.org/看到这样一个网站的真实例子。然而,与 djangosnippets.org 不同的是,我们将允许用户为多种语言创建片段。与任何新应用一样,让我们编译一个我们希望在应用中实现的功能列表。这是完整的功能列表,供您参考。
- 能够通过选择语言、曝光和过期时间以及可选的标题和标签来创建片段。
- 按标签组织片段。
- 趋势片段列表。
- 列出最近的片段。
- 语法感知代码突出显示。
- 按语言列出的片段列表。
- 追踪片段访问次数。
- 下载代码片段,查看原始代码并删除代码片段。
- 按关键字搜索片段。
- 登录用户。
- 重置密码。
- 更新密码。
- 更改帐户首选项。
- 查看帐户详细信息
- 联系页面。
我们需要什么数据?
为了实现上一节中概述的特性,我们需要存储关于四个实体的信息:作者、语言、片段和标签。
作者实体表示关于作者的信息。作者有姓名、电子邮件、创作日期等属性。类似地,语言、代码片段和标签实体表示关于编程语言、代码片段和标签本身的信息,并且每个实体都有自己的一组属性。下面是我们希望存储在数据库中的属性列表。
作者:
- 名字——作者的名字
- 电子邮件-作者的电子邮件
- active -一个布尔字段,指定用户是否可以登录
- 创建日期-创建日期
- 上次登录日期-上次登录日期
语言:
- 名称-语言的名称。
- 语言代码-加载适当的 py grade * lexer 的语言代码。
- slug -语言的唯一标识符。
- 用于发送代码片段文件的 mime - mime。
- 文件扩展名-下载代码片段时使用的文件扩展名。
- 出版日期-创作日期
片段:
- 标题-代码片段的标题
- slug -代码片段的唯一标识符
- original_code -用户输入的代码
- 反白 _ 代码-语法-反白的 HTML 版本的代码
- 过期-代码片段的过期时间(例如,永不过期,1 个月,6 个月,1 年)
- 曝光-片段曝光(即公开、私人和非公开)*。
- 点击量-访问次数
- 标签-标签列表
标签:
- 名称-标签的名称
- slug -标签的唯一标识符。
**注意:**py segments 是一个突出显示代码片段的库。要了解更多信息,请访问本培训教程。
**注:**那么什么是 Snippet 上下文中的公共、私有和未列出?公共片段对每个人都是可见的,而私有片段只对你可见。要创建私人代码片段,您必须使用用户名和密码登录。此外,公共片段将出现在整个站点的搜索、存档页面和最近片段列表中。您也可以创建未列出的片段,这些项目不会出现在搜索、存档页面和最近的片段列表中。换句话说,未列出的片段对用户来说是不可见的,除非您共享片段网址。创建未列出的代码段不需要登录。有许多网站使用这样的系统,一个流行的例子是 pastebin.com。
在数据库的上下文中,每个实体对应一个表,每个属性对应一个列。在 Django 的上下文中,每个实体都是一个模型,每个属性都是一个字段。
默认情况下,Django 会自动为所有模型添加一个名为id
的主键字段。可以用id
或者pk
来参考。每次添加新记录时,该键都会自动递增。
你可能想知道鼻涕虫是什么意思。鼻涕虫是标识页面的网址的一部分。鼻涕虫只能由字母、数字、下划线或连字符组成。比如在 URL http://example.com/very-important-post/
中,鼻涕虫就是very-important-post
。正如我们将看到的,Django 可以从标题中自动生成 slug。
现在让我们看看这些实体之间存在什么类型的关系。
作者可以创建一个或多个片段,但一个片段只能属于一个作者。所以作者和片段之间存在一对多的关系。
作者可以创建一个或多个相同语言的片段,但一个片段只能属于一种语言。所以语言和代码片段之间还是存在一对多的关系。
一个片段可以用一个或多个标签来标记。同样,一个标签可以属于一个或多个片段。因此,片段和标签之间存在多对多的关系。
总之,我们有 2 个一对多关系和 1 个多对多关系。
我们将在后面看到如何在我们的模型中定义这些关系。现在,只要记住这些关系存在于我们的实体之间。
下面是每个实体的属性的更新列表以及它们之间的关系。
作者:
- 名字
- 电子邮件
- 活跃的
- 编成日期
- 上次登录日期
语言:
- 名字
- 语言代码
- 鼻涕虫
- 哑剧
- 文件扩展名
- 出版日期
片段:
- 标题
- 鼻涕虫
- 原始代码
- 突出显示的代码
- 呼气
- 暴露
- 打击
- 标签
- 作者的外键
- 语言的外键
标签:
- 名字
- 鼻涕虫
连接表(表示片段和标签之间的多对多关系)
- 片段和标签
创建模型
打开 djangobin app 目录内的models.py
文件。应该是这样的:
djangobin/django _ project/djangobin/models . py
from django.db import models
# Create your models here.
让我们从创建一个Author
模型开始。模型只是一个继承自models.Model
类的 Python 类。将以下代码添加到models.py
文件中。
djangobin/django _ project/djangobin/models . py
from django.db import models
# Create your models here.
class Author(models.Model):
pass
从语法上来说,Author
是一个有效的模型,虽然不包含任何字段(或属性),但仍然有效。通常,当我们定义模型时,我们指定所有的字段及其类型。如前所述,Django 会自动为所有模型添加一个名为id
的主键字段,因此无需手动定义。对于Author
模型,我们需要以下字段:
- 名字
- 电子邮件
- 活跃的
- 账户创建日期
- 上次登录时间
下面是添加这些字段的代码:
djangobin/django _ project/djangobin/models . py
from django.db import models
# Create your models here.
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
active = models.BooleanField(default=False)
created_on = models.DateTimeField()
last_logged_in = models.DateTimeField()
让我们浏览一下我们添加的代码。
在第 7 行,我们正在定义类型为CharField
的name
字段,在数据库的上下文中把CharField
类型想象成VARCHAR
。max_length
参数指定该字段可以存储的最大字符数。
在第 8 行,我们定义了类型为EmailField
的email
字段。unique=True
表示该字段的值在整个表中必须是唯一的。我们可以将unique
和max_length
参数传递给几乎所有类型的字段。
在第 9 行,我们定义了active
字段,该字段指定用户是否处于活动状态。该字段的类型为BooleanField
。一个BooleanField
字段只能取两个值True
或者False
。你也可以把1
和0
传给它。default
是一个特殊的参数,用于设置列的默认值。此默认值用于创建新记录时未提供列值的情况。
在第 10 行和第 11 行,我们定义了两个字段,DateTimeField
类型的created_on
和last_logged_in
。一个DateTimeField
用来存储日期和时间,对应 Python 中的datetime.datetime
对象。
此时,您可能会想“数据库已经提供了各种类型的数据类型来存储数据”。那么在 Django 重新定义它们的目的是什么呢?
它们有两个重要目的:
- 他们在数据保存到数据库之前对其进行验证。
- 它们还控制如何使用 Widgets 在表单中显示数据。
小部件指的是字段的 HTML 表示。每个字段都有一个默认的小部件。例如Charfield
的默认小部件是<input type="text" >
。当我们创建表单时,小部件的效用开始发挥作用。我们将在以后的课程中讨论如何做到这一点。
注意models.Model
和模型字段(即models.CharField
、models.EmailField
等)都是 Python 类。在 Python 的上下文中CharField
是一个字符串。在数据库的上下文中CharField
是VARCHAR
。CharField
是非常特殊的字段,因为它是所有其他基于字符串的字段的基础。Django 不验证CharField
类型字段的内容。换句话说,Django 不会检查提供给CharField
字段的值。仔细想想还是有道理的。由于CharField
本质上是一个字符串字段,它可以接受几乎任何类型的数据。所以,1
、mymail@example.com
、100e100
、DJANG0
、%^%*&^%$#
都是有效的数据示例。
然而,继承自CharField
的字段确实执行验证。例如EmailField
。EmailField
常用于存储电子邮件,如果你试图在里面存储其他东西,它会引发异常。
Django 提供了许多其他字段来存储不同类型的数据。下表列出了一些常见字段:
字段名 | 它是做什么用的? |
---|---|
CharField |
用于存储小到中等大小字符串的字段。它充当其他基于字符串的字段的基础。它不提供任何验证 |
SlugField |
一CharField 储存蛞蝓。它只接受字母、数字、下划线和连字符。它提供验证。 |
DateField |
存储日期的字段。它对应于 Python 的datetime.date 实例。它不是从CharField 继承的。它提供验证。 |
DateTimeField |
存储日期和时间的字段。它对应于 Python 的datetime.datetime 实例。它提供验证。 |
EmailField |
存储电子邮件地址的CharField 。它验证输入的值是否是有效的电子邮件值 |
URLField |
一个CharField 用来存储 URL。它提供验证。注意与SlugField 不同。SlugField 只包含网址的一部分,不包含网址本身。 |
TextField |
存储大量文本的字段。使用此字段存储博文、新闻、故事等内容。在 Python 中,该字段转换为字符串。在数据库中,它被翻译成TEXT 字段。像CharField 一样,该字段不提供任何验证 |
BooleanField |
存储True 或False 的字段。在 Python 的上下文中,它翻译成布尔型True 和False 。在数据库的上下文中(我们使用的是 SQLite),它存储为整数(1 ) True 和(0 ) False 。记住 SQLite 有整数类型来处理整数。 |
IntegerField |
存储从-2147483648 到2147483647 整数值的字段。它检查输入的值是否为数字。 |
DecimalField |
存储十进制数的字段。 |
ForeignKey |
定义一对多关系的字段。 |
ManyToManyField |
定义多对多关系的字段。 |
OneToOneField |
定义一对一关系的字段。 |
可选字段参数
以下是适用于所有类型字段的可选字段参数。要使用这些参数,请将它们作为关键字参数传递给模型字段类。
空白的
正如我们将看到的,Django 可以通过从模型类中推断字段类型来自动生成表单。此外,Django 希望您在每个字段中输入数据。如果您试图提交一个字段而不输入任何数据,那么 Django 将显示验证错误。在模型中定义字段时,将字段设为可选设置blank=True
。该参数的默认值为False
。设置blank=True
后,如果没有给字段提供任何值,Django 会自动在字段中插入一个空字符串。
空
如果设置为True
,Django 会将空值作为NULL
存储在数据库中。默认为False
。Django 不建议为基于字符串的字段设置null=True
。因为如果您没有为该字段提供任何值,那么 Django 将自动向该字段插入一个空字符串。如果您仍然为基于字符串的字段添加null=True
,那么您将得到“无数据”的两个可能值:NULL
和空字符串。在这种情况下,Django 将插入NULL
而不是空字符串。但是,在某些情况下,在基于字符串的字段中设置null=True
会很有用。我们将在下一节看到这样一个例子。
初学者经常会发现blank
和null
混淆不清,最后使用不当。如果您仍然感到困惑,请记住,blank
参数的效用仅限于验证机制,而不是您的数据库。而null
在数据库级别控制一个字段是否可以是NULL
。
系统默认值
您可以通过此参数来设置字段的默认值。当在表中插入新记录时未提供字段值时,将使用默认值。
独一无二的
如果设置为True
,则字段中的值在整个表格中是唯一的。默认值为False
。
帮助 _ 文本
此参数用于指定帮助字符串。帮助字符串描述了如何使用字段或关于字段的一些其他重要信息。
db_index
如果设置为True
,将创建一个索引列。默认为False
。
数据库 _ 列
此参数用于指定数据库中列的名称。如果不提供这个选项,Django 将使用模型中的字段名。
选择
此参数将字段的预定义值指定为元组。例如:
STATUS = (
('pen', 'Pending'),
('pub', 'Published'),
('tra', 'Trash'),
)
class Post(models.Model):
status = models.CharField(choices=STATUS)
如果指定了此参数,默认小部件将是一个<select>
框,而不是
<input type="text">
。每个元组中的第一个元素是我们分配给模型实例的,第二个元素的值将显示给用户。
还有许多其他可选参数,要查看完整列表,请查看文档。
相识
不要被各种字段类型及其参数所淹没。你不需要记住所有的。假设你想用URLField
想知道它的参数。打开浏览器,访问https://docs.djangoproject.com/en/1.11/ref/models/fields/。该页面包含关于 Django 1.11 中每个字段类型及其可用选项(参数)的文档。在页面搜索URLField
关键字。向下滚动页面的一半,直到找到描述URLField
的部分。
URLField
文档告诉您以下内容:
第一行指定类签名。换句话说,应该传递什么参数来创建
URLField
的实例。是
CharField
场表示继承自CharField
。该字段的默认表单部件是
TextInput
。TextInput
是对应于<input type="text">
元素的小部件类的名称。换句话说,URLField
将使用<input type="text">
标签显示。我们将在Django 表单基础一章中了解更多关于小部件以及如何覆盖它们的信息。下一行告诉我们,我们还可以向
URLField
传递一个可选的max_length
参数,以限制该字段可以容纳的字符数。如果不指定max_length
,将使用200
的默认值。
一个精明的读者可能会说,但是类签名中的第二个参数,即
**options
呢?
**options
是指所有字段都可以接受的可选关键字参数。在前一节中,我们已经介绍了其中的一些。
假设您想要创建唯一的URLField
长度字段100
。下面是这样做的代码:
url = models.URLField(max_length=100, unique=True)
默认情况下,所有字段都是必需的。如果您没有为必填字段指定数据,Django 将显示验证错误。要使网址字段可选,添加blank=True
。
url = models.URLField(max_length=100, unique=True, blank=True)
你注意到什么奇怪的事情了吗?
这里我们已经将url
字段设为可选。这意味着如果您不输入任何值,Django 将自动插入一个空字符串。但是我们也将unique
设置为True
,这意味着没有两列可以有相同的值。要解决冲突,您必须将null=True
添加到url
字段定义中:
url = models.URLField(max_length=100, unique=True, blank=True, null=True)
完成其他模型
让我们为剩下的模型添加代码。
djangobin/django _ project/djangobin/models . py
from django.db import models
from .utils import Preference as Pref
# Create your models here.
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
active = models.BooleanField(default=False)
created_on = models.DateTimeField(auto_now_add=True)
last_logged_in = models.DateTimeField(auto_now=True)
class Language(models.Model):
name = models.CharField(max_length=100)
lang_code = models.CharField(max_length=100, unique=True)
slug = models.SlugField(max_length=100, unique=True)
mime = models.CharField(max_length=100, help_text='MIME to use when sending snippet as file.')
file_extension = models.CharField(max_length=10)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
class Snippet(models.Model):
title = models.CharField(max_length=200, blank=True)
original_code = models.TextField()
highlighted_code = models.TextField()
expiration = models.CharField(max_length=10, choices=Pref.expiration_choices)
exposure = models.CharField(max_length=10, choices=Pref.exposure_choices)
hits = models.IntegerField(default=0)
slug = models.SlugField()
created_on = models.DateTimeField(auto_now_add=True)
class Tag(models.Model):
name = models.CharField(max_length=200, unique=True)
slug = models.CharField(max_length=200, unique=True)
除了传递给DateTimeField
构造函数的auto_now
和auto_now_add
参数外,以上代码没有什么新内容。当设置为True
时,auto_now
参数会在每次保存对象(即Model
的实例)时自动将字段值更新为当前日期和时间。我们使用这个参数来存储最近修改的时间戳。关于这个参数需要注意的重要一点是,它只在模型实例上调用Model.save()
方法时更新字段。在这一点上,我们还没有涵盖save()
方法,所以请记住这一点,当我们在Django ORM basic一课中讨论save()
方法时,它会更有意义。
当auto_now_add
参数自动设置为True
时,会将字段值设置为首次创建对象时的当前日期和时间。
也可以将这两个参数传递给DateField
。在这种情况下,auto_now
会在每次保存对象时自动将字段值更新为当前日期。设置为True
时auto_now_add
参数自动将字段值设置为对象首次创建时的当前日期。
Snippet
模型中expiration
和exposure
字段的选择来自于utils.py
文件中定义的Preference
类,如下所示:
决哥/决哥 _ 项目/决哥/utils.py】
class Preference:
SNIPPET_EXPIRE_NEVER = 'never'
SNIPPET_EXPIRE_1WEEK = '1 week'
SNIPPET_EXPIRE_1MONTH = '1 month'
SNIPPET_EXPIRE_6MONTH = '6 month'
SNIPPET_EXPIRE_1YEAR = '1 year'
expiration_choices = (
(SNIPPET_EXPIRE_NEVER, 'Never'),
(SNIPPET_EXPIRE_1WEEK, '1 week'),
(SNIPPET_EXPIRE_1MONTH, '1 month'),
(SNIPPET_EXPIRE_6MONTH, '6 month'),
(SNIPPET_EXPIRE_1YEAR, '1 year'),
)
SNIPPET_EXPOSURE_PUBLIC = 'public'
SNIPPET_EXPOSURE_UNLIST = 'unlisted'
SNIPPET_EXPOSURE_PRIVATE = 'private'
exposure_choices = (
(SNIPPET_EXPOSURE_PUBLIC, 'Public'),
(SNIPPET_EXPOSURE_UNLIST, 'Unlisted'),
(SNIPPET_EXPOSURE_PRIVATE, 'Private'),
)
我们在一个单独的类中定义选择,以便应用的其他模块可以轻松地访问它们。
为我们的模型添加关系
在本节中,我们将学习如何在模型之间添加关系:
回想一下,我们已经概述了以下模型之间的关系。
- 作者和片段模型之间的一对多关系。
- 语言和代码片段模型之间的一对多关系。
- 片段和标记模型之间的多对多关系。
创建一对多关系很简单,只需在关系的多面添加ForeignKey
字段即可。ForeignKey
构造函数需要两个参数:与模型相关的类名和on_delete
选项。on_delete
选项指定当父表中的行被删除时,Django 如何处理子表中的行。下表列出了on_delete
选项的可能值。
[计]选项 | 描述 |
---|---|
CASCADE |
删除父表中的行时,自动删除子表中的一行或多行。 |
PROTECT |
如果子表中有相关行,则限制删除父表中的行。 |
SET_NULL |
将子表中的外键列设置为NULL ,并从父表中删除该行。 |
SET_DEFAULT |
将子表中的外键列设置为某个默认值。要使用该选项,您必须使用default 参数设置一些默认值。 |
DO_NOTHING |
如果父表中的记录被删除,则对子表不采取任何操作。如果您的数据库强制引用完整性,那么这将产生一个错误。 |
SET() |
通过可调用的为外键列设置一个值。这不是 SQL 标准的一部分。 |
现在让我们将ForeignKey
字段添加到模型中。打开models.py
文件,向Snippet
模型添加两个ForeignKey
字段,如下所示:
djangobin/django _ project/djangobin/models . py
#...
class Snippet(models.Model):
title = models.CharField(max_length=200, blank=True)
original_code = models.TextField()
highlighted_code = models.TextField()
expiration = models.CharField(max_length=10, choices=Pref.expiration_choices)
exposure = models.CharField(max_length=10, choices=Pref.exposure_choices)
hits = models.IntegerField(default=0)
slug = models.SlugField()
created_on = models.DateTimeField(auto_now_add=True)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
这里我们添加了两个一对多关系:
- 第一个在
Language
和Snippet
之间。 - 第二个在
Author
和Snippet
之间。
同样,创建多对多关系也很容易。在我们的例子中,Snippet
和Tag
模型之间存在多对多的关系。要创建多对多关系,请将ManyToManyField
字段添加到关系的任何一方。换句话说,您可以在Snippet
车型或Tag
车型上添加ManyToManyField
。就像ForeignKey
字段一样,它要求你传递你要连接的型号名称。
下面是在Snippet
和Tag
模型之间添加多对多关系的代码。
djangobin/django _ project/djangobin/models . py
#...
class Snippet(models.Model):
title = models.CharField(max_length=200, blank=True)
original_code = models.TextField()
highlighted_code = models.TextField()
expiration = models.CharField(max_length=10, choices=Pref.expiration_choices)
exposure = models.CharField(max_length=10, choices=Pref.exposure_choices)
hits = models.IntegerField(default=0)
slug = models.SlugField()
created_on = models.DateTimeField(auto_now_add=True)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
tags = models.ManyToManyField('Tag')
class Tag(models.Model):
name = models.CharField(max_length=200, unique=True)
slug = models.CharField(max_length=200, unique=True)
#...
请注意,在第 15 行,我将模型的名称作为字符串而不是类对象传递,这是因为Tag
模型的定义在Snippet
模型的下面。你可以在ForeignKey
领域做同样的事情。此时,models.py
文件应该是这样的:
djangobin/django _ project/djangobin/models . py
from django.db import models
from .utils import Preference as Pref
# Create your models here.
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
active = models.BooleanField(default=False)
created_on = models.DateTimeField(auto_now_add=True)
last_logged_in = models.DateTimeField(auto_now=True)
class Language(models.Model):
name = models.CharField(max_length=100)
lang_code = models.CharField(max_length=100, unique=True)
slug = models.SlugField(max_length=100, unique=True)
mime = models.CharField(max_length=100, help_text='MIME to use when sending snippet as file.')
file_extension = models.CharField(max_length=10)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
class Snippet(models.Model):
title = models.CharField(max_length=200, blank=True)
original_code = models.TextField()
highlighted_code = models.TextField()
expiration = models.CharField(max_length=10, choices=Pref.expiration_choices)
exposure = models.CharField(max_length=10, choices=Pref.exposure_choices)
hits = models.IntegerField(default=0)
slug = models.SlugField()
created_on = models.DateTimeField(auto_now_add=True)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
tags = models.ManyToManyField('Tag')
class Tag(models.Model):
name = models.CharField(max_length=200, unique=True)
slug = models.CharField(max_length=200, unique=True)
添加网址模式和视图
目前,我们的 app djangobin 的urls.py
文件包含 6 种 URL 模式:
决哥/决哥 _ 项目/决哥/URL . py】
from django.conf.urls import url
from . import views
# app_name = 'djangobin'
urlpatterns = [
url(r'^time/$', views.today_is, name='time'),
url(r'^$', views.index, name='index'),
url(r'^user/(?P<username>[A-Za-z0-9]+)/$', views.profile, name='profile'),
url(r'^books/$', views.book_category, name='book_category'),
url(r'^books/(?P<category>[\w-]+)/$', views.book_category, name='book_category'),
url(r'^extra/$', views.extra_args, name='extra_args'),
]
我们已经完成了前三个和后三个网址模式及其相应的视图功能。所以你可以安全地删除它们。
接下来,我们将添加 4 个新的网址模式。下表描述了每个网址模式的作用:
path | 描述 |
---|---|
/trending/ |
显示趋势(最常查看的)片段列表。 |
/trending/<language> |
显示特定语言的趋势片段列表 |
/<snippet_id>/ |
显示突出显示的片段 |
/tag/<tag>/ |
显示用特定标签标记的片段列表。 |
打开urls.py
添加更新,包括新的网址模式,如下所示:
决哥/决哥 _ 项目/决哥/URL . py】
#...
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^user/(?P<username>[A-Za-z0-9]+)/$', views.profile, name='profile'),
url('^trending/$', views.trending_snippets, name='trending_snippets'),
url('^trending/(?P<language_slug>[\w]+)/$', views.trending_snippets, name='trending_snippets'),
url('^(?P<snippet_slug>[\d]+)/$', views.snippet_detail, name='snippet_detail'),
url('^tag/(?P<tag>[\w-]+)/$', views.tag_list, name='tag_list'),
]
现在在views.py
中的index()
视图后增加 3 个新的视图功能如下:
djangobin/django_project/djangobin/views.py
from django.shortcuts import HttpResponse, render
def index(request):
return HttpResponse("<p>Hello Django</p>")
def snippet_detail(request, snippet_slug):
return HttpResponse('viewing snippet #{}'.format(snippet_slug))
def trending_snippets(request, language_slug=''):
return HttpResponse("trending {} snippets".format(language_slug if language_slug else ""))
def tag_list(request, tag):
return HttpResponse('viewing tag #{}'.format(tag))
def profile(request, username):
return HttpResponse("<p>Profile page of #{}</p>".format(username))
现在,视图函数只是显示简单输出的存根。我们将继续更新。
添加模型方法
Django 模型只是继承自models.Model
类的普通 Python 类。这意味着我们也可以在模型类中定义普通的 Python 方法。
为了使用 Pygments 实现语法高亮显示,我们必须向Language
和Snippet
模型添加一些辅助方法。但是在此之前,让我们首先通过键入以下内容来安装 Pygments:
$ pip install pygments
突出显示代码片段的第一步是确定 lexer 的名称。lexer 是一个分析编程语言语法的程序。Pygments 附带了大量语言的 lexers,每个语言都有一个唯一的名称。我们可以使用pygments.lexers
包的get_lexer_by_name()
功能获取 lexer。
让我们给Language
模型添加一个方法,为给定的语言返回适当的 lexer:
djangobin/django _ project/djangobin/models . py
from django.db import models
from pygments import lexers, highlight
from pygments.formatters import HtmlFormatter, ClassNotFound
from .utils import Preference as Pref
#...
class Language(models.Model):
#...
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
def get_lexer(self):
return lexers.get_lexer_by_name(self.lang_code)
给定一个Language
实例l1
,我们现在可以通过调用get_lexer()
方法获得 lexer,如下所示:
l1.get_lexer()
下一步是向Snippet
模型添加一个知道如何突出显示片段的方法。打开models.py
文件,将highlight()
方法添加到Snippet
模型,如下所示:
djangobin/django _ project/djangobin/models . py
#...
class Snippet(models.Model):
#...
author = models.ForeignKey(Author, on_delete=models.CASCADE)
tags = models.ManyToManyField('Tag')
def highlight(self):
formatter = HtmlFormatter(linenos=True)
return highlight(self.original_code, self.language.get_lexer(), formatter)
#...
为了突出显示代码,Pygments 提供了highlight()
功能。它需要三个参数,突出显示的代码、lexer 和格式化程序。并返回高亮显示的代码。您可以在这里了解更多关于highlight()
功能的参数。
现在,我们的模型能够突出显示片段,但是我们要到第 16 章才能测试它。
为了使处理模型实例更容易,Django 提倡在模型类中实现以下两种方法。
__str__()
方法get_absolute_url()
方法
先说__str__()
法。
str ()法
__str__()
方法告诉 Python 如何以人类可读的形式显示对象。如果您没有定义__str__()
方法,那么它的默认实现将像这样打印对象:
<Snippet: Snippet object>
这种表示没有太大帮助,因为除了模型名称之外,它没有给出关于对象的任何其他信息。
类似地,对象列表如下所示:
[<Snippet: Snippet object>, <Snippet: Snippet object>, <Snippet: Snippet object>]
如您所见,列表中的对象实际上是无法区分的。
我们可以通过简单地在模型类中定义一个__str__()
方法来解决这些问题。打开models.py
文件,在每个模型类中定义__str__()
,如下所示:
djangobin/django _ project/djangobin/models . py
#...
class Author(models.Model):
#...
last_logged_in = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name + " : " + self.email
class Language(models.Model):
#...
updated_on = models.DateTimeField(auto_now=True)
def get_lexer(self):
return lexers.get_lexer_by_name(self.lang_code)
def __str__(self):
return self.name
class Snippet(models.Model):
#...
created_on = models.DateTimeField(auto_now_add=True)
def highlight(self):
formatter = HtmlFormatter(linenos=True)
return highlight(self.original_code, self.language.get_lexer(), formatter)
def __str__(self):
return (self.title if self.title else "Untitled") + " - " + self.language.name
class Tag(models.Model):
#...
slug = models.CharField(max_length=200, unique=True)
def __str__(self):
return self.name
get_absolute_url()方法
get_absolute_url()
方法返回对象的规范网址。最终,我们的大多数模型实例将显示在其唯一的网址中。get_absolute_url()
方法的工作是返回这个唯一的网址。
使get_absolute_url()
特别的是,与{% url %}
标签和reverse()
函数不同,我们可以在 Python 代码和模板中使用get_absolute_url()
。
除此之外,Django 管理站点(我们将在第课中讨论)使用get_absolute_url()
方法在对象编辑页面中创建一个指向对象公共视图的“查看站点”链接。
最重要的是,我们从定义get_absolute_url()
方法中得到的最重要的好处是,如果我们需要更改 URL 的结构,那么我们只需要修改get_absolute_url()
方法,所有的模板和 Python 模块都会自动拾取更改。这也意味着您不必记住 Snippet 或 Tag 对象是否接受 id 或任何其他参数。
让我们在所有的模型类中定义get_absolute_url()
。打开models.py
,修改如下:
djangobin/django _ project/djangobin/models . py
#...
from django.shortcuts import reverse
from .utils import Preference as Pref
#...
class Language(models.Model):
#...
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('djangobin:trending_snippets', args=[self.slug])
class Snippet(models.Model):
#...
def __str__(self):
return (self.title if self.title else "Untitled") + " - " + self.language.name
def get_absolute_url(self):
return reverse('djangobin:snippet_detail', args=[self.slug])
class Tag(models.Model):
#...
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('djangobin:tag_list', args=[self.slug])
使用内部元类定制模型行为
Meta
类处理不特定于任何字段的选项。例如,数据库表的名称、查询数据库时的默认排序顺序、添加复合索引等等。添加Meta
类完全是可选的。我们将Meta
类定义为模型类中的嵌套类。
以下是我们可以在Meta
类中指定的一些常见选项。
排序
此选项指定对象的默认排序方案。如果指定,每次我们使用 Django ORM 访问数据时都会使用这个排序方案(我们将在接下来的课程中学习什么是 Django ORM。).它接受字段名称的列表或元组。例如:
class Post
title = models.CharField()
content = models.TextField()
pub_date = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-pub_date']
本示例按降序将Post
对象的默认顺序设置为pub_date
。如果我们现在列出Post
对象,Django 会自动按照相反的时间顺序返回它们(最新的第一个)。
独特 _ 在一起
此选项指定两个或多个字段名组合在一起时的唯一约束。它接受两个或多个字段名作为元组的元组。例如:
class Author:
name = models.CharField()
email = models.EmailField()
class Meta:
unique_together = (('name', 'email'),)
本示例添加了跨越两列name
和email
的UNIQUE
约束。换句话说,name
和email
的价值观放在一起一定是独一无二的
指数
此选项用于在一个或多个字段上创建索引。它接受一个models.Index
实例列表。models.Index
有两个参数:fields
和一个可选的name
。fields
指的是要添加索引的模型字段列表,name
是索引的名称。如果没有指定name
,Django 将自动生成一个名称。在fields
列表中指定多个字段,将创建一个复合索引(即在多个列上的索引)。这里有一个例子:
class Employee
first_name = models.CharField()
last_name = models.CharField()
address = models.CharField()
class Meta:
indexes = [
models.Index(fields=['last_name', 'first_name']),
models.Index(fields=['address'], name='address_idx'),
]
这个例子创建了两个索引:在last_name
和first_name
字段上的综合索引和在address
字段上的常规索引。
详细名称
此选项指定模型的单数名称。它接受一个字符串。例如:
class JusticeLeague
#...
class Meta:
verbose_name = 'JusticeLeague'
verbose_name
选项的值在 Django 管理站点的很多地方都有使用,例如,对象编辑页面、对象列表页面等等。如果你没有定义这个选项,那么 Django 将生成一个类名称的蒙版,也就是说JusticeLeague
将变成justice league
。
verbose _ name _ 复数
此选项指定模型的复数名称。它接受一个字符串。例如:
class Category(models.Model):
#...
class Meta:
verbose_plural_name = "Category"
就像verbose_name
选项一样,verbose_name_plural
的值在 Django 管理站点的许多页面中使用。如果不定义此选项,Django 将使用 verbose_name + s
。
db_table
默认情况下,Django 会自动为我们创建表名。例如,如果我们有一个名为论坛的应用和一个名为Bounty
的模型类,那么表名将是forum_bounty
。如果出于某种原因,您想要覆盖此命名约定,请使用db_table
选项。它接受字符串作为参数。例如:
class Bounty
#...
class Meta:
db_table = "bounties"
有关Meta
选项的完整列表,请访问 Django 文档。
现在让我们更新我们的models.py
来为Language
、Snippet
和Tag
模型添加一些元选项,如下所示:
djangobin/django _ project/djangobin/models . py
#...
class Language(models.Model):
#...
updated_on = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['name']
#...
class Snippet(models.Model):
#...
tags = models.ManyToManyField('Tag')
class Meta:
ordering = ['-created_on']
#...
class Tag(models.Model):
name = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
class Meta:
ordering = ['name']
#...
我们将继续在models.py
文件中添加缺失的元素,但目前来说,这已经足够了。
我们现在已经创建了一些模型。那么接下来呢?
下一步是从模型创建数据库表。为此,我们使用了一种叫做迁移的方法。
Django 的迁移
最后更新于 2020 年 7 月 27 日
迁移是改变数据库模式的一种方式。同时,迁移还可以作为数据库的版本控制。在 Django 中,使用模型的常见工作流程如下:
- 在
models.py
文件中创建或更改模型。 - 创建迁移文件。
- 使用步骤 2 中创建的迁移文件提交对数据库的更改。
在开发过程中,创建新模型和更改现有模型是非常常见的。Django 迁移系统允许我们随着应用的发展改变数据库模式。除此之外,迁移系统还跟踪对数据库所做的更改,以便我们可以及时向前或向后移动。如果我们不使用迁移系统,那么我们将不得不手动跟踪数据库更改,然后在服务器上执行原始 SQL 来提交更改。
当你和一个团队一起工作的时候,迁移系统也非常出色。假设您对数据库进行了一些重要的更改,并将其推送到远程服务器。您的团队成员需要做的就是进行迁移并应用它。仅此而已。他们不需要手动执行任何 SQL。
让我们创建一些迁移。
检查模型的错误
Django 提供了一个check
子命令,用于检查models.py
文件中的错误。
$ ./manage.py check
System check identified no issues (0 silenced).
"System check identified no issues (0 silenced)"
表示没有发现错误。现在,我们准备好创建我们的第一次迁移。在创建迁移文件之前,您应该始终使用check
命令来查找模型中的错误。
创建迁移
我们使用makemigrations
子命令来创建迁移。打开终端并键入以下命令。
$ ./manage.py makemigrations djangobin
您将看到如下输出:
Migrations for 'djangobin':
djangobin/migrations/0001_initial.py
- Create model Author
- Create model Language
- Create model Snippet
- Create model Tag
- Add field tags to snippet
在前面的命令中,我们指定了要为其创建迁移的应用的名称,即 djangobin。如果我们不指定应用名称,那么 Django 将为所有已安装的应用创建迁移,假设自上次makemigrations
运行以来,相应应用的models.py
文件中有任何更改。换句话说,只有当 Django 检测到models.py
文件中的变化时,它才会创建迁移。再次尝试运行makemigrations
命令,但这次没有指定应用的名称。
$ ./manage.py makemigrations
No changes detected
因为自上次执行makemigrations
命令后没有新的变化,所以得到"No changes detected"
。
那么makemigrations
命令做了什么?
makemigrations
命令在 djangobin 应用的migrations
子目录中创建了一个名为0001_initial.py
的迁移文件。这个迁移文件反映了我们模型的当前状态。我们将使用这个迁移文件来更新我们的数据库。换句话说,Django 将使用迁移文件创建 SQL 查询来更新数据库的当前状态,使其与models.py
同步。在下一节中,我们将查看迁移文件。
检查迁移
在 djangobin app 的migrations
子目录中打开名为0001_initial.py
的迁移文件。应该是这样的:
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-05-25 06:14
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Author',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('email', models.EmailField(max_length=254, unique=True)),
('active', models.BooleanField(default=False)),
('created_on', models.DateTimeField(auto_now_add=True)),
('last_logged_in', models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
name='Language',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('lang_code', models.CharField(max_length=100, unique=True)),
('slug', models.SlugField(max_length=100, unique=True)),
('mime', models.CharField(help_text='MIME to use when sending snippet as file.', max_length=100)),
('file_extension', models.CharField(max_length=10)),
('created_on', models.DateTimeField(auto_now_add=True)),
('updated_on', models.DateTimeField(auto_now=True)),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='Snippet',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(blank=True, max_length=200)),
('original_code', models.TextField()),
('highlighted_code', models.TextField()),
('expiration', models.CharField(choices=[('never', 'Never'), ('1 week', '1 week'), ('1 month', '1 month'), ('6 month', '6 month'), ('1 year', '1 year')], max_length=10)),
('exposure', models.CharField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('private', 'Private')], max_length=10)),
('hits', models.IntegerField(default=0)),
('slug', models.CharField(max_length=100)),
('created_on', models.DateTimeField(auto_now_add=True)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='djangobin.Author')),
('language', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='djangobin.Language')),
],
options={
'ordering': ['-created_on'],
},
),
migrations.CreateModel(
name='Tag',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, unique=True)),
('slug', models.CharField(max_length=200, unique=True)),
],
options={
'ordering': ['name'],
},
),
migrations.AddField(
model_name='snippet',
name='tags',
field=models.ManyToManyField(to='djangobin.Tag'),
),
]
如您所见,迁移文件包含一个继承自migrations.Migration
的常规 Python 类。在第 11 行中,initial=True
表示这是我们对 djangobin 应用的第一次迁移。dependencies
属性定义了在迁移之前应该运行哪些迁移。因为这是我们第一次迁移应用,所以设置为空列表。Django 使用迁移文件的dependencies
属性来决定迁移文件的运行顺序。假设在对models.py
文件中的Language
模型进行了一些更改之后,我们已经创建了另一个迁移文件。那么第二个迁移文件的dependencies
属性将如下所示:
class Migration(migrations.Migration):
dependencies = [
('djangobin', '0001_initial'),
]
在这种情况下,dependencies
属性指的是 djangobin app 的0001_initial
迁移。这意味着 Django 将在运行第二次迁移之前运行迁移0001_initial
。
最后,在第 16 行,我们有operations
属性。它按顺序定义了要为当前迁移运行的操作列表。现在您知道迁移文件是如何构造的了。让我们看一下它将生成的提交更改的 SQL 命令。
正在检查 SQL
您可以使用sqlmigrate
命令查看0001_initial.py
将生成的实际的 SQL 查询,以改变数据库的当前状态。sqlmigrate
需要两个参数,应用名称和迁移名称。您不需要传递迁移文件的全名,开始几个字符就足够了。
$ ./manage.py sqlmigrate djangobin 0001
BEGIN;
--
-- Create model Author
--
CREATE TABLE "djangobin_author" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(100) NOT NULL UNIQUE, "email" varchar(254) NOT NULL UNIQUE, "active" bool NOT NULL, "created_on" datetime NOT NULL, "last_logged_in" datetime NOT NULL);
--
-- Create model Language
--
CREATE TABLE "djangobin_language" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(100) NOT NULL, "lang_code" varchar(100) NOT NULL UNIQUE, "slug" varchar(100) NOT NULL UNIQUE, "mime" varchar(100) NOT NULL, "created_on" datetime NOT NULL, "updated_on" datetime NOT NULL);
--
-- Create model Snippet
--
CREATE TABLE "djangobin_snippet" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(200) NOT NULL, "code" text NOT NULL, "html_code" text NOT NULL, "expiration" varchar(10) NOT NULL, "exposure" varchar(10) NOT NULL, "hits" integer NOT NULL, "slug" varchar(100) NOT NULL, "created_on" datetime NOT NULL, "author_id" integer NOT NULL REFERENCES "djangobin_author" ("id"), "language_id" integer NOT NULL REFERENCES "djangobin_language" ("id"));
--
-- Create model Tag
--
CREATE TABLE "djangobin_tag" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(200) NOT NULL, "slug" varchar(200) NOT NULL);
--
-- Add field tag to snippet
--
CREATE TABLE "djangobin_snippet_tag" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "snippet_id" integer NOT NULL REFERENCES "djangobin_snippet" ("id"), "tag_id" integer NOT NULL REFERENCES "djangobin_tag" ("id"));
CREATE INDEX "djangobin_snippet_author_id_683799a5" ON "djangobin_snippet" ("author_id");
CREATE INDEX "djangobin_snippet_language_id_f4016a66" ON "djangobin_snippet" ("language_id");
CREATE UNIQUE INDEX "djangobin_snippet_tag_snippet_id_tag_id_c040c9fb_uniq" ON "djangobin_snippet_tag" ("snippet_id", "tag_id");
CREATE INDEX "djangobin_snippet_tag_snippet_id_c40481f2" ON "djangobin_snippet_tag" ("snippet_id");
CREATE INDEX "djangobin_snippet_tag_tag_id_e054d4df" ON "djangobin_snippet_tag" ("tag_id");
COMMIT;
以--
开头的行是注释,其他都是普通的旧 SQL。注意sqlmigrate
并不针对数据库执行生成的 SQL,它只是打印出了 Django 用来改变数据库模式的 SQL。
跟踪迁移
showmigrations
子命令显示每个已安装应用下已应用和未应用迁移的列表。回想一下,我们已经在创建 Django 项目一章中多次使用过这个命令。让我们再执行一次。
$ ./manage.py showmigrations
admin
[X] 0001_initial
[X] 0002_logentry_remove_auto_add
auth
[X] 0001_initial
[X] 0002_alter_permission_name_max_length
[X] 0003_alter_user_email_max_length
[X] 0004_alter_user_username_opts
[X] 0005_alter_user_last_login_null
[X] 0006_require_contenttypes_0002
[X] 0007_alter_validators_add_error_messages
[X] 0008_alter_user_username_max_length
contenttypes
[X] 0001_initial
[X] 0002_remove_content_type_name
djangobin
[ ] 0001_initial
sessions
[X] 0001_initial
前面有[X]
的所有迁移都被应用到数据库中。唯一未应用的迁移是 djangobin 应用中的0001_initial
。
提交更改
要应用迁移概述的更改,我们使用migrate
子命令。migrate
子命令的行为取决于传递给它的参数数量。如果您运行migrate
子命令而没有任何参数,它将应用所有已安装应用的所有未应用迁移。如果将应用名称作为参数传递,则migrate
将仅从指定的应用运行未应用的迁移。
我们也可以使用migrate
来取消迁移。为此,请指定应用名称,后跟要迁移到的迁移名称。我们将在下一节中看到一个这样的例子。
但是在我们运行migrate
命令之前。让我们看看数据库的当前状态。
现在,我们的数据库中有 11 个表。好了,现在让我们运行migrate
看看会发生什么。
$ ./manage.py migrate djangobin
Operations to perform:
Apply all migrations: djangobin
Running migrations:
Applying djangobin.0001_initial... OK
如果您还没有数据库,那么migrate
命令将在项目的根目录下创建一个名为db.sqite3
的 SQLite 数据库,即djangobin/django_project
。
另一方面,如果您已经有了一个数据库,那么 Django 将修改现有的数据库,使其与我们的模型同步。
让我们刷新 SQLite 数据库,看看现在有多少表。
我们现在有 16 个表,迁移命令已经为我们创建了 5 个新表。等等!为什么是 5?我们只创造了 4 款新车型,对吗?为什么多一个?
Django 创建了一个额外的表(djangobin_snippet_tags
)来管理Snippet
和Tag
模型之间的多对多关系。这不是 Django 特有的,事实上,这是数据库的工作方式。
所有以"djangobin_"
开头的表格都属于 djangobin app。以下是每张表的概要:
桌子 | 描述 |
---|---|
djangobin_author |
存储作者。 |
djangobin_language |
存储语言。 |
djangobin_snippet |
存储代码片段。 |
djangobin_tag |
存储标签。 |
djangobin_snippet_tags |
管理代码片段和标签模型之间的多对多关系。在数据库术语中,该表被称为连接表。 |
再次迁移
只是为了确保您完全理解迁移。我们将再演示一次。在本节中,我们将把Snippet
模型的一个字段从hits
重命名为visits
,然后我们将使用migrate
命令恢复更改。
打开 djangobin 应用内的models.py
,将Snippet
模型的hits
字段重命名为visits
,如下所示:
djangobin/django _ project/djangobin/models . py
#...
class Snippet(models.Model):
title = models.CharField(max_length=200, blank=True)
original_code = models.TextField()
highlighted_code = models.TextField(blank=True)
expiration = models.CharField(max_length=10, choices=Pref.expiration_choices)
exposure = models.CharField(max_length=10, choices=Pref.exposure_choices)
visits = models.IntegerField(default=0)
slug = models.SlugField()
created_on = models.DateTimeField(auto_now_add=True)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
tags = models.ManyToManyField('Tag')
#...
步骤 1 -使用check
命令检查模型中的错误。
$ ./manage.py check djangobin
System check identified no issues (0 silenced).
models.py
文件中没有任何错误。让我们现在创建我们的迁移文件。
步骤 2 -使用makemigrations
命令创建迁移文件。
$ ./manage.py makemigrations djangobin
Did you rename snippet.created_on to snippet.publication_date (a DateTimeField)? [y/N] y
Migrations for 'djangobin':
djangobin/migrations/0002_auto_20180323_0623.py
- Rename field created_on on snippet to publication_date
执行makemigrations
命令后,Django 会提示您验证更改。按Y
或y
确认。这将在 djangobin 应用的migrations
目录中创建一个新的迁移文件。
步骤 3(可选) -使用sqlmigrate
命令查看 Django 用来更新数据库的 SQL 查询。
$ ./manage.py sqlmigrate djangobin 0002
BEGIN;
--
-- Rename field hits on snippet to visits
--
ALTER TABLE "djangobin_snippet" RENAME TO "djangobin_snippet__old";
CREATE TABLE "djangobin_snippet" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "visits" integer NOT NULL, "title" varchar(200) NOT NULL, "original_code" text NOT NULL, "highlighted_code" text NOT NULL, "expiration" varchar(10) NOT NULL, "exposure" varchar(10) NOT NULL, "slug" varchar(100) NOT NULL, "created_on" datetime NOT NULL, "author_id" integer NOT NULL REFERENCES "djangobin_author" ("id"), "language_id" integer NOT NULL REFERENCES "djangobin_language" ("id"));
INSERT INTO "djangobin_snippet" ("slug", "id", "expiration", "visits", "author_id", "title", "original_code", "created_on", "exposure", "highlighted_code", "language_id") SELECT "slug", "id", "expiration", "hits", "author_id", "title", "original_code", "created_on", "exposure", "highlighted_code", "language_id" FROM "djangobin_snippet__old";
DROP TABLE "djangobin_snippet__old";
CREATE INDEX "djangobin_snippet_author_id_683799a5" ON "djangobin_snippet" ("author_id");
CREATE INDEX "djangobin_snippet_language_id_f4016a66" ON "djangobin_snippet" ("language_id");
COMMIT;
对于单个字段重命名来说,这是一个很大的代码量。如果您不使用迁移,那么您必须自己编写所有这些代码。多亏了移民,你很幸运。
步骤 4(可选) -要查看已应用和未应用的迁移,请使用showmigrations
命令:
$ ./manage.py showmigrations djangobin
djangobin
[X] 0001_initial
[ ] 0002_auto_20180323_0623
如您所见,迁移0002_auto_20180323_0623
目前尚未应用。
步骤 5 -最后,使用migrate
命令将迁移文件中存储的更改提交到数据库。
$ ./manage.py migrate djangobin
Operations to perform:
Apply all migrations: djangobin
Running migrations:
Applying djangobin.0002_auto_20180323_0623... OK
回滚迁移
假设出于某种原因,我们不喜欢对Snippet
模型所做的更改。有两种方法可以恢复我们所做的更改:
- 第一种方法是在
models.py
中将字段名称从visits
更改为hits
,创建迁移文件,然后提交更改。 - 第二种方法快速简单。只需使用
migrate
命令并指定您想要迁移到的应用名称和迁移文件名称,而不是更改模型和创建新的迁移。
您已经看到了如何创建迁移并提交对数据库的更改,因此,我们将使用第二种方法。
这种情况下,app 名称为djangobin
,我们要迁移到的迁移文件名称以0001
开头。因此,要恢复更改,请执行以下命令。
$ ./manage.py migrate djangobin 0001
Operations to perform:
Target specific migration: 0001_initial, from djangobin
Running migrations:
Rendering model states... DONE
Unapplying djangobin.0002_auto_20180323_0623... OK
此时,您可以再次应用0002_auto_20180323_0623
迁移。通过键入以下命令。
$ ./manage.py migrate djangobin 0002
然而,在这种情况下,我们确信我们再也不想回到
0002_auto_20180323_0623
迁徙了。所以最好从migrations
目录中删除。
之后,打开models.py
文件,再次将Snippet
模型的visits
字段重命名为hits
。
你现在可能已经掌握了。
何时创建迁移
Django 仅在您添加、修改或删除模型字段或元类时检测更改。添加、修改或删除模型方法不被视为更改。您可以通过向任何模型类添加任意方法并运行makemigrations
命令来测试这一事实。
$ ./manage.py makemigrations
No changes detected
希望您现在能更好地理解一般的迁移,以及它们在 Django 是如何使用的。如果你还有些困惑,我建议你再读一遍这一章。