V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
woodytang
V2EX  ›  程序员

写了 2 天的 Python ,有点奇怪的感觉。。。

  •  
  •   woodytang · 2024-03-28 20:00:55 +08:00 · 4750 次点击
    这是一个创建于 367 天前的主题,其中的信息可能已经有所发展或是发生改变。

    听说 pandas 很好用,于是决定试一下

    目的是这样的: user, amount, datetime A 10 2021-02-12 10:00:00 A 20 2021-03-12 10:00:00 B 30 2021-05-12 10:00:00 B 10 2021-06-12 10:00:00 C 5 2021-05-12 10:00:00 C 10 2021-06-12 10:00:00 C 20 2021-06-12 10:00:00 这是我们常见的数据库的表

    最后要做成这样的效果 2021-02 2021-03 2021-05 2021-06 A 10 20 0 0 B 0 0 30 10 C 0 0 5 30


    首先我想问一下,这样的需求,大家首先会想到用什么方案(语言,框架,库)做?

    column_names = [desc[0] for desc in cursor.description] df = pd.DataFrame(rows, columns=column_names) df['datetime'] = pd.to_datetime(df['datetime']) df['归属年月'] =(df['datetime'].dt.year.astype(str)+ df['datetime'].dt.strftime('%m').astype(str)).astype(int)

    到这里把时间字段理好了,变成数字,让它可以排序

    grouped_data = df.groupby(['user', '归属年月'])['amount'].sum().reset_index() grouped_data = grouped_data.sort_values('归属年月') 来个二维分组,然后按照‘2021-05’这个来排序一下

    df_pivot = pd.pivot_table(grouped_data, index=['user'], columns='归属年月', values='amount', aggfunc='sum').reset_index().fillna(0)

    到这里就好了

    代码十分简洁,但是这种代码真的有可读性吗?好维护吗?

    如果我要算 A 用户所有金额汇总,可以这样写: df_pivot['余额'] = df_pivot.iloc[:, 1:].sum(axis=1)

    这个= 让我震惊了,明明执行了函数操作,看上去像是一个赋值操作,这个 python 是怎么实现的?

    我编辑器用的 vsc ,python 没有 type hint ,这个有解吗,难道一边写要一边查文档?

    还有元组的简写法

    a= 1,2,3

    看到这个我也是懵了

    有没有 pythoner 解答一下,这个语言真的适合写正经项目吗?


    js 有 ts 就变得完美了,我很喜欢 python 的生态,有什么可以愉快写 python 的办法?

    33 条回复    2024-04-02 11:23:59 +08:00
    cmdOptionKana
        1
    cmdOptionKana  
       2024-03-28 20:13:32 +08:00
    老生常谈了,越灵活的语言,越需要团队自己制定规范,否则很难维护。

    以前 IDE 比较落后,Python 之类的动态语言很方便,但现在 IDE 先进了,优势就来到静态语言这边。

    你可以想象一下(或者试一下),用记事本去写代码,你一定会更喜欢 Python 。可惜时代变了,Python 在现代确实没有特别优势。
    woodytang
        2
    woodytang  
    OP
       2024-03-28 20:23:24 +08:00
    我昨天用了一下 python 的 fast-api, 这个设计得真好,,但是我对 python 没信心,用 python 做大项目的感觉是怎样的?
    woodytang
        3
    woodytang  
    OP
       2024-03-28 20:25:14 +08:00
    另外很多 ai 的库都用 python 写的,听说很多搞 AI 的人都不怎么会编程,所以搞得效率很低下,是真的吗?
    june4
        4
    june4  
       2024-03-28 20:28:11 +08:00
    我虽然之前写了很多 python,但现在我更喜欢 js/ts 。js 保持了很好的语言简洁性/可读性/灵活性的平衡,ruby 简直可怕,python 搞得越来越复杂,但写起来体验不如 js
    cmdOptionKana
        5
    cmdOptionKana  
       2024-03-28 20:33:56 +08:00
    @woodytang 用 python 还是做小项目比较合适,大项目看团队擅长语言。

    AI 领域 python 只是作为胶水语言而已,涉及性能的部分都是 C++之类的去实现,python 调用。
    woodytang
        6
    woodytang  
    OP
       2024-03-28 20:34:43 +08:00
    js 是在这些老 语言里面,一个沙盒脚本语言,感觉设计很合理的了,有 ts 后,感觉非常和谐,但是生态大多都是界面交互一类的,没啥意思,
    python 功能强大多了,但是这个写起来蛋疼,可能是我不懂的缘故
    FYFX
        7
    FYFX  
       2024-03-28 21:15:06 +08:00
    好久没用过 pandas 了,不过感觉你这是写法问题。。。
    而且我记得 pandas 的里面坑挺多的,不过听说好像[Polars]( https://docs.pola.rs/) 不错?
    Donahue
        8
    Donahue  
       2024-03-28 22:13:49 +08:00
    python 有 type hint 的, 类型注解啊
    比如 a:int = 10,
    def add(a:int, b:int) -> int:
    跟 ts 差不多
    mumbler
        9
    mumbler  
       2024-03-28 22:18:02 +08:00
    python 正经项目还少吗
    flyqie
        10
    flyqie  
       2024-03-28 22:33:14 +08:00
    @cmdOptionKana #1

    静态语言确实好维护,但有些需求不太需要特别好的维护性,追求速度的话,这样就很合适。

    原型我一般会尽可能用动态语言写,但上线肯定还是静态语言优先。
    fatigue
        11
    fatigue  
       2024-03-28 22:41:47 +08:00
    你懵是因为你用的少见得少,习惯了就好了,可读性是建立在大家都有基本共识的前提下的,你用上一年你就觉得太顺手了
    musi
        12
    musi  
       2024-03-28 22:55:52 +08:00 via iPhone
    @woodytang js 生态在 ai 后端都有吧,虽然 npm 设计的有问题,但是 npm 生态是一点都不弱啊
    sunzhuo
        13
    sunzhuo  
       2024-03-28 23:43:57 +08:00
    python 不觉得容易,就是因为库多而已
    zhzy
        14
    zhzy  
       2024-03-29 00:15:58 +08:00   ❤️ 1
    1. 可能还是不熟悉吧, 首先那一大堆生成归属年月的代码其实直接格式化成字符串就行了, groupby 和 pivot_table 都是支持字符串的, 而且你也不需要先 groupby 再 pivot_table.



    2. 至于 df_pivot['余额'] = df_pivot.iloc[:, 1:].sum(axis=1), 不就是把 sum 函数的返回值赋值给余额那一列么. 只是说它帮你处理了一下, 如果不存在这一列的话就新创建一列. 具体来说这是一个语法糖, 在类的 __setitem__ 方法里实现.

    3. Python 有 type hint. 3.6 就有了, 不过要到好用的程度的话至少要到 3.9 和 3.10 吧.

    4. 不要这样创建元组. 格式化工具会帮你加上括号的. 这个地方确实容易踩坑, 特别是只有一个元素的时候. 我是这样理解的, 在 Python 里 tuple 实际上是逗号定义而不是括号定义的.



    5. 如果你是团队的话, 是会有规范的. 至于正经项目怎么说呢, 要写肯定能写, 毕竟 Instagram 也在用 (虽然是魔改的). 而且所有的语言都或多或少有一些黑魔法, 为了工程化不用就是了. 真要变成 Go 那样说实话有的时候也挺难受的.
    xingfa
        15
    xingfa  
       2024-03-29 00:25:46 +08:00
    NoOneNoBody
        16
    NoOneNoBody  
       2024-03-29 01:01:49 +08:00   ❤️ 1
    主要是你不懂 pandas/numpy ,所以觉得难读,用惯了的人,一眼就看得懂

    1. df_pivot['余额'] = df_pivot.iloc[:, 1:].sum(axis=1)
    这个确实是赋值,是整列批量赋值,用的是向量化函数,当数据量很大时,你就知道向量化计算的作用了
    没有向量化函数,都不知道要写多少个 for

    2. 可读性问题
    在 pandas 的计算,一般不会太考究如何实现计算,更重视的是输入输出格式,以及值的类型和逻辑准确性,以及性能
    因为 pandas 一般都是处理大量数据的,很难逐个值考究,只要保证格式、类型、逻辑准确性
    例如有三百个白色乒乓球要变成红色,原生写法是逐个刷红色,pandas/numpy 是全部扔进红色颜料池,搅匀后捞上来就是了。所以需要搞清楚的是:扔进去的是否乒乓球、是否白色、多少个,颜料是否红色,池子是否容得下,以及捞起来后乒乓球有没有破损,数量够不够……至于中间如何染色搅匀,就只能相信这个操作搅匀的机器不会打烂乒乓球了
    所以最好是函数加上 __doc__描述,便于以后查阅

    np.lib.stride_tricks.as_strided(s, (len(s) - (window - 1), window), (s.values.strides * 2))
    这句我从别人那里抄过来的,至今都没搞清其中原理,但我知道 numpy 模拟实现 pandas.rolling 需要用到这句,且值和 pandas.roolling 的结果一致,这就够了

    numpy 和 pandas 的手册很庞大,个人读不完,就算读完了也记不全,目前最好方案是借助 gpt 帮我查某个函数的意义

    3.你这段代码并没有“规律时间序”,目的只是 groupby 分组,其实没必要用时间函数那么复杂,直接按字符串提取前 7 个字符,再 groupby 就可以了

    vsc 有支持 hint 的扩展,如 pyright ,但如果代码没有写指定类型,也是按默认类型提示,所以想全程提示,需要自己在代码中指定 types hint
    vituralfuture
        17
    vituralfuture  
       2024-03-29 01:41:44 +08:00 via Android
    元组一定要加括号,千万别省,可以配置格式化工具,每次格式化自动加上。
    曾经在某行赋值语句末尾不小心按了逗号按键,刚好位置比较边缘,debug 半天,最后发现怎么值是个元组,才发现是这种无聊的原因
    Jirajine
        18
    Jirajine  
       2024-03-29 01:53:15 +08:00
    R with tidyverse https://www.tidyverse.org/
    这实质上是一个专为 dataframe 设计的基于 R 的 dsl ,可以说是单独这类需求理论上最适合的工具。
    如果是一般语言的话,julia 也是非常适合数据分析的。
    YsHaNg
        19
    YsHaNg  
       2024-03-29 05:52:45 +08:00 via iPhone
    pandas/numpy 都算是 array oriented 类似的还有 Matlab 觉得懵你要不看看 apl
    arischow
        20
    arischow  
       2024-03-29 06:12:53 +08:00 via iPhone
    你都这么想了,那肯定是不合适,用 Java 重写啦
    dekuofa
        21
    dekuofa  
       2024-03-29 06:47:50 +08:00 via iPhone
    建议直接上 polars
    bianhui
        22
    bianhui  
       2024-03-29 08:22:14 +08:00
    静态语言后遗症。多写就好了。世界上流行的语言都有他自己的优势,你要抛开你固有观念,去接纳新鲜事物
    nno
        23
    nno  
       2024-03-29 08:46:29 +08:00
    "这个= 让我震惊了,明明执行了函数操作,看上去像是一个赋值操作,这个 python 是怎么实现的?"
    这里其实是 pandas 的 DataFrame 重写了__setitem__接口,当你将对象当做一个 map 来操作的时候,就会触发__getitem__、__setitem__这些内置接口
    darkengine
        24
    darkengine  
       2024-03-29 08:47:13 +08:00
    光考虑要实现什么功能还不够,还需要考虑:

    1 ,这个功能需要长期维护迭代,还是就一次性的数据处理
    2 ,团队/个人的能力:包括能不能快速上手某个语言,熟悉某些库,使用某语音,某些库后续好不好招人
    suuuch
        25
    suuuch  
       2024-03-29 09:19:40 +08:00
    Python 在我的理解里面,是完全为了非科班的人员进入编程的最便捷的路径。

    贴近自然语言的语法,脚本直接执行。在学习和自己想快速实现某些小功能的情况下,将环境准备成本将到最低。

    比如说,openai 接口刚出来那会,我跟两个朋友弄了个账号一起用。我为了方便,在我买的美帝服务器上做一个转发,及我从国内把 请求服务器上的接口,再从服务器上转发到 openai 的 api 上。。写一下 flask 的固定 token 鉴权,用 supervisor 监控一下。不考虑任何性能。半个小时搞完。。。
    SmiteChow
        26
    SmiteChow  
       2024-03-29 10:38:48 +08:00
    还需要多用用,少提见解。
    yjhatfdu2
        27
    yjhatfdu2  
       2024-03-29 11:12:26 +08:00
    这不是一句 sql 查询就能解决的事情吗,为啥要变复杂?
    xgdgsc
        28
    xgdgsc  
       2024-03-29 12:35:39 +08:00
    不喜欢这种可以用 julia 无脑 for 循环处理数据
    song135711
        29
    song135711  
       2024-03-29 13:25:55 +08:00
    ide 试试 pycharm 社区版。
    计算机语言是工具,重要的是使用工具的人。
    mkroen
        30
    mkroen  
       2024-03-29 14:04:07 +08:00   ❤️ 1
    @xingfa 感谢分享,已经在做眼保健操了
    woodytang
        31
    woodytang  
    OP
       2024-03-29 16:55:14 +08:00
    @zhzy 大哥,感谢你啊,把我的问题写出来了
    woodytang
        32
    woodytang  
    OP
       2024-03-29 17:01:46 +08:00
    @yjhatfdu2 请赐教,sql 怎么一句写出来~~
    yjhatfdu2
        33
    yjhatfdu2  
       362 天前
    @woodytang pg 下,如果可以用程序生成返回列然后拼接 SQL ,那么很简单
    //创建测试表
    create table test3("user" text,amount int,datetime timestamp);
    insert into public.test3 (user, amount, datetime)
    values ('A', 10, '2021-02-12 10:00:00.000000'),
    ('A', 20, '2021-03-12 10:00:00.000000'),
    ('B', 30, '2021-05-12 10:00:00.000000'),
    ('B', 10, '2021-06-12 10:00:00.000000'),
    ('C', 10, '2021-06-12 10:00:00.000000'),
    ('C', 20, '2021-06-12 10:00:00.000000'),
    ('C', 5, '2021-05-12 10:00:00.000000');
    //使用 crosstab 进行 pivot
    select *
    from crosstab($$select "user",date_trunc('month',datetime)::date::text,sum(amount)
    from test3 group by 1,2 order by 1,2 $$,
    'select distinct date_trunc(''month'',datetime)::date::text from test3 order by 1') as ct(
    "user" text,
    "2021-02" text,
    "2021-03" text,
    "2021-05" text,
    "2021-06" text
    );
    //结果
    user | 2021-02 | 2021-03 | 2021-05 | 2021-06
    ------+---------+---------+---------+---------
    A | 10 | 20 | |
    B | | | 30 | 10
    C | | | 5 | 30

    如果要动态生成返回列名,那么确实是相当的麻烦,因为 pg 的查询必须显式制定列,只能用非常别扭的方式
    -- 创建一个函数,实际查询的 SQL ,可以反复使用
    create or replace function crosstabquery(_t anyelement) returns setof anyelement as
    $$
    declare
    column_list text;
    begin
    select string_agg(format('%I text', d), ',')
    into column_list
    from (select distinct date_trunc('month', datetime)::date::text d from test3 order by 1) r;
    return query execute ($b$select *
    from crosstab($a$select "user",date_trunc('month',datetime)::date::text,sum(amount)
    from test3 group by 1,2 order by 1,2 $a$,
    'select distinct date_trunc(''month'',datetime)::date::text from test3 order by 1') as ct("user" text,$b$ ||
    column_list||')' );
    end ;
    $$
    language plpgsql;
    -- 创建返回类型,每次使用前调用
    do
    $$
    declare
    column_list text;
    begin
    select string_agg(format('%I text', d), ',')
    into column_list
    from (select distinct date_trunc('month', datetime)::date::text d from test3 order by 1) r;
    drop type if exists __t;
    execute format('create type __t as ("user" text, %s)', column_list);
    end
    $$ language plpgsql;
    -- 返回实际结果
    select * from crosstabquery(null::__t);
    user | 2021-02-01 | 2021-03-01 | 2021-05-01 | 2021-06-01
    ------+------------+------------+------------+------------
    A | 10 | 20 | |
    B | | | 30 | 10
    C | | | 5 | 30
    (3 rows)

    不过如果可以动态生成 sql ,就别用下面这个方案,实在是别扭
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2529 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 15:26 · PVG 23:26 · LAX 08:26 · JFK 11:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.