首页 > 编程语言 >社区发现之标签传播算法(LPA)python实现

社区发现之标签传播算法(LPA)python实现

时间:2024-04-26 12:44:19浏览次数:32  
标签:社区 Mat python 标签 算法 np array LPA

社区发现在图领域中备受关注,其根源可以追溯到子图分割问题。在真实的社交网络中,用户之间的联系紧密度不尽相同,导致形成了不同的社区结构。社区发现问题主要分为两类:非重叠和重叠社区。非重叠社区发现指的是每个节点仅属于一个社区,社区之间没有交集。在非重叠社区发现中,有多种解决方法。其中,基于模块度的算法通过最大化模块度来划分社区,以找到最优的社区结构。另一种常见的方法是基于标签传播的算法,例如标签传播算法(LPA)。这种算法通过在节点之间传播标签信息,直到达到收敛条件,从而识别出社区结构。标签传播算法相对简单而且高效,适用于大规模图的社区发现。这里将重点讨论标签传播算法,探讨其原理、优缺点以及在实际应用中的效果和局限性。

一、标签传播算法概述

标签传播算法(Label Propagation Algorithm,简称LPA)是一种用于图领域中的社区发现方法。其基本原理是利用节点之间标签的传播来将具有相似特征的节点划分到同一社区。算法开始时,每个节点被赋予一个唯一的标签,代表其所属的潜在社区。随后,每个节点根据其邻居节点的标签信息进行标签更新,迭代更新直至达到收敛状态。具体而言,标签传播算法的迭代过程如下:

初始化: 为每个节点分配一个唯一的标签。
节点标签更新: 随机选择一个节点,统计其邻居节点的标签分布情况,选择出现频率最高的标签作为该节点的新标签。若有多个标签出现频率相同,则随机选择一个作为新标签。
迭代: 重复节点标签更新步骤,直到标签分布不再发生变化或达到预设的迭代次数。
通过这个过程,标签传播算法能够发现网络中的社区结构,将具有相似特征的节点归为同一社区。

1.1 标签传播算法的优缺点

优点:
逻辑简单: 算法的核心思想直观易懂,易于理解和实现。
无需预设社区数量: 不需要事先确定社区的数量,能够根据网络结构自动划分社区。
运行效率高: 算法在迭代过程中只关注局部标签更新,避免了全局优化问题,因此具有较高的运行效率。
缺点:
随机性强: 算法结果的稳定性可能受到初始化条件和迭代顺序的影响。
无法处理重叠社区: 每个节点只能属于一个社区,无法处理节点属于多个社区的情况。
对噪声和异常值敏感: 容易受到噪声和异常值的影响,可能导致社区划分不准确。

1.2标签传播算法在实际应用中的效果与局限性

标签传播算法在实际应用中取得了一定的成功,尤其在处理大规模网络数据时表现出较高的效率。它可以帮助人们理解和分析复杂网络中的社交关系、用户行为等。然而,算法也存在一些局限性:
稳定性问题: 算法结果可能受到初始化和迭代顺序的影响,导致结果不稳定。
无法处理重叠社区: 算法无法准确处理节点同时属于多个社区的情况,限制了其适用范围。
对噪声和异常值敏感: 在存在噪声和异常值的情况下,算法可能无法准确识别社区结构。
为了克服这些局限性,研究者们提出了一些改进算法,如COPRA、SLPA等。这些改进算法通过引入更复杂的标签传播策略或额外的信息来提高算法的稳定性和准确性。
综上标签传播算法作为一种社区发现方法,具有简单易懂、无需预设社区数量等优点,但也存在随机性强、无法处理重叠社区等缺点。在实际应用中,需要根据具体情况选择合适的算法或进行改进以提高效果。随着图领域研究的不断深入,我们期待看到更多针对标签传播算法的改进和优化,使其在社区发现及其他相关领域发挥更大的作用。

二、标签传播算法

2.1 算法原理

LPA是基于图的半监督方法,所以首先就需要对原数据进行图方法的展示,如下图:

那蓝色的球(即节点)表示没有标签的样本,棕色和黄色则可以表示有标签的样本。那样本与样本之间的连线(即权重)则可以表示它们之间的相似度。那么这里相似度的计算方法为:

\[w_{ij}=\exp^{\frac{\| \mathbf{x_i-x_j} \|^2}{\alpha^2}} \]

这里的\(\alpha\)只是一个超参数而已。

