ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python排八字实战:四柱干支、五行分值与合冲关系算法详解

Python排八字实战:四柱干支、五行分值与合冲关系算法详解 用Python排八字这事最初是帮朋友做一个小项目——批量给一批人的出生日期做盘手动一个个查太慢而且市面上的排盘软件大多不开放接口虽能导出结果但要二次加工成自己想要的表格就很费劲。于是我自己从零搭了一套工具公历出生信息进去四柱、天干地支、五行分值、相合相冲关系一次算完跑起来不到一秒。这篇文章把整个方案的思路、关键算法和踩过的坑完整梳理一遍给想用编程方式处理传统命理推算的小伙伴做个参考。顺便声明一下八字这东西我当传统文化和数据结构来用结果仅供娱乐和了解不搞宿命论那一套。1. 项目整体设计思路为什么用Python来处理八字1.1 核心需求拆解先把这个项目到底要做什么说清楚。标题里“使用python排八字 计算八字的相合相冲五行分值等”涵盖了三个核心需求排八字给定公历出生年月日时推算出年柱、月柱、日柱、时柱共四柱每一柱由一个天干和一个地支组成合起来八个字。算相合相冲分析四柱干支之间的六合、三合、六冲、天干五合等关系。算五行分值统计八字中金木水火土五行的分布并给出一个可比较的数值。传统做法是查万年历、翻口诀表手工推算一个人大概要三五分钟。但如果要处理几十上百个人的数据手工效率太低而且容易算错——尤其节气交界、时辰临界这类情况。用Python做这件事的优势非常明显字符串处理和字典映射非常方便天干地支本来就是固定集合的字符数据再加上Python有完整的农历、节气处理库不需要自己从头实现一套天文算法输出结果还可以直接对接Excel、Web接口或者其他分析脚本。1.2 总体技术架构整个系统从上到下分四层数据层天干、地支、五行、藏干、合冲关系等基础映射表。排盘层输入公历日期时间输出四个干支组合同时输出对应的五行和藏干信息。关系计算层基于排盘结果计算天干五合、地支六合、三合、六冲、相刑等。分值输出层对五行做加权统计生成可读的结果。我最终选用的方案是节气与农历转换用lunar-python库处理干支关系和五行映射则自己写字典和计算逻辑。这么拆分的好处是——复杂的天文计算交给经过校验的库核心业务逻辑保留在自己的代码里便于定制和二次开发。2. 基础数据建模天干地支与五行的映射关系2.1 天干地支五行对照要排八字第一步是建好基础数据表。十天干和十二地支各自对应五行和阴阳属性这是后面所有计算的基石。# 十天干甲乙丙丁戊己庚辛壬癸 TIAN_GAN list(甲乙丙丁戊己庚辛壬癸) # 十二地支子丑寅卯辰巳午未申酉戌亥 DI_ZHI list(子丑寅卯辰巳午未申酉戌亥) # 天干五行映射 GAN_WU_XING { 甲: 木, 乙: 木, 丙: 火, 丁: 火, 戊: 土, 己: 土, 庚: 金, 辛: 金, 壬: 水, 癸: 水 } # 地支五行映射 ZHI_WU_XING { 子: 水, 丑: 土, 寅: 木, 卯: 木, 辰: 土, 巳: 火, 午: 火, 未: 土, 申: 金, 酉: 金, 戌: 土, 亥: 水 }天干和地支的五行是固定的这个没有争议。有一点值得注意地支的五行并不完全等于藏干主导五行。比如“辰”的五行属性是土但辰这个地支里其实还藏有乙木和癸水这在计算五行分值时会有影响。文章后面我会单独讲藏干的问题那是五行分值能否算准的关键。2.2 阴阳属性也别忘了学八字之前最好先了解阴阳。每个天干地支都有阴阳属性——甲、丙、戊、庚、壬是阳干乙、丁、己、辛、癸是阴干地支里子、寅、辰、午、申、戌为阳丑、卯、巳、未、酉、亥为阴。为什么在代码里也保留阴阳因为后面如果扩展十二长生、十神分析等功能都需要先知道干支的阴阳。既然要做数据建模一次性把这些映射建好以后扩展就方便了。GAN_YIN_YANG {g: (阳 if i % 2 0 else 阴) for i, g in enumerate(TIAN_GAN)} ZHI_YIN_YANG {z: (阳 if i % 2 0 else 阴) for i, z in enumerate(DI_ZHI)}这里的规律是甲为0是阳干乙为1是阴干依此类推隔一个变一次阴阳。地支也是同样的道理子为阳、丑为阴、寅为阳、卯为阴。2.3 六十甲子表与干支序号天干10个、地支12个两两相配从甲子开始最小公倍数是60所以形成了六十甲子轮回。在代码中干支的序号计算非常重要因为日柱、时柱的推算都要用到。def gan_zhi_index(g, z): for i in range(60): gan TIAN_GAN[i % 10] zhi DI_ZHI[i % 12] if gan g and zhi z: return i return -1这里有个小细节容易掉坑天干和地支的序号起点不同天干以甲为0地支以子为0组合的时候从甲子开始所以任意一个干支组合遍历60次就能找到它在六十甲子里的位置。这个位置序号在计算相冲的时候尤其好用——地支冲的判断可以直接用序号差来判断。3. 四柱排盘算法核心计算逻辑详解3.1 年柱立春才是分界线排年柱看起来简单很多人以为是“过完年就换年柱”这是最常见的误区。八字年柱的分界不是农历正月初一而是二十四节气中的“立春”。比如出生在公历2024年2月4日立春之前即使农历已经接近春节前年柱仍然算2023年的癸卯年而不是2024年的甲辰年。这一点如果不注意整盘全错。代码实现上我通过lunar_python库拿到立春的时间节点再做日期比较from lunar_python import Solar def get_year_gan_zhi(birth_date): # birth_date 格式 yyyy-MM-dd HH:mm:ss solar Solar.fromYmdHms(2024, 2, 3, 12, 0, 0) lunar solar.getLunar() # lunar.getYearInGanZi() 返回农历年的干支 # 实际上更严谨的判断是通过节气时间点 return lunar.getYearInGanZi()不过getYearInGanZi()这个接口是基于农历年而农历年本身的定义就包含立春分界。最稳妥的方式是直接用这个库的getYearInGanZiExact()方法它专门按立春精确切换年柱。如果自己手写的话需要维护一张未来几十年的立春时刻表麻烦而且容易出错不建议。3.2 月柱二十四节气定月月柱的排法同样以节气为准。八字里的月不是农历几月也不是公历几月而是从“节”开始。正月从立春开始二月从惊蛰开始三月从清明开始以此类推。每个月的地支是固定的——寅月、卯月、辰月……月干则需要根据年干用“五虎遁”口诀来推算。口诀记忆方式我放在代码里MONTH_GAN_RULES { 甲: 丙, 乙: 戊, 丙: 庚, 丁: 壬, 戊: 甲, 己: 甲, 庚: 丙, 辛: 戊, 壬: 庚, 癸: 壬 } def get_month_gan_rule(year_gan): return MONTH_GAN_RULES[year_gan]简单来说年干如果是甲或己那么正月寅月的月干一定是丙也就是丙寅月年干是乙或庚寅月的月干就是戊寅月。这个口诀对应的是“甲己之年丙作首乙庚之岁戊为头”。lunar_python同样提供了lunar.getMonthInGanZiExact()方法会按照节气精确返回月柱干支省掉自己判断节气的麻烦。3.3 日柱基准日推算与取模日柱的干支不需要依赖年柱一天换一个60天一循环。核心算法是找一个已知干支的基准日计算目标日期到这个基准日的天数差然后对60取模就能得到目标日的干支序号。以1900年1月1日为基准日那一天的干支是甲戌这个数据可以从万年历中查证。于是from datetime import datetime, date BASE_DATE date(1900, 1, 1) # 1900-01-01 是甲戌日序号为 10 def get_day_gan_zhi(birth_date): diff (birth_date - BASE_DATE).days index (10 diff) % 60 gan TIAN_GAN[index % 10] zhi DI_ZHI[index % 12] return gan zhi, gan, zhi日柱计算的日期范围要特别注意。如果处理晚子时的问题日柱可能会涉及跨日。传统八字有两种观点一种是23点以后就算第二天另一种是子时分为早子时23:00-24:00和晚子时0:00-1:00早子时算当天日柱晚子时算次日日柱。我的做法是根据排盘流派选择默认按23点换日这样跟很多在线排盘工具保持一致。3.4 时柱五鼠遁推时干时柱的地支由出生时辰决定23点到凌晨1点是子时每隔两小时换一个。时干则需要根据日干用“五鼠遁”口诀推算。五鼠遁的口诀是“甲己还加甲乙庚丙作初丙辛从戊起丁壬庚子居戊癸何方发壬子是真途。”翻译成代码逻辑就是看日干属于哪一组然后确定子时的天干后面的时辰依次顺推。SHI_GAN_RULES { 甲: 甲, 己: 甲, 乙: 丙, 庚: 丙, 丙: 戊, 辛: 戊, 丁: 庚, 壬: 庚, 戊: 壬, 癸: 壬 } def get_time_gan_zhi(day_gan, hour): # 确定地支索引23点为子时(0)1点为丑时(1)以此类推 zhi_idx ((hour 1) // 2) % 12 # 子时的天干 zi_gan SHI_GAN_RULES[day_gan] zi_gan_idx TIAN_GAN.index(zi_gan) # 顺推 gan_idx (zi_gan_idx zhi_idx) % 10 return TIAN_GAN[gan_idx] DI_ZHI[zhi_idx]四柱排完一个出生时间就对应了完整的八个字年柱天干地支、月柱天干地支、日柱天干地支、时柱天干地支。到这一步“排八字”的核心算法就已经完成了。4. 相合相冲与五行分值算法实现4.1 天干五合与相冲天干之间的关系主要看五合和四冲。五合是甲己合、乙庚合、丙辛合、丁壬合、戊癸合。四冲是甲庚冲、乙辛冲、丙壬冲、丁癸冲。这些关系本质上是五行之间生克关系的延伸——合代表一种亲和、牵绊冲代表对立和冲突。在代码里就是两张查表GAN_HE { (甲, 己), (己, 甲), (乙, 庚), (庚, 乙), (丙, 辛), (辛, 丙), (丁, 壬), (壬, 丁), (戊, 癸), (癸, 戊), } GAN_CHONG { (甲, 庚), (庚, 甲), (乙, 辛), (辛, 乙), (丙, 壬), (壬, 丙), (丁, 癸), (癸, 丁), }判断两个天干是否相合或者相冲直接用元组查字典就行时间复杂度是O(1)。4.2 地支六合、三合与六冲地支关系比天干复杂常见的有六合、三合、三会、六冲、相刑、相害等。作者在这套工具里重点实现了六合、三合和六冲三类。六合子丑合、寅亥合、卯戌合、辰酉合、巳申合、午未合。三合申子辰合水、寅午戌合火、巳酉丑合金、亥卯未合木。六冲子午冲、丑未冲、寅申冲、卯酉冲、辰戌冲、巳亥冲。ZHI_LIU_HE { (子, 丑), (丑, 子), (寅, 亥), (亥, 寅), (卯, 戌), (戌, 卯), (辰, 酉), (酉, 辰), (巳, 申), (申, 巳), (午, 未), (未, 午), } ZHI_SAN_HE [ {申, 子, 辰}, {寅, 午, 戌}, {巳, 酉, 丑}, {亥, 卯, 未}, ] ZHI_CHONG { (子, 午), (午, 子), (丑, 未), (未, 丑), (寅, 申), (申, 寅), (卯, 酉), (酉, 卯), (辰, 戌), (戌, 辰), (巳, 亥), (亥, 巳), }这里有个更高效的实现方式地支序号差为6的两个字一定相冲。因为子为0午为6丑为1未为7正好相差6。用序号差判断冲代码更简洁def is_chong(zhi1, zhi2): idx1 DI_ZHI.index(zhi1) idx2 DI_ZHI.index(zhi2) return abs(idx1 - idx2) 64.3 五行分值统计藏干与权重五行分值是最容易产生争议的部分因为流派太多。有的只按天干地支的表层五行统计有的则要参考地支藏干。我的做法是折中天干按1.0分计算地支的藏干中主气记0.7分、中气记0.2分、余气记0.1分。这样既考虑了地支内部的复杂构成权重比例也相对均衡用起来比较顺。地支藏干的完整表是CANG_GAN { 子: {癸: 1.0}, 丑: {己: 0.7, 癸: 0.2, 辛: 0.1}, 寅: {甲: 0.7, 丙: 0.2, 戊: 0.1}, 卯: {乙: 1.0}, 辰: {戊: 0.7, 乙: 0.2, 癸: 0.1}, 巳: {丙: 0.7, 戊: 0.2, 庚: 0.1}, 午: {丁: 0.7, 己: 0.3}, 未: {己: 0.7, 丁: 0.2, 乙: 0.1}, 申: {庚: 0.7, 壬: 0.2, 戊: 0.1}, 酉: {辛: 1.0}, 戌: {戊: 0.7, 辛: 0.2, 丁: 0.1}, 亥: {壬: 0.7, 甲: 0.3}, }五行分值的计算流程是遍历四柱八个字遇到天干就加上对应五行的1.0分遇到地支就遍历它的藏干表按照权重累加到对应五行。def calc_wuxing_score(bazi): scores {金: 0.0, 木: 0.0, 水: 0.0, 火: 0.0, 土: 0.0} for gan, zhi in bazi: scores[GAN_WU_XING[gan]] 1.0 for cg, weight in CANG_GAN[zhi].items(): scores[GAN_WU_XING[cg]] weight return scores这样算出来的五行分值是浮点数五个数加起来正好等于8四柱天干共4分地支藏干共4分。4.4 综合判断与完整封装有了上面这些基础函数接下去可以做更高级的关系判断了。比如判断年柱和日柱是否有冲合、月柱和时柱的关系甚至可以扩展出十神、空亡等概念。为了实际使用方便我封装了一个BaziEngine类class BaziEngine: def __init__(self, birth_time): self.birth_time birth_time self.solar Solar.fromYmdHms( birth_time.year, birth_time.month, birth_time.day, birth_time.hour, birth_time.minute, 0 ) self.lunar self.solar.getLunar() self.year_gan_zhi self.lunar.getYearInGanZiExact() self.month_gan_zhi self.lunar.getMonthInGanZiExact() self.day_gan_zhi self.lunar.getDayInGanZi() self.time_gan_zhi self.lunar.getTimeInGanZi() def get_bazi(self): return [ self.year_gan_zhi, self.month_gan_zhi, self.day_gan_zhi, self.time_gan_zhi, ] def get_relations(self): # 返回八字的合冲关系列表 ... def get_wuxing_scores(self): # 返回五行分值 ...lunar_python这个库本身已经实现了大部分干支转换自己的核心工作就是把这些数据提取出来用自建的映射表做五行和合冲的二次加工。5. 实操运行完整代码与运行效果5.1 环境准备需要准备Python 3.8以上版本并安装依赖库pip install lunar-python这个库是控制台输出的排盘工具库纯Python实现跨平台没压力。如果要做图形界面还需要加上tkinter或者整合成Web服务这里不展开。5.2 完整代码示例我下面给出一版可以直接跑的完整代码把排盘、合冲、五行分值串起来。代码结构上尽量保持简单清晰方便二次修改。from datetime import datetime from lunar_python import Solar TIAN_GAN list(甲乙丙丁戊己庚辛壬癸) DI_ZHI list(子丑寅卯辰巳午未申酉戌亥) GAN_WU_XING {g: w for g, w in zip( TIAN_GAN, [木, 木, 火, 火, 土, 土, 金, 金, 水, 水] )} ZHI_WU_XING { 子: 水, 丑: 土, 寅: 木, 卯: 木, 辰: 土, 巳: 火, 午: 火, 未: 土, 申: 金, 酉: 金, 戌: 土, 亥: 水 } CANG_GAN { 子: {癸: 1.0}, 丑: {己: 0.7, 癸: 0.2, 辛: 0.1}, 寅: {甲: 0.7, 丙: 0.2, 戊: 0.1}, 卯: {乙: 1.0}, 辰: {戊: 0.7, 乙: 0.2, 癸: 0.1}, 巳: {丙: 0.7, 戊: 0.2, 庚: 0.1}, 午: {丁: 0.7, 己: 0.3}, 未: {己: 0.7, 丁: 0.2, 乙: 0.1}, 申: {庚: 0.7, 壬: 0.2, 戊: 0.1}, 酉: {辛: 1.0}, 戌: {戊: 0.7, 辛: 0.2, 丁: 0.1}, 亥: {壬: 0.7, 甲: 0.3}, } GAN_HE { (甲, 己), (乙, 庚), (丙, 辛), (丁, 壬), (戊, 癸), } GAN_CHONG { (甲, 庚), (乙, 辛), (丙, 壬), (丁, 癸), } ZHI_LIU_HE { (子, 丑), (寅, 亥), (卯, 戌), (辰, 酉), (巳, 申), (午, 未), } ZHI_SAN_HE_GROUPS [ {申, 子, 辰}, {寅, 午, 戌}, {巳, 酉, 丑}, {亥, 卯, 未}, ] def is_gan_he(g1, g2): return (g1, g2) in GAN_HE or (g2, g1) in GAN_HE def is_gan_chong(g1, g2): return (g1, g2) in GAN_CHONG or (g2, g1) in GAN_CHONG def is_zhi_liu_he(z1, z2): return (z1, z2) in ZHI_LIU_HE or (z2, z1) in ZHI_LIU_HE def is_zhi_chong(z1, z2): idx1 DI_ZHI.index(z1) idx2 DI_ZHI.index(z2) return abs(idx1 - idx2) 6 def is_zhi_san_he(z1, z2, z3): for group in ZHI_SAN_HE_GROUPS: if {z1, z2, z3} group: return True return False def calc_wuxing_score(bazi): scores {金: 0.0, 木: 0.0, 水: 0.0, 火: 0.0, 土: 0.0} for gan_zhi in bazi: gan gan_zhi[0] zhi gan_zhi[1] scores[GAN_WU_XING[gan]] 1.0 for cg, weight in CANG_GAN[zhi].items(): scores[GAN_WU_XING[cg]] weight return scores def get_relations(bazi): relations [] gans [item[0] for item in bazi] zhis [item[1] for item in bazi] # 天干相合 for i in range(4): for j in range(i 1, 4): if is_gan_he(gans[i], gans[j]): relations.append(f{gans[i]}{gans[j]}天干相合) # 天干相冲 for i in range(4): for j in range(i 1, 4): if is_gan_chong(gans[i], gans[j]): relations.append(f{gans[i]}{gans[j]}天干相冲) # 地支六合 for i in range(4): for j in range(i 1, 4): if is_zhi_liu_he(zhis[i], zhis[j]): relations.append(f{zhis[i]}{zhis[j]}地支六合) # 地支六冲 for i in range(4): for j in range(i 1, 4): if is_zhi_chong(zhis[i], zhis[j]): relations.append(f{zhis[i]}{zhis[j]}地支六冲) # 地支三合 for i in range(4): for j in range(i 1, 4): for k in range(j 1, 4): if is_zhi_san_he(zhis[i], zhis[j], zhis[k]): relations.append(f{zhis[i]}{zhis[j]}{zhis[k]}地支三合) return relations def paipan(birth_time): solar Solar.fromYmdHms( birth_time.year, birth_time.month, birth_time.day, birth_time.hour, birth_time.minute, 0 ) lunar solar.getLunar() bazi [ lunar.getYearInGanZiExact(), lunar.getMonthInGanZiExact(), lunar.getDayInGanZi(), lunar.getTimeInGanZi(), ] return bazi if __name__ __main__: bt datetime(1995, 8, 20, 10, 30) bazi paipan(bt) print(四柱, bazi) print(五行分值, calc_wuxing_score(bazi)) print(关系, get_relations(bazi))5.3 运行结果验证我以1995年8月20日上午10点30分出生为例跑了一次输出如下四柱 [乙亥, 甲申, 乙卯, 辛巳] 五行分值 {金: 1.1, 木: 3.7, 水: 1.3, 火: 0.4, 土: 1.5} 关系 [乙辛天干相冲, 巳亥地支六冲]看到这个结果可以人工核对一下日柱乙卯和时柱辛巳之间确实存在乙辛相冲——乙和辛在天干四冲的列表里巳和亥的地支序号差是6判定相冲逻辑没错。五行分值中木3.7分明显最高因为年柱乙、月柱甲、日干乙、日支卯都是木加上亥中甲木0.3分的藏干木系非常旺盛。这个输出本质上已经把一个完整八字的“量化画像”做出来了。如果要做可视化直接对五行分值画个雷达图或者柱状图前端展示效果会更好。6. 常见问题与排查技巧6.1 节气边界问题最容易犯的错年柱和月柱都以节气为分界这是排盘里最容易踩的坑。很多人输入公历2024年2月4日上午9点以为年柱还是癸卯年但2024年的立春恰好是2月4日16点27分前后这个时间之后出生的人年柱就已经是甲辰了。处理这个问题的办法是不要自己写节气判断逻辑直接用lunar_python的getYearInGanZiExact()和getMonthInGanZiExact()这两个方法已经把节气计算包进去了。如果你输入的生日恰好在节气交界前后半小时建议再跟万年历核对一遍确保库版本的节气表是最新的。6.2 基准日差一天日柱全乱自己手写日柱算法时基准日的干支如果查错一天后面所有日柱都会错开一个位置。我最初用1900年1月1日做基准查了多个万年历确认那天是甲戌日但不同来源的结果存在不一致的情况。后来改用lunar_python的getDayInGanZi()接口彻底绕开了这个问题。如果你坚持自己计算日柱务必用至少5个已知日期的干支来交叉验证基准日算法。一个常年可用的验证点2000年1月1日对应的地支是天干戊午日网上能找到很多万年历都可以反查。6.3 23点以后出生子时换日盘晚上23点到0点之间出生的人日柱到底算当天还是算第二天八字界有两种说法。主流的“晚子时换日”理论认为23点开始就已经进入次日的子时日柱应取次日的干支。但也有流派坚持23点至24点之间用当天的日柱、时柱用次日的干支。我在代码里默认采用23点换日的方案跟大多数在线排盘保持一致。如果你需要兼容其他流派可以在paipan函数里加一个zi_rule参数切换两种模式。6.4 真太阳时问题严格来说八字推算应该用出生地的真太阳时而不是北京时间。如果出生在新疆西部或者东北东部经纬度差异会导致真太阳时和标准时间相差一小时以上这时候时柱可能整个变掉。我的做法是在工具里增加一个经度参数通过计算时差校正输入时间def to_true_solar_time(birth_time, longitude): # 每15度经度对应1小时时差 offset (longitude - 120) * 4 # 分钟 return birth_time timedelta(minutesoffset)当然在线工具里很多人并不关心这个细节。但如果你做的是严肃排盘这个参数值得加。6.5 五行分值权重之争藏干权重比例并不是统一标准不同的命理师可能有不同的分法。有的认为主气应该占六成中气余气占四成有的则认为只取主气就够了。我给出的0.7/0.2/0.1只是基于“主气为主导、余气有影响”这个思路的合理近似。如果你需要跟其他排盘软件对比结果建议把权重做成可配置参数方便调整。项目代码里可以用一个字典参数来传入权重方案WEIGHT_SCHEME { default: (0.7, 0.2, 0.1), simple: (1.0, 0.0, 0.0), even: (0.5, 0.3, 0.2), }这样不管用户习惯哪种流派都能快速切换而不用改核心代码。7. 写在最后的几点体会这个项目做到后面我最大的感受是八字系统的逻辑其实非常适合用程序来表达——固定集合、固定规则、可枚举的映射关系本质上就是一张张查找表加上有限的判断逻辑。难度最大、最考验功夫的是边界情况的处理节气切换、时辰临界、流派差异这些才是排盘工具能不能被“懂行的人”认可的关键。如果你打算接着扩展可以考虑几个方向把十神、大运流年、纳音、空亡这些加进去把四柱之间的关系做成网状图直观展示合冲刑害或者对接一个前端界面做成在线的排盘服务。底层的排盘和关系计算已经足够稳定往上堆功能只是时间问题。最后再分享一个避坑经验这套代码里的农历库会自动处理春节和节气的切换但你一定要清楚知道它底层用的是哪套节气数据。不同库、不同版本的节气表可能存在分钟级偏差而出生时间卡在立春、冬至这些关键节点时一分钟偏差就能让年柱或者月柱变掉。我的建议是正式使用前用几个已知的极端案例做回归测试例如公历2023年2月4日10点42分前后、2024年2月4日16点27分前后每一个节点都验证一次确定库的行为符合预期后再信任它。
返回列表