V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
woodytang
V2EX  ›  程序员

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

  •  
  •   woodytang · 245 天前 · 4518 次点击
    这是一个创建于 245 天前的主题,其中的信息可能已经有所发展或是发生改变。

    听说 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  
       245 天前
    老生常谈了,越灵活的语言,越需要团队自己制定规范,否则很难维护。

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

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

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

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

    原型我一般会尽可能用动态语言写,但上线肯定还是静态语言优先。
    fatigue
        11
    fatigue  
       245 天前
    你懵是因为你用的少见得少,习惯了就好了,可读性是建立在大家都有基本共识的前提下的,你用上一年你就觉得太顺手了
    musi
        12
    musi  
       245 天前 via iPhone
    @woodytang js 生态在 ai 后端都有吧,虽然 npm 设计的有问题,但是 npm 生态是一点都不弱啊
    sunzhuo
        13
    sunzhuo  
       245 天前
    python 不觉得容易,就是因为库多而已
    zhzy
        14
    zhzy  
       245 天前   ❤️ 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  
       245 天前
    NoOneNoBody
        16
    NoOneNoBody  
       244 天前   ❤️ 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  
       244 天前 via Android
    元组一定要加括号,千万别省,可以配置格式化工具,每次格式化自动加上。
    曾经在某行赋值语句末尾不小心按了逗号按键,刚好位置比较边缘,debug 半天,最后发现怎么值是个元组,才发现是这种无聊的原因
    Jirajine
        18
    Jirajine  
       244 天前
    R with tidyverse https://www.tidyverse.org/
    这实质上是一个专为 dataframe 设计的基于 R 的 dsl ,可以说是单独这类需求理论上最适合的工具。
    如果是一般语言的话,julia 也是非常适合数据分析的。
    YsHaNg
        19
    YsHaNg  
       244 天前 via iPhone
    pandas/numpy 都算是 array oriented 类似的还有 Matlab 觉得懵你要不看看 apl
    arischow
        20
    arischow  
       244 天前 via iPhone
    你都这么想了,那肯定是不合适,用 Java 重写啦
    dekuofa
        21
    dekuofa  
       244 天前 via iPhone
    建议直接上 polars
    bianhui
        22
    bianhui  
       244 天前
    静态语言后遗症。多写就好了。世界上流行的语言都有他自己的优势,你要抛开你固有观念,去接纳新鲜事物
    nno
        23
    nno  
       244 天前
    "这个= 让我震惊了,明明执行了函数操作,看上去像是一个赋值操作,这个 python 是怎么实现的?"
    这里其实是 pandas 的 DataFrame 重写了__setitem__接口,当你将对象当做一个 map 来操作的时候,就会触发__getitem__、__setitem__这些内置接口
    darkengine
        24
    darkengine  
       244 天前
    光考虑要实现什么功能还不够,还需要考虑:

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

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

    比如说,openai 接口刚出来那会,我跟两个朋友弄了个账号一起用。我为了方便,在我买的美帝服务器上做一个转发,及我从国内把 请求服务器上的接口,再从服务器上转发到 openai 的 api 上。。写一下 flask 的固定 token 鉴权,用 supervisor 监控一下。不考虑任何性能。半个小时搞完。。。
    SmiteChow
        26
    SmiteChow  
       244 天前
    还需要多用用,少提见解。
    yjhatfdu2
        27
    yjhatfdu2  
       244 天前
    这不是一句 sql 查询就能解决的事情吗,为啥要变复杂?
    xgdgsc
        28
    xgdgsc  
       244 天前
    不喜欢这种可以用 julia 无脑 for 循环处理数据
    song135711
        29
    song135711  
       244 天前
    ide 试试 pycharm 社区版。
    计算机语言是工具,重要的是使用工具的人。
    mkroen
        30
    mkroen  
       244 天前   ❤️ 1
    @xingfa 感谢分享,已经在做眼保健操了
    woodytang
        31
    woodytang  
    OP
       244 天前
    @zhzy 大哥,感谢你啊,把我的问题写出来了
    woodytang
        32
    woodytang  
    OP
       244 天前
    @yjhatfdu2 请赐教,sql 怎么一句写出来~~
    yjhatfdu2
        33
    yjhatfdu2  
       240 天前
    @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   ·   实用小工具   ·   1771 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 16:37 · PVG 00:37 · LAX 08:37 · JFK 11:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.