如何使用 FastAPI?

下面的步骤将帮助你开始使用 FastAPI 构建一个简单的 Web 应用。

1.安装 FastAPI:在命令行中运行以下命令安装 fastapi,并安装 uvicorn :

pip install fastapi

2. 创建 FastAPI 应用:在你喜欢的 IDE 编辑器中创建一个新的 Python 文件,例如fastapi-demo.py。然后将以下代码复制到文件中:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

上面的代码创建了一个名为 app 的 FastAPI 实例,并定义了一个根路由,返回一个简单的 JSON 响应。

3. 运行 FastAPI 应用:在命令行中运行以下命令启动 FastAPI 应用:

uvicorn main:app --reload

FastAPI 将在本地启动一个服务器,并监听默认端口(8000)。你可以在浏览器中访问 http://127.0.0.1:8000,看到 {"Hello": "World"} 的响应。

4. 添加更多的路由和功能:你可以继续在应用中添加更多的路由和功能,根据自己的需求进行扩展:

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

上面的代码添加了一个名为 read_item 的新路由,它接受一个 item_id 参数和一个可选的 q 参数,并返回一个 JSON 响应。

实践案例

以下是一个使用 FastAPI 构建的实践案例,能够在 IDE 编辑器中直接运行的代码。

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": f"接口id:{item_id}"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000)

复制以上代码到你的 IDE 编辑器中,并运行它。然后通过浏览器访问 http://localhost:8000/,你将看到 {"Hello": "World"} 的响应。同样地,你也可以访问 http://localhost:8000/items/42?q=somequery,查看带有参数的响应。

轻量快速的 Python ASGI 框架 uvicorn

什么是 Uvicorn ?

答:Uvicorn 是基于 uvloop 和 httptools 构建的非常快速的 ASGI 服务器。

什么是 uvloop 和 httptools ?

答: uvloop 用于替换标准库 asyncio 中的事件循环,使用 Cython 实现,它非常快,可以使 asyncio 的速度提高 2-4 倍。asyncio 不用我介绍吧,写异步代码离不开它。

httptools 是 nodejs HTTP 解析器的 Python 实现。

什么是 ASGI 服务器?

答: 异步网关协议接口,一个介于网络协议服务和 Python 应用之间的标准接口,能够处理多种通用的协议类型,包括 HTTP,HTTP2 和 WebSocket。

请简单介绍下 Uvicorn

答:目前,Python 仍缺乏异步的网关协议接口,ASGI 的出现填补了这一空白,现在开始,我们能够使用共同的标准为所有的异步框架来实现一些工具,ASGI 帮助 Python 在 Web 框架上和 Node.JS 及 Golang 相竟争,目标是获得高性能的 IO 密集型任务,ASGI 支持 HTTP2 和 WebSockets,WSGI 是不支持的。

Uvicorn 目前支持 HTTP1.1 和 WebSocket,计划支持 HTTP2。

使用方法:

$ pip install uvicorn
创建一个文件 example.py

async def app(scope, receive, send):
    assert scope['type'] == 'http'
    await send({
        'type': 'http.response.start',
        'status': 200,
        'headers': [
            [b'content-type', b'text/plain'],
        ]
    })
    await send({
        'type': 'http.response.body',
        'body': b'Hello, world!',
    })
启动 Uvicorn

$ uvicorn example:app
你也可以不使用命令行,直接运行你的脚本也是可以的,如下:

import uvicorn

async def app(scope, receive, send):
    ...

if __name__ == "__main__":
    uvicorn.run("example:app", host="127.0.0.1", port=5000, log_level="info")



FastAPI使用uvicorn

import uvicorn
from fastapi import FastAPI
 
app = FastAPI()
 
@app.get("/")
async def root():
    return {"message": "Hello World"}
 
if __name__ == '__main__':
    uvicorn.run(app=app)
深入到uvicorn.run()方法里面,看到一个:

def run(app, **kwargs):
    config = Config(app, **kwargs)
    server = Server(config=config)
 
    if (config.reload or config.workers > 1) and not isinstance(app, str):
        logger = logging.getLogger("uvicorn.error")
        logger.warn(
            "You must pass the application as an import string to enable 'reload' or 'workers'."
        )
        sys.exit(1)
 
    if config.should_reload:
        sock = config.bind_socket()
        supervisor = StatReload(config, target=server.run, sockets=[sock])
        supervisor.run()
    elif config.workers > 1:
        sock = config.bind_socket()
        supervisor = Multiprocess(config, target=server.run, sockets=[sock])
        supervisor.run()
    else:
        server.run()
