python日志:logging模块使用

何时使用logging

想要执行的任务工具
对于命令行或程序的应用,结果显示在控制台。print()
在对程序的普通操作发生时提交事件报告 (比如:状态监控和错误调查)http://logging.info() 函数 (当有诊断目的需要详细输出信息时使用
logging.debug() 函数)
提出一个警告信息基于一个特殊的运行时事件warnings.warn() 位于代码库中,该事件是可以避免的,需要修改
客户端应用以消除告警logging.warning() 不需要修改客户端应用,但是该事件还是需要引起关注
报告错误而不引发异常 (如在长时间运行中的服务端进程的错误处理)logging.error(),
logging.exception() 或 logging.critical() 分别适用于特定的错误及应用领域

日志事件级别

级别何时使用
DEBUG细节信息,仅当诊断问题时适用。
INFO确认程序按预期运行
WARNING表明有已经或即将发生的意外(例如:磁盘空间不足)。程序仍按预期进行
ERROR由于严重的问题,程序的某些功能已经不能正常执行
CRITICAL严重的错误,表明程序已不能继续执行

默认的级别是 “WARNING“,意味着只会追踪该级别及以上的事件,除非更改日志配置。所追踪事件可以以不同形式处理。最简单的方式是输出到控制台。另一种常用的方式是写入磁盘文件。


basicConfig()设置

# -*- coding:utf-8 -*-

import logging

#默认的warning级别,只输出warning以上的
#使用basicConfig()来指定日志级别和相关信息

logging.basicConfig(level=logging.DEBUG #设置日志输出格式
                    ,filename="demo.log" #log日志输出的文件位置和文件名
                    ,filemode="w" #文件的写入格式,w为重新写入文件,默认是追加
                    ,format="%(asctime)s - %(name)s - %(levelname)-9s - %(filename)-8s : %(lineno)s line - %(message)s" #日志输出的格式
                    # -8表示占位符,让输出左对齐,输出长度都为8位
                    ,datefmt="%Y-%m-%d %H:%M:%S" #时间输出的格式
                    )

logging.debug("This is  DEBUG !!")
logging.info("This is  INFO !!")
logging.warning("This is  WARNING !!")
logging.error("This is  ERROR !!")
logging.critical("This is  CRITICAL !!")

#在实际项目中,捕获异常的时候,如果使用logging.error(e),只提示指定的logging信息,不会出现
#为什么会错的信息,所以要使用logging.exception(e)去记录。

try:
    3/0
except Exception as e:
    # logging.error(e)
    logging.exception(e)

输出日志如下:

2021-03-26 02:03:01 - root - DEBUG     - basiclogging : 16 line - This is  DEBUG !!
2021-03-26 02:03:01 - root - INFO      - basiclogging : 17 line - This is  INFO !!
2021-03-26 02:03:01 - root - WARNING   - basiclogging : 18 line - This is  WARNING !!
2021-03-26 02:03:01 - root - ERROR     - basiclogging : 19 line - This is  ERROR !!
2021-03-26 02:03:01 - root - CRITICAL  - basiclogging : 20 line - This is  CRITICAL !!

2021-03-26 03:16:47 - root - ERROR     - basiclogging : 27 line - division by zero
Traceback (most recent call last):
  File "E:/pycharm_project/examples-of-web-crawlers-master/logginng/basiclogging", line 24, in <module>
    3/0
ZeroDivisionError: division by zero


logging的高级应用

logging采用了模块化设计,主要包含四种组件:

Loggers:记录器,提供应用程序代码能直接使用的接口。(类似于笔,使用不同的笔记录不同的日志,例如一个项目里有很多功能模块,每个模块可以设置不同的Logger去记录,然后设置每只笔的不同格式)

Handlers:处理器,将记录器产生的日志发送到目的地。(输出到文件或者控制台或者邮件等,一个记录器可以对应多个处理器。)

Filters:过滤器,提供更好的粒度控制,决定哪些日志会被输出。

Formatters:格式化器,设置日志内容的组成结构和消息字段。

Loggers记录器

  1. 提供应用程序的调用接口
logger = logging.getLogger(__name__)
#logger是单例的,也就是__name__不变,获取到的logger都是同一个

2.决定日志记录的级别

logger.setLevel()

3.将日志内容传递到相关联的handlers中

logger.addHandler()
logger.removeHandler()

Handler处理器

将日志发送到不同的地方

#常见的处理器
StreamHandler #屏幕输出
FileHandler #文件记录
BaseRotatingHandler #标准的分割文件日志
RotatingFileHandler #按文件大小记录日志
TimeRotatingFileHandler #按时间记录日志

StreamHandler

标准输出分发器,也就是屏幕显示

sh = logging.StreamHandler(stream=None)

FileHandler

将日志保存到文件中

fh = logging.FileHandler(filename,mode='a',encoding=None,delay=False)

setFormatter()

设置当前Handler对象使用的消息格式

Formatters格式

formatter对象用来最终设置日志信息的顺序,结构和内容

ft = logging.Formatter.__init__(fmt=None,datafmt=None)

logging编程方式代码demo

# -*- coding:utf-8 -*-

import logging

#编程的方式记录日志

#记录器
logger1 = logging.getLogger("logger1")
logger1.setLevel(logging.DEBUG)
print(logger1)
print(type(logger1))

logger2 = logging.getLogger("logger2")
logger2.setLevel(logging.INFO)
print(logger2)
print(type(logger2))


#处理器
#1.标准输出
sh1 = logging.StreamHandler()

sh1.setLevel(logging.WARNING)

sh2 = logging.StreamHandler()


# 2.文件输出
# 没有设置输出级别,将用logger1的输出级别(并且输出级别在设置的时候级别不能比Logger的低!!!),设置了就使用自己的输出级别
fh1 = logging.FileHandler(filename="fh.log",mode='w')

fh2 = logging.FileHandler(filename="fh.log",mode='a')
fh2.setLevel(logging.WARNING)

# 格式器
fmt1 = logging.Formatter(fmt="%(asctime)s - %(levelname)-9s - %(filename)-8s : %(lineno)s line - %(message)s")

fmt2 = logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)-9s - %(filename)-8s : %(lineno)s line - %(message)s"
                        ,datefmt="%Y/%m/%d %H:%M:%S")

#给处理器设置格式
sh1.setFormatter(fmt1)
fh1.setFormatter(fmt2)
sh2.setFormatter(fmt2)
fh2.setFormatter(fmt1)

#记录器设置处理器
logger1.addHandler(sh1)
logger1.addHandler(fh1)
logger2.addHandler(sh2)
logger2.addHandler(fh2)

#打印日志代码
logger1.debug("This is  DEBUG of logger1 !!")
logger1.info("This is  INFO of logger1 !!")
logger1.warning("This is  WARNING of logger1 !!")
logger1.error("This is  ERROR of logger1 !!")
logger1.critical("This is  CRITICAL of logger1 !!")

logger2.debug("This is  DEBUG of logger2 !!")
logger2.info("This is  INFO of logger2 !!")
logger2.warning("This is  WARNING of logger2 !!")
logger2.error("This is  ERROR of logger2 !!")
logger2.critical("This is  CRITICAL of logger2 !!")

控制台输出:

2021-03-26 02:38:56,370 - WARNING   - advancelogging : 56 line - This is  WARNING of logger1 !!
2021-03-26 02:38:56,370 - ERROR     - advancelogging : 57 line - This is  ERROR of logger1 !!
2021-03-26 02:38:56,370 - CRITICAL  - advancelogging : 58 line - This is  CRITICAL of logger1 !!
2021/03/26 02:38:56 - logger2 - INFO      - advancelogging : 61 line - This is  INFO of logger2 !!
2021/03/26 02:38:56 - logger2 - WARNING   - advancelogging : 62 line - This is  WARNING of logger2 !!
2021/03/26 02:38:56 - logger2 - ERROR     - advancelogging : 63 line - This is  ERROR of logger2 !!
2021/03/26 02:38:56 - logger2 - CRITICAL  - advancelogging : 64 line - This is  CRITICAL of logger2 !!
<Logger logger1 (DEBUG)>
<class 'logging.Logger'>
<Logger logger2 (INFO)>
<class 'logging.Logger'>

文件fh.log输出:

2021/03/26 02:38:56 - logger1 - DEBUG     - advancelogging : 54 line - This is  DEBUG of logger1 !!
2021/03/26 02:38:56 - logger1 - INFO      - advancelogging : 55 line - This is  INFO of logger1 !!
2021/03/26 02:38:56 - logger1 - WARNING   - advancelogging : 56 line - This is  WARNING of logger1 !!
2021/03/26 02:38:56 - logger1 - ERROR     - advancelogging : 57 line - This is  ERROR of logger1 !!
2021/03/26 02:38:56 - logger1 - CRITICAL  - advancelogging : 58 line - This is  CRITICAL of logger1 !!
2021-03-26 02:38:56,370 - WARNING   - advancelogging : 62 line - This is  WARNING of logger2 !!
2021-03-26 02:38:56,370 - ERROR     - advancelogging : 63 line - This is  ERROR of logger2 !!
2021-03-26 02:38:56,370 - CRITICAL  - advancelogging : 64 line - This is  CRITICAL of logger2 !!

过滤器在代码里的应用

#定义一个过滤器
flt1 = logging.Filter("logger1") #名字就是logger记录器的名字

#关联过滤器
#1.logger关联过滤器
logger.addFilter(flt1)

#2.处理器关联过滤器
sh1.addFilter(flt1)
#fh1.addFilter(flt1)

#过滤器定义在记录器上的时候,不管你设置了任何关于这个过滤器的任何设置,都不起作用。
#过滤器定义在哪个处理器上,哪个处理器就不起作用,也就可以控制日志的输出,想要还是不想要。
#特别是在项目调试的时候,不想让日志记录到文件里去,直接在屏幕显示,就过滤到fh
#代码上线后,日志记录到文件,不用在屏幕显示,就过滤掉sh

文件配置logging编程

上述的方法都写的很死板,在小工程或者单个文件还可以,但是当项目大了,我们就必须使用配置文件的方式配置logging的信息,从而来灵活使用,主要有两种方式去配置,一种是conf文件,一种是字典文件。

import logging.config
logging.config.fileConfig("/path/to/configfile")

logging.config.dictConfig("/path/to/configfile")

yaml配置文件:

version: 1
#是否覆盖掉已经存在的loggers
disable_existing_loggers: True

formatters:

  tostrout:
    format: "%(asctime)s - %(name)s - %(levelname)-9s - %(filename)-8s : %(lineno)s line - %(message)s"
    datefmt: "%Y/%m/%d %H:%M:%S"

  tofile:
    format: "%(asctime)s - %(name)s - %(levelname)-9s - %(filename)-8s : %(lineno)s line - %(message)s"

handlers:
  sh:
    class: logging.StreamHandler
    level: WARNING
    formatter: tostrout
    stream: ext://sys.stdout

  fh:
    class: logging.handlers.TimedRotatingFileHandler
    filename: logconfdd.log
    interval: 1
    backupCount: 2
    when: D
    level: INFO
    formatter: tofile

loggers:
  logger1:
    level: DEBUG
    handlers: [sh]
    #是否往上级Logger传递,如果为yes的话,root选择了两个logger,这里的日志也会在两个logger的配置中输出,会重复。所以选No,自己记录自己的日志。
    propagate: no

  logger2:
    level: INFO
    handlers: [fh]
    propagate: no

root:
  level: DEBUG
  handlers: [sh,fh]
  propagate: no

调用代码:

# -*- coding:utf-8 -*-

import logging
import logging.config
import yaml

with open("logconf.yml", "r") as f:
    dict_conf = yaml.safe_load(f)

logging.config.dictConfig(dict_conf)
root = logging.getLogger()
logger1 = logging.getLogger('logger1')
logger2 = logging.getLogger('logger2')

#打印日志代码

root.debug("This is  DEBUG of root !!")
root.info("This is  INFO of root !!")
root.warning("This is  WARNING of root !!")
root.error("This is  ERROR of root !!")
root.critical("This is  CRITICAL of root !!")

logger1.debug("This is  DEBUG of logger1 !!")
logger1.info("This is  INFO of logger1 !!")
logger1.warning("This is  WARNING of logger1 !!")
logger1.error("This is  ERROR of logger1 !!")
logger1.critical("This is  CRITICAL of logger1 !!")

logger2.debug("This is  DEBUG of logger2 !!")
logger2.info("This is  INFO of logger2 !!")
logger2.warning("This is  WARNING of logger2 !!")
logger2.error("This is  ERROR of logger2 !!")
logger2.critical("This is  CRITICAL of logger2 !!")

标准输出:

021/03/27 12:18:58 - root - WARNING   - loggingofconfing : 19 line - This is  WARNING of root !!
2021/03/27 12:18:58 - root - ERROR     - loggingofconfing : 20 line - This is  ERROR of root !!
2021/03/27 12:18:58 - root - CRITICAL  - loggingofconfing : 21 line - This is  CRITICAL of root !!
2021/03/27 12:18:58 - logger1 - WARNING   - loggingofconfing : 25 line - This is  WARNING of logger1 !!
2021/03/27 12:18:58 - logger1 - ERROR     - loggingofconfing : 26 line - This is  ERROR of logger1 !!
2021/03/27 12:18:58 - logger1 - CRITICAL  - loggingofconfing : 27 line - This is  CRITICAL of logger1 !!

文件输出(logconfdd.log):

2021-03-27 12:18:58,301 - root - INFO      - loggingofconfing : 18 line - This is  INFO of root !!
2021-03-27 12:18:58,301 - root - WARNING   - loggingofconfing : 19 line - This is  WARNING of root !!
2021-03-27 12:18:58,301 - root - ERROR     - loggingofconfing : 20 line - This is  ERROR of root !!
2021-03-27 12:18:58,301 - root - CRITICAL  - loggingofconfing : 21 line - This is  CRITICAL of root !!
2021-03-27 12:18:58,301 - logger2 - INFO      - loggingofconfing : 30 line - This is  INFO of logger2 !!
2021-03-27 12:18:58,301 - logger2 - WARNING   - loggingofconfing : 31 line - This is  WARNING of logger2 !!
2021-03-27 12:18:58,301 - logger2 - ERROR     - loggingofconfing : 32 line - This is  ERROR of logger2 !!
2021-03-27 12:18:58,301 - logger2 - CRITICAL  - loggingofconfing : 33 line - This is  CRITICAL of logger2 !!

本文转自知呼

音乐播放器API

为WordPress网站添加一个音乐播放器,网上大部分的教程都是使用各种插件,有的插件还需要花钱买授权,用着不爽。本文用纯代码为网站添加音乐播放器,安全可靠,可以任意修改。

1、Aplayer

其实很简单,Github有很多大神开源了相关的内容。用着最舒服的,当属Aplayer了,可以去官网体验一下:

​ ​https://aplayer.js.org/​​

文档手册也很详细:

​ ​https://github.com/MoePlayer/APlayer​​

方法很简单,加载Aplayer的js和css,在想要展先的位置调用即可。Aplayer支持很多模式,上图展示的效果为吸底模式,就是在网页的页面最左下角展示。本文以吸底模式为例进行说明,如果想采用其它模式,可以根据文档手册修改代码。

2、Meting

如果只使用Aplayer,那么需要指定音乐的图片地址,mp4地址,歌词等,用着不是很方便,这时就可以使用Meting作为辅助:

​ ​https://github.com/metowolf/MetingJS​​

Meting为Aplayer提供了网易云音乐的API接口,只要获得网易云音乐歌单的ID,就可以自动加载歌单里所有的歌曲,直接调用,方便很多,当然除了网易云音乐的API,还有其他的接口。

3、开工

如果上述这些说了都不懂,没关系,跟着接下来的步骤走即可。

首先下载为各位打包好的js和css文件(下载地址)

解压后,会看到三个文件APlayer.min.css、APlayer.min.js、Meting.min.js,将两个js文件放到主题的js文件夹中,将css文件放到主题的css文件夹中。

在主题目录下的footer.php中,添加如下代码:


将代码中的wp-content/themes/construction-base改为你的WordPress主题的根目录。保存好后,刷新网页就可以看到播放器了。

1. <link rel="stylesheet" href="wp-content/themes/construction-
2. base/css/APlayer.min.css"><script src="wp-content/themes/construction-base/js/APlayer.min.js">
3. </script>
4. <div class="aplayer"
5. data-id="969649908"
6. data-fixed="true" data-
7. server="netease"
8. data-volume="0.8"
9. data-type="playlist">
10. </div>
11. <script src="wp-content/themes/construction-base/js/Meting.min.js">
12. </script>

但是这个播放器的歌单是我指定的歌单,如果想用自己的网易云音乐歌单,可以修改data-id的值,这个值的获取方法也很简单:

打开浏览器访问网页版的网易云音乐,地址栏的id参数就是对应的data-id值,自己创建一个歌单,想放什么音乐就放什么音乐。

PS:歌单更新歌曲实时更新

形象的理解Class的init、self 、@staticmethod、 @classmethod、@property

