Python基础入门

Python基础入门

1. 注释

1.1 什么是注释

注释:用于在源代码中解释代码的功用,可以增强程序的可读性,可维护性,代码的解释和说明,其目的是让人们能够更加轻松地了解代码。

注释只是为了提高可读性,在源代码进入预处理器或编译器处理后会被移除,不会在目标代码中保留其相关信息。

在Python语言中注释有两种方式:

  1. 井号#, 单行注释

    print("Hello World!") #这是一个打印语句
    
  2. 使用三引号"单引号或双引号",多行注释

    单引号注释
    '''
    这是单引号注释
    这是单引号注释
    这是单引号注释
    '''
    双引号注释
    """
    这是双引号注释
    这是双引号注释
    这是双引号注释
    """
    

2. 变量与常量

2.1 什么是变量

变量来源于数学,是计算机语言中能储存计算结果或能表示值的抽象概念。

变量:即可变的量,反映事物状态变化的量。在计算机中主要作用,就是用来存储信息,然后在程序中使用这些信息。

通过给变量赋值,将数据与一些能够描述的名字连接起来,简单说,就是给数据一个能让人理解的名字,12可以指年龄,也可以指距离,所以,需要通过这个定义,12就有了不同的含义。

2.2 声明变量

声明一个变量很简单,格式:

变量名 = 值

name = "hans"
"""
name 为变量名
= 为赋值符
hans 为 值

把hans这个值赋值给name这个变量
"""

2.3 变量使用

>>> name = "Hans"
>>> age = 18
>>> print(name, age)
Hans 18

2.4 变量赋值原理

age = 20

赋值过程:

  1. 在内存空间中申请一块内存空间存储20
  2. 将20所在的内存空间地址绑定给变量名age
  3. 之后如果要访问20就通过变量名age访问即可

image

查看一个变量的内存地址可以使用函数:id()

>>> age = 20
>>> id(age)
140512923944768
>>> age
20
2.4.1变量三要素

变量三要素:

  1. 变量的值
  2. 变量的内存地址
  3. 变量的数据类型
1. 查看变量的值:
print(age)
2. 查看变量的内存地址
print(id(age))
3. 查看变量的数据类型
print(type(age))

2.5 常量

常量:可以理解为一直不变的量,是反映事物相对静止状态的量。

在程序运行过程中,有些值是固定的、不应该被改变,比如圆周率 3.141592653…,所以不能更改赋给常量的值。但是在python中没有真正意义上的常量 我们墨守成规的将全大写的变量看成是常量。

HOST = "www.pymysql.com" 
'''
变量名HOST全大写我们就默认为这是一个常量,在python中这个值是可以改的。
但我们一般不要去改。
'''

>>> HOST = "www.pymysql.com"
>>> print(HOST)
www.pymysql.com
>>> 
>>> HOST = "www.pysql.cn"  #虽然可以改,但我们不推荐去改。
>>> print(HOST)          
www.pysql.cn

在其他编程语言中存在真正意义的常量,定义了就无法被修改。如C语言

cat constant.c
#include <stdio.h>

int main(void)
{
        const float pi = 3.14; // This is a constant, don't change it's value.
        printf("pi = %.2f\n", pi);

        pi = 3.1415;
        printf("pi = %.2f\n", pi);

}
编译的时候出错:
constant.c: In function ‘main’:
constant.c:8:5: error: assignment of read-only variable ‘pi’   # pi 是只读的,不能修改。
  pi = 3.1415;
     ^

2.6 变量命名规范与风格

2.6.1 命名规范

命名规范

  • 变量名只能由数字、字母、下划线任意组合
  • 变量名不能以数字开头,下划线建议不要开头因为有特殊含义
  • 变量名不能与关键字冲突
  • 变量名的命名一定要做到见名知意(重要)
2.6.2 Python关键字

Python常用关键字

python中共有33个关键字

False None True and as assert
break class continue def del elif
else except finally for from global
if import in is lambda nonlocal
not or pass raise return try
while with yield

如何查看关键字

>>> import keyword
>>> keyword.kwlist
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
>>> len(keyword.kwlist)
33
2.6.3 命名风格
  • 驼峰命名法

    image

    • 小驼峰命名法:除第一个单词之外,其他单词首字母大写,例如:myFirstNamemyLastName。常用于变量名,函数名。

    image

    • 大驼峰命名法(又称为帕斯卡命名法):相比小驼峰法,大驼峰法把第一个单词的首字母也大写了,例如:MyFirstName。常用于类名,属性,命名空间等。
  • 下划线命名法

    • 名称中的每一个逻辑断点都用一个下划线来标记,例如:print_employee。下划线命名法是随着C语言的出现流行起来的,在UNIX/LIUNX这样的环境,以及GNU代码中使用非常普遍。

    用那种风格主要看个人爱好。