当然这里也只是说了这么一种构建图的方法,除此以外,也可以用KNN的方式来构建节点图。不过KNN的方法会存在一种震荡的情况,导致数据的不稳定(主要是因为采用了同步更新法,换成异步更新就没有这个问题了)。不过KNN方法下的理论性没有这种强,所以以这个方法展开来叙述LPA算法。
有了上面的样本与样本间的相似度计算方法之后,我们可以构建一个基于相似矩阵的概率转移矩阵\(P\),\(P\)是一个N×N的矩阵:

\[P_{ij}=\frac{w_{ij}}{\sum_{k=1}^{n}w_{ik}} \]

显然这里是在做一个归一化的行为。\(P_{ij}\)表示的就是节点\(i\)到\(j\)转移的可能性,通过所有节点间转移可能性的汇总,就可以得到我们需要的概率转移矩阵P。
接着需要构建一个初始的状态矩阵:

\[F = \begin{bmatrix} Y_l \\ Y_u \end{bmatrix} \]

上面\(Y_l\)是有标签的样本,下面的\(Y_u\)是没有标签的样本。我们想要实现的就是把\(Y_l\)这些样本也打上应有的标签。既然有了概率转移矩阵,也构建了初始的状态矩阵,下面就是LPA算法的实现过程了:

  • \(F=PF\),通过这个计算,就可以把\(F\)的\(Y_u\)中部分无标签数据打上了标签。
  • 更新\(F\)中的\(Y_u\)样本情况
  • 重复迭代上面过程这个过程,直到\(Y_u\)都有标签为止。

思考一下:

既然我们关注点是\(Y_u\)部分,但是实际上我们每次在计算\(F=PF\)的过程中,\(Y_l\)都参与计算了。我们有没有什么更加简便的计算方式,让我们在计算过程中忽略\(Y_l\)呢?这是可以做到进一步优化:我们在构建概率转移矩阵的时候,考虑把有标签的样本放在矩阵的前方,把无标签的样本放在矩阵的后方即可。那么概率转移矩阵就可以拆分为这个样子了:

\[\begin{aligned} & P=\left[\begin{array}{ll} P_{L L} & P_{L U} \\ P_{U L} & P_{U U} \end{array}\right] \end{aligned} \]

这里:
PLL表示已有标签数据到其对应标签的转移概率,那肯定是1啊,所以PLL就是一个单位矩阵;
PLU表示已有标签数据到其对不应标签的转移概率,那自然就是0了,所以PLU就是一个0矩阵;
PUL和PUU两个矩阵则是未知的情况,因为没有标签的样本究竟是属于前面已经出现过的标签,还是未知的标签,目前还是未知状态。
所以上面的P矩阵可以写为:

\[\begin{aligned} P=\left[\begin{array}{cc} I & 0 \\ P_{U L} & P_{U U} \end{array}\right] \end{aligned} \]

继续,我们采用这个\(P\)矩阵和\(F\)矩阵来实现上面\(LP\)算法的迭代过程:

\[\begin{aligned} & \lim _{t \rightarrow \infty} P^t=\left[\begin{array}{cc} I & 0 \\ P_{U L} & P_{U U} \end{array}\right] \times\left[\begin{array}{cc} I & 0 \\ P_{U L} & P_{U U} \end{array}\right] \times \ldots \times\left[\begin{array}{cc} I & 0 \\ P_{U L} & P_{U U} \end{array}\right] \\ & =\left[\begin{array}{cc} I & 0 \\ \left(\sum_{t=0}^{\infty} P_{U U}^t\right) \cdot P_{U L} & P_{U U}^{\infty} \end{array}\right] \\ & =\left[\begin{array}{cc} I & 0 \\ \left(I-P_{U U}\right)^{-1} \cdot P_{U L} & 0 \end{array}\right] \\ & \end{aligned} \]

有两点说明:

  • 正常情况下,未知的样本不管属于哪一个标签,它的可能性是绝不会大于已知样本属于已知标签的可能性(即1),所以:

\[P_{uu}<0\Rightarrow \lim_{t \rightarrow \infty}P_{\infty}^t=0 \]

  • 级数求和公式:

\[\sum_{k=0}^n x^k=\frac{1}{1-x} \quad|x|<1 \]

所以:

\[\left(\sum_{t=0}^{\infty} P_{U U}^t\right) \cdot P_{U L}=\left(I-P_{U U}\right)^{-1} \quad 0 \leq P_{U U}<1 \]

