首页 文章 精选 留言 我的

精选列表

搜索[前端工程师],共10000篇文章
优秀的个人博客,低调大师

百度工程师浅析强化学习

作者 |Jane 导读 本文主要介绍了强化学习(Reinforcement Learning,RL)的基本概念以及什么是RL。强化学习让智能体通过与环境的交互来学习如何做出决策,以获得最大的累积奖励。文章还介绍了策略梯度(Policy Gradient,PG)和近端策略优化(PPO)等强化学习算法。 全文7099字,预计阅读时间18分钟。 01 强化学习(Reinforcement Learning,RL) 1.1 基本概念&什么是RL 强化学习(reinforcement learning,RL)专注于让智能体(agent)通过与环境的交互来学习如何做出决策,以使其在不断变化且不确定的环境中获得最大的累积奖励。 这个过程可以用以下几个要素来描述: 智能体(Agent):智能体是执行动作并与环境进行交互的实体。它可以是一个机器人、一个虚拟角色、一个算法等。 环境(Environment):环境包括智能体所处的所有上下文,包括外部条件、状态变量和其他影响智能体的因素。 动作(Action):智能体可以在环境中执行的操作,这些操作可能会改变环境的状态。 状态(State):状态是描述环境的当前情况的信息。智能体的决策通常依赖于当前状态。 奖励(Reward):在每个时间步,智能体执行一个动作后会获得一个奖励信号,用于指示动作的好坏。目标是最大化累积奖励。 策略(Policy):策略定义了智能体如何根据当前状态选择动作。强化学习的目标之一是找到一个最优策略,使智能体能够获得最大的累积奖励。 价值函数(Value Function):价值函数衡量了在某个状态下执行某个策略能够获得的预期累积奖励。这个函数帮助智能体评估不同状态的重要性。 学习过程:强化学习的核心是智能体通过尝试不同的动作并观察奖励信号来学习适应性策略。它可以使用不同的算法,如Q-learning、Deep Q-Networks(DQN)、Policy Gradient等。 △图1 强化学习过程 其中状态(State)是对整个环境的完整描述,包括了所有对智能体的决策和行动有影响的信息。状态通常用来指导智能体的决策,因为它提供了有关环境当前状态的全部信息。 观测(Observation)则是对状态的一种抽象描述,它可能会忽略或省略一些状态信息。在实际问题中,有时候智能体无法直接观测到完整的状态,而只能获取到一部分信息,即观测。这可能是因为某些信息无法直接测量,或者因为为了简化问题而对状态进行了抽象。 因此,智能体基于它的观测来做出决策,而不是直接使用完整的状态。这就需要智能体具备从观测中学习有关状态的信息的能力,以便更好地进行决策和行动。 下面举一些可应用强化学习的示例: 1、在电子游戏中,摇杆是智能体,主机是环境,显示屏上的游戏画面(像素)是观测,击杀怪物得分则是奖励。 2、在阿法狗围棋对弈中,阿法狗是智能体,人类棋手是环境,棋局是观测,围棋的输赢则是奖励。 在强化学习中,智能体持续与环境进行交互,采取一系列的动作,从而形成一个状态和动作的序列,这个序列被称为轨迹(trajectory): 一个交互序列被称为一个回合(episode),在每个回合中,智能体根据当前状态选择动作,与环境互动,并根据奖励信号进行学习和策略优化,以获得最大化累积奖励。 这里需要注意的是,环境对我们来说是黑盒,是不可控的,它在当前状态下跳转到下一状态是服从一定的分布的,也就是说它是有一定的随机性的,那么它产出的奖励也具有了一定的随机性。我们可控的是actor的policy,即可以通过学习,让policy在面对状态 s 时采取一个能让他获得最大奖励的action。所以实际上我们的目标是最大化期望的累积奖励(expected cumulative reward)。 1.2RL和监督学习的区别 监督学习: 1. 数据独立同分布(i.i.d.):监督学习假设样本是从一个未知分布中独立地采样得到的,数据之间没有时序关联。 2. 标签信息:在监督学习中,每个样本都有正确的标签信息,即模型需要学习从输入到标签的映射关系。 3. 训练目标:监督学习的目标是使模型的预测尽可能地接近真实标签,通常通过最小化预测与真实标签之间的损失函数来实现。 4. 局限性:监督学习在需要大量标记数据、难以获取所有可能状态的标签信息或者数据之间存在时序关联的情况下可能受限。 强化学习: 1. 时序关联数据:强化学习中智能体的观测是时序关联的,当前的决策会影响未来的观测和奖励。 2. 延迟奖励:强化学习中智能体在做出决策后不会立即得到奖励,奖励可能在未来的时间步骤才会出现,这就需要智能体通过试错来学习哪些决策导致了最终的奖励。 3. 探索与学习:强化学习中智能体需要在探索和利用之间找到平衡,以发现能够最大化长期累积奖励的有效策略。 4. 超越人类表现:强化学习的一个优势在于它可以在某些情况下超越人类表现,因为智能体可以自主探索环境并发现人类难以想象的优化策略。 1.3 RL算法类型 按不同类型的智能体方法划分: 1.基于价值的智能体(Value-Based Agents):智能体显式地学习价值函数,然后从学习到的价值函数中隐式地推导出策略,例如Q-learning和Sarsa,这些算法在训练过程中更新状态-动作值函数(Q值),然后根据Q值选择最佳动作。 2.基于策略的智能体(Policy-Based Agents):智能体直接学习策略,即在给定状态下选择每个动作的概率分布。策略梯度(Policy Gradient)方法是一种常见的基于策略的强化学习方法,它通过梯度下降或其他优化算法来更新策略参数,以最大化累积奖励。 3.演员-评论员智能体(Actor-Critic Agents):这是一种综合了前两种方法的智能体。演员(Actor)负责学习策略,而评论员(Critic)负责学习价值函数。演员根据策略选择动作,评论员基于环境的反馈和学习的价值函数提供有关动作的评估。这种结合可以帮助解决价值和策略学习中的某些困难问题,并且在实际应用中表现出很好的性能。 按是否需要对环境建模来划分: 1.Model-Based 方法:需要对环境建模,包括状态转移函数(描述状态之间如何转换)和奖励函数(描述在每个状态下获得的奖励)。一旦模型被建立,智能体可以在模型中进行"心算",即通过模型进行推演,而无需真正地与环境进行交互。这使得智能体可以在模型上进行试错,寻找在真实环境中表现良好的策略。然而,这种方法的关键挑战在于如何准确地建立一个模型,因为环境可能非常复杂且不确定。 2.Model-Free 方法:不需要显式地构建环境模型,而是智能体直接与环境交互,通过尝试和观察来学习策略。这种方法通常需要更多的交互时间和数据,因为智能体需要通过实际的试验来逐步改进策略。Model-free 方法可以分为基于价值和基于策略的方法,前者关注学习状态或状态-动作的价值函数,后者则直接学习策略。 在实际应用中,"model-based"方法可能需要更少的交互次数来找到较优策略,因为它在模型上进行预测和规划。然而,这个模型的建立可能会面临挑战,尤其是在复杂和不确定的环境中。"model-free"方法则更加适用于无法准确建模的情况,但可能需要更多的实际交互来学习策略。选择使用哪种方法通常取决于问题的性质以及可用的数据和计算资源。 02 策略梯度(Policy Gradient,PG) 强化学习的3个核心组成部分:actor(policy、agent)、环境 和 奖励函数。环境和奖励函数不是我们可以控制的,我们能做的是调整Actor的策略,使得它获得最大的奖励。策略一般记作\(\pi\) ,如果使用深度学习来做强化学习,策略就是一个network,其参数记作\(\theta\)。其输入是观测(或states),输出是动作空间的一个概率分布 。(同一个states,同一个env,所采去的action也是可能不同的,所以这里是概率分布,action是具有一定的随机性的)。 △图2 神经网络作为策略 奖励的稀疏性和延迟性? 如果把玩一场游戏看做一个回合(episode),在一个episode里,actor不断与环境进行交互,即在s1时采取了action a1,并获得奖励r1,环境接收a1后跳转到s2,actor看到s2后采取了action a2,并获得奖励r3,...,直至达到某个条件结束游戏。这一个episode里,把环境输出 s 和actor输出 a 以及奖励输出 r 的序列组合起来,就形成了一个轨迹(trajactory): △图3 最大化奖励 计算某个轨迹发生的概率就是: 目标是通过调整actor的参数\(\theta\),最大化整个轨迹中获得的total奖励:\(R(\tau)=\sum_{t=1}^T r^t\) 但是由于采取action的随机性,环境输出states的随机性,得到的reward也是有随机性的,所以在计算中我们需要最大化的不是某个回合的奖励,而是最大化期望奖励。(直观上理解:平均意义上的奖励最大化了,那么某个具体的回合他的奖励也不会小到哪儿去),所以目标就是最大化\(R(\tau)\)的期望值 最大化问题,可以使用梯度上升(gradient ascent)来求解,使用梯度更新公式来更新参数,这就是策略梯度算法。(核心就是采样数据,更新参数,再采样数据,再更新参数。。。) 接下来就是计算\(\bar{R_{\theta}}\)的梯度 再看下\(\bar{R_{\theta}} =\sum_{\tau} R(\tau)p_{\theta}(\tau) = E_{\tau \sim p_{\theta}} [R(\tau)]\) 期望的这个公式的意义:从分布\(p_{\theta}(\tau)\)采样一个轨迹 \(\tau\),计算\(R(\tau)\)的期望值,就是期望奖励(expected reward)。我们要最大化期望奖励。即相当于我们穷举所有的轨迹,每个轨迹对应一个概率。但这个期望无法计算,因为没法穷举所有的轨迹。如何解决呢?=>大数定理:做N次采样,用平均代替期望。 注意, \(p(s_1)\)和\(p(s_{t+1}|s_t,a_t)\)来自环境,由环境决定,\(\theta\)与 无关,因此\(\nabla \log p(s_1)=0\),\(\nabla\sum_{t=1}^{T}\log p(s_{t+1}|s_t,a_t)=0\) 2.1 2个问题 接下来探讨2个问题: 1.如果采取每个action的奖励都是正的,只是有大有小,会出现什么问题? 2.整个episode里每个(s, a)pair都使用了同一个total reward合适吗?这样公平吗?一场游戏输了那里面的每个step的操作都是失败的吗?一场游戏赢了,中间就不会有失误吗? 如何解决这些问题? 解决问题1:增加baseline 如果采取每个action的奖励都是正的,只是有大有小,会出现什么问题? 例如如果所有的 r >= 10,那么r=10就相对来说是『负』的。也就是说在采样中,没有被采样到的动作的概率会被下降,但该动作不一定是不好的动作,因此奖励最好是有正有负的,这可以增加baselinse解决: 通过这种方法,当\(R(\tau)-b>0\)时即总奖励\(R(\tau)>b\),就让\((s,a)\)的概率上升;当\(R(\tau)<b\),即便\(R(\tau)\)是正的,但如果它值很小也是不好的,就让\((s,a)\)的概率下降。 \(b\)可通过对\(\tau\)值取期望来进行设置, 即计算\(\tau\)的平均值,令\(b \approx E[R(\tau)]\)。实践中把训练期间的\(R(\tau)\)的值不断记下来然后计算平均值,用来设置b。 解决问题2:分配合适的分数 1、reward-to-go:未来的总回报 把\(\nabla\log p_{\theta}(a_{t}^n|s_t^n)\)前面的\((R(\tau)-b)\)看作是它的权重,表征了动作a的好坏。这个权重原先是整个轨迹上奖励的总和,现在改成从某个时刻\(t\)开始,一直到游戏结束得到的所有奖励的总和。也就是说计算某个状态-动作对的奖励的时候,不再是把整场游戏得到的奖励全部加起来,而是只计算从这个动作执行以后得到的奖励。直观上也比较容易理解:因为这场游戏在执行这个动作之前发生的事情是与执行这个动作是没有关系的,所以在执行这个动作之前得到的奖励都不能算是这个动作的贡献。我们把执行这个动作以后发生的所有奖励加起来,才是这个动作真正的贡献。 2、discount有折扣的:衰减的回报 进一步,我们在未来的奖励中引入折扣因子。为什么要把未来的奖励做一个折扣呢?因为虽然在某一时刻,执行某一个动作,会影响接下来所有的结果(有可能在某一时刻执行的动作,接下来得到的奖励都是这个动作的功劳),但在一般的情况下,时间拖得越长,该动作的影响力就越小。 折扣因子\(\gamma \in [0,1]\),一般设置为0.9或0.99。其中\(G_t = r_t^n + \gamma r_{t+1}^n + \gamma^2 r_{t+2}^n + \gamma^3 r_{t+3}^n +...+ \gamma^{T_n-t} r_{T_n}^n =r_t^n+\gamma G_{t+1}\) 。从\(G_t\)的展开式可以看到未来的奖励在离t时刻越远时,折扣就越多,这样显然更合理。 2.2 重要性采样 策略梯度算法的缺点: 1.采样效率低:每次当前actor策略与环境交互收集到一批训练资料后,策略一更新,这批数据就不能再使用了,因为actor已经发生了变化,需要重新拿新的actor再次与环境与做交互,拿到新的一批训练资料。而与环境做交互是非常费时间的(相对于在GPU上作训练),整个过程可能大部分时间在做交互收集(采样)数据。 2.迭代步长过长,或步长/学习率难以确定,训练不稳定。 采样效率低的问题,考虑下能不能用另外一个策略\(\pi_{\theta‘}\)和一个actor\(\theta’\)与环境交互,用\(\theta’\)采样到的数据去训练\(\theta\),并且利用这些数据去让\(\theta\)多次执行梯度上升,多次更新参数, 这样就能大幅提升效率。 如何实现这个过程呢? 这个公式可以看做是先从\(q\)里面从采样\(x\),再计算\(f(x) \frac{p(x)}{q(x)}\),再取期望值。所以即便没法从\(p\)里面采样数据,但从\(q\)里面采样数据,我们依然可以通过这种变换来计算从采样\(p\)代入\(x\)以后的期望值。因为是从采样数据,所以我们从采样出来的每一笔数据,都需要乘一个\(\frac{p(x)}{q(x)}\)来修正这两个分布的差异,这个修正银子就叫做重要性权重(importance weight)。 对于用 actor\(\theta\)采样出\(s_t\)与\(a_t\)的状态-动作的对时,我们使用状态-动作对的优势(advantage):\(A_{\theta}(s_t,a_t)\), 来表示累积奖励减去基线这一项,这一项代表的是在状态\(s_t\)采取动作\(a_t\)的好坏(有没有优势)。如果\(A_{\theta}(s_t,a_t)\)是正的,就要增大概率;如果是负的,就要减小概率。 那么回到上面策略梯度的采样效率的问题,就可以考虑,由一个\(p_{\theta’}\)也就是另一个actor(其实就是需要更新的actor的一个副本)去和环境做交互然后收集采样数据,再用这个数据去多次更新\(p_{\theta}\)以提升采样效率。也就是说利用重要性采样,就可以把公式转化为: 上式第3行利用条件概率公式展开,最后去掉\(p_{\theta}(s_t)\)和\(p_{\theta‘}(s_t)\)得到最终的简化的结果。所以根据梯度公式我们可以写出目标函数: 重要性采样解决了采样效率的问题,但也可能会引入新问题。我们再来看下重要性采样的公式:\(E_{x\sim p}[f(x)]=E_{x\sim q}[f(x)\frac{p(x)}{q(x)}]\),虽然它们期望是相等的,但它们的方差如何呢?根据方差计算公式\({Var} [X]=E[X^2]-(E[X])^2\),分别计算出\(f(x)\)和\(f(x)\frac{p(x)}{q(x)}\)的方差: 可以看到2个方差的第一项是不同的,\(\operatorname{Var}_{x \sim q}\left[f(x) \frac{p(x)}{q(x)}\right]\)的第一项多乘了\(\frac{p(x)}{q(x)}\),如果\(\frac{p(x)}{q(x)}\)差距很大,\(p_{\theta}\left(a_{t} | s_{t}\right)\)的方差就会很大。也就是说如果\(p_{\theta}\left(a_{t} | s_{t}\right)\)与\(p_{\theta'}\left(a_{t} | s_{t}\right)\)相差太大,即这两个分布相差太多,重要性采样的结果就会不好。也就是说通过有限次的采样,如果\(p(x)\)和\(q(x)\)的差距过大,我们是无法保证这个期望的等式在采样数据上一定成立。 怎么避免它们相差太多呢?这就是TRPO和PPO要做的事情。 2.3 TRPO 信任区域策略优化(trust region policy optimization,TRPO)就是对\(\theta\)与\(\theta'\)进行一个约束,让它们的 KL 散度小于: 虽然TRPO解决了分布差距不会很大的问题,但计算有难度(引入约束项,使用拉格朗日的对偶法处理后,采用共轭梯度法进行优化),因此我们一般使用在实现上更容易但性能差不多的PPO。 03 近端策略优化(PPO) 3.1 PPO1(近端策略优化惩罚(PPO-penalty)) **PPO(ProximalPolicyOptimization)**解决TRPO计算量大的问题,不是把约束放在条件里,而是直接放到目标函数里: 其中惩罚项\(\beta \mathrm{KL}\left(\theta, \theta^{k}\right)\)称之为自适应KL散度(adaptive KL divergence),\(\beta\)可以动态调整,其调整策略为: 如果\(KL(\theta,\theta^k)>KL_{max}\),则增加\(\beta\) 如果\(KL(\theta,\theta^k)<KL_{min}\),则减小\(\beta\) 3.2 PPO2(近端策略优化裁剪(PPO-clip)) 如果我们觉得计算 KL 散度很复杂,那么还有一个PPO2算法,PPO2 即近端策略优化裁剪。近端策略优化裁剪的目标函数里面没有 KL 散度,其要最大化的目标函数为 裁剪(clip)函数,裁剪函数是指,在括号里面有3项,如果第一项小于第二项,那就输出\(1-\varepsilon\);第一项如果大于第三项,那就输出\(1+\varepsilon\)。 \(\varepsilon\)是一个超参数,是我们要调整的,可以设置成 0.1 或 0.2 。 为什么这个式子就可以做到\(p_{\theta}\)和\(p_{\theta'}\)差距不会太大呢? 如果\(A>0\),也就是某一个\((a_t,s_t)\)是好的,则我们希望增大这个状态-动作pair的概率。也就是想让\(p_{\theta}(a_t∣s_t)\)越大越好,但它与\(p_{\theta^k}(a_t∣s_t)\)的比值不能超过\(1+\varepsilon\); 如果\(A<0\),也就是某一个\((a_t,s_t)\)是不好的,那则我们希望把\(p_{\theta}(a_t∣s_t)\)减小,当减到比值是\(1-\varepsilon\)的时候停止。 通用这个clip的限制,我们不会让\(p_{\theta}(a_t∣s_t)\)和\(p_{\theta^k}(a_t∣s_t)\)差距太大,并且实现这个其实也很简单,如下: ratios = paddle.exp(cur_batch_log_probs - batch_log_probs.detach()) surr1 = ratios * A_k surr2 = paddle.clip(ratios, 1-self.clip, 1-self.clip) * A_k actor_loss = -paddle.minimum(surr1, surr2) # 负号,把最大化问题转化为最小化问题ratios = paddle.exp(cur_batch_log_probs - batch_log_probs.detach()) surr1 = ratios * A_k surr2 = paddle.clip(ratios, 1-self.clip, 1-self.clip) * A_k actor_loss = -paddle.minimum(surr1, surr2) # 负号,把最大化问题转化为最小化问题 04 总结 ——END—— 参考资料: [1]https://datawhalechina.github.io/easy-rl/#/ [2]https://speech.ee.ntu.edu.tw/~hylee/ml/2020-spring.php 推荐阅读: 浅谈统一权限管理服务的设计与开发 百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践 百度知道上云与架构演进 百度APP iOS端包体积50M优化实践(四)代码优化 百度App启动性能优化实践篇

优秀的个人博客,低调大师

假装网络工程师15——IGP综合实验

一、背景介绍 IGP协议的学习即将结束,最后通过一个综合实验最为结尾,实验拓扑如下图所示: 每个路由器的route-id与lo0接口ip地址一致,除基础配置外,还需满足如下约束: RIP需求: 1.R1-R4 运行RIP V2 2.R1仅从R4接收199.172.0.0、199.172.1.0、199.172.2.0、199.172.3.0网段的路由更新 OSPF 进程1需求: 1.所有路由器的router-id均为X.X.X.X(X为路由器编号,如R3为3.3.3.3) 2.运行OSPF的路由器环回口都宣告进ospf且在路由表里看到的环回口网段路由都是24位路由 3.区域0采用区域密文验证,密码为HUAWEI 4.OSPF重分布进入RIP 5.RIP重分布进入OSPF ,OSPF只从RIP接收14.0.0.0/24的路由 OSPF 进程2需求: 1.R4-R5 运行OSPF进程2 2.把OSPF进程2引入进OSPF进程1后要求每一跳metric可变 3.把OSPF进程1引入进OSPF进程2后要求每一跳metric可变 4.要求R4优选R5去往所有网段 5.R4从R5学到的路由条目走2条路径且等价负载均衡 6.RIP-OSPF进程2之间不做重分布 二、基础配置 除了基础的配置,在上图中还包含了一个隐藏条件,ospf1的area 2没有与area 0直接相连,这就需要在R2和R3之间建立一条虚链路,建立的配置如下: [R2]ospf 1 [R2-ospf-1]area 1 [R2-ospf-1-area-0.0.0.1]vlink-peer 3.3.3.3 [R3]ospf 1 [R3-ospf-1]area 1 [R3-ospf-1-area-0.0.0.1]vlink-peer 2.2.2.2 ospf的虚链路无法直接通过display ospf peer brief 查看,只能通过虚链路查看 [R3]display ospf vlink OSPF Process 1 with Router ID 3.3.3.3 Virtual Links Virtual-link Neighbor-id -> 2.2.2.2, Neighbor-State: Full Interface: 23.0.0.3 (Serial2/0/1) Cost: 48 State: P-2-P Type: Virtual Transit Area: 0.0.0.1 Timers: Hello 10 , Dead 40 , Retransmit 5 , Transmit Delay 1 GR State: Normal 此时的基础配置就以完成,接下来进行约束条件的配置 三、约束条件配置 RIP需求: R1-R4 运行RIP V2 [R1]rip [R1-rip-1]version 2 network 14.0.0.0 [R4]rip [R4-rip-1]version 2 network 14.0.0.0 network 4.0.0.0 network 199.172.0.0 network 199.172.1.0 network 199.172.2.0 network 199.172.3.0 network 199.172.4.0 network 199.172.5.0 R1仅从R4接收199.172.0.0、199.172.1.0、199.172.2.0、199.172.3.0网段的路由更新 [R1]rip 1 [R1-rip-1]filter-policy ip-prefix RIP import [R1]ip ip-prefix RIP index 10 permit 199.172.0.0 22 greater-equal 24 less-equal 24 OSPF 进程1需求: 所有路由器的router-id均为X.X.X.X(X为路由器编号,如R3为3.3.3.3) [R1]ospf 1 router-id 1.1.1.1 运行OSPF的路由器环回口都宣告进ospf且在路由表里看到的环回口网段路由都是24位路由 [R1]interface LoopBack 0 [R1-LoopBack0]ospf network-type broadcast 区域0采用区域密文验证,密码为HUAWEI [R1]ospf 1 [R1-ospf-1]area 0 [R1-ospf-1-area-0.0.0.0]authentication-mode md5 1 cipher HUAWEI 在配置区域0认证时需要住的是,虽然R3没有之间跟area0连接,但是R2与R3之间建立了虚链路,相当于把area0的范围扩大了,尽管R3上没有配置area0,但是仍要创造一个area0来用于认证 <R3>display current-configuration configuration ospf [V200R003C00] # ospf 1 area 0.0.0.0 authentication-mode md5 1 cipher %$%$kcn17z")3I-,au"KJu*4^8!%%$%$ area 0.0.0.1 network 3.3.3.3 0.0.0.0 network 23.0.0.3 0.0.0.0 vlink-peer 2.2.2.2 area 0.0.0.2 network 35.0.0.3 0.0.0.0 # return OSPF重分布进入RIP 作为rip域和ospf域的边界路由,引入的动作需要在R1上来进行 [R1]rip 1 [R1-rip-1]import-route ospf 1 RIP重分布进入OSPF ,OSPF只从RIP接收14.0.0.0/24的路由 [R1]ospf 1 [R1-ospf-1]import-route rip 1 route-policy R-2-O [R1]route-policy R-2-O permit node 10 [R1-route-policy]if-match ip-prefix R-2-O [R1]ip ip-prefix R-2-O permit ip ip-prefix R-2-O index 10 permit 14.0.0.0 24 OSPF 进程2需求: R4-R5 运行OSPF进程2(略) 把OSPF进程2引入进OSPF进程1后要求每一跳metric可变(与条件3配置相同,此处略) 把OSPF进程1引入进OSPF进程2后要求每一跳metric可变 由于ospf引入外部路由默认使用type2,即外部路由cost值为1,且不累加内部开销,所以按照要求改为type1,该配置在ospf进程1与ospf进程2的边界路由器R5上操作 [R5]ospf 1 [R5-ospf-1]import-route ospf 2 type 1 要求R4优选R5去往所有网段 路由选路时先比较优先级,rip优先级为100,ospf外部引入优先级为150,所以默认R4去往所有路由是走R1,需要更改rip优先级 [R4]rip 1 [R4-rip-1]preference 151 R4从R5学到的路由条目走2条路径且等价负载均衡 此时R4到任何网段经过R5都有2条链路,且优先级均为150,所以此时比较的是cost开销,由于S2/0/0是串口cost值为48,大于g0/0/0接口的cost值(值为1),所以默认的路由表只会加载g0/0/0所在的45.0.0.0/24网段,所以如果要负载均衡,就需要将s2/0/0接口的cost值改为1,或将g0/0/0接口的cost值改为48 [R4]int g0/0/0 [R4-GigabitEthernet0/0/0]ospf cost 48 RIP-OSPF进程2之间不做重分布(无需操作)

优秀的个人博客,低调大师

PHP工程师学Python数据类型

一、背景 继上篇文章比较了PHP与Python语法之后,这周又学习了Python数据类型,准备从通过这篇文章给自己进行一些总结,也给其他读者一些参考。 二、内容概要 字符串(str) 列表(list) 元组(tup) 字典(dict) 数据操作 三、字符串(str) Python中字符串操作基本和PHP类似,下面介绍一下Python中字符串的常规操作方法 3.1 索引下标取值 在PHP中可以通过索引下标的方式获取到字符串中的某一个字符,在Python中也同样可以获取到,如下代码所示 #定义字符串 testStr = 'tangqingsong' #通过索引下标获取字符 print(testStr[1]) #打印结果 a 3.2 切片 在Python中有一个非常好用的功能叫做切片,比如当你想获取字符串中第n个字符到第m个字符中的区间字符,使用Python的切片实现非常简单,如下代码所示 #定义字符串 testStr = 'tangqingsong' #通过索引下标获取字符 print(testStr[0:4]) #打印结果 tang 3.3 循环 在Python中for不仅仅可以循环类似于PHP中的数组,还可以循环字符串,如下代码所示 testStr = 'tangqingsong' for value in testStr[0:4]: print(value) #打印结果 tang 3.4 字母大小写转换 在Python中实现字母大小写转换和PHP的写法稍有不同,Python中操作有点类似于PHP操作对象的感觉,如下代码所示 #定义字符串 testStr = 'tQs' #大写转换 aStr = testStr.upper() #小写转换 bStr = testStr.lower() print(aStr,bStr) #打印结果 TQS tqs 3.4 分割和合并 接着我们再来看一下怎么把字符串切割与合并,如下代码所示 s = 'tang|qing|song' l = s.split('|') print(l) #打印结果 ['tang', 'qing', 'song'] s2 = '|'.join(['tang', 'qing', 'song']) print(s2) #打印结果 tang|qing|song 3.5 字符串首尾过滤 在PHP中字符串首尾过滤通常使用trim()函数过滤,而Python中字符串首尾过滤的语法有点类似于PHP操作对象的方式,代码如下所示 # 去掉边界上的内容 strip s1 = ' tang qing song ' print(s1.strip()) #打印结果 ‘tang qing song’ 如果你想指定删除某一个字符,代码如下所示 s2 = '<汤青松>' print(s2.strip('>')) #打印结果 '<汤青松' 四、列表(list) 在Python中有一种叫做list的数据类型,在PHP中我们通常称之为索引数组 4.1 数据定义 定义list数据方式如下代码所示 #定义一个list数据 lst = ['张三','李四','王五','马六','田七'] 取数据方式和PHP基本一致,比如想获取lst里面的李四,可以这样获取 $name = lst[1] 4.2 切片 除了正向取数据还可以反着顺序取数据,比如你要获取马六,你可以这样写 $name = lst[-2] 在Python中有一种获取数据非常简便的方法,叫做切片,比如你想获取lst中的李四到马六区间的数据,在PHP中你得用array_slice函数去截取,但在Python则非常方便,如下代码所示 print(lst[1:4]) # 打印结果 ['李四','王五','马六'] 如果你想隔一个数据取一个数据,切片还支持设置步长,步长默认为1,也就是说连续取数据,当你设置步长后,会按照指定的步长,如下代码所示 print(lst[1:4:2]) # 打印结果 ['李四','马六'] 同样切片也支持反着取数据,比如你想获取田七到王五区间的数据,参考代码如下 print(lst[-1:-3:-1]) # 打印结果 ['田七','马六','王五'] 4.3 修改数据 在Python中如果你想向一个list数据中追加一个数据可以使用append方法向末尾增加,如下代码所示 lst = lst.append('王八') 也可以使用pop方法弹出最末尾一个数据,如下代码所示 value = lst.pop() 如果你想吧数据清空,也可以使用clear方法,当然你也可以直接将值重新赋值一个空list,如下代码所示 lst.clear() #或者 lst = [] 4.4 值的引用 在Python中需要注意一种引用内存地址的特殊情况,如下代码所示 # 定义两个变量 aList = ['张三', '李四', '王五'] bList = [] # 在bList中追加aList,此时bList为二维list bList.append(aList) #修改aList变量里面的值 aList[0] = '张三丰' #打印bList print(bList) # 打印结果 [['张三丰', '李四', '王五']] 在上面代码中可以看到,已经将一个变量赋给另外一个变量,按照常规理解来说,他们是相互独立的,但实际上并不是;在Python中如果存在二维的list,一维里面往往是记录一个内存地址,并不是把值完整的复制一份,这种引用操作Python中也称之为浅拷贝; 五、元组(tup) 在Python中有一种数据类型和list非常相似,称之为元组(tuple);它们最明显区别就是list定义之后可以修改结构,而元组定义之后不能再次修改,其次是在定义的时候有一点点不同,如下代码所示 #定义list数据 lst = ['张三', '李四', '王五'] #定义元组数据 tple ('张三', '李四', '王五') 在取数据的时候同样支持切片,也同样支持用索引(下标)方式来取值,唯一的区别就是不能再更改里面的值,比如说追加数据或者里面的某一个数据重新赋值。 六、字典 (dict ) 在PHP中我们知道数组有两种类型,索引数组和关联数组,而关联数组在Python中称之为字典,定义方式和lst区别不大,如下代码所示 6.1 数据定义 demo = { 'k1':'v1', 'k2':'v2', 'k3':'v3', } 在Python中定义字典相对PHP来说比较严格,对key有要求,不像PHP中可以只输入值而不输入可以,在Python中必须同时输入key和value。 6.2 增删改查 Python中的字典增删改查和PHP基本类似,获取字典数据参考代码如下所示 #明确存在某一个key可以这样取值,如果值不存在会报错 d['存在的key'] #不知道是否存在的情况 d.get('不知道存不存在的key') 增加数据的方式比较常规,这里不再特别说明,示例代码如下所示 #增加一项数据 data['不存在的key'] = '值' 删除数据的方式有两种形式,第一种是弹出指定数据,第二种是通过方法删除,和PHP基本类似,如下代码所示 #弹出数据 data.pop('key') #删除数据 del data['key'] 修改数据直接赋值即可,如下代码所示 # 修改数据 data['存在的key'] = 新的值 七、杂项 在Python中对数据会经常处理,这里记录三个常用的操作方法; 7,1 range 函数 有时需要定义一个列表,里面包含1~100的所有数值,在PHP中可能需要通过for循环来实现,但在Python中有一个函数range()可以创建一个整数列表,一般用在 for 循环中,如下代码所示 #range(start, stop[, step]) for i in range(0,5,1): print(i) # 打印结果 0 1 2 3 4 5 start参数代表计数从 start 开始,stop是代表计数停止的数值,step则代表一次递增几个数值 7.2 布尔型说明 在Python中的布尔值中,数字 0为 False,其他数字都为true,字符串除了''是false以外,其他非空的字符串都是true,tuple类型中除()和{}是false,其他都是true,还有Python中表示空的none也是false 7.3 数据操作 在Python中如果你想判断一个列表是否包含某一个值可以使用in方式,如下代码所示 #定义一个列表 lst = ['tang','qing','song'] #判断lst是否包含某一个值 result = 'tang' in lst print(result) #打印结果 True 如果你想知道一个list中是否不包含可以使用not in方式,如下代码所示 #定义一个列表 lst = ['tang','qing','song'] #判断lst是否不包含某一个值 result2 = 'tang' not in lst print(result2) #打印结果 False 在Python中如果你想查看一个列表的长度,可以使用len(lstt)方法 查看列表的长度 lst = ['tang','qing','song'] print(len(lst)) #打印结果 3 作者:汤青松 微信:songboy8888 更新日期:2019-07-21

优秀的个人博客,低调大师

Python全栈工程师(多继承、函数重写)

ParisGabriel 每天坚持手写一天一篇 决定坚持几年 为了梦想为了信仰 开局一张图 Python人工智能从入门到精通 补充: 对象 -------------------------> 类实例变量(属性) 类变量实例方法 类方法( @classmethond) 静态方法( @staticmethond)(类内普通函数) 继承/派生 单继承: 一个子类只有一个父类 一个父类可以有多个子类 -------------------------------------------------------------------------------------------- 用于类的函数: issublclass(cls, cls_or_tuple) 判断这个类是否继承自其他类, 如果此cls是class 或tuple中的一个派生子类, 返回True 否则返回False示例: class A: pass class B(A): pass class C(B): pass issublclass(C, B) # Trueissublclass(B, C) # Falseissublclass(C, (int, str)) # Falseissublclass(C, (int, B, str)) # True 封装 enclosure 封装是指隐藏类的实现细节,让使用者不关心这些细节 封装的目的是让使用者通过尽可能少的方法(或属性)操作对象私有属性和私有方法: python类中以双下划线(__)开头, 不以双下划线结尾的标识符为私有成员,私有成员或只能用类的方法进行访问和修改 以__开头的实例变量有私有属性 以__开头的方法有私有方法 示例: class A: def __init__(self): self.__p1 = 100 # 私有属性 def show_A(self): print("self.__p1", self.__p1) self.__m1() # 可以调用自己的方法 def __m1(self): # 私有方法 print("__m1(self)方法被调用") a = A() a.show_A() # 100 # print(a.__p1) # 出错 不能在类外部访问a.__p1私有属性 也不能在子类中访问 # a.__m1() # 出错 不能在类外部访问a.__p1私有方法 也不能在子类中访问 class B(A): pass b = B() print(b.__p1) # 出错,子类不能访问父类的私有成员 b.__m1() # 出错 b._A__p1 = 200 # python的假封装可以用此方法访问 print(b.__p1) # 200 多态polymorphic: 什么是多态 就是多种状态 多态是指在继承/派生关系的类中,调用基类对象的方法, 实际能够调用子类的覆盖方法的现象叫多态 状态: 静态(编译时状态)执行速度快 动态(运行时状态)执行速度慢说明: 多态调用方法与对象相关,不与类相关 Python的全部对象只有“运行时状态(动态)” 没有C++语言里的“编译时状态(静态)” 由于Python是解释执行的 是动态 没有静态 是在运行时编译(解释执行) 示例: # Pytgon中的运行时状态 class Shape: def draw(self): print("Shape的 draw 方法被调用") class Point(Shape): def draw(self): print("画车") class Circle(Shape): def draw(self): print("画圈") # python无法实现静态 除非不出现任何覆盖 def my_draw(s): # 其他静态语言 def my_draw(Circle s) 指定调用者 无法进行改变 s.draw() # 此处显示出"动态" s1 = Circle() s2 = Point() my_draw(s1) # 只有在调用时才能能确定调用哪一个 my_draw(s2) 面向对象不是编程而是一种思想面向对象的语言的特征: 继承 封装 多态 多继承:multiple inheritance 多继承是指一个子类继承自两个或两个以上的基类 语法: class 类名(基类名1, 基类名2, ...) 说明: 1.一个子类同时继承自多个父类,父类中的方法可以同时被继承下来 2.如果两个父类中同时有同名方法,而在子类中又没有覆盖此方法时, 调用结果难以确定 (也可以确定 C3算法)示例: class Car: def run(self, speed): print("汽车", speed, "Km/h 的速度行驶") class Plane: def fly(self, height): print("飞机以海拔", height, "米高度飞行") class PlaneCar(Plane, Car): '''同时继承Plane,Car的方法''' p1 = PlaneCar() p1.fly(10000) p1.run(300) 多继承的问题(缺陷): 标识符冲突的问题 要谨慎使用继承示例: # 多继承名字冲突问题 class A: def m(self): print("A.m被调用") class B: def m(self): print("B.m被调用") class AB(A, B): # 优先调用先出现的父类 有先后顺序 pass ab = AB() ab.m() # A.m被调用 class BA(B, A): pass ba = BA() ba.m() # B.m被调用 多继承的MRO(Method Resolution Order): 类的__mro__属性 __mro__ 是一个元组 里面存放类 此属性用来记录方法查找顺序示例: class A: def go(self): print("A") class B(A): def go(self): print("B") class C(A): def go(self): print("C") class D(B, C): def go(self): print("D") super().go() d = D() d.go() # B # 多继承的super调用关系一定是mro元组内的下一个类 和子父类没关系 # 如果没有方法则报错 调用算法(C3算法) # 正常调用也是mro顺序 函数重写: 在自定义类内添加相应的方法,让自定义类创建的实例 能像内建对象一样进行内建函数操作对象转字符串函数: repr(obj) 返回一个能代表此对象的表达式字符串,通常eval(repr(obj))== obj (这个字符串通常给Python解释执行器运行用的) str(obj) 返回的字符串(这个字符串通常给人阅读用的) 对象转字符串函数的重写方法: repr(obj) 函数的重写方法: def __repr__(self): pass str(obj) 函数的重写方法: def __str__(self): pass 说明: 1.str(obj)函数先查找, obj.str()方法 调用此方法并返回结果 2.如果没有obj.__str__方法时,返回obj.__repr__() 方法的结果并返回 3.如果obj.__repr__方法不存在,则调用obj类的__repr__ 实例方法显示:<__main__.XXXX object at 0x7f4b1c36fa90>示例: class MyNumber: def __init__(self, value): '构造函数,初始化MyNumber' self.data = value def __str__(self): '''转换为人能够识别的字符串''' print("__str__方法被调用") return "自定义数字类型对象:%d" % self.data def __repr__(self): '''转换为eval能够识别的字符串''' return 'MyNumber(%d)' % self.data n1 = MyNumber(100) n2 = MyNumber(200) print("repr(n1):--->", repr(n1)) print("strr(n2):--->", str(n2)) print("strr(n2):--->", n2.__str__()) 其他内建函数的重写方法:__abs__ abs(obj) 函数__len__ len(obj) 函数(必须返回整数)__reversed__ reversed(obj) 函数(必须返回可迭代对象)__round__ round(obj) 函数 示例: # 函数重写 class MyNumber: def __init__(self, v): self.data = v def __repr__(self): return 'MyNumber(%d)' % self.data def __abs__(self): '''__abs__函数重写 求绝对值函数''' # return -self.data v = abs(self.data) return MyNumber(v) # 创建一个新的MyNumber对象 def __len__(self): '''__len__函数重写 求长度函数''' # return len(self.data) i = 0 for x in self.data: i += 1 return i i1 = MyNumber(-10) print(i1) i2 = abs(i1) print(i2) i3 = MyNumber("123d5sa") print(len(i3)) 数据转换函数的重写:__complex__ complex(obj) 函数__int__ int(obj) 函数__float__ float(obj) 函数__bool__ bool(obj) 函数 示例: 数据转换构造函数重写 class MyNumber: def __init__(self, v): self.data = v def __repr__(self): return 'MyNumber(%d)' % self.data def __int__(self): # return 999999 # 可以自定义返回规则 return self.data n1 = MyNumber(100) x = int(n1) print(type(x)) # <class 'int'> print(bool(n1)) # True n2 = MyNumber(0) print(bool(n2)) # True # 默认返回 # True 布尔测试函数重写: 格式: __bool__ 作用: 用于bool(obj)函数取值 用于if语句的真值表达式中 说明: 1.当自定义的类内有__bool__(self) 方法时,此方法的返回值为bool(obj)的返回值 2.当不存在__bool__(self),bool(x) 返回__len__(self)方法的返回值是否为零来测试布尔值 3.当不存在__len__方法时,则直接返回True示例: # bool(x)函数重写 class MyList: '''自定义类型的列表,用来保存数据,内部用列表来储存数据''' def __init__(self, iterable): self.data = [x for x in iterable] def __repr__(self): return " MyList(%s)" % self.data def __len__(self): '''返回长度''' print("__bool__方法被调用") return len(self.data) # def __bool__(self): # print("__bool__方法被调用") # return False # 此处定义所有对象返回False def __bool__(self): print("__bool__方法被调用") for x in self.data: if not x: return False return True # 自定义返回规则(这就是函数重写) myl = MyList((1, 2, -3, 4, -5, 5)) print(myl) print(bool(myl)) if myl: print("myl是真值") else: print("myl是假值") 迭代器(高级): 什么是迭代器: 可以通过next(it)函数取值的对象就是迭代器 迭代器协议: 迭代器是指对象能够使用next函数获取下一项数据, 在没有下一项数据时触发一个StopIteration异常来终止迭代的重写 迭代器协议实现方法: __next__(self) 方法来实现迭代协议 语法形式: class MyIterator: def __next__(self): 迭代器协议 return 数据什么是可迭代对象: 是指用iter(obj) 函数返回迭代对象(实例) 可迭代对象内部需要定义__iter__(self)方法来返回迭代器对象 # 此示例示意让自定义的作为可迭代对象能让 for 语句迭代访问 class MyList: '''创建MyList类''' def __init__(self, iterable=()): '''初始化self iterable:可迭代对象 ''' # 用iterable生成列表 self.data = [x for x in iterable] def __repr__(self): '''返回一个MyList字符串''' return 'MyList(%s)' % self.data def __iter__(self): '''此方法必须返回一个迭代器对象 此方法创建一个迭代器对象并返回 ''' return MyListIterator(self.data) class MyListIterator: '''此类用来创建迭代器,此类型的迭代器可以迭代访问 MyList类型的对象''' def __init__(self, lst): self.data = lst self.cur_index = 0 # 初始化迭代器的起始位置 def __next__(self): '''此方法用于实现迭代器协议''' if self.cur_index >= len(self.data): # 如果索引越界就发终止迭代通知 raise StopIteration value = self.data[self.cur_index] # 要返回的值 self.cur_index += 1 return value myl = MyList([0, -1, 2, -3]) print(myl) # it = iter(myl) # print(next(it)) for x in myl: # 迭代访问自定义类型的可迭代对象 print(x) 练习: 写一个类Bicycle类 ,有 run方法.调用时显示骑行里程km class Bicycle: def run(self, km): print('自行车骑行了', km, '公里') 再写一个类EBicycle(电动自行车类), 在Bicycle类的基础上添加了电池电量 volume 属性, 有两个方法: 1. fill_charge(self, vol) 用来充电, vol为电量 2. run(self, km) 方法每骑行10km消耗电量1度,同时显示当前电量,当电量耗尽时调用 父类的run方法继续骑行 b = EBicycle(5) # 新买的电动有内有5度电 b.run(10) # 电动骑行了10km还剩 4 度电 b.run(100) #电动骑行了40km,还剩0度电,其余60用脚登骑行 b.fill_charge(10) # 又充了10度电 b.run(50) # 骑行了50公里剩5度电 答案: class Bycycle: def run(self, km): '''自行车''' print("自行车骑行了", km, "公里") class EBycyle(Bycycle): def __init__(self, volume=0): '''初始化电动车''' self.volume = volume print("新买的电动车有", volume, "度电") def fill_charge(self, vol): '''冲电''' self.volume += vol print("又冲了", vol, "度电") def run(self, km): self.volume -= (km / 10) # 减去使用的电量 x = abs(self.volume) * 10 # 计算超出的公里 if self.volume < 0: # 判断是否超出电量 self.volume = 0 print("电动车骑行了", km, "千米 还剩", self.volume, "度电 其余", x, "千米用脚蹬") else: print("电动车骑行了", km, "千米 还剩", self.volume, "度电") b = EBycyle(10) b.run(10) b.run(100) b.fill_charge(10) b.run(50) # 新买的电动车有 10 度电 # 电动车骑行了 10 千米 还剩 9.0 度电 # 电动车骑行了 100 千米 还剩 0 度电 其余 10.0 千米用脚蹬 # 又冲了 10 度电 # 电动车骑行了 50 千米 还剩 5.0 度电 b = EBycyle(50) b.run(10) b.run(100) b.fill_charge(10) b.run(50) # 新买的电动车有 50 度电 # 电动车骑行了 10 千米 还剩 49.0 度电 # 电动车骑行了 100 千米 还剩 39.0 度电 # 又冲了 10 度电 # 电动车骑行了 50 千米 还剩 44.0 度电 练习: 1. 修改原有的学生信息管理系统, 将学生对象的,全部属性 都变为私有属性,不让外部直接访问来实现封装 源码: https://pan.baidu.com/s/1szQDS5cfzVHLlZrrJcCs8A 2. 写一个列表类MyList实现存储整数列表,写类的定义如下: class MyList: def __init__(self, iterator): self.data = ... 让此类的对象能用for语句进行迭代访问 L = MyList(range(5)) print(L) L2 = [x ** 2 for x in L] print(L2) # [0, 1, 4, 9, 16] 答案: class MyList: def __init__(self, iterable=()): self.lst = [x for x in iterable] def __repr__(self): return "%s" % self.lst def __iter__(self): return MyIterable(self.lst) class MyIterable: def __init__(self, lst): self.lst = lst self.myindex = 0 def __next__(self): if self.myindex >= len(self.lst): raise StopIteration value = self.lst[self.myindex] self.myindex += 1 return value L = MyList(range(5)) print(L) for x in L: print(x) L2 = [x ** 2 for x in L] print(L2) 3. 写一个类Fibonacci 实现迭代器协议 ,此类的对象可以作为可迭代对象生成相应的斐波那契数 1 1 2 3 5 class Fibonacci: def __init__(self, n) # n代表数据的个数 ... ... 实现如下操作: for x in Fibonacci(10): print(x) # 1 1 3 5 8 .... L = [x for x in Fibonacii(50)] print(L) F = fibonicci(30) print(sum(F)) 答案: 直接继承第二题的类 class Fibonicci(MyList): def __init__(self, n): L = [1, 1] for _ in range(n - 2): L.append(L[-1] + L[-2]) self.lst = L for x in Fibonicci(10): print(x) # 1 1 3 5 8 .... L = [x for x in Fibonicci(50)] print(L) F = Fibonicci(30) print(F) print(sum(F))

优秀的个人博客,低调大师

Python全栈工程师(迭代器、字节串)

ParisGabriel 每天坚持手写一天一篇 决定坚持几年 为了梦想为了信仰 Python人工智能从入门到精通 迭代器 Iterator: 用<>括号表示的一定是对象 什么是迭代器? 迭代器是访问可迭代对象的工具 迭代器是指用iter(obj) 函数返回的对象(实例) 迭代器可以用next(it) 函数获取可迭代对象的数据 迭代器函数iter和next iter(iterable) 从可迭代对象中返回一个迭代器,iterable 必须是能提供一个迭代器的对象 next(iterator) 从迭代器iterator中获取下一个记录,如果无法获取下一条记录,则触发StopIteration异常说明: 迭代器只能向前取值,不会后退 用iter函数可以返回一个可迭代对象的迭代器作用: 迭代器对象能用next函数获取下一个元素. 示例: L = [2, 3, 5, 7] it = iter(L) # 用L对象返回能访问L的迭代器, it绑定迭代器 next(it) # 2 next(it) # 3 next(it) # 5 next(it) # 7 next(it) # StopIteration 通知(没有数据) it = iter(range(1, 10, 30)) next(it) # 1 next(it) # 4 next(it) # 7 next(it) # StopIteration 用迭代器访问可迭代对象: L = [2, 3, 5, 7] for x in L: print(x) else: print('循环结束') it = iter(L) # 从L中获取一个迭代器 while True: try: x = next(it) print(x) except StopIteration: print("循环结束") break 生成器 Generator (python 2.5及之后)什么是生成器? 生成器是能够动态提供数据的对象,生成器对象也是可迭代对象(实例) 动态就是现用现生成数据 生成器有两种: 1. 生成器函数 2. 生成器表达式 生成器函数的定义 含有yield语句的函数是生成器函数,此函数被调用将返回一个生成器对象 yield 翻译为(产生或生成) yield 语句 语法: yield 表达式 说明: yield 用于 def 函数中,目的是将此函数作用生成器函数使用 yield 用来生成数据,供迭代器的next(it) 函数使用 yield 单步生成数据 在函数内可以一个或多少 实例: # 此示例示意用生成器函数生成一定范围内的自然数 def myinteger(n): i = 0 # 自然数从0开始 while i < n: yield i i += 1 for x in myinteger(3): print(x) 生成器函数说明: 生成器函数的调用将返回一个生成器对象,生成器对象是一个可迭代对象 在生成器函数调用return 会触发一个StopIteration异常生成器表达式: 语法: (表达式 for 变量 in 可迭代对象 [if 真值表达式 ]) 说明: if 子句可以省略 作用: 用推导式的形式创建一个新的生成器 示例: gen = (x ** 2 for x in range(1, 5)) it = iter(gen) next(it) # 1 next(it) # 4 next(it) # 9 next(it) # 16 next(it) # StopIteration迭代工具函数 迭代工具函数的作用是生成一个个性化的可迭代对象 函数:zip(iter1[, iter2[, ...]]) 返回一个zip对象,此对象用于生成元组,此元组的个数由最小的可迭代对象决定 enumerate(iterable[, start]) 生成带索引的枚举对象,返回迭代类型为索引-值对(index-value对),默认索引从零开始,也可以用start指定 示例: numbers = [10086, 10000, 10010, 9558] names = ['中国移动', '中国电信', '中国联通'] for t in zip(numbers, names): print(t) for x, y in zip(numbers, names): print(y, '的客服电话是:', x) x, y = (10086, '中国移动') # 序列赋值 zip函数的实现示例2: def myzip(iter1, iter2): it1 = iter(iter1) # 拿出一个迭代器 it2 = iter(iter2) while True: a = next(it1) b = next(it2) yield (a, b) for t in myzip(numbers, names): #能实现与zip同样的功能 print(t) enumerate 示例: names = ['中国移动', '中国电信', '中国联通'] for t in enumerate(names): print(t) enumerate 实现方法示意: def myenum(iterable): it = iter(iterable) i = 0 while True: a = next(it) yield (i, a) i += 1 字节串和字节数组 字节串bytes (也叫字节序列) 作用: 存储以字节为单位的数据 字节串是不可变的字节序列 字节: 字节是由8个位(bit)组成的数据单位,是计算机进行数据管理的单位 字节是用 0 ~ 255 范围内的整数表示的 创建空字节串的字面值 B = b'' B = b"" B = b'''''' B = b"""""" 创建非空字节串的字面值 B = b'ABCD' B = b"ABCD" B = b'\x41\x42' 字节串的构造函数 bytes bytes() 生成一个空的字节串 等同于 b'' bytes(整数可迭代对象) # 用可迭代对象初始化一个字节串 bytes(整数n) 生成n个值为0的字节串 bytes(字符串, encoding='utf-8') 用字符串转为编码生成一个字节串 示例: b = bytes() # b = b'' b = bytes(range(65, 69)) # b = b'ABCD' b = bytes(5) # b = b'\x00\x00\x00\x00\x00' b = bytes('abc中文') # b=b'ABC\xe4\xb8\xad\xe6\x96\x87' bytes 的运算: + += * *= < <= > >= == != in / not in 索引和切片 len(x) max(x) min(x) sum(x) any(x) all(x) bytes 和 str 的区别: bytes 存储字节( 通常值在 range(0, 256)) str 存储unicode字符( 通常值在0~65535) bytes 与 str 的转换 编码(encode) str ------------> bytes b = s.encode(encoding='utf-8') 解码(decode) bytes ----------> str s = b.decode(encoding='utf-8') 字节数组 bytearray可变的字节序列 字节数组的构造函数: bytearray bytearray() 创建空的字节数组 bytearray(整数) 用可迭代对象初始化一个字节数组 bytearray(整型可迭代对象) 生成n个值为0的字节数组 bytearray(字符串, encoding='utf-8') 用字符串的转换编码生成一个字节数组 bytearray 的运算: + += * *= < <= > >= == != in / not in 索引和切片 (字节数组支持索引和切片的赋值操作,规则同列表的索引和切片赋值规则) 例: ba = bytearray(b'aBCDEfG') ba[0] = 65 ba[-2] = 70 bytearray的方法: BA.clear() 清空 BA.append(n) 追加一个字节(n为0~255的整数) BA.remove(value) 删除第一个出现的字节,如果没有出现,则触发ValueError错误 BA.reverse() 字节顺序反转 BA.decode(encoding='utf-8') # 解码为字符串 BA.find(sub[, start[,end]]) # 查找 sub 练习: 有一个集合: s = {'唐僧', '悟空', '八戒', '沙僧'} 用 for语句来遍历所有元素如下: for x in s: print(x) else: print('遍历结束') 请将上面的for语句改写为 用while语句和迭代器实现 练习: 写一个生成器函数 myeven(start, stop) 此函数用来生成从 start开始到stop结束(不包含)区间内的一系列偶数 def myeven(start, stop): .... evens = list(myeven(10, 20)) print(evens) # [10, 12, 14, 16, 18] for x in myeven(21, 30): print(x) # 22, 24, 26, 28 L = [x**2 for x in myeven(3, 10)] print(L) # 16 36 64 练习: 已知有一个列表L L = [2, 3, 5, 7] 用生成器表达式从此列表中拿到数,生成 列表中数据的平方 gen = ...... # 此处用生成器表达式实现 L2 = list(gen) # L2 = [4, 9, 25, 49] 练习: 写一个程序,读入任意行的文字,当输入空行时结束输入 打印带有行号的输入结果: 如: 请输入: hello<回车> 请输入: world<回车> 请输入: tarena<回车> 请输入: <回车> # 直接回车结束输入 输出如下: 第1行: hello 第2行: world 第3行: tarena 练习: 写一个程序,从键盘输入一段字符串存入s变量 1. 将此字符串转为字节串用变量b绑定,并打印出b 2. 打印字符串s的长度和字节串b的长度 3. 将b字节串再转换为字符串用变量s2 绑定,判断 s2 和 s是否相同? s = input("please input string:") b = s.encode("utf-8") print(s) print(b) print(len(s)) print(len(b)) s2 = b.decode("utf-8") if s2 == s: print("相等") 练习: 1. 写一个生成器函数primes生成素数, 此生成器函数为 primes(begin, end) 如: [x for x in primes(10, 20)] 将得到列表 [11, 13, 17, 19] 答案: def get_ss(x): if x <= 0: return False for i in range(2, x): if x % i == 0: return False return True def primes(begin, end): if begin <= 2or end <= 2: pass for x in range(begin, end): if get_ss(x): yield x L = [x for x in primes(10, 20)] print(L) 2. 写一个生成器函数,生成斐波那契数列的前n个数 1 1 2 3 5 8 13 def fibonacci(n): ... yield... 1) 输出前20个数: for x in fibonacci(20): print(x) 2) 打印前40个数的和: print(sum(fibonacci(40))) 答案: def fibonacci(n): L = [1, 1] for x in range(n): s = L[x] + L[x + 1] L.append(s) return L print(fibonacci(20)) print(sum(fibonacci(40))) 3. 写 程序打印杨辉三角(只打印6层) 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 答案: def yh_sj(): L = [1] while True: yield L L.append(0) L = [L[x - 1] + L[x] for x in range(len(L))] def lst(): i = 0 for x in yh_sj(): print(str(x).center(50)) if i == 10: break i += 1 思考: L = [2, 3, 5, 7] L2 = [x ** 2 for x in L] # 列表推导式 it = iter(L2) print(next(it)) # 4 L[1] = 10 print(next(it)) # 9 L = [2, 3, 5, 7] G3 = (x ** 2 for x in L) # 生成器表达式 it = iter(G3) print(next(it)) # 4 L[1] = 10 print(next(it)) # 100 生成器是动态的现用现去 跟着数据变化所变化

优秀的个人博客,低调大师

Python全栈工程师(包、模块 的导入)

ParisGabriel 每天坚持手写一天一篇 决定坚持几年 为了梦想 为了信仰 Python人工智能从入门到精通 $ pip3 install tensorflow: 第三方模块 tensorflow 的安装 电脑必须有网 命令行自动下载安装dir(模块名): 查看模块所有属性 自定义模块导入 示例: 自定义模块的实现方法 def myfac(n): print('正在计算', n, '的阶乘') def mysum(n): print("正在计算1+2+3+...+%d的和" % n) name1 = "Audi" name2 = "TESLA" print("mymod模块被加载!") 注意事项: 自定义模块的文件要求以.py结尾,文件名必须是标识符的规则 导入 语句 import 语句等 搜索块的路径顺序 import mymod from mymod import myfac from mymod import * 去哪儿找这个mymod.py? 搜索顺序: 1. 搜索程序运行时的路径(当前路径) 2. sys.path 提供的路径 sys.path是一个列表,里面放的都是模块的搜索路径 3. 搜索内建模块模块的加载过程: 1. 在模块导入时,模块的所有语句都会执行 2. 如果一个模块已经导入,则再次导入时不会重新执行模块内的语句 模块的重新加载 import mymod import imp imp.reload(mymod) # 重新加载mymod模块 模块以导入和执行的过程: 1. 先搜索相关的路径,找到模块名.py 2. 判断是否有此模块对应的.pyc文件。如果.pyc比.py文件新,则直接加载.pyc文件 3. 否则 用模块.py,文件生成.pyc,并加载执行 pyc python的编译 编译 解释执行 mymod.py --------> mymod.pyc --------> python3 模块--------> 编译好的Python3文件--------> Python3解释执行器 模块的文档字符串: 模块内第一个没有赋值给任何变量的字符串称为模块的文档字符串 模块的 __doc__ 属性 此属性用于绑定模块的文档字符串 模块的 __file__属性 此属性用于记录模块对应的文件路径名 示例: # 模块的文档字符串:'''我的自定义模块mymod2此模块有两个函数 myfac和 mysum...''' def myfac(n): '''自定义用来测试用的myfac的文档字符串''' print('正在计算', n, '的阶乘') def mysum(n): print("正在计算1+2+3+...+%d的和" % n) name1 = "Audi" name2 = "TESLA" print("mymod模块被加载!") 模块的 __name__属性 __name__属性 用来记录模块自身名字 作用: 1. 记录模块名 2. 用来判断是否为主模块 说明: 当此模块为主模块(也就是第一个运行的模块时)运行时, __name__绑定 '__main__' 当此模块不是主模块时,而是被其它模块导入时,此时 __name__ 绑定模块名 示例: '''此示例示意 mymod3的 __name__属性的用法''' def test(): pass print("__name__属性绑定的值是:", __name__) if __name__ == '__main__': print("当前mymod3.py 正在以主模块运行") else: print("当前mymod3.py 正在被其它模块导入") print("我的模块名是", __name__) 模块的 __all__列表 模块中的 __all__列表是一个用来存放可导出属性的字符串列表 作用: 当用 from xxx import *语句导入时,只导入 __all__ 列表内的属性 并且只对 from xx import * 有效 示例: # __all__属性只对 from import * 语句有效 __all__ = ['hello1', 'name1'] def hello1(): pass def hello2(): pass def hello3(): pass name1 = 'aaaaaa' name2 = 'aaaaaa' 模块的隐藏属性 模块中以'_'开头的属性,在from xxx import * 导入时,将不被导入,通常称这些属性为隐藏属性 示例: 此模块在用from mymod5 import *导入时,只能导入: # f1和 name1 def f1(): pass def _f2(): pass def __f3(): pass name1 = 'aaaaa' _name2 = 'bbbbb' 随机模块 random 模块名: random 作用: 用于模拟或生成随机输出的模块 随机模块 random 说明: random模块是用于模拟或生成随机输出的模块. import random as R 函数名 描述 R.random() 返回一个[0, 1) 之间的随机实数 R.uniform(a,b) 返回[a,b) 区间内的随机实数 R.randrange([start,] stop[, step]) 返回range(start,stop,step)中的随机数 R.choice(seq) 从序列中返回随意元素 R.shuffle(seq[, random]) 随机指定序列的顺序(乱序序列) R.sample(seq,n) 从序列中选择n个随机且不重复的元素 系统模块 sys 运行时系统相关的信息 sys模块的属性 属性 描述 sys.path 模块搜索路径 path[0] 是当前脚本程序的路径名,否则为 '' sys.modules 已加载模块的字典 sys.version 版本信息字符串 sys.version_info 版本信息的命名元组 sys.platform 操作系统平台名称信息 sys.argv 命令行参数 argv[0] 代表当前脚本程序路径名 sys.copyright 获得Python版权相关的信息 sys.builtin_module_names 获得Python内建模块的名称(字符串元组) sys模块的函数 函数名 描述 sys.exit([arg]) 退出程序,正常退出时sys.exit(0) sys.getrecursionlimit() 得到递归嵌套层次限制(栈的深度) sys.setrecursionlimit(n) 得到和修改递归嵌套层次限制(栈的深度) 包(模块包) package 包的定义 包是将模块以文件夹的组织形式进行分组管理的管理方法 作用: 将一系列模块进行分类管理,有利于防止命名冲突 可以在需要时加载一个或部分模块则不是全部模块包目录树示例: mypack/ (包名) __init__.py (__init__.py 文件) menu.py (模块) games/ (子包名1) __init__.py (__init__.py 文件) contra.py (模块) supermario.py (模块) tanks.py (模块) office/ (子包名2) __init__.py (__init__.py 文件) word.py (模块) excel.py (模块) powerpoint.py (模块) __init__.py 文件 __init__.py是常规包内必须存在的文件 __init__.py会在包加载时自动调用 作用: 1. 编写此包的内容 2. 在内部填写文档字符串 3. 在__init__.py文件内可以加载此包所依懒的一些其它模块 包的导入 同模块的导入规则 # import 语句 import 包名 [as 包新名] import 包名.模块名 [as 模块新名] import 包名.子包名.模块名 [as 模块新名] # from import语句 from 包名 import 模块名 [as 模块新名] from 包名.子包名 import 模块名 [as 模块新名] from 包名.子包名.模块名 import 属性名 [as 属性新名] # from import *语句 from 包名 import * from 包名.模块名 import * ... 导入包时的索引路径顺序: 1. 搜索程序的当前路径 2. sys.path 提供的路径 __init__.py内的 __all__ 列表 作用: 用来记录此包中有哪儿些子包或模块在用from import *语句 时被导入 说明: __all__列表只对 from xxx import *语句起作用 包的相对导入: 包的相对导入是指包内模块的相互导入(不能超过包的最高界限) 语法: from 相对路径包或模块 import 属性或模块名 from 相对路径包或模块 import * 相对路径 . 代表当前目录 .. 代表上一级目录 ... 代表上二级目录 .... 以此类推 注: 相对导入时不能超出包的外部 模块的导入分三种 import tiem 直接导入模块 from time import time 导入time模块的time功能 from mymod import * 导入time模块的所有功能 这里*导入可以直接使用函数不需要加模块名.函数 如果有__all__属性只导入__all__列表里的 包也是分三种 差不多 包所导入的是文件夹 as 给包或者模块、属性、等 改名 (.)统一代表什么什么下的什么什么 解释执行器就是先把.py文件翻译成二进制码.pyc文件再进行运行(先解释后执行) 但运模块时自动创建__pycache__ 文件夹 里面放.pyc文件 文档字符串和函数一样 一般用三引号括起来 写就可以了 第一行是标题 换行 第三行开始写详细内容 __name__属性返回一个字符串 如果返回 __main__代表程序主入口 也就是第一个运行的模块 否则返回当前模块的名字 __all__ 属性是一个列表 当三种导入方式导入时只导入列表里的其他的不导入__开头的函数 属性 当三种导入方式导入时不导入_开头的隐藏函数 其他的全导入__init__.py 是必须存在的文件 不存在 此文件夹就不是包 里面可以写属性 包的文档字符串 导入路径 优先相对路径 其次sys.path路径 相对路径 就是 当前路径 或 路径名加上模块 path是系统环境变量列表 相当于全局变量 在任何目录都能调用这个列表里的路径 练习: 1. 猜数字游戏 随机生成一个 0~100之间的一个整数,用变量x绑定 让用户输入一个数y,输出猜数字的结果: 1) 如果y大于x则提示: "您猜大了" 2) 如果y小于x则提示: "您猜小了" 3) 如果y等于生成的数x,则提供示用户"恭喜您猜对了 "并退出猜数字 循环重复上述步聚,直到猜对为止。 猜对了,显示用户猜数字的次数,然后退出程序 答案: import random x = random.randrange(101) i = 0 while True: s = int(input("请输入要猜的数字0~100:")) i += 1 if s > x: print("大了") elif s < x: print("小了") else: print("猜对了") print("猜了%d次" % i) break 这里有个2分算法 100内的猜数字一般不超过7次猜对 10000不超过20次就能猜对 练习: 1. 写一个闹钟程序,启动时设置定时时间, 到时间后打印一句"时间到...." 然后退出程序 import time def time_out(): h = int(input("please input:")) m = int(input("please input:")) s = int(input("please input:")) d = (h, m, s) while True: s = time.localtime() print('%02d:%02d:%02d' % (s[3], s[4], s[5]), end = "\r") time.gmtime(1) if s[3: 6] >= d: print("Time out") break time_out() 2. 模拟斗地主发牌,扑克牌共54张 黑桃('\u2660'), 梅花('\u2663'), 方块('\u2665'), 红桃('\u2666') A2-10JQK 大王、小王 三个人玩,每人发17张牌,底牌留三张 输入回车, 打印出第1个人的17张牌 输入回车, 打印出第2个人的17张牌 输入回车, 打印出第3个人的17张牌 输入回车, 打印三张底牌 答案: import random L = {"大王", "小王"} range(2, 11) for x in {"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"}: L.add("\u2660 " + x) L.add("\u2663 " + x) L.add("\u2665 " + x) L.add("\u2666 " + x) print("共54张牌:", L) print() for x in range(3): input() s = set(random.sample(L, 17)) if x == 0: x = "第一个玩家:" elif x == 1: x = "第二玩家:" elif x == 2: x = "第三个玩家:" print(x, s) L -= s print() print("底牌:", L) 今天的: 3. 修改学生信息管理程序,将原student_info.py 拆分为模块 要求: 1. 主事件循环放在main.py中 2. show_menu 函数放在menu.py中 3. 与学生操作相关的函数放在 student_info.py中 还有昨天的: 3. 改写之前的学生信息管理程序 要求添加四个功能: | 5) 按学生成绩高-低显示学生信息 | | 6) 按学生成绩低-高显示学生信息 | | 7) 按学生年龄高-低显示学生信息 | | 8) 按学生年龄低-高显示学生信息 | 下载地址:https://pan.baidu.com/s/17xFe_r0g8a-nP1PrUoTXCA 已 吐血身亡 下次补上 太晚了 嘤嘤嘤......... # 创建输入函数:input_student开始 封装信息输入 # 创建输出函数:student完成 临时菜单封装 def student(): for a in range(1, 8, 2): x = "*" * a print(x.center(9), x.center(10), x.center(10), x.center(9)) else: for y in range(1, 4 + 1): c = "*" if 4 >= 10: c = "***" print(c.center(9), c.center(10), c.center(10), c.center(9)) print("^^^^^^^^^^^^^ System menu ^^^^^^^^^^^^^") print("---------------------------------------") print(" 1): Add Student information ") print("---------------------------------------") print(" 2): print Student information ") print("---------------------------------------") print(" 3): delete Student information ") print("---------------------------------------") print(" 4): revamp Student information ") print("---------------------------------------") print(" 5): Results sorting High --> low") print("---------------------------------------") print(" 6): Results sorting low --> High") print("---------------------------------------") print(" 7): Age sort High --> low ") print("---------------------------------------") print(" 8): Age sort low --> High ") print("---------------------------------------") print(" please input number 1~8 select ") print("---------------------------------------") print(" input :(Q)Exit procedure ") print("---------------------------------------") # 创建输出函数:perform完成 def input_student(): L = [] print("--------Student information enty--------") while True: name = input("please input name:") if name == "": q = input("operation succeed! return y / n :") if q == "y": break else: input_student() age = int(input("please input age:")) score = int(input("please input score:")) d = {} d = {"name": name, "age": age, "score": score} print("+---------------+----------+----------+") name = d["name"].center(15) age = str(d["age"]).center(10) score = str(d["score"]).center(10) print('''|%s|%s|%s|''' % (name, age, score)) print("+---------------+----------+----------+") print("-----Not input Enter be no revamp!-----") L.append(d) return L # 创建输入函数:input_student完成 # 创建输出函数:output_student开始 封装列表打印 def output_student(lst): print("-------Student information sheet-------") print("+---------------+----------+----------+") print("| Name | Age | Score |") print("+---------------+----------+----------+") for x in lst: name = x["name"].center(15) age = str(x["age"]).center(10) score = str(x["score"]).center(10) print('''|%s|%s|%s|''' % (name, age, score)) print("+---------------+----------+----------+") q = input("operation succeed! return y / n :") if q == "y": pass else: output_student() # 创建输出函数:output_student完成 # 创建输出函数:delete_student开始 封装删除信息 def delete_student(lst): name = input("please input name delete:") for x in lst: if x["name"] == name: print("+---------------+----------+----------+") name = x["name"].center(15) age = str(x["age"]).center(10) score = str(x["score"]).center(10) print('''|%s|%s|%s|''' % (name, age, score)) print("+---------------+----------+----------+") delet = input("whether delete (y:yes/y:no):") if delet == "y": lst.remove(x) q = input("operation succeed! return y / n :") if q == "y": pass else: delete_student() # 创建输出函数:delete_student完成 # 创建输出函数:revamp_student()开始 封装更改信息 def revamp_student(lst): name = input("please input name revamp:") for x in lst: if x["name"] == name: print("+---------------+----------+----------+") name = x["name"].center(15) age = str(x["age"]).center(10) score = str(x["score"]).center(10) print('''|%s|%s|%s|''' % (name, age, score)) print("+---------------+----------+----------+") print("-----not input Enter be no revamp!-----") rage = input("please input age revamp:") rscore = input("please input score revamp:") if rage != "": x["age"] = rage if rscore != "": x["score"] = rscore print("+---------------+----------+----------+") name = x["name"].center(15) age = str(x["age"]).center(10) score = str(x["score"]).center(10) print('''|%s|%s|%s|''' % (name, age, score)) print("+---------------+----------+----------+") print("-----------revamp perform!------------") q = input("operation succeed! return y / n :") if q == "y": pass else: revamp_student() # 创建输出函数:revamp_student()完成 # 创建输出函数:output_student_by_score_desc开始 成绩高~低排序 def output_student_by_score_desc(lst): def k(d): return int(d['score']) L = sorted(lst, key=k, reverse=True) output_student(L) # 创建输出函数:output_student_by_score_asc开始 成绩低~高排序 def output_student_by_score_asc(lst): L = sorted(lst, key=lambda d: int(d['score'])) output_student(L) # 创建输出函数:output_student_by_age_desc开始 年龄高~低排序 def output_student_by_age_desc(lst): L = sorted(lst, key=lambda d: int(d['age']), reverse=True) output_student(L) # 创建输出函数:output_student_by_age_asc开始 年龄低~高排序 def output_student_by_age_asc(lst): L = sorted(lst, key=lambda d: int(d['age'])) output_student(L) # 创建输出函数:System_menu()开始 临时封装系统 def System_menu(): L = [] while True: student() select = input(" please input:") if select == '1': L += input_student() elif select == '2': output_student(L) elif select == '3': delete_student(L) elif select == '4': revamp_student(L) elif select == '5': output_student_by_score_desc(L) elif select == '6': output_student_by_score_asc(L) elif select == '7': output_student_by_age_desc(L) elif select == '8': output_student_by_age_asc(L) elif select == 'q': break # 创建输出函数:System_menu()完成 临时封装系统 # 运行

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册