Skip to content

Latest commit

 

History

History
506 lines (393 loc) · 13.6 KB

File metadata and controls

506 lines (393 loc) · 13.6 KB
Error in user YAML: (<unknown>): found a tab character that violate indentation while scanning a plain scalar at line 3 column 3
---
- oeasy Python 0121
- 这是 oeasy 系统化 Python 教程,从基础一步步讲,扎实、完整、不跳步。愿意花时间学,就能真正学会。
- 本教程同步发布在: 
	- 个人网站: `https://oeasy.org` 
	- 蓝桥云课: `https://www.lanqiao.cn/courses/3584` 
	- GitHub: `https://github.com/overmind1980/oeasy-python-tutorial` 
	- Gitee: `https://gitee.com/overmind1980/oeasypython` 
---

列表 - 深拷贝 deepcopy

回忆

  • 赋值的 三种情况
赋值方式 核心特点(大白话口诀)
= 共用地址,一改全改
copy() 浅层独立,深层共用
deepcopy() 全部独立,互不干扰

图片描述

  • 我想复制节奏

图片描述

  • 这里面 有什么说法吗?🤔

直接相加

  • 两种节奏小节
    • 动词打次
    • 动动打次
b1 = list("动次打次")
b2 = list("动动打次")
r1 = [b1] * 3 + [b2]
song = r1 * 2

图片描述

  • 想 对song[0] 直接赋值

直接赋值

b1 = list("动次打次")
b2 = list("动动打次")
r1 = [b1] * 3 + [b2]
song = r1 * 2
song[0] = list("次次次次")
  • 第一个 小节
    • 鼓点变了

图片描述

  • 如果想要 所有的
    • 动词打次 都成 次次次次 呢?

变化

b1 = list("动次打次")
b2 = list("动动打次")
r1 = [b1] * 3 + [b2]
song = r1 * 2
song[0][0:] = list("次" * 4)
  • 把 直接替换列表项
    • 换成了 切片赋值

图片描述

  • 如果 有三首歌
    • 都重复 这套节奏 呢?

三首歌

  • 三首歌 节奏重复
    • 又增了 一层结构
b1 = list("动次打次")
b2 = list("动动打次")
r1 = [b1] * 3 + [b2]
song = r1 * 2
songs = [song] * 3
  • 三首歌
    • 都指向 同一首歌 的 地址

图片描述

  • 想要 只修改
    • 第一首歌的 第一小节

修改

# 初始代码
b1 = list("动次打次")
b2 = list("动动打次")
r1 = [b1] * 3 + [b2]
song = r1 * 2
songs = [song] * 3

# 直接替换列表
songs[0][0] = list("次次次次")
  • 三首歌的第一小节 都修改了

图片描述

  • 我只想修改 第一首的第一小节

浅拷贝

# 初始化原始数据
b1 = list("动次打次")
b2 = list("动动打次")
r1 = [b1] * 3 + [b2]
song = r1 * 2
songs = [song] * 3  # 此时3首歌共享同一个song对象

# 关键步骤:先给第一首歌创建独立的浅拷贝,切断引用
songs[0] = songs[0].copy()  # 或 list(songs[0]) / songs[0][:]

# 现在修改第一首歌的第一小节,仅影响第一首
songs[0][0] = list("次次次次")
  • 确实只修改 第一首歌的 第一小节

图片描述

  • 但这个结构还是特别乱
    • 我想给每个小节
    • 都分配独立空间

深拷贝

from copy import deepcopy

# 基础节拍单元(单层列表,无嵌套)
beat_b1 = list("动次打次")
beat_b2 = list("动动打次")

# 用 deepcopy 替代 copy(),效果完全一致
song_verses = [
    deepcopy(beat_b1),  # 第1小节(独立,无引用)
    deepcopy(beat_b1),  # 第2小节(独立)
    deepcopy(beat_b1),  # 第3小节(独立)
    deepcopy(beat_b2)   # 第4小节(独立)
]
single_song = deepcopy(song_verses * 2)
three_song = deepcopy([single_song]) +  deepcopy([single_song]) + deepcopy([single_song])
  • 先 完成 基础节奏

图片描述

  • 然后完成一首歌

图片描述

最后的三首歌

  • three_song中的每个小节
    • 都有自己独立的位置
    • 互不影响
    • 但是比较 占用空间

图片描述

  • 但是 pattern当中的 4个小节还是有重复

彻底独立

from copy import deepcopy

# 基础节拍单元(单层列表,无嵌套)
beat_b1 = list("动次打次")
beat_b2 = list("动动打次")

# 用 deepcopy 替代 copy(),效果完全一致
song_verses = [
    deepcopy(beat_b1),  # 第1小节(独立,无引用)
    deepcopy(beat_b1),  # 第2小节(独立)
    deepcopy(beat_b1),  # 第3小节(独立)
    deepcopy(beat_b2)   # 第4小节(独立)
]
single_song = deepcopy(song_verses) + deepcopy(song_verses)
three_song = deepcopy([single_song]) +  deepcopy([single_song]) + deepcopy([single_song])