@staticmethod、@classmethod、@property这几个带@符号的语法是Python里的「语法糖」。这些语法中文常译作「装饰器」,对于没有任何编程经验的新手,这些都是经常难以理解的地方。再有就是类的self参数,新手通常留于表面的认识造成一些误会。

需要说明一下,本教程针对Python3。

要理解这几样,我们先要对Python类有一个认识,让我们先简单回顾一下「类」。

class 类

我们学习编程语言时,了解到语言有面向过程和面向对象之分,可以面向对象的语言一般都是「高级语言」,为什么高级?因为使用更容易,理解更容易,当然这一切都有代价,但那是另一件事情了,我们有机会在说。

「在Python中一切皆对象」这句话估计你也不是头一回听到了。Python中对象最直接的化身便是class语法,中文称之为「类」,让我们基于Python造「一批」Girlfriend(对象)吧。注意:这里我强调了「一批」这个词。

    # 定义类
    class Girlfriend:
        age = 22
        sex = '女'
        size = 'C'

    # 生产几个女朋友
    lili = Girlfriend()
    mimi = Girlfriend()

以上,我们做了这几件事情:

1)我们定义了一个Girlfriend类,用来一会生产女朋友当相好的对象;

2)基于我低俗的品味,女朋友要定义几个属性:年纪要22岁,罩杯要C,为了谨防意外,性别定义清楚是女,免得造出人妖;

3)基于我的承受能力,我实例化了lili和mimi两个女朋友,两个就够了。

通过上述例子,我们回顾了类与实例的关系:类就好比生产具体产品的「模具」或者「模子」,先做好模具,定义好将来生产的产品期望的样子,然后再用实例化语法通过模具生产一些产品。

我可以随时通过「实例 . 属性」这个语法知道女朋友的一些属性值,比如:

    lili.age  # 会得到22这个值
    mimi.age  # 也是22
    lili.size  # C杯

显然,我的两个女朋友年纪、罩杯都是一致的,因为我在定义模具(类)的时候就明确指定了这些属性。

class的init方法和self参数

什么都一样没意思,我希望至少每个妞的发型不要一样,并且我能指定,想要什么发型就造出来什么发型的女朋友。注意,我的需求有一个细节:女朋友的头发是在女朋友造出来后就已经有的。要想实现这个需求细节,意味着必须在生产女朋友的过程中就完成发型的构建。如果你还有印象,应该依稀还记得Python有一个内置的配合class用的方法init。

init的作用很容易理解,就是每次实例化一个类的时候,会默认自动调用执行这个方法,完成这个方法里定义的行为。这正好符合我们上面的需求,即在生产过程中,加一个发型工艺环节。现在我们改造一下模具和生产工艺:

    # 定义类
    class Girlfriend:
        age = 22
        sex = '女'
        size = 'C'
        def __init__(self, hair):
            self.hair = hair
    # 生产几个女朋友
    lili = Girlfriend('乌黑长发')
    mimi = Girlfriend('金黄短发')

我们来检验一下结果:

    lili.age  # 会得到22这个值
    mimi.age  # 也是22
    lili.hair  # 结果是'乌黑长发'
    mimi.hair  # 结果是'金黄短发'

非常好,两个妞发型已经不一样了。

我们已然知道init的作用:在实例化一个类的时候,会默认自动调用这个方法执行。这就是说,我们不必明显的告诉Python去执行这个方法,在运行下面这行代码时,Python已经悄悄自己执行这个方法了:

 lili = Girlfriend('乌黑长发')

需要明显的告诉程序去干什么事情,有一个针对的术语叫「显式」;相对应的,不需要明显的告诉程序去干事情,程序会按照既定的规则自己办好事情叫「隐式」。在以后的文章中,我们将会再看见这些词,请大家理解和记住意思。

显然,init方法被隐式调用了。我们只在定义class时定义了这个方法,但后续并没有显式的调用他,但他确实执行了,帮我的女朋友做好了发型。如前所述,隐式调用是程序按照「既定」的规则办事,既定的意思就是事先都定下来了怎么做,既然如此,就不能随意变更事先定下的细节。因此,init方法的在名称上很特别,init前后各有两根下划线,这种加下划线的方法名你现在只需记得其目的就是不要让你随便改动。所以,init的方法名不能有任何改动,少根下划线都不行。如果改动,则Python不会把改动后的方法当成init方法去隐式调用了。

让我们再来看看self这个参数。当我们在定义类时,显然无法确定后续会造多少个女朋友,也无法确定女朋友的名字,更无法确定她们的发型。所以我们不能再用定义年纪、性别、罩杯类似办法去定义发型,否则只会导致发型千篇一律。基于此,我们用了init方法,在生产过程中完成发型工艺,并在init方法里定义接收两个参数:self和hair,当我们生产女朋友时:

    # 生产几个女朋友
    lili = Girlfriend('乌黑长发')
    mimi = Girlfriend('金黄短发')

对于init工艺,其实我们是这样告诉程序的:

lili和mimi年纪、性别、罩杯都一样

lili的hair是’乌黑长发’

mimi的hair是’金黄短发’

对于「lili的hair是’乌黑长发’」这句话,本质上是在说明「谁的头发是什么发型?」。所以有两层意思:1)谁的头发?2)头发的发型是什么?,两层意思组合在一起才是有意义的,完整的意思表达,缺一不可。我们套上程序来看,显然程序刚好也要求传入两个参数。参数hair对应「头发的发型是什么?」再显然不过,那么剩下的self应该就是对应「谁的?」了。但是「谁的」在英文里不是「whose」吗?所以这样行不行:

    # 定义类
    class Girlfriend:
        age = 22
        sex = '女'
        size = 'C'

        def __init__(whose, hair):
            whose.hair = hair

    # 生产几个女朋友
    lili = Girlfriend('乌黑长发')
    mimi = Girlfriend('金黄短发')

即self参数更名为whose,行不行?行!

举上述的这个例子,是要明确的告诉你self这个参数名并不是Python的关键字,没有不能更名的限制,然而由于全世界程序员的习惯,约定俗成总是用self这个英文单词,所以你在Python代码中经常看见他。而在Python中,「谁的?」这个信息通常就是实例本身。实例就是造出来的女朋友,女朋友是「对象」,所以实例也是对象。既然self是用来接收实例,那么self最后也是对象,综上,形成了你以前也许已经看到或了解的几个概念:

1)self的作用是用来接收实例的,用来形成实例的命名空间(这涉及Python里「命名空间」的概念,你要有一个初步的认识,否则这句话看不懂。命名空间的概念以后会讲到)。

2)self类似于中文里的「你、我、他、她、它」的概念,在中文中「你、我、他、她、它」指代了具体的人或物,在Python中self则指代了具体的实例,实例就是「lili = Girlfriend(‘乌黑长发’)」这段代码等于号左边的那个具体的东西。指代在中文里也可以换种说法:「引用」。

3)既然self其实就是指代了具体的实例,那么self也将是对象。

现在,你应该对class的init和self的应用场景以及含义有了进一步的认识。

@staticmethod

让我们接着再对模具做一个微小的改进:

  # 定义类
    class Girlfriend:
        age = 22
        sex = '女'
        size = 'C'

        def __init__(self, hair):
            self.hair = hair

        def about_me(self):
            print('年芳{0},性别{1},身材{2}棒棒哒,一头{3}'.format(self.age, self.sex, self.size, self.hair))

    # 生产几个女朋友
    lili = Girlfriend('乌黑长发')
    mimi = Girlfriend('金黄短发')

上面微小的改进中,我们增加了一个名为「about_me()」方法,顾名思义,通过这个方法两位美女能来一小段自我介绍,我们尝试一下:

    lili.about_me()  # 结果:年芳22,性别女,身材C棒棒哒,一头乌黑长发
    mimi.about_me()  # 结果:年芳22,性别女,身材C棒棒哒,一头金黄短发

真好,我们不用再逐个查询美女的属性了。通过上面的这个例子,美女会通过自我介绍直接告诉我她的全部信息。format方法是一个在Python3中出现的基础方法,用来格式化字符串,任何Python3的基本教程里都会谈及这个方法,这里不再赘述。

在你用PyCharm或其他比较智能的编辑器写about_me这个方法时,编辑器会自动往括号里写一个self参数。我十分好奇去掉会怎么样,那就去掉试试:

    # 定义类
    class Girlfriend:
        age = 22
        sex = '女'
        size = 'C'

        def __init__(self, hair):
            self.hair = hair

        def about_me():
            print('年芳{0},性别{1},身材{2}棒棒哒,一头{3}'.format(age, sex, size, hair))

    # 生产几个女朋友
    lili = Girlfriend('乌黑长发')
    mimi = Girlfriend('金黄短发')

