邪门,python寄了,被迫换用cpp

原来写了个py脚本,用于重命名从qq音乐下载的米哈游的音乐(ogg、mp3、flac均有)

Python
import os
import re

# QQ音乐
prog = re.compile(r"(.*\\)(.*) - (.*)( \[.*\])(\..*)")

# 网易云
# prog = re.compile(r"(.*) - (.*)")


def concat(*args, sep=""):
    return sep.join(args)


for curDir, dirs, files in os.walk(r"D:\LanguageLearning\python\music"):
    for file in files:
        # qq
        if file[-5:] == "].mp3" or file[-5:] == "].ogg" or file[-6:] == "].flac":
            # 网易
            # if (file[-3:] not in {"exe",".py","cmd","bat"}):
            name = os.path.join(curDir, file)
            # print(name)
            # print("")
            # QQ
            new_name_list = prog.split(name, 5)
            new_name = (
                '"' + concat(new_name_list[1], new_name_list[3], new_name_list[5]) + '"'
            )
            name = '"' + name + '"'
            print(new_name)
            # ---
            # 网易
            # new_name_list = prog.split(name, 2)
            # print(new_name_list)
            # new_name = os.path.split(os.path.realpath(__file__))[0] + "\\" + new_name_list[2]
            try:
                # qq
                os.renames(name, new_name)
                # 网易
                # os.renames(name, new_name)
            except:  # noqa: E722
                os.remove(name[1:-1])
            print("Success!", end="\n\n")

邪门玩意,明明py3.10都没问题的,也没看着py更新了与文件重命名相关的函数啊,怎么自从更新到py最新版本3.12突然就寄了……

典型表现就是运行到一半突然卡住,也不报错,也不提示,也不崩溃。任务管理器python占用cpu:0%-0.4%(在我的电脑上很正常),内存长久无变化,调试了一番硬是没发现问题出在哪里……明明一切都完美运行,然后突然毫无征兆的,进度就卡死在那里了……真是邪门……

正好我对Python的运行效率头大很久了,Rust又不太会用,于是直接决定用自己不怎么熟悉的cpp直接重写一个……

得益于Cpp糟糕max的UTF8(CJK)支持,我从昨晚12点写到3点,又从今天11点写到刚刚终于弄明白了……本来咱就不咋会cpp,还得想着做CJK兼容……真是服了cpp了

好在最后自己憋出来了……累死,AI用了吗?如用。解答的东西狗屁不通,思路都不对……

放小众留个痕迹吧,万一以后我手贱删了还有地方找,也懒得上传github了

cpp
#include "locale.h"
#include "wchar.h"
#include <filesystem>
#include <iostream>
#include <regex>
#include <string>
#include <windows.h>
using namespace std;
using namespace filesystem;

int main() {
  /*Set local of the rigion to avoid error caused by wrong coding*/
  std::setlocale(LC_ALL, "en_US.utf8");
  /*Init file path*/
  path file_dir = L"(D:\\LanguageLearning\\python\\music)";
  if (!exists(file_dir))
    exit(1);
  /*Start regex*/
  wsmatch regex_result_group;
  wregex reg(L"(.*\\\\)(.*) - (.*)( \\[.*\\])(\\.)(ogg|mp3|flac|wav)");
  /*Start to enum the dir*/
  directory_iterator list(file_dir);
  for (auto &it : list) {
    wstring temp = it.path().wstring();
    /*if path could match the regex, set the new_name*/
    if (regex_search(temp, regex_result_group, reg)) {
      wstring result = regex_result_group[1].str() +
                       regex_result_group[3].str() + L"." +
                       regex_result_group[6].str();
      wstring original_name = regex_result_group[0].str();
      wstring brief_name = regex_result_group[3].str();
      /*Try to rename*/
      if (0 != _wrename(original_name.c_str(), result.c_str())) {
        wcout << "Error" << brief_name << endl;
      } else {
        wcout << "Successfully! " << brief_name << endl << endl;
      }
    }
  }
  return 0;
}

(注:编译参数:-std=c++23 -Wall -Werror -O3 -pthread,请使用std23版本)


二编:

并且终于在大佬的分析下整明白了python为啥会崩(但是还是说上个版本没崩 :wl:),大抵就是错误的引号导致的rename和remove函数崩溃

在ai的帮助下改用rust全面重写,彻底解决了cpp的中文传参问题(我就没整明白过)