图片描述

  • 能把这个写成代码吗?

代码

  • 三首歌
    • 24个小节

图片描述

import copy
import mido
from mido import Message, MidiFile, MidiTrack

# ===================== 1. 基础节拍定义(修正乐器映射) =====================
# 重新定义4/4拍的"动次打次"乐器映射(按你的要求)
# 动: 低音鼓 (Kick, MIDI 36) - 1拍
# 次: 闭合踩镲 (Hi-Hat, MIDI 42) - 1拍
# 打: 军鼓 (Snare, MIDI 38) - 1拍
beat_mapping = {
    '动': [36],    # 低音鼓 (Kick)
    '次': [42],    # 闭合踩镲 (Hi-Hat)
    '打': [38]     # 军鼓 (Snare)
}

# 基础节拍单元(标准4/4拍,每小节4拍)
beat_b1 = list("动次打次")  # 动(1拍)、次(1拍)、打(1拍)、次(1拍)
beat_b2 = list("动动打次")  # 动(1拍)、动(1拍)、打(1拍)、次(1拍)

# 用 deepcopy 确保每个小节独立
song_verses = [
    copy.deepcopy(beat_b1),  # 第1小节(4拍)
    copy.deepcopy(beat_b1),  # 第2小节(4拍)
    copy.deepcopy(beat_b1),  # 第3小节(4拍)
    copy.deepcopy(beat_b2)   # 第4小节(4拍)
]
single_song = copy.deepcopy(song_verses * 2)  # 完整单曲(8小节,32拍)
three_song = [
    copy.deepcopy(single_song),
    copy.deepcopy(single_song),
    copy.deepcopy(single_song)
]  # 三首连播

# ===================== 2. 生成MIDI文件核心逻辑(修正时序) =====================
def create_beat_midi(beat_sequence, filename="dongci_daci.mid", tempo=120):
    """
    生成标准4/4拍的打击乐MIDI文件
    :param beat_sequence: 嵌套的节拍列表
    :param filename: 输出MIDI文件名
    :param tempo: 速度(BPM),120对应每分钟120拍
    """
    # 创建MIDI文件和音轨
    mid = MidiFile()
    track = MidiTrack()
    mid.tracks.append(track)

    # 设置速度(120 BPM = 500000 微秒/拍)
    track.append(mido.MetaMessage('set_tempo', tempo=mido.bpm2tempo(tempo)))
    # 10号通道(索引9)是MIDI标准打击乐通道
    track.append(Message('program_change', channel=9, program=0, time=0))

    # 4/4拍的核心:每拍时长 = 480 ticks(MIDI标准四分音符)
    tick_per_beat = 480

    # 扁平化嵌套的节拍序列(转为一维列表,确保逐拍处理)
    flat_beats = []
    for song in beat_sequence:
        for bar in song:  # bar = 小节
            flat_beats.extend(bar)

    # 生成MIDI音符(关键修正:确保每拍严格占480 ticks)
    # 初始时间偏移为0
    current_time = 0
    for beat_char in flat_beats:
        if beat_char in beat_mapping:
            note = beat_mapping[beat_char][0]  # 每个节拍对应一个乐器
            # 音符开启(note_on):在current_time时刻触发
            track.append(Message(
                'note_on', 
                channel=9, 
                note=note, 
                velocity=80,  # 音量(64-100为常用范围)
                time=current_time
            ))
            # 音符关闭(note_off):间隔1拍(480 ticks)后释放
            track.append(Message(
                'note_off', 
                channel=9, 
                note=note, 
                velocity=80, 
                time=tick_per_beat
            ))
            # 重置时间偏移,确保下一拍从0开始
            current_time = 0

    # 保存MIDI文件
    mid.save(filename)
    print(f"标准4/4拍MIDI文件已生成:{filename}")

# ===================== 3. 执行生成 =====================
if __name__ == "__main__":
    # 生成三首连播的标准4/4拍MIDI
    create_beat_midi(three_song, "three_songs_4_4_beat.mid", tempo=120)

新想法

  • 想要 闭擦踩满

图片描述

闭擦踩满

import copy
import mido
from mido import Message, MidiFile, MidiTrack

# ===================== 1. 基础节拍定义(重新设计数据结构) =====================
# 定义MIDI打击乐音符编号
KICK = 36    # 底鼓
SNARE = 38   # 军鼓
HI_HAT = 42  # 闭合踩镲

# 重新定义4/4拍的基础小节结构(完全按你的要求)
# 第0拍: kick + hh | 第1拍: hh | 第2拍: snare + hh | 第3拍: hh
basic_bar = [
    [KICK, HI_HAT],  # 第0拍(动):底鼓+踩镲
    [HI_HAT],        # 第1拍(次):仅踩镲
    [SNARE, HI_HAT], # 第2拍(打):军鼓+踩镲
    [HI_HAT]         # 第3拍(次):仅踩镲
]