我们最新的模具里,将about_me方法中原来的参数self去掉了。而运行生产过程时,居然没有任何报错,可以运行。使用类似lili.age的属性查询也正常,接着我们试试lili.about_me(),程序报错:

Traceback (most recent call last):

File “<input>”, line 1, in <module>

TypeError: about_me() takes 0 positional arguments but 1 was given

报错信息很好理解,意思是about_me()方法不接受任何参数,但我们试图要传入一个。这就奇怪了,我们刚才还有之前运行about_me() 方法时,都没有传入任何参数。确实,我们的确没有在about_me() 的括号里显式的传入任何参数,但前面我们已经知道程序会隐式的干一些事情。没错,答案就是每次运行一个实例方法时,程序会自动将实例作为第一个参数传入到方法,这是程序在「硬肛」方法,而显然我们把方法原来的唯一的「入口」self参数给去掉了,没了「入口」,程序「硬肛」折了,所以报错埋怨我们了。再来看about_me方法里,我们在format里亦传入了age, sex, size, hair这四个变量。format方法是在about_me里运行的,基于我们已经了解的基本语法原则,显然这有问题。因为方法里运行的没有定义赋值的变量,都要靠方法本身传入的参数去一一对应,这便是方法参数存在的意义。所以我们恢复原样,先对about_me方法要求传入一个参数用来接应实例,先解决程序要「硬肛」的需求。文章之前讲self时,我们已经知道接收实例的参数约定俗称用self这个单词,所以我们也用这个。那又冒出一个问题:既然format要用到四个参数,为什么我们只传入了一个?答案也许你有了。没错,self传入的是一整个实例对象,或者说是一整个美女,所以美女的年龄,美女的性别,美女的头发都可以知晓了,只需要用我们熟悉的「美女.属性」就可以得到,而结合之前谈到的指代关系,那么顺理成章就是用「self.age」这样的方式就能获取到所需的属性了,因而我们只需要self一个参数即可。

对恢复原样后的模具,我们再加以改进,一样,「做一点微小的工作」:

# 定义类
    class Girlfriend:
        age = 22
        sex = '女'
        size = 'C'

        def __init__(self, hair):
            self.hair = hair

        def about_me(self):
            print('年芳{0},性别{1},身材{2}棒棒哒,一头{3}'.format(self.age, self.sex, self.size, self.hair))

        def bio():
            print('全心全意为主人服务!')

为了节省一点篇幅,我就不重复「生产几个女朋友」的过程了。让我们看上面的这个模具,我们又增加了一个名为bio的方法,顾名思义,就是让造出来的美女口头禅就是这个。有了前面的经验,我们可以猜到bio这个方法肯定会出问题。因为程序还会没节操的「硬肛」,所以肯定会报错。但bio这个方法本身就是打印一句口头禅,不需要任何参数参与,总不能脱了裤子放屁,为了能正常运行在不需要的情况下也开个入口(设置一个参数)吧?

非常好的是,Python早已贴心的为我们应对这种场景提供了手段,是时候请出本节的主角@staticmethod了,让我们稍加改进一下方法:

# 定义类
    class Girlfriend:
        age = 22
        sex = '女'
        size = 'C'

        def __init__(self, hair):
            self.hair = hair

        def about_me(self):
            print('年芳{0},性别{1},身材{2}棒棒哒,一头{3}'.format(self.age, self.sex, self.size, self.hair))

        @staticmethod    
        def bio():
            print('全心全意为主人服务!')

真是微小的工作,我们在bio方法上面一行增加了@staticmethod。先别管太多,让我们运行一下lili.bio()看看:

 lili.bio()  # 结果:全心全意为主人服务!

结果正常!现在再抱着钻研的心态,对bio方法增加一个self参数,我们再运行一把看看。结果报错:

Traceback (most recent call last):

File “<input>”, line 1, in <module>

TypeError: bio() missing 1 required positional argument: ‘self’

意思也很好理解,这回程序埋怨的是「我不想肛了你非要弄个入口让我肛」,即方法要求传入一个参数,但程序没有任何参数传入。

所以@staticmethod的应用场景和作用也显而易见了。一旦对一个方法应用@staticmethod,则在实例调用这个方法时,不会再试图传入实例本身。这样很容易联想到@staticmethod就是用在那些不需要实例直接参与的方法上。那什么样的类方法不需要实例直接参与呢?最常见的莫过于两种:一种是我们上面举的例子,即实例通用的方法。另一种是供实例方法调用的方法,比如类里有方法一,方法二,方法三,方法二和方法三需要借助方法一运算出一个结果,这时会在方法二、方法三的代码里调用方法一,方法一的作用只在接收两个参数然后返回一个运算结果,不需要传入实例,这时可以在方法一上写@staticmethod,而且这种例子你可以猜到方法一应该是算一个(仅供内部调用的)特殊方法,并不希望被外部随意调用,所以这种方法的函数名通常会在前面加一根下划线。

在谈到@staticmethod时举的例子其实相当无聊,上面说的方法一、二、三的例子其场景是很少发生的,所以@staticmethod的出镜率相对而言其实很低,很少被用到。

@classmethod

当我们理解@staticmethod的场景和作用后,理解@classmethod也将变得容易得多。因为某种程度上说,@classmethod和@staticmethod有共通的地方。还是让我们还是从改造模具开始:

 # 定义类
    class Girlfriend:
        age = 22
        sex = '女'
        size = 'C'

        def __init__(self, hair):
            self.hair = hair

        def about_me(self):
            print('年芳{0},性别{1},身材{2}棒棒哒,一头{3}'.format(self.age, self.sex, self.size, self.hair))

        @staticmethod
        def bio():
            print('全心全意为主人服务!')

        @classmethod
        def cls_name(cls):
            print('当前使用模具是{0}'.format(cls.__name__))

这次我将模具进行了一个小的改进,添加了一个名为cls_name的方法。因为我后续打算再做一些其他各种friend的模具(类),为了避免我弄混了不知道现在用的是哪个模具在生产,我加了这个方法。这个方法的作用是可以让程序显示出当前模具的名称,也就是代码class的名称。让我们看看如何使用:

    # 生产几个女朋友
    lili = Girlfriend('乌黑长发')
    mimi = Girlfriend('金黄短发')     

    lili.cls_name()  # 显示:当前使用模具是Girlfriend
    Girlfriend.cls_name()  # 显示:当前使用模具是Girlfriend

从刚才的例子我们可以发现,用了两种不同的方式得到了同一个结果,有关于这点我们稍后再谈,先让我们回到开头,从开始说起。

当我们学习Python基础的时候,几乎所有的图书教程都会告诉我们一个知识点:Python为了内置的对象提供了一些内置的方法和属性,目的是为了更方便使用。简而言之,Python为class这个对象内置了一些方法和属性供我们直接使用,比如__name__和__dict__等等,我们已经谈到过是内置的名称前后就会都有双下划线,这里也是这样。在上面最新的模具中,我们应用了class的一个内置的name属性,而这个name记载了class的名称,所以用类似「class.__name__」这样的语法就能获得这个名称。

我们已经知道,如果对class里的方法(函数)不用@staticmethod装饰,调用的时候就会被程序硬塞一个实例作为第一个参数传入,用@staticmethod则调用的时候不隐式的传东西了,而现在我们的需求是查看显示模具本身的一个属性,跟实例无关,所以可以想到必须让方法能接收模具(类)而不是实例作为一个参数传入。到目前为止,我们学会了要么隐式传实例,要么啥也不传的手段,那么这种需要隐式传模具(类)可怎么办?Python早已考虑到了这种场景并准备了对应的装饰器供我们使用,没错,就是@classmethod。

可想而知,@classmethod就是让实例在调用被@classmethod装饰的方法时,隐式的传入模具(类)本身。类似self,由于约定俗称和class是一个Python关键字因而不能瞎用的原因,通常用「cls」这个英文作为指代类的参数名,这是Python里cls参数的由来。

@classmethod 的出场率也不高,但比@staticmethod用途更大,因为@classmethod 可以作为一种媒介,让我们更方便的操纵Python为class内置的一些属性和方法。那么,@classmethod 的应用场景也就不言而喻了。

@property

@property 是本文最后要谈及的一款装饰器了,让我们还是从改造模具开始:

# 定义类
    class Girlfriend:
        age = 22
        sex = '女'

        def __init__(self, hair):
            self.hair = hair
            self.bra = 'C'


        def about_me(self):
            print('年芳{0},性别{1},身材{2}棒棒哒,一头{3}'.format(self.age, self.sex, self.size, self.hair))

        @staticmethod
        def bio():
            print('全心全意为主人服务!')

        @classmethod
        def cls_name(cls):
            print('当前使用模具是{0}'.format(cls.__name__))

        @property
        def size(self):
            return self.bra

在刚才定义的最新的模具中,我做了这样几件事情:先将原来的属性size变成了一个同名方法,然后对其应用了@property装饰器,这些改变了什么?

在谈及@classmethod和之前的装饰器时,我们举例的模具在生产出女朋友之后,实际上都可以这么做:

    # 生产几个女朋友
    lili = Girlfriend('乌黑长发')
    mimi = Girlfriend('金黄短发')     

    lili.size = 'D'  # 增大女朋友的罩杯
    lili.size  # 显示:D

也就是如果对女朋友的属性(胸)不满意,可以再调整。刚才的例子中我们对lili进行了「丰胸」,将lili原来的size由C升到了D。丰胸的过程是很简单的属性赋值语法,不用太多解释。但让我们用最新的定义了@property 装饰器的模具时,「丰胸」就失败了。在最新的模具中,我们可以使用「lili.size」得到C这个结果,但是无法再运行「lili.size = ‘D’」,一旦试图「丰胸」程序就会报错,报错信息大意是:不能写入size的属性值,原始报错信息如下:

Traceback (most recent call last):

File “<input>”, line 1, in <module>

AttributeError: can’t set attribute

那么过程和结果以及区别都看到了,但要理解@property还得从定义最新的模具开始说起。由于比较喜欢纯天然的妹纸,所以我对模具进行了改进,使得一旦生产出女朋友,则不能随意再去改变女朋友的size,并且只能是C。理解成对于代码的修改需求,就是让实例的size的值不能写入新的,但是可以读旧的,并且size仍旧要是一个属性。

Python也为我们考虑到了这种需求场景,并提供了@property 这个装饰器来满足这种需求,于是乎就有了本节最开始的新模具的改变。首先,我们注意到size由属性变成了一个方法,而基于我们Python的基础知识,对于Python方法的调用语法,都是类似「size()」这样方法(函数)名称后面加括号的形式,但就在刚才我们对新模具仍旧使用了「lili.size」并得到了结果,反而「lili.size()」无法运行(程序会报错),于是我们发现了@property的第一个作用:

@property 会将被装饰的类方法模拟成一个类属性

将原来是类的一个属性变成了这个类的方法,并且还行不更名坐不改姓,然后又将这个方法模拟成类属性一样可以调用,感觉这真是绕了好大一个圈。但我们并没有回到原点,因为既然是方法,那么就可以在方法里定义很多赋值语句无法完成的行为。但是我们举一反三之下紧接着有了一个疑问:不错,@property 把方法装饰成属性,就可以实现方法能做的,而原本的属性不能做的事情;那原来属性能做的事情,@property 装饰的方法是否也相应的不行了呢?

我们基础知识告诉我们,对于一个对象的属性,起码能做增、删、改、查这几件基本的事情,在前面最后的例子中,我们通过「lili.size」得到了女朋友的size属性,这是查询,但我们试图修改时,程序则报错了。看起来@property 将方法变成的属性并没有完整实现基本属性该有的几样功能,真是这样吗?让我们再继续改造一下模具,这是最后的一次改造:

    # 定义类
    class Girlfriend:
        age = 22
        sex = '女'

        def __init__(self, hair):
            self.hair = hair
            self.bra = 'C'

        def about_me(self):
            print('年芳{0},性别{1},身材{2}棒棒哒,一头{3}'.format(self.age, self.sex, self.size, self.hair))

        @staticmethod
        def bio():
            print('全心全意为主人服务!')

        @classmethod
        def cls_name(cls):
            print('当前使用模具是{0}'.format(cls.__name__))

        @property
        def size(self):
            return self.bra

        @size.setter
        def size(self, size):
            self.bra = size
            print('丰胸手术完成!')

在最后的一次改造中,我们增加了一个名为size()的方法,并对之使用了「@size.setter」这个装饰器。现在我们可以很容易的理解最新的size()方法干了些什么:先接收一个参数,并将这个参数赋给bra,这毫无疑问就是在丰胸。然后如果顺利,用print打印一个结果。在学习Python的基础时,我们已经知道同一命名空间下有两个重名方法是有问题的,会造成最后一个同名方法覆盖掉先前的那个。这里有两个size()方法让我们有些疑惑,@size.setter 又是什么呢?还是先试试:

    # 生产几个女朋友
    lili = Girlfriend('乌黑长发')
    mimi = Girlfriend('金黄短发')     

    lili.size  # 显示:C
    lili.size = 'D'  # 增大女朋友的罩杯,显示:丰胸手术完成!
    lili.size  # 显示:D

看起来丰胸手术已经可以顺利实施,与之前不同,程序不会再报错。可以联想到这一切一定是最新添加的那一小段代码的作用。

原来,Python不仅光提供@property 装饰器供你将一个方法变成属性调用,还提供了一系列配套的工具(setter、getter、deleter),这些正好对应增、删、改、查,这些工具让方法去更加完美的模拟属性的行为。这其中,当你用@property去装饰一个方法名为A的方法时,你可以再用@A.setter去装饰另一个同名方法。顾名思义,setter有写入的含义,那么@A.setter定义的就是方法A赋值时的行为。在上述的代码中,我们就定义了作为属性使用的size方法,在赋值时可以打印一个语句,提示「丰胸手术完成」。其余的几个工具就不多说,从字面意思就能理解做的事情,而使用方法都是一样的。所以,@property 和之后的工具使用,本质上都是针对@property装饰的方法做更多的定制,所以像上面的那样重名方法才不会有问题。

现在,你应该大致也理解了@property能干的事情,其作用和场景也很明显了。让我们在本文最后再看一段来自《Flask Web开发:基于Python的Web应用开发实战》P78,示例8-1的代码:

     class User(db.Model):
         password_hash = db.Column(db.String(128))

         @property
         def password(self):
             raise AttributeError('password is not a readable attribute')

         @password.setter
         def password(self, password):
             self.password_hash = generate_password_hash(password)

        def verify_password(self, password):
             return check_password_hash(self.password_hash, password)

这段代码中,作者使用了@property把原本是User的password方法变成属性一样可以调用。由于password是加密存储到数据库中的,于是需要一个方法完成加密过程,这正好可以由工具setter去干,于是乎我们看到@password.setter又装饰了一个password方法,并且这个方法我们能够看出是调用generate_password_hash()把原来的密码进行了加密,并将加密后的内容传给了password_hash这个属性,这个属性就是数据库中的用户密码字段。

Linux 常用命令总结

网上找了不少 Linux 方面的资料,令我想不通的是,这么简单的东西,为什么可以写的这么复杂。。我本地装了一个 Ubuntu 虚拟机,云上装了一个 CentOS,用着用着就熟练了

Linux 的命令格式

[root@localhost~]#

  • root: 当前登录用户
  • localhost: 主机名
  • ~ : 当前所在目录
  • : 超级用户的提示符
  • $ : 普通用户的提示符
  • / 开头的是绝对路径
  • . 是当前目录,统计目录可以省略 ./
  • .. 当前目录的父目录
  • cd 更改当前目录到家目录
  • cd – 更改当前目录到先前的目录
  • 每个用户都有一个家目录,默认在 /home/用户名
  • root 用户的家目录是 /root

文件命令

常规

  • 命令格式 [选项] [参数]
  • tap 自动补全命令
  • 打印当前工作目录: pwd
  • 查看文件类型 file
  • 查询目录中内容 ls [选项] [文件或目录]
    • ls -l 显示所有文件
    • ls -lh 人性化显示所有文件
    • ls -a 显示所有文件 以.开头的都是隐藏文件
  • cd (change direcotry):
    • 更改工作目录到先前的工作目录
  • cp (copy):
    • cp a.txt b.txt
    • 复制 a.txt 并把新文件命名为 b.txt
    • 复制目录要加上 -r 参数 cp -r a b
  • mkdir (创建一个目录)
    • -p 可以一次性创建多层目录
    • mkdir -p a/b/c
  • rmdir (remove empty directories) 删除一个空目录
  • rm
    • 这个命令直接删除东西,很危险,一般不要用
    • 删除文件或者目录
    • -f 强制删除
    • -r 用来删除目录
  • mv (用来移动文件\文件夹 或者改名)
    • mv a.txt b.txt 此为改名
    • mv b.txt ../
    • mv b.txt ../gua.txt
    • 可以用 mv xx /tmp 的方式来将文件放入临时文件夹(/tmp 是操作系统提供的临时文件夹,重启会删除里面的所有文件,可以替代 rm 删除命令)
  • cat 显示文件内容
  • tac 反过来显示文件内容
  • nl 显示内容并附带行号
  • more(分屏分批看文件内容)
  • less(比 more 好用,可以前后退看文件)
  • head,tail (显示文件的前,后10行)
  • head 和 tail 有一个 -n 参数 head-n 20 a.txt 显示20行
  • touch a.txt 如果文件存在就更新修改时间,不存在就创建文件