三编

Rust重写了一点点,增加了一点奇怪的路径支持功能

四编,改了rust的一点点冗余

main.rs
use regex::Regex;
use std::{env, fs, path::PathBuf};

fn main() {
    let mut args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!(
            "\nRegex QQMusic Renamer    version 1.6\n\nUsage:\n{} <directory>\n{} <files>\n",
            &args[0], &args[0]
        );
        return;
    }

    let input_path: PathBuf = PathBuf::from(&args[1]);
    let (input_is_dir, checked_path) = check_path_dir(input_path);
    if input_is_dir {
        println!("Enum {:?} ...", checked_path);
        for files in fs::read_dir(&checked_path.as_path()).unwrap() {
            rename_files(&files.unwrap().path());
        }
    } else {
        args.remove(0);
        for i in args {
            rename_files(&PathBuf::from(&i));
        }
    }
}

fn check_path_dir(mut path_to_check: PathBuf) -> (bool, PathBuf) {
    path_to_check = if path_to_check.ends_with("\"") {
        PathBuf::from(path_to_check.to_string_lossy().strip_suffix("\"").unwrap())
    } else {
        path_to_check
    };

    if !path_to_check.exists() {
        panic!("{:?} does not exist.", path_to_check.to_str());
    }

    (path_to_check.is_dir(), path_to_check)
}

fn get_new_name(regex: Regex, original_name: &str) -> &str {
    regex
        .captures(original_name)
        .unwrap()
        .get(2)
        .unwrap()
        .as_str()
}

fn rename_files(each_path: &PathBuf) {
    let reg = Regex::new(r"(.*) - (.*)( \[.*)").unwrap();
    //File name example: "HOYO-MiX - 枫丹 Fontaine [qmmc2].flac"
    if !each_path.is_dir() {
        let ext_list = ["ogg", "mp3", "flv", "flac", "wav", "mgg2", "mgg", "mflac"];
        let file_name = each_path.file_stem().unwrap().to_str().unwrap();
        let file_ext = each_path.extension().unwrap().to_str().unwrap();
        let dir_path = each_path.parent().unwrap().to_str().unwrap();

        if ext_list.contains(&file_ext) && reg.is_match(file_name) {
            let new_file_name = get_new_name(reg, file_name);
            let new_file_path =
                PathBuf::from(dir_path).join(new_file_name.to_string() + "." + file_ext);
            fs::rename(each_path, new_file_path).unwrap();
            println!("Renamed {} to {}", file_name, new_file_name);
        }
    }
}
Cargo.toml
[package]
name = "_regex_qqmusic_renamer"
version = "1.3.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[profile.release]
lto = true
codegen-units = 1
opt-level = "z"
strip = true


[dependencies]
regex = { version = "1.10.2" }
winapi = { version = "0.3.9" }

如果有大神能明白python为啥寄了也行,疑惑很久很久了……

典型路径:
D:\LanguageLearning\python\music\HOYO-MiX - 枫丹 Fontaine [qmmc2].flac

你的 Python 脚本某种程度上来说有点难以形容,首先不要用 os.walk 方法了,和路径相关的方法应该用 Path 对象来处理,里面的 rglob 专用于搜索某个路径下的文件。