# 变体小节(保留差异化,第0/1拍都是kick+hh)
variant_bar = [
    [KICK, HI_HAT],  # 第0拍:底鼓+踩镲
    [KICK, HI_HAT],  # 第1拍:底鼓+踩镲(变体)
    [SNARE, HI_HAT], # 第2拍:军鼓+踩镲
    [HI_HAT]         # 第3拍:仅踩镲
]

# 构建歌曲结构(用deepcopy确保每个小节独立)
song_verses = [
    copy.deepcopy(basic_bar),   # 第1小节
    copy.deepcopy(basic_bar),   # 第2小节
    copy.deepcopy(basic_bar),   # 第3小节
    copy.deepcopy(variant_bar)  # 第4小节(变体)
]
single_song = copy.deepcopy(song_verses * 2)  # 完整单曲(8小节)
three_song = [
    copy.deepcopy(single_song),
    copy.deepcopy(single_song),
    copy.deepcopy(single_song)
]  # 三首连播

# ===================== 2. 生成MIDI文件核心逻辑(适配新结构) =====================
def create_beat_midi(beat_sequence, filename="dongci_daci.mid", tempo=120):
    """
    生成标准4/4拍的打击乐MIDI文件(适配新的拍子列表结构)
    :param beat_sequence: 嵌套的节拍列表(每个拍子是乐器编号列表)
    :param filename: 输出MIDI文件名
    :param tempo: 速度(BPM)
    """
    # 创建MIDI文件和音轨
    mid = MidiFile()
    track = MidiTrack()
    mid.tracks.append(track)

    # 设置速度(120 BPM = 500000 微秒/拍)
    track.append(mido.MetaMessage('set_tempo', tempo=mido.bpm2tempo(tempo)))
    # 10号通道(索引9)是MIDI标准打击乐通道
    track.append(Message('program_change', channel=9, program=0, time=0))

    # 4/4拍核心:每拍时长 = 480 ticks(MIDI标准四分音符)
    tick_per_beat = 480

    # 扁平化嵌套的节拍序列(转为一维的"拍子列表")
    flat_beats = []
    for song in beat_sequence:
        for bar in song:        # bar = 小节(包含4个拍子)
            for beat in bar:    # beat = 拍子(包含1个/多个乐器)
                flat_beats.append(beat)

    # 生成MIDI音符(适配新结构:直接遍历乐器列表)
    current_time = 0
    for beat_instruments in flat_beats:
        # 触发当前拍子的所有乐器(note_on)
        for instrument in beat_instruments:
            track.append(Message(
                'note_on',
                channel=9,
                note=instrument,
                velocity=80,  # 音量(适中)
                time=current_time
            ))
        # 释放当前拍子的所有乐器(note_off),间隔1拍
        for instrument in beat_instruments:
            track.append(Message(
                'note_off',
                channel=9,
                note=instrument,
                velocity=80,
                time=tick_per_beat if instrument == beat_instruments[0] else 0
            ))
        # 重置时间偏移,确保下一拍从0开始
        current_time = 0

    # 保存MIDI文件
    mid.save(filename)
    print(f"标准4/4拍MIDI文件已生成:{filename}")

# ===================== 3. 执行生成 =====================
if __name__ == "__main__":
    # 生成三首连播的MIDI文件
    create_beat_midi(three_song, "three_songs_4_4_full_hh.mid", tempo=120)

缝合(zip)

  • 列表还有一种运算方式
    • 叫做缝合(zip)
students = ["oeasy", "o2z", "o3z"]
math = [95, 96, 97]
chinese = [91, 92, 93]
score = list(zip(students, math, chinese))
print(score)
  • zip 可以 把 不同列表
    • 缝到一起

图片描述

  • 同样位置的元素
    • 缝到一个新元素里面

查询帮助

图片描述

  • 查询关于zip的帮助
    • help(zip)

图片描述

  • 缝合出来的列表可以用lambda排序吗?

排序

sorted_by_chinese = sorted(score, key=lambda x: x[2])
sorted_by_chinese
  • 确实可以按照 语文成绩 排序

图片描述

总结

  • 这次我们 玩了节奏
    • 可以 让节奏成为套路
    • 也可以 让节奏独立空间
students = ["oeasy", "o2z", "o3z"]
math = [95, 96, 97]
chinese = [91, 92, 93]
score = list(zip(students, math, chinese))
print(score)
  • 缝合操作
    • 把列表里 同样位置的元素
      • 缝合 在一起

图片描述

  • 缝合里 的列表项
    • 怎么不是 中括号 包裹的呢??🤔
  • 下次再说 👋

  • 本文来自 oeasy Python 系统教程。
  • 想完整、扎实学 Python,
  • 搜索 oeasy 即可。