文件解压缩

  • tar -cf name.tar name 压缩
  • tar -tf name.tar 显示压缩的文件 tar -tvf name.tar 显示详细信息(都是只显示,不解压。v表示显示详细信息)verbose(详细)
  • tar -xf name.tar 解压(抽取)
  • tar -czvf name.tar.gz name (加了一个 z,以gz格式压缩)
  • tar -tzvf name.tar.gz (加了一个 z,以gz格式查看)
  • tar -xzvf name.tar.gz (加了一个 z,以gz格式抽取)
  • tar -cvf|tvf|xvf tar -czvf|tzvf|xzvf 记住这一行就行了
  • compression(create)listextract(提取)

文件编辑(vim)

  • touch 可以新建文件,vim 也可以直接新建文件
  • 打开后,写入内容,按 i 键(insert)
  • 退出 按 esc :wq 保存退出
  • 移到第一行 gg;移到最后一行 G
  • 删除一整行 dd;恢复 u。前提是从 insert 模式中切换出来
  • 复制一整行 yy;粘贴 p
  • 文件的权限 r-w-x 4-2-1 读-写-可执行

权限操作

  • sudo 用管理员账户执行程序(安装程序或修改一些系统配置都需要管理员权限)
  • su (switch user) 切换用户 su root
  • ll 全部显示

信息查找

  • file 显示文件类型
  • uname 显示操作系统名字或者其它信息 -r(内核) -a(全部)
  • whoami 查看当前身份
  • find . -name “*.py” 查找所有以 .py 结尾的文件

符号

  • ~ 家目录快捷方式
  • > 覆盖式重定向
  • >> 追加重定向

网络

  • ifconfig(查看 ip 一般使用这个)
  • ip addr(也可查看 ip)

SSH

  • ssh,安全外壳协议,建立在应用层,专为远程登录会话和其它网络服务提供安全性的协议
  • 以下内容在 CentOs 中操作,Ubuntu 中相关文件位置自行 Google

服务器安装 SSH 服务

  • 安装 yum install openssh-server
  • 启动 service sshd start
  • 设置开机运行 chkconfig sshd on
  • 服务器版本的操作系统一般都已经装好。桌面版的可能没有

客户端安装 SSH 工具

  • windows下很多工具支持,Xshell,Putty,secureCRT
  • Linux平台安装 yum install openssh-clients
  • 客户端连接SSH服务 ssh root@公网ip 实际与新建会话是一样的+
  • exit 回到本地

SSH config(配置)

  • 以下讲的都是linux客户端的config),客户端的config才能对自己生效
  • config 方便管理员批量管理多个 ssh
  • config 存放在 ~/.ssh/config(.ssh目录下没有的话就touch config新建一个)
  • SSH config 语法关键字:
    • Host 别名
    • HostName 主机名
    • Port 端口(ssh服务的默认端口为22)
    • User 用户名
    • IdentityFile 密钥文件的路径
    • 配置了config之后,连接服务器直接 ssh 别名 “` host “tencent” HostName xxx.xx.x.xx User root Port 22

### SSH安全免密码登录:ssh  key

+ 普通登录:`ssh username@xxx.xx.x.xx`
+ ssh key 使用非对称加密方式生成公钥和私钥
+ 私钥存放在本地 `~/.ssh` 目录
+ 公钥可以对外公开,放在服务器的 `~/.ssh/authorized_keys` 
+ Linux 平台生成 ssh key:
	+ 客户端cd ~/.ssh/
	+ 客户端使用 ssh-keygen -t rsa 命令亦可 ssh-keygen -t dsa
	+ 服务端在 authorized_keys 文件中写入客户端生成的公钥
	+ 客户端将密钥加载到ssh服务中:`ssh-add ~/.ssh/私钥文件`
+ windows 平台生成 ssh key:
	+ 直接在 xshell 工具栏中生成
	+ 在 ~/.ssh 目录下建立 authorized_keys 文件,编辑放入公钥
+ 如果是新用户,则还要修改一下权限

cd /home/username/ chmod 700 .ssh cd /home/username/.ssh chmod 600 authorized_keys


### SSH安全端口

+ 避免服务器的远程连接端口被别人知道
+ 改变 SSH 服务端口:修改 `/etc/ssh/sshd_config`(里面也可以添加连接的端口,这是对服务器端的操作)

## 软硬件的安装、查看

### 软件操作(CentOs)

+ 软件包管理器: yum
+ 安装软件: `yum intall xxx`
+ 卸载软件: `yum remove xxx`
+ 搜索软件: `yum serach xxx`
+ 清理缓存: `yum clean packages`
+ 列出已安装: `yum list`
+ 软件包信息: `yum info xxx`

### 软件操作(Ubuntu)

+ `sudo apt-get install name` `sudo apt install`
+ 备份Ubuntu默认源地址 `sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup`
+ 更新源服务器列表,即 `/etc/apt/sources.list`
+ `sudo apt-get update` sudo apt install 
+ `sudo apt-get remove name` 删除包
+ `sudo apt-cache search package` 搜索软件包
+ `sudo apt-cache show package`  获取包的相关信息,如说明、大小、版本等

### 硬件操作

+ 内存: `free -m`
+ 硬盘:`df -h`
+ 负载: `w/top`
+ cpu: `cat /proc/cpuinfo`

## 其它

### kill

+ `sudo netstat -tnlp` 查看所有进程端口占用情况
+ `sudo kill xxpidxx` 杀死进程

### 防火墙

+ `yum install firewalld`				安装
+ `service firewalld start`			启动
+ `service firewalld status`			检查状态
+ `service firewalld stop/disable` 	关闭/禁用防火墙

+ 可能已默认安装,用 `yum list |grep firewall` 检查

+ `firewall-cmd --version` 	查看版本
+ `firewall-cmd --help`		查看帮助文档
+ `firewall-cmd --state`		查看运行状态

> 在防火墙有区域和端口的区分

+ `firewall-cmd --get-zones` 查看区域
+ `firewall-cmd --get-default-zone` 默认区域
+ `firewall-cmd --list-all-zone` 列出每一个区域的配置信息
+ `firewall-cmd --query-service=ssh` 查询服务
+ `firewall-cmd --remove-serivce=ssh` 删除服务
+ `firewall-cmd --add-service=ssh` 添加服务
+ `firewall-cmd --list-services` 列出所有服务
+ `firewall-cmd --query-port=22/tcp` 查询端口是否开启
+ `firewall-cmd --add-port=22/tcp` 开启端口
+ `firewall-cmd --remove-port=22/tcp` 关闭端口
+ `firewall-cmd --list-ports` 查看所有打开的端口

+ 端口和服务的概念
+ 删除服务后开启端口依然可以(已实测)
+ 安装了服务都会开启默认端口,不用特意操作。测试时关闭防火墙

### 提权 、上传、下载

+ 提权:sudo
+ root 下通过 visudo 命令将用户加入到文件中

Allows people in group wheel to run all commands

%wheel ALL=(ALL) ALL %rong ALL=(ALL) ALL “`

  • 一般不用 root 进行操作。都是将用户加入文件,用用户操作,特殊命令用 sudo 提权
  • 文件下载
    • wget wget http://www.baidu.com
    • curl curl -o filename http://www.baidu.com 将下载下来的文件命名为 filename
  • 文件上传
    • scp上传文件格式为 scp 文件名 用户@公网地址:路径 例:scp a.txt rong@192.168.x.xxx:/tmp/ (scp是 secure copy的缩写, scp是linux系统下基于ssh登陆进行安全的远程文件拷贝命令)
    • 下载 scp rong@192.168.x.xxx:/tmp/a.txt ./ 格式为:scp 用户名@公网地址:服务器上文件地址 文件要保存的本机地址以上是 linux 下的操作,windows 下见下
    • 服务器上安装软件 yum install lrzsz(在linux里可代替ftp上传和下载)
    • 服务器端 rz 上传命令(服务器端 receive)
    • 服务器端 sz filename 从服务器端下载文件(服务器端 send)
    • ZMODEM 协议