接着实现 \(F=P F\),因为:

\[F=\left[\begin{array}{l} Y_L \\ Y_U \end{array}\right] \]

所以 \(F=P F\) 可以写为:

\[\begin{bmatrix} \hat{Y}_L \\ \hat{Y}_U \end{bmatrix} = \lim_{t\rightarrow \infty}P^t =\begin{aligned} P=\left[\begin{array}{cc} I & 0 \\ (1-P_{UU})^{-1} \cdot P_{UL} & 0 \end{array}\right] \end{aligned} \begin{bmatrix} Y_L \\ Y_U \end{bmatrix} \]

最终得到:

\[\hat{Y}_U = (I - P_{UU})^{-1} \cdot P_{UL} \cdot Y_L \]

这样,\(Y_L\)就可以完全排除我们的计算之外了。

所以对于LPA算法的求解也有两种办法:

  • 正常求\(I-P_{UU}\)矩阵的逆,然后正常计算出结果即可。显然这个在数据量较大时,比较耗费算力。
  • 迭代法,设定迭代的次数或者误差的范围,进行迭代即可。

2.2 算法实例

再看个实际的例子吧:

接下来,按照上面的图例来构建我们的概率转移矩阵\(P\):

\[P=\left[\begin{array}{ccccccc} 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ 0.33 & 0 & 0.33 & 0 & 0.33 & 0 & 0 \\ 0 & 0 & 0 & 0.5 & 0 & 0.5 & 0 \\ 0 & 0.33 & 0 & 0 & 0.33 & 0 & 0.33 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 \end{array}\right] \]

列表示的节点为\([1,7,2,3,4,5,6]\),行表示为\([1,7,2,3,4,5,6]\),如\(P\)的第一行:[1,0,0,0,0,0,0]表示:1号球属于标签1的可能性为1,属于其余标签的可能性为0;P的第四行:[0.33,0,0.33,0,0.33,0,0]表示:3号球连接了1、2和4号三个球,故共有三种属于的可能性,分别为0.33,所以在1、2以及4标签处的连接可能性为0.33,其余可能性为0,其他行的数据都是这么计算而来。

接着,我们采用迭代法,计算\(P\)的无穷大次方:

import numpy as np
# 定义矩阵P
P = np.array([
    [1, 0, 0, 0, 0, 0, 0],
    [0, 1, 0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0, 0, 0],
    [0.33, 0, 0.33, 0, 0.33, 0, 0],
    [0, 0, 0, 0.5, 0, 0.5, 0],
    [0, 0.33, 0, 0, 0.33, 0, 0.33],
    [0, 0, 0, 0, 0, 1, 0]
], dtype=float)
# 计算矩阵P的n次幂
n=100
Pn = np.linalg.matrix_power(P, 100)
# 输出结果
print(Pn)

计算的结果是:

\[P=\left[\begin{array}{ccccccc} 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0.7316 & 0.239 & 0 & 0 & 0 & 0 & 0 \\ 0.7316 & 0.239 & 0 & 0 & 0 & 0 & 0 \\ 0.4853 & 0.4853 & 0 & 0 & 0 & 0 & 0 \\ 0.239 & 0.7316 & 0 & 0 & 0 & 0 & 0 \\ 0.239 & 0.7316 & 0 & 0 & 0 & 0 & 0 \end{array}\right] \]

再反过来看当时的\(P\)的求极限过程, 这个结果显然验证了当初的假设都是正确的。所以:

\[\begin{aligned} & \hat{Y}_U=\left(I-P_{U U}\right)^{-1} \cdot P_{U L} \times Y_L \\ & =\left[\begin{array}{cc} 0.7316 & 0.239 \\ 0.7316 & 0.239 \\ 0.4853 & 0.4853 \\ 0.239 & 0.7316 \\ 0.239 & 0.7316 \end{array}\right] \times\left[\begin{array}{ll} 1 & 0 \\ 0 & 1 \end{array}\right]=\left[\begin{array}{cc} 0.7316 & 0.239 \\ 0.7316 & 0.239 \\ 0.4853 & 0.4853 \\ 0.239 & 0.7316 \\ 0.239 & 0.7316 \end{array}\right] \end{aligned} \]