2.7 python底层优化

当值数据量很小的时候,如果有多个变量名的值相同, 那么会指向同一块地址。如

>>> a = 20
>>> b = 20
>>> id(a)
140272960579392
>>> id(b)
140272960579392

这是Python在底层做的优化。但是数据量大的时候就不会这样了。

>>> lista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
>>> listb = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
>>> id(lista)
140272935267592
>>> id(listb)
140272927936584

如果这时还想让两个变量指向同一个内存地址,可以:
>>> listc = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
>>> id(listc)
140272935267848
>>> listd = listc
>>> id(listd)    
140272935267848

注意:

  • 一个内存地址可以有多个变量名指向
  • 一个变量名只能指向一个内存地址

image

示例:

1. 一个内存地址可以有多个变量名指向
>>> age = 12
>>> grade = age
>>> print(age)
12
>>> print(grade)
12

2.一个变量名只能指向一个内存地址
>>> age = 12
>>> age = 20
>>> print(age)  
20

3. 垃圾回收机

有些数据被使用之后,可能就不再需要了,我们把这种数据称为垃圾数据。如果这些垃圾数据一直保存在内存中,那么内存会越用越多,所以需要对这些垃圾数据进行回收,以释放有限的内存空间。

在一些语言(如:C,C++,汇编)中对于内存空间的释放是需要编程人员来手动进行的,这种与底层硬件直接打交道的操作是十分的危险与繁琐的,而基于C语言开发而来的Python为了解决掉这种顾虑则自带了一种垃圾回收机制。

Python中的垃圾回收机制(Garbage Collection)简称:GC,程序在运行中会产生大量的变量用于保存数据,而有时候有些变量已经没有用了就需要被清理释放掉该变量所占据的内存空间。

python 采用的是引用计数机制为主,标记 - 清除分代回收两种机制为辅的策略。

3.1 引用计数

Python 语言默认采用的垃圾收集机制是『引用计数法 Reference Counting』,该算法最早 George E. Collins 在 1960 的时候首次提出。

『引用计数法』的原理是:每个对象维护一个ob_refcnt字段,用来记录该对象当前被引用的次数,每当新的引用指向该对象时,它的引用计数ob_refcnt加1,每当该对象的引用失效时计数ob_refcnt1,一旦对象的引用计数为0,该对象立即被回收,对象占用的内存空间将被释放。

 typedef struct_object {
 int ob_refcnt;    
 struct_typeobject *ob_type;
} PyObject;

例如:

>>> CEO = "Hans"
>>> CTO = "Hans" 
>>> CFO = "Hans"  
>>> id(CEO)
140272927922696
>>> id(CTO)
140272927922696
>>> id(CFO)
140272927922696
// 现在要给CEO,CTO,CFO的值改名为Wang,则Hans所在的内存地址没有被引用了,就会被变成垃圾数据。

image

当使用del或者对变量名重新赋值后,该变量值的引用计数就会 -1 。当引用计数为 0 时候下次 Python内存回收机制 进行内存扫描时便会将该变量值当做垃圾进行回收。

image

引用计数的优缺点:

  • 优点:

    实时性:一旦没有引用,内存就直接释放了

  • 缺点:

    1. 维护引用计数消耗资源
    2. 循环引用(内存泄漏)

循环引用:

>>> l1 = [1, 2, 3]
>>> l2 = [1, 2, 3, l1]
>>> l1
[1, 2, 3] 
>>> l2
[1, 2, 3, [1, 2, 3]]
>>> l1.append(l2)
>>> l1
[1, 2, 3, [1, 2, 3, [...]]]
>>> l2
[1, 2, 3, [1, 2, 3, [...]]]

现在l1l2全部作为互相引用了。那么对于这种引用方式叫做循环引用(也被称为交叉引用),循环引用会带来一个问题:

l1 变量值 的引用计数 目前为 2
l2 变量值 的引用计数 目前为 2
当使用 del l1 与 del l2 后呢?
它们的引用变量都减1,但是引用方式的变量名都互相删除了,按理说这些变量值都成了垃圾变量。单根据引用计数是无法清理这些垃圾变量的。

>>> del l1
>>> del l2
// 现在怎么访问 l1 或者 l2 呢?访问不到,但是他们的变量值依然存在于内存,引用计数从2变为1

如何要手动释放内存:
>>> import gc
>>> gc.collect()
2

3.2 标记-清除

标记清除用来解决循环引用产生的问题。