ORM,操作数据库

一 ORM是什么?为何要有ORM?

​ 我们在使用Django框架开发web应用的过程中,不可避免地会涉及到数据的管理操作(增、删、改、查),而一旦谈到数据的管理操作,就需要用到数据库管理软件,例如mysql、oracle、Microsoft SQL Server等。

如果应用程序需要操作数据(比如将用户注册信息永久存放起来),那么我们需要在应用程序中编写原生sql语句,然后使用pymysql模块远程操作mysql数据库

针对应用程序的数据操作,直接编写原生sql语句会存在两方面的问题,严重影响开发效率,如下

#1. sql语句的执行效率问题:应用开发程序员需要耗费一大部分精力去优化sql语句
#2. 数据库迁移问题:针对mysql开发的sql语句无法直接应用到oracle数据库上,一旦需要迁移数据库,便需要考虑跨平台问题

为了解决上述问题,django引入了ORM的概念,ORM全称Object Relational Mapping,即对象关系映射,是在pymysq之上又进行了一层封装,对于数据的操作,我们无需再去编写原生sql,取代代之的是基于面向对象的思想去编写类、对象、调用相应的方法等,ORM会将其转换/映射成原生SQL然后交给pymysql执行

​ 有了ORM框架,开发人员既不用再去考虑原生SQL的优化问题,也不用考虑数据库迁移的问题,ORM都帮我们做了优化且支持多种数据库,这极大地提升了我们的开发效率

如何使用:
1、数据来源于数据库的表,而ORM的模型类对应数据库表,所以若我们想操作数据,必须先创建模型。
2、再注册到应用
3、再配置数据库连接
以下是一个 mysql的数据库连接配置,包含用的pymysql库,用户名zwq,密码5dc580,服务器zxz.xtaa.cn,数据库名douy
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql+pymysql://zwq:5dc580@zxz.xtaa.cn/douyin?charset=utf8"







