Python学习日记-1

135 阅读8分钟

标识符命名规则

类型规则例子
模块和包名全是小写字母, 若多个单词之间用下划线math, sys, os
函数名全是小写字母, 若多个单词之间用下划线add, update
类名首字母大写, 采用驼峰命名法, 多个单词时, 每个单词首字母大写MyPhone
常量名全是大写字母, 多个单词使用下划线隔开MAX_SPEED

变量和对象

  • 变量没有类型, 但是引用类型(对象id地址), 存放在栈内存stack
  • 对象是有类型的, 存放在堆内存heap

时间表示

计算机中时间的表示是从"1970年1月1日00:00:00"开始, 以毫秒(1/1000)秒进行计算, 这个时刻称为"unix时间点"

python中可以通过time.time()获得当前时刻, 返回的值以秒为单位, 带微妙(1/1000)毫秒精度的浮点值, 例如:1693407364.4558322

可变字符串

  • python中, 字符串属于不可变对象, 不支持原地修改, 如果需要修改其中的值, 只能创建新的字符串对象
  • 确实需要原地修改字符串, 可以使用io.StringIO对象或者array模块
import io
a = "123456"
# print(id(a))
# a = "123789"
# print(id(a))

b = io.StringIO(a)
print(b.getvalue())
print(id(b))
b.seek(3)
b.write("***")
print(b.getvalue())
print(id(b))

序列

序列是一种数据存储方式, 用于存储一系列的数据, 在内存中, 序列就是一块用来存放多个值的连续的内存空间

列表效率问题

当列表增加和删除元素时, 列表会自动进行内存管理, 大大减少了程序员的负担, 但是这个特点涉及列表元素的大量移动, 效率较低

除非必要, 一般只在列表的尾部添加元素或者删除元素, 这会大大提高列表的操作效率

append()方法: 速度最快, 推荐使用

+运算符操作: 不是尾部添加元素, 而是创建新的列表对象, 将原列表的元素和新列表的元素依次复制到新的列表对象中, 这样, 会涉及到大量的复制操作, 对于操作大量元素不建议使用

a = [10, 20]
print(id(a))
a = a + [30]
print(id(a)) # 两次地址不一样, 创建了新的对象

extend()方法: 把目标列表的所有元素添加到本列表的尾部, 属于原地操作, 不创建新的列表对象

insert()插入元素方法:使用insert()方法课将指定的元素插入到列表对象的任意指定位置, 这样会让插入位置后面所有的元素进行移动, 会影响到处理速度

乘法操作: 生成新对象

函数内存分析

函数也是对象

函数运行在栈内存stack, 创建对象在堆内存heap

Python中, 圆括号意味着调用函数, 在没有圆括号的情况下, Python会把函数当作普通对象

全局变量和局部变量

全局变量

  • 在函数和类定义之外声明的变量, 作用域为定义的模块, 从定义位置开始直到模块结束
  • 全局变量降低了函数的通用性和可读性, 应尽量避免全局变量的使用
  • 要在函数内改变全局变量的值, 使用global声明一下

局部变量

  • 在函数体中(包括形式参数)声明的变量
  • 局部变量的引用比全局变量快, 优先考虑使用
  • 如果局部变量和全局变量同名, 则在函数内隐藏全局变量, 只使用同名的局部变量

新函数

a = 100


def f1():
    global b
    b = 33
    a = 11
    c = 22
    print(locals())		#打印输出的局部变量
    print(globals())	#答应输出的全局变量


print(a)
f1()

效率问题代码演示

import time

a = 1000


def demo01():
    start = time.time()
    global a
    for i in range(100000000):
        a += 1
    end = time.time()
    t = end - start
    print(f"耗时{t}")


def demo02():
    start = time.time()
    c = 1000
    for i in range(100000000):
        c += 1
    end = time.time()
    t = end - start
    print(f"耗时{t}")


demo01()
demo02()

参数传递

函数的参数传递本质就是: 从实参到形参的赋值操作, Python中"一切皆对象", 所有的赋值操作都是"引用的赋值", 所以, Python中的参数的传递都是"引用传递", 不是"值传递"

具体操作时分为两类:

  • 对"可变对象"进行"写操作", 直接作用于原对象本身, 其中"可变对象"有: 字典, 列表, 集合, 自定义的对象等
  • 对"不可变对象"进行"写操作", 会产生一个新的"对象空间", 并用新的值填充这块空间, 其中"不可变对象"有: 数字, 字符串, 元组, function等

注意:

  • 传递不可变对象时, 不可变对象里面包含的子对象是可变的话, 如果方法内修改了这个可变对象, 则源对象也会发生变化

浅拷贝和深拷贝