其次判断文件也应该是 Path.suffix == “.mp3” 这种模式,哪怕真的要按你的这个匹配模式也应该是正则表达式的 re.search(r’.mp3$') 的逻辑,或者字符串的 “.mp3” in file 的方式,总之完全不应该考虑字符串切片样式……

from pathlib2 import Path

呃,用其它语言也是条路子,起码如果你用 Python , 您应该过不了我的面试

1 个赞

大哥,我是个从未选修过计算机,从未接触过竞赛的普通医学专业学生啊……要是我能通过面试,那后面发生什么我都不敢想 :xk:

可以试试下面两个软件:

应该满足你的需求,就不用手撸脚本了……

这类需求其实用 py 就足够了,不用强行上 cpp 的,没有科班基础学 cpp 事倍功半……
然后 py 可以不用追更新,稳定用就一直用,建议一个功能脚本给它弄一个虚拟环境,杜绝系统环境带来的一些奇奇怪怪的问题
你正好稍微重新优化一下脚本应该也不用像用cpp花那么多时间

=====

我用你提供的典型路径试了试应该是没问题的
猜测可能是碰到哪个路径了没法处理吧…?按理说用 print 大法可以观察卡在哪个语句啊

因为非科班出身所以说也没看明白怎么优化py啊,大悲……这是一个死结

呃,你都更新 3.12 了…(不要随便更新啊,看看更新内容啊
几个点:

  1. 廖雪峰,面向小白基础的基础知识。在上面看看自己还有哪些不了解的语言特性,主要就是查阅起来比较方便,偶尔我有些记不太咋用了会去看看。

  2. cookbook,实用经验技巧。比较全面了,告诉你细节方面的最佳实践,自己编程时的逻辑功能等多参考上面的建议。不过这个是几年前写的了,有些内容随着版本更新会有更优实践。

  3. 官方文档,备查,主要看各种标准库的示例, 更新大版本时有什么新变化。有些版本更新会带来一些实用的新语法糖,写起来更优雅性能更好,比如 3.4 的 pathlib、3.6 的 f-string、3.7 的 dataclasses 等等,写新的脚本的时候就尽量用这些新语法。

  4. 大佬文章,我一般是更新的时候知乎V2EX这类地方随手搜一下看看有没有博客文章,学习的同时可以顺便关注一些比较优质的博客。

=========

举例来说:

if file[-5:] == "].mp3" or file[-5:] == "].ogg" or file[-6:] == "].flac",目的是判断文件类型,假如我这方面不熟(其实 glob 已经挺经典了,用过就会有印象),放狗搜索大概浏览一下各内容并综合分析,很容易就知道现在有 pathlib 标准库,然后就是fp = pathlib.Path(file)if fp.suffix in ['.mp3', '.ogg', '.flac'],就很简洁直观。包括后面用到的路径拼接、替换新文件名等等……

1 个赞

我也是自学py的,很早之前学的,基本也是要用了就现学,写东西时看到了新东西就看例子学着用,但始终感觉是代码的复制粘贴,没有长进。
话说有没有人推荐高质量的python教程,不用像字典一样解释所有概念、函数那样的,也不用太高深的知识,又有编程思维/规范/经验,这样实用的?又或者是分析高质量代码,说一说设计思路、优点这样的?

改名可以用我的改名小工具试试 :grinning:
https://abc100.net/106/

以前也有很长一段时间喜欢玩Python,后来吧,发现这玩意就是个可以闲暇之余玩的泥巴,不要因为用的人多就跟风,要有自己的判断。。。最大的问题就是写好了程序千万别更新环境或者版本。。。所以我现在写程序宁可用C#或者已经忘得差不多了的C++,反正大不了用ChatGTP4把C#转成C++,或者反过来转。。。

在 3.12 增加的 pathlib.Path.walk 功能
这样感觉基本完全替代 os.path 了

说实话,对非科班出身的我已经完全超纲了……至今我也没能整明白py的类到底是啥玩意,全都是函数实现……

以及,你似乎漏了最重要的正则(

令我奇怪的是rename那一步出的问题,遍历完全没问题,就在rename的时候奇怪的卡住了……

巧了,我除了你的rename,其他基本都没看明白 :wl: 尤其不明白一堆以"_"开头的变量是要干啥……果然没经过正规教学还是不大行啊……

那估计就是遇到非法路径了吧……我比较纳闷的是你说没报错信息
不知道你是怎么调试的,如果在终端里执行出错就会马上打印的啊

邪门就邪门在这里啊,ark工具都上了,硬生生没明白哪里有占用的……并且理论上py应该抛出异常的啊……

卡住了,不动弹……
我截个屏去

就这样10多分钟不动……

调试:
遍历正常


一切正常

突然卡死:

紧急使用openark查看占用:

时间已过去一分钟,仍无反应(文件大小200kb左右)

下划线的变量是我自己的命名风格,这个不是那么重要。你先读我提供给你的代码,你看看是否能读懂,从main方法作为入口。
另外遇到了代码跑飞的情况,DEBUG一下比较好,能比较清楚地了解出错的原因。建议上B站看一下“断点"和”调试“的用法。

你看楼上,调了,有问题吗?如有……

完全没明白哪里炸了,更关键的是,那行代码在调试时成功运行了好几次,如果再次运行,有可能不卡在这里,像是随机事件……

cpu占用极小,完全卡住了像是,内存也不动弹……所以我说邪门啊,其他的bug我都能大体明白,这个bug是真没明白