Flask是一个轻量级的Web框架,其中集成的ORM工具SQLAlchemy,使得在Python中进行数据库操作变得更加简单。在Flask中,使用ORM可以通过使用db.session对象访问数据库,将操作转换为Python对象的方法。 在ORM中的一个核心概念是关系,即对象之间的关系。在Flask中,使用db.relationship来创建关系。本文将通过几个方面详细介绍db.relationship的使用。
一、创建一对多关系
在ORM中,一对多关系是指一个对象(如一个用户)可以有多个子对象(如多个订单)。在Flask中,可以通过db.relationship和backref参数来定义一对多关系。在这种关系中,一个对象(如User)可以有多个子对象(如Order),而一个子对象(如Order)只能归属于一个对象(如User)。

 class User(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(50))
        orders = db.relationship("Order", backref="user")

    class Order(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        total_price = db.Column(db.Float)
        user_id = db.Column(db.Integer, db.ForeignKey("user.id"))

    user = User.query.first()
    orders = user.orders
    for order in orders:
        print(order.total_price)

在这个示例中,User和Order模型之间建立了一对多关系。User模型中通过db.relationship定义了一个名为orders的属性。在Order模型中通过user_id列的外键来关联User模型。同时,backref参数定义了子对象如何访问父对象。在这个示例中,我们可以通过user.orders访问该用户的所有订单,并通过for循环遍历订单对象,打印其总价。

二、创建多对多关系

多对多关系是指两个对象之间相互关联,一个对象可以有多个子对象,同时一个子对象也可以归属于多个对象。在Flask中,可以通过db.relationship和secondary参数来定义多对多关系。在这种关系中需要创建一个关联表,用来保存多对多的关系。

association_table = db.Table(
        'association', db.Model.metadata,
        db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
        db.Column('project_id', db.Integer, db.ForeignKey('project.id')))

    class User(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(50))
        projects = db.relationship("Project", secondary=association_table, backref="users")

    class Project(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(50))

    user = User.query.first()
    projects = user.projects
    for project in projects:
        print(project.name)

在这个示例中,User和Project模型之间建立了多对多关系。User模型中定义了名为projects的属性。在Project模型中同样定义了名为users的属性来反向访问父对象。关联表association_table通过db.Table来创建。在User模型中通过secondary参数来指定关联表。在示例中,我们可以通过user.projects访问该用户所参与的所有项目,并通过for循环遍历打印每个项目的名字。

三、定义关系属性的限制

在ORM中,还可以为关系属性定义一些限制,如elenment_order、primaryjoin、secondaryjoin等等。这些限制可以帮助我们更好地控制我们的关系。例如,为了控制查询结果,我们可以添加primaryjoin参数

四、使用back_populates来反向引用一对多关系

在Flask中,我们可以使用back_populates来反向引用一对多关系。这种方式可以帮助我们更好地控制关系,并避免循环引用

推荐个alist网盘聚合神器

https://github.com/alist-org/alist

有windows和liunx版本,可加入多种网盘,还可配合aria2等来离线下载。

如果要让别人查看的话在后台里把来宾帐户启用,再把无需密码访问选上。

安装后可以更改去掉相关特征信息

在设置-全局-自定义头部

<script src="https://polyfill.io/v3/polyfill.min.js?features=String.prototype.replaceAll"></script>
<script src="https://polyfill.io/v3/polyfill.min.js?features=String.prototype.replaceAll"></script>
<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
<style>
.footer {
    display: none!important;
}
body:before {
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    opacity: .3;
    z-index: -1;
    display: block;
    position: fixed;
    background-repeat: no-repeat;
    background-size: cover;
    background-position: center center;
    background-attachment: fixed;
    content: '';
    background-image: url(https://oss.iymark.com/2022/09/vback.jpg);
}
.hope-c-PJLV-ikSuVsl-css {
  background: none!important;
}
.markdown-body a {
  color: #000!important;
}
.hope-ui-dark .markdown-body a {
  color: #fff!important;
}
.copyright .link {
    padding: 4px;
    background: #26517c;
    border-radius: 0 8px 8px 0;
}
.copyright .name {
    padding: 4px;
    background: #fff;
    border-radius: 8px 0 0 8px;
    color:#000;
}
.copyright {
    padding: 50px;
    background: #2d3236!important;
}
.copyright a {
  color: #fff;
}
br.phone, br.pad{
    display:none;
}
@media (max-width: 891px){
br.pad {
    display:block;
}
}
@media (max-width: 561px){
br.phone {
    display:block;
}
}
.hope-c-PJLV-ieESZju-css {
    display: none;
}
img.hope-c-PJLV-ibwASZs-css {
    width: auto;
}
.hope-ui-dark .copyright .name {
    background: #000;
}
.runtime {
    margin-top: 20px;
    color: #fff;
    text-align: right!important;
}
.about, .state {
    width: min(99%, 980px);
    text-align: center;
    padding-inline: 2%;
}
.state {
    margin-top: 20px;
    color:#fff;
}
</style>

在自己定义内容里

<div class="copyright" align="center"><div class="about"><p>
<span class="name">© 2020-2023</span><span class="link"><a href="https://www.xtaa.cn">奇言</a></span><br class="phone"><br class="phone">
</p>
<div class="runtime">
<span id="runtime_span"></span>
<script type="text/javascript">
    function show_runtime() {
        window.setTimeout("show_runtime()", 1000);
        X = new Date("2/24/2023 00:00:00");
        Y = new Date();
        T = (Y.getTime() - X.getTime());
        M = 24 * 60 * 60 * 1000;
        a = T / M;
        A = Math.floor(a);
        b = (a - A) * 24;
        B = Math.floor(b);
        c = (b - B) * 60;
        C = Math.floor((b - B) * 60);
        D = Math.floor((c - C) * 60);
        runtime_span.innerHTML = "<span class=\"name\">稳定运行" + A + "天</span><span class=\"link\">" + B + "时" + C + "分" + D + "秒</span>"
    }
    show_runtime();
</script>
</div>
</div>
<div class="state"><p>免责声明:本站为个人网盘,网盘所发布的一切影视、源代码、注册信息及软件等资源仅限用于学习和研究目的</p></div>
</div>

微软azure免费云服务器实操

进azure,

注册,绑卡(可用双币信用卡),

里面可以送200刀有效期1个月,第一个月内改成即用即付模式就liunx和windows各一台1年免费,如果1个月内没改就会直接帐户失效

还有一些其它始终免费的服务,比如翻译每月有200万的免费额度,个人完全够用。

创建虚拟机资源,注意选可以免费用的镜像和免费用的64G硬盘,在可用性选项中要选没有基础结构冗余的,大小选择B1s这种1核1G的,在网络中新建公网IP,在里面SKU选基本,动态IP,要不会产生费用,但弹性动态公网IP会在每次关机再启动后变动,直接重启是不变的,所以要固定IP的就会要另想办法。

选的ubuntu在建资源时一般会建一个azureuser用户,并下载一个ppm私钥,如连接不上可以在里面修改密码然后不用私钥,直接用密码连接,

我一般在创建时就直接选择用帐号密码,比如自己建一个帐号ppp,那你建好你的ubuntu机器后就直接用这个登录,如果要root权限,可以sudo passwd root先把root的密码设置下,然后su root就可以用了。

实测韩国中心区ping值88

要注意还有流量限制,每月只有15G,但好像有人说实际是100G,你可以在之前创建资源时建的《订阅》右下角有个热门免费服务,再在最下面的查看所有免费服务中查看免费资源的使用情况和到期时间等信息!

Flask实现频繁请求限制

要实现频繁请求限制,可以使用Flask插件Flask-Limiter来实现。以下是对代码的修改:

1. 首先,安装Flask-Limiter插件:
```
pip install flask-limiter
```

2. 在代码中导入Flask-Limiter和限制器的配置:
```python
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

# 在app之前配置限制器
limiter = Limiter(app, key_func=get_remote_address)
```

3. 在`get_file_url`函数上添加限制器的装饰器,并设置限制规则:
```python
@app.route('/api/fs/get')
@limiter.limit("10/minute")  # 设置每分钟最多允许10次请求
def get_file_url():
    ...
```

这样一来,每个IP地址每分钟最多只能发送10个请求。可以根据实际需要调整限制规则的参数。

注意:还可以根据需要设置其他限制规则,比如限制特定路径或特定用户的请求频率。详细文档可以参考Flask-Limiter的官方文档

Vue和Flask实现前后端分离

后端采用Flask提供几个Restfull风格的API,前端放弃惯用的flasky常用的Jinja模板引擎,采用灵活的Vue.js框架,顺便实践一把前后端分离。既然前后端分离,那么就需要在开发环境独立创建两个项目。下面逐一介绍。

安装node.js

安装vue,安装webpack,安装vue-cli,创建工程

vue是以组件为单位组织成复杂的页面。我们直接在componets下的HelloWorld.vue上进行代码调整和编写。template部分主要包含一个select元素,value对应后端API,触发button会向选择的后端API发送请求。script部分methods部分实现对元素事件的响应,通过axios与后端服务器进行通信,分别采用get和post方法。具体代码如下图:

<template>
  <div class="hello">
    <button @click="test">测试</button>
    <select v-model="selected" name="url-option">
      <option value="">选择一个Url</option>
      <option value="/api/speech/event_extraction">思必驰警情信息抽取</option>
      <option value="/api/speech/addr_fix">思必驰地址理解</option>
      <option value="/api/gaode/get_poi">高德关键字搜索</option>
    </select>
    <input v-model="input_query" style="width: 400px">
    <button @click="sendRequest">发送请求</button>
    <br></br>
    <textarea style="width: 600px; height: 300px">{{ resp }}</textarea>
  </div>
</template>

<script>
import axios from 'axios'
// 后端服务器地址
axios.defaults.baseURL = 'http://10.67.*.*:5000';
export default {
  name: 'HelloWorld',
  data () {
    return {
      selected: '',
      input_query: '',
      resp: ''
    }
  },
  methods: {
    test () {
      axios
        .get('/api/hello')
        .then(response => {
          console.log(response.data.name)
          this.resp = response.data.name
        })
        .cache(error => {
          console.error(error)
        })
    },
    sendRequest () {
      console.log(this.selected)
      axios
        .post(this.selected, {query: this.input_query})
        .then(response => {
          console.log(response)
          this.resp = response.data
        }).catch(error => {
          console.error(error)
        })
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

在vue中还用

v-for 来在前端页面中做for循环,v-on:click=点击,v-if=表示IF语句

后端

打开pycharm,新建一个flask项目

在app.py里编写几个Restfull 风格的API,用于响应前端请求

from flask import Flask, jsonify, request
from flask_cors import CORS
import requests
import json

app = Flask(__name__)
# 实现跨域访问
cors = CORS(app, resources={r"*": {"origins": "*"}})

# get请求
@app.route('/api/hello')
def hello_world():
    content = {
            "name": "网站",
            "num": 3,
            "sites": [{"name": "Google", "info": ["Android", "Google 搜索", "Google 翻译"]},
                      {"name": "Runoob", "info": ["菜鸟教程", "菜鸟工具", "菜鸟微信"]},
                      {"name": "Taobao", "info": ["淘宝", "网购"]}],
            }

    return jsonify(content)

# post请求
@app.route('/api/gaode/get_poi', methods=['POST'])
def get_poi():
    json_data = request.get_json()
    url_prefix = "***"   
    url = url_prefix + '&keywords=' + json_data['query']
    headers = {"Content-Type": "application/json"}
    resp = requests.get(url, headers=headers)
    text = resp.json()
    return text

# 其它接口此处可进行补充.............

if __name__ == '__main__':
    app.run(
        # TODO 设置无效,原因未知
        # host='10.67.*.*',
        # port='5000',
        # debug=True
    )

通过 Data URI Scheme 的方式来引入图片到网页

常见的图片加载方式

一般而言,最常见的图片加载方式是 HTTP URI Scheme 简称 HTTP URI。例如:某图片存储在又拍云存储上,链接为 https://f.lxpzyy.top/upyun/upcdn.svg。我们可以通过引入图片 HTTP 链接的方式来加载图片。

除此之外,我们可以通过 Data URI Scheme 的方式来引入图片。

<img src=“data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGlu……/>

Data URI Scheme 的方式和 HTTP URI 的使用较为类似,只是看起来多了很多“乱码”。那这些乱码有什么作用呢?和 HTTP URI 相比,它有什么优势让我们选择它呢?

初识 Data URI

Data URI scheme 简称 Data URI ,是在 RFC2397 中进行定义的。目的是将一些小的数据,直接嵌入到网页中,从而不用再从外部文件载入。以上方的 Data URI 链接为例:

<img src=“data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGlu……/>

我们可以看到它由 data,image/svg+xml,base64 以及后面的字符串组成。

data:[][;charset=][;],

这几部分分别为:

  • data: 表示取得数据的协定名称,表明这是一个 Data URI。
  • mime type: 数据类型名称,也是就是 image/svg+xml,如果传入的是个 png 图片,那么可以指定类型为 image/png。
  • charset=: 可不填,默认是 charset=US-ASCII 编码。
  • base64: 是数据的编码方法。
  • encoded data: 经过 base64 编码后的数据。

我们可以看到,除去可不填的 charset=,使用 Data URI 必然会用到 base64 编码,那这个要如何获得呢?

如何获得 base64 编码

最简单的方式是直接上网找一个编码小工具。

现在网上有很多的 base64 编码小工具,他们可以直接将文件或者字符进行 base64 解编码。

除此之外各代码语言也都有其对应的 base64 编码方式,可以很方便地进行编码解码转换:

Data URI 的使用方式及优势

了解了 Data URI 的大致情况,我们来看看它要如何使用。

它主要有两种使用方式,第一种是将处理好的 Data URI 格式数据,放入 HTML 页面代码 img 标签的 src 属性中。这种方式的好处是减少了 HTTP 请求,缺点是无法进行缓存。另一种方式则是将 Data URI 放入到 css 里,优点是浏览器会积极缓存 css 文件来提升加载页面时的速度,缺点是增加了 css 的长度。

那么回到我们最初的问题 Data URI 比 HTTP URI 的优势是什么呢?

相比 HTTP URI,Data URI 拥有以下优势:

  • 使用 Data URI 能够有效减少 HTTP 请求数
  • 不依赖于网络环境,即没有网络的时候页面中的资源也可以被加载出来
  • 可以免除一些极小文件对 HTTP 请求的占用

当然 Data URI 也有不少缺点。经过 Base64 编码后的文件或者数据,通常来说比起原文件体积增大了30%左右,然后 Data URI 通常会写在 css 文件中,不易维护,另外手机端加载 Data URI 资源也比较消耗 CPU 资源。

相比之下,Data URI 还是很值得使用的,现在就有很多场景都使用 Data URI 的方式引入资源,比如百度首页的小图标以及谷歌的首页等。

如果想让网站拥有不同的加载方式,特别是避免因网络状况差导致的无法加载问题,那一定要试试 Data URI。

在考虑使用 Data URI 的过程中,我们可以从以下几点着重考虑:

  • 图片的实际尺寸比较小
  • 不经常更新的资源图片
  • 需要在页面中经常使用的图片