根据计算的结果:对于样本1的橙色标签来说,样本2、3、4、5和6属于它的可能性分别为:[0.7316,0.7316,0.4853,0.239,0.239];对于样本7的蓝色标签来说,样本2、3、4、5和6属于它的可能性分别为:[0.239,0.239,0.4853,0.7316,0.7316],综上:2和3是橙色;5和6是蓝色;对于4来说,还是无法确定的。如图:

除此以外,还有对一个对label propagation改进的一个算法,即label spreading算法。这个算法本质上和label propagation是一样的,不过它在迭代过程中,对label propagation的迭代方程进行了一个简单的改进(就是加上了一个正则化),如公式:

\[F=P F \xrightarrow{\text { 正则化 }} F=\alpha P F+(1-\alpha) Y \]

这里前后的\(P\)是有点区别的,但是基本一样的方式计算得到的。不过重点不是这个概率转移矩阵,而是折中参数α,它的取值在[0,1]之间,一般都是取0.2。

2.3 电商评价同步行为

  • 电商评价同步行为示例数据group

如第一行 x∩y 代表用户 BUY_01 和 BUY_02 在同一个小时内对同一商户进行评价的情况有 2 次,即他们出现同步行为的次数为 2 。

这里同步行为次数是基于商户(约束条件)去重作统计的,即A和B对同一个商家Y在不同时间段有多次匹配行为时,只算作一次。我们也可以考虑不对商家作去重处理,不过需要注意的是在杰斯卡相似度计算过程用到的\(x_cnt\)等指标逻辑也要保持一致,要么都去重,要么都不去重。如果采用不去重法得到的应是:

  • 将同步行为数据转化成图

由于 LPA 的算法输入为图,需要将其转换成图形式

import matplotlib.pyplot as plt
import networkx as nx
tuples = [tuple(x) for x in group[['Buy_x','Buy_y','x∩y']].values] ##x∩y为weight
G = nx.Graph() ##本次采用无向图,对应有DiGraph, MultiGraph, MultiDiGraph等画图选项
G.add_weighted_edges_from(tuples) ##如果是二元组,使用add_edges_from;weight另外定义
pos=nx.spring_layout(G)
nx.draw_networkx(G,pos)

示例样本量太少,不易作社区划分,手工添加多个社区节点

G.add_edge('BUY_04','BUY_05',weight=2)
G.add_edge('BUY_05','BUY_06',weight=1)
G.add_edge('BUY_05','BUY_07',weight=2)
G.add_edge('BUY_06','BUY_08',weight=1)
G.add_edge('BUY_07','BUY_08',weight=3)
G.add_edge('BUY_08','BUY_09',weight=1)
G.add_edge('BUY_08','BUY_10',weight=4)
G.add_edge('BUY_09','BUY_11',weight=2)
pos=nx.spring_layout(G)
labels = nx.get_edge_attributes(G,'weight')
nx.draw_networkx(G,pos)
nx.draw_networkx_edge_labels(G,pos,edge_labels=labels)

  • 使用 LPA 算法划分社区

使用同步行为出现的次数即 x∩y 值作为输入权重

from networkx.algorithms.community import asyn_lpa_communities as lpa
# LPA本身不稳定,会存在波动
com = list(lpa(G,'weight')) #seed
print('社区数量',len(com))


  • 对划分的社区进行业务打标

算法将用户分成了三个社区,可以将每个社区中的用户的明细数据拉取出来,观察他们的行为特征。比如对于同一社区的用户,观察他们的评级时间密集程度如何,评价商户有没有集中性,评论相似性,所用设备环境是否正常或者有共性等,基于业务考虑对社区进行打标,如人肉刷评群体。

三、标签传播算法的Python算例



import numpy as np
import matplotlib.pyplot as plt
from sklearn.semi_supervised import LabelPropagation
import math

def show(Mat_Label, labels, Mat_Unlabel, unlabel_data_labels):
    for i in range(Mat_Label.shape[0]):
        if int(labels[i]) == 0:
            plt.plot(Mat_Label[i, 0], Mat_Label[i, 1], 'Dr')
        elif int(labels[i]) == 1:
            plt.plot(Mat_Label[i, 0], Mat_Label[i, 1], 'Db')
        else:
            plt.plot(Mat_Label[i, 0], Mat_Label[i, 1], 'Dy')

    for i in range(Mat_Unlabel.shape[0]):
        if int(unlabel_data_labels[i]) == 0:
            plt.plot(Mat_Unlabel[i, 0], Mat_Unlabel[i, 1], 'or')
        elif int(unlabel_data_labels[i]) == 1:
            plt.plot(Mat_Unlabel[i, 0], Mat_Unlabel[i, 1], 'ob')
        else:
            plt.plot(Mat_Unlabel[i, 0], Mat_Unlabel[i, 1], 'oy')

    plt.xlabel('X1')
    plt.ylabel('X2')
    plt.xlim(0.0, 12.)
    plt.ylim(0.0, 12.)
    plt.show()