标记清除的意思在于当应用程序可用内存空间即将被耗尽时便开始扫描栈区,并且会顺着栈区变量名对堆区中的变量值做一个标记,如果堆区中存在没有与栈区变量名做对应关系的数据则会被认为是垃圾数据从而被Python垃圾回收机制清理。

标记清除算法是一种基于对象可达性分析的回收算法,该算法分为两个步骤,分别是标记和清除。标记阶段,将所有活动对象进行标记,清除阶段将所有未进行标记的对象进行回收即可。

image

GC又是如何判断哪些是活动对象哪些是非活动对象的呢?

image

在标记-清除算法中,有两个链表,一个是root链表(root object),另外一个是unreachable链表

从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。

把上图中小黑圈视为全局变量,也就是把它作为root object,从小黑圈出发,对象1可直达,那么它将被标记,对象2、3可间接到达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收。

为什么要用这两个链表?

之所以是两个链表,是基于这样的一种考虑:现在的unreachable可能存在被root链表中的对象,直接或间接引用的对象,这些对象是不能被回收的,一旦在标记的过程中,发现这样的对象,就将其从unreachable链表中移到root链表中;当完成标记后,unreachable链表中剩下的所有对象就是名副其实的垃圾对象了,接下来的垃圾回收只需限制在unreachable链表中即可。

标记清除算法作为 Python 的辅助垃圾收集技术主要处理的是一些容器对象,比如 list、dict、tuple,instance 等,因为对于字符串、数值对象是不可能造成循环引用问题。虽说该方法完全可以做到不误杀不遗漏,但 GC 时必须扫描整个堆内存,即使只有少量的非可达对象需要回收也需要扫描全部对象。这是一种巨大的性能浪费。

标记清除算法主要缺点在于正常的程序将会被阻塞

3.3 分代回收

分代回收算法是基于:标记清除(Mark and Sweep)。

Python 通过“分代回收”(Generational Collection)以空间换时间的方法提高垃圾回收效率。

分代回收思想将对象分为三代(generation 0,1,2)

  • 0 代表年轻代

  • 1 代表中年代

  • 2 代表老年代

Python 将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python 将内存分为了 3“代”,分别为年轻代(第 0 代)、中年代(第 1 代)、老年代(第 2 代),他们对应的是 3 个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。

新创建的对象都会分配在年轻代年轻代链表的总数达到上限时,Python 垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。

分代回收同样作为 Python 的辅助垃圾收集技术处理那些容器对象

3.3.1 弱代假设(Weak generational hypothesi)

弱代假设:一个对象存活的时间越长,它下一刻被释放的概率就越低。

简单来说就是:对象存在时间越长,越可能不是垃圾,应该越少去收集。

对象存活时间 释放概率 回收频率
3.3.2 自动回收阈值

GC阈值,所谓阈值就是一个临界点的值

gc 模块有一个自动垃圾回收的阀值,即通过gc.get_threshold函数获取到的长度为 3 的元组,例如(136, 11, 0),每一次计数器的增加,gc 模块就会检查增加后的计数是否达到阀值的数目,如果是,就会执行对应的代数的垃圾检查,然后重置计数器

>>> gc.get_count()
(136, 11, 0)

  • 136指距离上一次一代垃圾检查,Python 分配内存的数目减去释放内存的数目,注意:是内存分配,而不是引用计数的增加。
  • 11指距离上一次二代垃圾检查,一代垃圾检查的次数。
  • 0是指距离上一次三代垃圾检查,二代垃圾检查的次数。

GC总结:

  • 每新增 701 个需要 GC 的对象,触发一次年轻代 GC
  • 每执行 11 次年轻代 GC ,触发一次中年代 GC
  • 每执行 11 次中年代 GC ,触发一次老年代 GC (老年代 GC 还受其他策略影响,频率更低);
  • 执行某个代 GC 前,年轻生代对象链表也移入该代,一起 GC
  • 一个对象创建后,随着时间推移将被逐步移入老年代代,回收频率逐渐降低;

4. 数据类型

4.1 什么是数据类型

数据类型是一个值的集合和定义在此集合上一组操作(通常是增删改查或者操作读写的方法)的总称。

数据类型又分:

  • 原子类型:比如编程语言的int,double,char,byte,boolean。

  • 复合类型:又称结构类型,通过原子类型封装的更复杂的类型,比如面向对象语言里面的类

4.2 整型:int

整型:没有小数部分的数据,即整数。在计算机中使用int表示。

>>> num = 9
>>> type(num)
<class 'int'>

4.3 浮点型:float

浮点型简单来说就是表示带有小数的数据,即小数。

>>> salary = 30000.5
>>> type(salary)
<class 'float'>
posted on 2021-11-02 21:28  Hans_Wang  阅读(327)  评论(0编辑  收藏  举报

回到顶部