再深入到 config = Config(app, **kwargs)里面,就看到一些很多的相关的配置信息项:

class Config:
    def __init__(
        self,
        app,
        host="127.0.0.1",
        port=8000,
        uds=None,
        fd=None,
        loop="auto",
        http="auto",
        ws="auto",
        lifespan="auto",
        env_file=None,
        log_config=LOGGING_CONFIG,
        log_level=None,
        access_log=True,
        use_colors=None,
        interface="auto",
        debug=False,
        reload=False,
        reload_dirs=None,
        workers=None,
        proxy_headers=True,
        forwarded_allow_ips=None,
        root_path="",
        limit_concurrency=None,
        limit_max_requests=None,
        backlog=2048,
        timeout_keep_alive=5,
        timeout_notify=30,
        callback_notify=None,
        ssl_keyfile=None,
        ssl_certfile=None,
        ssl_version=SSL_PROTOCOL_VERSION,
        ssl_cert_reqs=ssl.CERT_NONE,
        ssl_ca_certs=None,
        ssl_ciphers="TLSv1",
        headers=None,
    ):
....
所以还可以添加的参数可以看上面的几个配置的选项的信息来填:
于是乎还可以修改为:

uvicorn.run(app=app, host="127.0.0.1", port=8000, reload=True, debug=True)
发现本来想热更新代码,结果呐?有告警信息提示:

WARNING:  You must pass the application as an import string to enable 'reload' or 'workers'.
翻译过来就是说: 警告:必须将应用程序作为导入字符串传递,才能启用“重新加载” 然后呢: 我修改为:

  uvicorn.run(app='app', host="127.0.0.1", port=8000, reload=True, debug=True)
又提示:

ERROR:    Error loading ASGI app. Import string "app" must be in format "<module>:<attribute>".
好吧,我再看看官方文档说是:
在命令行下是需要:模块加app名称:刚好上面的错误提示也是说需要:

    uvicorn.run(app='main:app', host="127.0.0.1", port=8000, reload=True, debug=True)
这样之后就可以启动热更新重启服务了!

使用命令行时,你可以使用 uvicorn –help 来获取帮助。

Usage: uvicorn [OPTIONS] APP