def loadCircleData(num_data):
    center = np.array([5.0 ,5.0])
    radiu_inner = 2
    radiu_outer = 4
    num_inner = int(num_data / 3)
    num_outer = num_data - num_inner
    data = []
    theta = 0.0
    for i in range(num_inner):
        pho = (theta % 360) * math.pi / 180
        tmp = np.zeros(2 ,np.float32)
        tmp[0] = radiu_inner * math.cos(pho) + np.random.rand(1) + center[0]
        tmp[1] = radiu_inner * math.sin(pho) + np.random.rand(1) + center[1]
        data.append(tmp)
        theta += 2
    theta = 0.0
    for i in range(num_outer):
        pho = (theta % 360) * math.pi / 180
        tmp = np.zeros(2, np.float32)
        tmp[0] = radiu_outer * math.cos(pho) + np.random.rand(1) + center[0]
        tmp[1] = radiu_outer * math.sin(pho) + np.random.rand(1) + center[1]
        data.append(tmp)
        theta += 1
    Mat_Label = np.zeros((2 ,2) ,np.float32)
    Mat_Label[0] = center + np.array([-radiu_inner + 0.5 ,0])
    Mat_Label[1] = center + np.array([-radiu_outer + 0.5 ,0])
    labels = [0 ,1]
    Mat_Unlabel = np.vstack(data)
    return Mat_Label ,labels ,Mat_Unlabel

def loadBandData(num_unlabel_samples):
    Mat_label = np.array([[5.0 ,2.] ,[5.0 ,8.0]])
    labels = [0 ,1]
    num_dim = Mat_label.shape[1]
    Mat_Unlabel = np.zeros((num_unlabel_samples ,num_dim) ,np.float32)
    Mat_Unlabel[:num_unlabel_samples // 2 ,:] = (np.random.rand(num_unlabel_samples // 2 ,num_dim) - 0.5) * np.array \
        ([3 ,1]) + Mat_label[0]
    Mat_Unlabel[num_unlabel_samples // 2:num_unlabel_samples ,:] = (np.random.rand(num_unlabel_samples // 2
                                                                                  ,num_dim) - 0.5) * np.array([3 ,1]) + Mat_label[1]
    return Mat_label ,labels ,Mat_Unlabel

if __name__ == "__main__":
    num_unlabel_samples = 2000  # 样本数量增加以满足最近邻居的要求
    Mat_label, labels, Mat_Unlabel = loadCircleData(num_unlabel_samples)

    # 使用LabelPropagation算法进行标签传播
    label_prop_model = LabelPropagation(kernel='knn', n_neighbors=min(10, num_unlabel_samples -1), max_iter=300)
    label_prop_model.fit(np.vstack((Mat_label, Mat_Unlabel)), labels + [-1] * num_unlabel_samples)
    unlabel_data_labels = label_prop_model.transduction_

    # 显示结果
    show(Mat_label, labels, Mat_Unlabel, unlabel_data_labels)

总结

标签传播算法(Label Propagation Algorithm,LPA)是一种用于社区发现的简单而有效的半监督方法,其核心思想是通过节点之间标签的传播来划分社区,将具有相似特征的节点归为同一社区。算法适用于各种类型的图,包括社交网络、通信网络等。在实际应用中,标签传播算法被广泛应用于社交网络分析、推荐系统、生物信息学等领域。例如,在社交网络中,它可以帮助识别用户群体、发现潜在的社交圈子;在推荐系统中,可以利用用户行为数据进行社区划分,从而提高推荐的准确性和个性化程度;在生物信息学中,可以用于基因调控网络的模块化分析,发现基因间的关联性。此外,标签传播算法还可用于图像分割、文本聚类等领域。例如,在图像分割中,可以将像素点作为图的节点,利用它们的相似性进行社区划分,从而实现图像的自动分割;在文本聚类中,可以将文档表示为图的节点,通过词语之间的关联性进行社区划分,实现文本的自动分类和聚类。总之标签传播算法是一种简单而有效的社区发现方法,在各种领域都有着广泛的应用前景。通过利用其优势并克服其局限性,可以为社交网络分析、推荐系统、生物信息学等领域的问题提供有力的解决方案。