内置函数

  • copy(浅拷贝): 拷贝对象, 但不拷贝子对象的内容, 只是拷贝子对象的引用
  • deepcopy(深拷贝): 拷贝对象, 并且会连子对象的内容也会全部(递归)拷贝一份, 对子对象的修改不会影响源对象
import copy
def demo_copy():
    a = [11, 22, [33, 44]]
    b = copy.copy(a)
    print(a)
    print(b)
    b.append(55)
    b[2].append(66)
    print(a)
    print(b)
demo_copy()
print("-----------------")
def demo_deepcopy():
    a = [11, 22, [33, 44]]
    b = copy.deepcopy(a)
    print(a)
    print(b)
    b.append(55)
    b[2].append(66)
    print(a)
    print(b)
demo_deepcopy()

参数类型: 可变参数

可变参数指的是"可变数量的参数"

  • *param(一个星号), 将多个参数收集到一个"元组"对象中
  • **param(两个星号), 将多个参数收集到一个"字典"对象中

强制命名参数

在带星号的"可变参数"后面增加新的参数, 必须在调用的时候"强制命名参数"

lambda表达式和匿名函数

lambda表达式可以用来声明匿名函数, lambda函数是一种简单的, 在同一行中定义函数的方法

lambda函数实际生成了一个函数对象

lambda表达式只允许包含一个表达式, 不能包含复杂语句, 该表达式的计算结果就是函数的返回值

f = lambda a, b, c: a + b + c
print(f(1, 2, 3))

eval()函数

功能: 将字符串str当成有效的表达式来求值并返回计算结果

递归函数

基本思想: 自己调用自己

  1. 终止条件: 表示递归什么时候结束, 一般用于返回值, 不再调用自己
  2. 递归步骤: 把第n步的值和第n-1步相关联
def f(n):
    print("start:" + str(n))
    if n == 1:
        print("recuesion over!")
    else:
        f(n - 1)
    print("end:" + str(n))

def f1(n):
    if n == 1:
        return 1
    else:
        return n * f1(n-1)

嵌套函数

  • 定义: 在函数内部定义的函数
  • 一般在什么情况下使用嵌套函数?
    • 封装 - 数据隐藏(外部无法访问嵌套函数)
    • 贯彻DRY(dont repeat yourself)原则
    • 嵌套函数, 可以让我们在函数内部避免重复代码
    • 闭包
def printName(isChinese, name, familyName):
    def inner_print(a, b):
        print(f"{a} {b}")

    if isChinese:
        print(familyName, name)
    else:
        inner_print(name, familyName)


printName(True, "三", "张")
printName(False, "Geroge", "Bush")

nonlocal和global关键字

nonlocal: 用来在内部函数中, 声明外层的局部变量

global: 函数内部声明全局变量, 然后才使用全局变量

a = 100


def outer():
    b = 200

    def inner():
        nonlocal b
        print("inner", b)
        b = 300

        global a
        a = 400
    inner()
    print("outer", b)


outer()
print(a)

LEGB规则

Python在查找"名称"时, 是按照LEGB规则查找的:

Local > Enclosed > Global > Bulit in

Local: 指的就是函数或者类的方法内部

Enclosed: 值得是嵌套函数(一个函数包裹另一个函数, 闭包)

Global: 指的是模块中的全局变量

Bulit in: 指的是Python为自己保留的特殊名称

字典核心底层原理

字典对象的核心是散列表, 散列表是一个稀疏数组(总是有空白元素的数组), 数组的每个单元叫做bucket(桶), 每个bucket有两部分: 一个是键对象的引用, 一个是值对象的引用

由于, 所有bucket结构和大小一致, 我们可以通过偏移量来读取指定bucket

扩容

  • python会根据散列表的拥挤程度扩容, "扩容"指的是: 创造更大的数组, 将原有内容拷贝到新数组中
  • 接近2/3时, 数组就会扩容

用法总结

  • 字典在内存中开销巨大, 典型的空间换时间
  • 键查询速度很快
  • 往字典里面添加新键可能导致扩容, 导致散列表中键的次序变化, 因此, 不要在遍历字典的同时进行字典的修改
  • 键必须可散列
    • 数字, 字符串, 元组, 都是可散列的
    • 自定义对象需要支持下面三点
      • 支持hash()函数
      • 支持通过_eq_()方法检测相等性
      • 若a==b为真, 则hash(a)==hash(b)也为真

集合

集合是无序可变, 元素不可重复, 实际上, 集合底层是字典实现, 集合的所有元素是字典中的键对象, 因此是不能重复且唯一的

集合创建和删除: add set remove

集合相关操作: 并集, 交集, 差集