Options:
  --host TEXT                     Bind socket to this host.  [default:
                                  127.0.0.1]
  --port INTEGER                  Bind socket to this port.  [default: 8000]
  --uds TEXT                      Bind to a UNIX domain socket.
  --fd INTEGER                    Bind to socket from this file descriptor.
  --reload                        Enable auto-reload.
  --reload-dir TEXT               Set reload directories explicitly, instead
                                  of using the current working directory.
  --workers INTEGER               Number of worker processes. Defaults to the
                                  $WEB_CONCURRENCY environment variable if
                                  available. Not valid with --reload.
  --loop [auto|asyncio|uvloop|iocp]
                                  Event loop implementation.  [default: auto]
  --http [auto|h11|httptools]     HTTP protocol implementation.  [default:
                                  auto]
  --ws [auto|none|websockets|wsproto]
                                  WebSocket protocol implementation.
                                  [default: auto]
  --lifespan [auto|on|off]        Lifespan implementation.  [default: auto]
  --interface [auto|asgi3|asgi2|wsgi]
                                  Select ASGI3, ASGI2, or WSGI as the
                                  application interface.  [default: auto]
  --env-file PATH                 Environment configuration file.
  --log-config PATH               Logging configuration file.
  --log-level [critical|error|warning|info|debug|trace]
                                  Log level. [default: info]
  --access-log / --no-access-log  Enable/Disable access log.
  --use-colors / --no-use-colors  Enable/Disable colorized logging.
  --proxy-headers / --no-proxy-headers
                                  Enable/Disable X-Forwarded-Proto,
                                  X-Forwarded-For, X-Forwarded-Port to
                                  populate remote address info.
  --forwarded-allow-ips TEXT      Comma separated list of IPs to trust with
                                  proxy headers. Defaults to the
                                  $FORWARDED_ALLOW_IPS environment variable if
                                  available, or '127.0.0.1'.
  --root-path TEXT                Set the ASGI 'root_path' for applications
                                  submounted below a given URL path.
  --limit-concurrency INTEGER     Maximum number of concurrent connections or
                                  tasks to allow, before issuing HTTP 503
                                  responses.
  --backlog INTEGER               Maximum number of connections to hold in
                                  backlog
  --limit-max-requests INTEGER    Maximum number of requests to service before
                                  terminating the process.
  --timeout-keep-alive INTEGER    Close Keep-Alive connections if no new data
                                  is received within this timeout.  [default:
                                  5]
  --ssl-keyfile TEXT              SSL key file
  --ssl-certfile TEXT             SSL certificate file
  --ssl-version INTEGER           SSL version to use (see stdlib ssl module's)
                                  [default: 2]
  --ssl-cert-reqs INTEGER         Whether client certificate is required (see
                                  stdlib ssl module's)  [default: 0]
  --ssl-ca-certs TEXT             CA certificates file
  --ssl-ciphers TEXT              Ciphers to use (see stdlib ssl module's)
                                  [default: TLSv1]
  --header TEXT                   Specify custom default HTTP response headers
                                  as a Name:Value pair
  --help                          Show this message and exit.

使用进程管理器

使用进程管理器确保你以弹性方式运行运行多个进程,你可以执行服务器升级而不会丢弃客户端的请求。

一个进程管理器将会处理套接字设置,启动多个服务器进程,监控进程活动,监听进程重启、关闭等信号。

Uvicorn 提供一个轻量级的方法来运行多个工作进程,比如 --workers 4,但并没有提供进行的监控。

使用 Gunicorn

Gunicorn 是成熟的,功能齐全的服务器,Uvicorn 内部包含有 Guicorn 的 workers 类,允许你运行 ASGI 应用程序,这些 workers 继承了所有 Uvicorn 高性能的特点,并且给你使用 Guicorn 来进行进程管理。

这样的话,你可能动态增加或减少进程数量,平滑地重启工作进程,或者升级服务器而无需停机。

在生产环境中,Guicorn 大概是最简单的方式来管理 Uvicorn 了,生产环境部署我们推荐使用 Guicorn 和 Uvicorn 的 worker 类:

gunicorn example:app -w 4 -k uvicorn.workers.UvicornWorker

执行上述命令将开户 4 个工作进程,其中 UvicornWorker 的实现使用 uvloop 和httptools 实现。在 PyPy 下运行,你可以使用纯 Python 实现,可以通过使用UvicornH11Worker 类来做到这一点。

gunicorn -w 4 -k uvicorn.workers.UvicornH11Worker

Gunicorn 为 Uvicorn 提供了不同的配置选项集,但是一些配置暂不支持,如–limit-concurrency 。

使用 Supervisor

要supervisor用作流程管理器,您应该:

使用其文件描述符将套接字移交给uvicorn,supervisor始终将其用作0,并且必须在本fcgi-program节中进行设置。

或为每个uvicorn进程使用UNIX域套接字。

一个简单的主管配置可能看起来像这样: administratord.conf:

[supervisord]

[fcgi-program:uvicorn]
socket=tcp://localhost:8000
command=venv/bin/uvicorn --fd 0 example:App
numprocs=4
process_name=uvicorn-%(process_num)d
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
然后运行supervisord -n

使用 Circus

要circus用作流程管理器,您应该:

使用其文件描述符将套接字移交给uvicorn,马戏团可将其用作$(circus.sockets.web)。
或为每个uvicorn进程使用UNIX域套接字。

使用 Circus 与 Supervisor 很类似。配置文件 circus.ini 如下:

[watcher:web]
cmd = venv/bin/uvicorn --fd $(circus.sockets.web) example:App
use_sockets = True
numprocesses = 4

[socket:web]
host = 0.0.0.0
port = 8000

然后运行circusd circus.ini

与 Nginx 部署

Nginx 作为 Uvicorn 进程的代理并不是必须的,你可以使用 Nginx 做为负载均衡。推荐使用 Nginx 时配置请求头,如 X-Forwarded-For,X-Forwarded-Proto,以便 Uvicorn 识别出真正的客户端信息,如 IP 地址,scheme 等。这里有一个配置文件的样例:

http {
  server {
    listen 80;
    client_max_body_size 4G;

    server_name example.com;

    location / {
      proxy_set_header Host $http_host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_redirect off;
      proxy_buffering off;
      proxy_pass http://uvicorn;
    }

    location /static {
      # path for static files
      root /path/to/app/static;
    }
  }

  upstream uvicorn {
    server unix:/tmp/uvicorn.sock;
  }

}

使用 HTTPS

要使用https运行uvicorn,需要证书和私钥。推荐的获取方法是使用Let’s Encrypt

对于使用https进行本地开发,可以使用mkcert 生成有效的证书和私钥。

$ uvicorn example:app --port 5000 --ssl-keyfile=./key.pem --ssl-certfile=./cert.pem

使用 Gunicorn 也可以直接使用证书。

也可以与uvicorn的工人一起使用证书来获取gunicorn

$ gunicorn --keyfile=./key.pem --certfile=./cert.pem -k uvicorn.workers.UvicornWorker example:ap

记几个开源项目

https://github.com/kekewind/Kaleidoscope,python selenim QQ空间 虎牙 电影 微信 微博 抖音 知乎 B站 bili Bili 百度贴吧 小红书 tiktok youtube twitter 有颜色 爬虫

https://github.com/facert/awesome-spider爬虫收集

https://github.com/lonerge/tiktok_youtube_douyin_handling搜索关键字下载并可视化

https://github.com/Maryin-c/KeywordSpider_pictures_videos搜索关键字爬

https://github.com/Evil0ctal/Douyin_TikTok_Download_API是一个开箱即用的高性能异步抖音、快手、TikTok、Bilibili数据爬取工具,支持API调用,在线批量解析及下载

https://github.com/kekewind/MediaCrawler小红书笔记 | 评论爬虫、抖音视频 | 评论爬虫

https://github.com/cuemacro/findatapy,python来下载yahoo等的金融数据

https://gitee.com/AJay13/ECommerceCrawlers爬虫集

https://github.com/EstrellaXD/Auto_Bangumi,自动追动漫

https://github.com/kekewind/public-apis#open-data一些API的集合,可能新闻类有用

加密和解密技术

  1. 加密和解密的基本概念
    加密:加密是一种将数据(明文)转换为其他格式(密文)的过程,以防止未经授权的人员访问。加密使用特定的算法(称为加密算法)和密钥来执行此转换。

解密:解密是将加密后的数据(密文)还原为原始数据(明文)的过程。解密通常使用相同的加密算法和相应的密钥来进行。

密钥:密钥是用于加密和解密数据的特定字符序列。密钥的长度和复杂性直接影响到加密算法的安全性。

  1. 对称加密
    对称加密是一种使用相同的密钥进行加密和解密的加密方法。这意味着加密和解密过程中使用的密钥是相同的。对称加密算法通常更快,但密钥管理可能会成为一个问题,因为每对通信方都需要共享相同的密钥。

常见对称加密算法:

AES(Advanced Encryption Standard)
DES(Data Encryption Standard)
3DES(Triple Data Encryption Standard)
RC4(Rivest Cipher 4)
Blowfish
实例:使用Python的cryptography库进行AES加密和解密:

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
import os

# 生成一个随机密钥
key = os.urandom(32)

# 生成一个随机初始化向量(IV)
iv = os.urandom(16)

# 使用AES加密算法和CBC模式创建一个加密对象
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())

# 创建一个加密器对象
encryptor = cipher.encryptor()

# 明文数据
plaintext = b"Hello, world!"

# 使用PKCS7填充对明文进行填充
padder = padding.PKCS7(128).padder()
padded_data = padder.update(plaintext) + padder.finalize()

# 对填充后的明文进行加密
ciphertext = encryptor.update(padded_data) + encryptor.finalize()

# 创建一个解密器对象
decryptor = cipher.decryptor()

# 对密文进行解密
decrypted_data = decryptor.update(ciphertext) + decryptor.finalize()

# 使用PKCS7填充的解除器对解密后的数据进行解填充
unpadder = padding.PKCS7(128).unpadder()
unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()

assert plaintext == unpadded_data
  1. 非对称加密
    非对称加密是一种使用两个不同密钥进行加密和解密的加密方法:一个公钥用于加密数据,一个私钥用于解密数据。公钥可以公开分享,而私钥必须保密。非对称加密算法通常比对称加密慢,但解决了密钥管理的问题。

常见非对称加密算法:

RSA(Rivest-Shamir-Adleman)
DSA(Digital Signature Algorithm)
ElGamal
ECC(Elliptic Curve Cryptography)
实例:使用Python的cryptography库进行RSA加密和解密:

from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric importpadding as asym_padding
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.backends import default_backend

# 生成RSA密钥对
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
    backend=default_backend()
)
public_key = private_key.public_key()

# 明文数据
plaintext = b"Hello, world!"

# 使用公钥对明文进行加密
ciphertext = public_key.encrypt(
    plaintext,
    asym_padding.OAEP(
        mgf=asym_padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)

# 使用私钥对密文进行解密
decrypted_data = private_key.decrypt(
    ciphertext,
    asym_padding.OAEP(
        mgf=asym_padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)

assert plaintext == decrypted_data
  1. 哈希函数
    哈希函数是一种将任意长度的数据映射到固定长度的输出的单向函数。哈希函数具有以下特性:

输入变化时,输出有很大概率发生变化(敏感性)
不同输入产生相同输出的概率极低(碰撞抵抗)
无法从输出反推输入(单向性)
常见哈希算法:

MD5(Message-Digest Algorithm 5)
SHA-1(Secure Hash Algorithm 1)
SHA-2(Secure Hash Algorithm 2)
SHA-3(Secure Hash Algorithm 3)
实例:使用Python的hashlib库进行SHA-256哈希:

import hashlib

# 需要进行哈希的数据
data = b"Hello, world!"

# 计算SHA-256哈希值
hash_object = hashlib.sha256(data)
hash_hex = hash_object.hexdigest()

print("SHA-256 hash:", hash_hex)

5. 数字签名

数字签名是一种用于验证数据完整性和身份验证的技术。数字签名的基本原理是:

  1. 发送者使用私钥对数据生成签名。
  2. 接收者使用发送者的公钥验证签名。

实例:使用Python的cryptography库进行RSA数字签名和验证:

from cryptography.hazmat.primitives.asymmetric import padding as asym_padding
from cryptography.hazmat.primitives import hashes

# 需要签名的数据
data = b"Hello, world!"

# 使用私钥对数据生成签名
signature = private_key.sign(
    data,
    asym_padding.PSS(
        mgf=asym_padding.MGF1(hashes.SHA256()),
        salt_length=asym_padding.PSS.MAX_LENGTH
    ),
    hashes.SHA256()
)

# 使用公钥验证签名
try:
    public_key.verify(
        signature,
        data,
        asym_padding.PSS(
            mgf=asym_padding.MGF1(hashes.SHA256()),
            salt_length=asym_padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
    print("Signature is valid.")
except Exception as e:
    print("Signature is invalid:", e)
  1. 实际应用及案例代码
    在现实中,加密和解密技术被广泛应用于各种场景,如:

安全通信:HTTPS、SSH、SSL/TLS等协议使用加密技术保护数据在传输过程中的安全性。
数据存储:加密文件系统和数据库使用加密技术保护存储的数据。
身份验证:密码学可以用于实现各种身份验证机制,如一次性密码(OTP)、双因素身份验证(2FA)等。
数字货币:比特币等数字货币使用加密技术确保交易的安全性和匿名性。
上述示例代码已经展示了如何使用Python的cryptography和hashlib库进行加密、解密、哈希和数字签名操作。在实际应用中,请确保使用适当的加密算法、密钥长度和库,同时遵循最佳实践来保护数据的安全。

以下是一些加密和解密实践的建议:

保护密钥:确保密钥的安全存储和传输。对于对称加密,可以考虑使用密钥管理服务(如AWS KMS、Google Cloud KMS等)来管理密钥。对于非对称加密,保护私钥的安全至关重要。

使用现代、安全的加密算法:避免使用被认为是不安全或过时的算法,如DES、RC4等。相反,选择经过时间检验且被广泛认可的算法,如AES、RSA等。

加密模式和填充方案:选择正确的加密模式和填充方案也很重要。例如,对于对称加密,推荐使用诸如AES-CBC、AES-GCM等模式;对于非对称加密,推荐使用OAEP填充。

更新和维护:随着技术的发展,密钥长度、加密算法或其他加密相关技术可能变得不再安全。因此,请关注加密技术的最新发展,并根据需要更新和维护您的加密实践。

性能和效率:加密和解密操作可能会对性能产生影响。在选择加密算法时,请权衡安全性和性能。在某些情况下,可以考虑使用硬件加速来提高加密和解密操作的性能。

审计和合规:确保遵循相关法规和行业标准,如GDPR、HIPAA、PCI DSS等,这可能要求使用特定的加密算法、密钥长度或其他安全措施。

flask之REDIS+Dplayer消息队列实现电影弹幕

一、安装Redis

官网下载地址:https://redis.io/download

Redis  centos安装过程:

yum -y install gcc gcc-c++
wget http://download.redis.io/releases/redis-4.0.10.tar.gz
tar -zxvf redis-4.0.10.tar.gz
cd redis-4.0.10
make && make install
./utils/install_server.sh
#测试redis是否运行
redis-cli
> ping #返回pong表示成功

安装flask-redis

#清华源
pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple flask-redis

二、安装开源插件Dplayer

Dplayer官网地址:http://dplayer.js.org/#/    (有时候会打不开哦)

Dplayer github地址:https://github.com/MoePlayer/DPlayer

#html引入css/js
<link rel="stylesheet" href="{{ url_for('static',filename='dplayer/dist/Dplayer.min.css') }}" >
<script src="{{ url_for('static',filename='dplayer/dist/DPlayer.min.js') }}"></script>
#引用到页面
<div id="dplayer" style="height: 500px;width: 774px"></div>
#弹幕和视频接口
 <script>
        var dp1 = new DPlayer({
            container: document.getElementById('dplayer'),
            video: {
                url: "{{ url_for('static',filename='uploads/'+movie.url) }}"
            },
            danmaku: {
                id: '{{ movie.id }}',
                api: "/tm/"
            }
        });
 </script>
 
#Python返回弹幕的处理方法
@home.route("/tm/v3/",methods=["GET","POST"])
def tm():
    import json
    if request.method=="GET":
        #获取弹幕消息队列
        mid = request.args.get("id")
        key="movie"+str(mid)
        if rd.llen(key):
            msgs=rd.lrange(key,0,2999)
            res={
                "code":0,
                "data":[json.loads(v) for v in msgs]
            }
        else:
            res={
                "code":1,
                "danmaku":[]
            }
        resp=json.dumps(res)
    if request.method=="POST":
        #添加弹幕
        data=json.loads(request.get_data())
        msg= {
        "__v": 0,
        "_id": datetime.datetime.now().strftime("%Y%m%d%H%M%S") + uuid.uuid4().hex,
        "author": data["author"],
        "time": data["time"],
        "text": data["text"],
        "color": data["color"],
        "type": data["type"],
        "ip": request.remote_addr,
        "player": data["id"]
        }
        res = {
            "code": 0,
            "danmaku":msg
        }
        resp=json.dumps(res)
        msg=[data["time"],data["type"],data["color"],data["author"],data["text"]]
        rd.lpush("movie"+str(data["id"]),json.dumps(msg))
    return Response(resp,mimetype="application/json")

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 !!

本文转自知呼

形象的理解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这个属性,这个属性就是数据库中的用户密码字段。

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来反向引用一对多关系。这种方式可以帮助我们更好地控制关系,并避免循环引用

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的官方文档

Flask中jsonify和json.dumps用法以及区别(简单案例)

环境:python3.6, Flask1.0.3

flask提供了jsonify函数供用户处理返回的序列化json数据,

而python自带的json库中也有dumps方法可以序列化json对象.

其二者的区别,写个简单的案例实测一下便见分晓。

from flask import Flask
from flask import jsonify
import json

app=Flask(__name__)
app.config['JSON_AS_ASCII'] = False

# 随便定义个json字典
dic={"a":1,"b":2,"c":"你好"}
@app.route('/jsonify')
def jsonifys():
    # Content-Type: application/json
    return jsonify(dic)

@app.route('/jsondumps')
def jsondumps():
    # Content-Type: text/html; charset=utf-8
    return json.dumps(dic,ensure_ascii=False)

if __name__ == '__main__':
    app.run(debug=True)

1.我们先访问 http://127.0.0.1:5000/jsonify 地址查看jsonify返回的json内容为一个JSON格式的字典

{

“a”:1,

“b”:2,

“c”:”你好”

}

检查页面属性看Response内容:其中content-Type:application/json

2.再访问另一个地址 http://127.0.0.1:5000/jsondumps 查看返回的json内容差不多

{“a”:1,”b”:2,”c”:”你好”}

检查页面属性看Response内容:text/html

使用jsonify时响应的Content-Type字段值为application/json,

而使用json.dumps时该字段值为text/html。

ps:

使用jsonify方法是需要添加一句 app.config[‘JSON_AS_ASCII’] = False

而json.dumps方法需要添加参数 ensure_ascii=False

这样做可以避免显示中文乱码。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/142355.html原文链接:https://javaforall.cn