参考文献

  1. 两种 Python 方法实现社区发现之标签传播算法(LPA)
  2. LPA社区发现算法介绍和代码示例
  3. 27.标签传播算法

标签:社区,Mat,python,标签,算法,np,array,LPA
From: https://www.cnblogs.com/haohai9309/p/18159134

相关文章

  • python2 http响应中文显示unicode \uXXX的问题
    python2编码解码会以unicode作为中间码,要用decode和encode解码后再编码其中decode解码,是把bytes以给定的编码格式解码成unicodeencode是以给定的编码格式将unicode编码为bytes数据是以bytes形式传递和存储的,程序需要用正确的编码来将bytes解码显示decode:FrombytesToUnic......
  • Matlab转python的索引问题
    python中numpy库可以实现类似matlab多维数组的运算.但两者在索引方式上存在一些差异.这是需要注意的.例如:%定义一个4*4矩阵A=1:16;A=reshape(A,[4,4]);%提取2*2的子矩阵a=A([1,4],[1,4])%得到一个2*2矩阵:%[A(1,1)A(1,4);%A(4,1)A(4,4)]但是python中则不......
  • python多线程
    多线程的原理是在同一进程内创建多个线程来执行不同的任务,这些线程共享同一进程的资源,包括内存空间、文件句柄等。每个线程拥有独立的执行路径,可以并行执行任务,从而提高程序的效率。在代码中,通过调用threading.Thread类创建了多个线程对象。每个线程对象都有一个target参数......
  • 用python写一段将指定文件夹下的子文件夹下的“.en.srt”文件复制一份,并将复制的文件
    代码:importosimportshutildefcopy_and_rename_en_srt_files(parent_directory):#遍历指定的父目录及其所有子目录forroot,dirs,filesinos.walk(parent_directory):forfileinfiles:#检查文件是否以.en.srt结尾if......
  • KNN算法思想与Python实现
    古语说得好,物以类聚,人以群分;近朱者赤,近墨者黑。这两句话的大概意思就是,你周围大部分朋友是什么人,那么你大概率也就是这种人,这句话其实也就是K最近邻算法的核心思想。kNN(k-NearestNeighbor)法即k最邻近法,最初由Cover和Hart于1968年提出,是一个理论上比较成熟的方法,也是最简单的机......
  • Socket.D v2.4.12 发布(新增 python 实现)
    Socket.D协议?Socket.D是一个网络应用协议。在微服务、移动应用、物联网等场景,可替代http、websocket等。协议详情参考《官网介绍》。支持:tcp,udp,ws,kcp传输。目前:java,kotlin,javascript,node.js,python语言环境可用。go,rust,c/c++,.net正在开发中。forJava更新......
  • blender python api 使用脚本进行动画渲染
    1.摄像机“Camera”在一个名叫“渲染”的集合中2.代码:importbpy#设置输出路径和文件名output_path="/path/to/output/"#替换为你的输出路径filename="rendered_animation"#输出文件的前缀#获取名为“渲染”的集合render_collection_name="渲染"render_c......
  • 【python】pyqt中使用多线程处理耗时任务
    在PyQt中使用多线程通常是为了避免界面冻结,特别是在执行耗时的任务时。PyQt本身是基于Qt的,而Qt不允许在除主线程之外的线程中直接操作GUI元素。因此,任何涉及GUI更新的操作都应该在主线程中执行。importsysimportthreadingfromPyQt5.QtWidgetsimportQApplic......
  • Python 字符串格式化指南
    前言在Python中,字符串格式化是一种常见且重要的操作,用于将变量或值插入到字符串中,并控制输出的格式。本文将介绍几种常见的字符串格式化方法,帮助大家掌握在Python中有效地处理字符串的技巧。方法一:使用%操作符格式化字符串使用%操作符是一种传统的字符串格式化方法,可......
  • phpcms pc:get 标签用法;phpcms模板中使用PHP标签
     注意:变量 $catid 需要是从控制器里解析出来的<divclass="show-right-top"><divclass="sch-recruit-right-title"><p><imgsrc="/statics/boot/images/quan3.png">快速链接</p></div>......