#663 Python 源码学习 11: PyFrameObject

2021-09-25
// Include/cpython/frameobject.h
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    /* Call PyFrame_GetLineNumber() instead of reading this field
       directly.  As of 2.3 f_lineno is only valid when tracing is
       active (i.e. when f_trace is set).  At other times we use
       PyCode_Addr2Line to calculate the line from the current
       bytecode index. */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};

// Include/pyframe.h
typedef struct _frame PyFrameObject;

字节码

def sum(a, b):
    return a + b

def test():
    print('hello world')
    print(sum(1, 2))

import dis
dis.dis(sum)
dis.dis(test)
2           0 LOAD_FAST                0 (a)
            2 LOAD_FAST                1 (b)
            4 BINARY_ADD
            6 RETURN_VALUE
5           0 LOAD_GLOBAL              0 (print)
            2 LOAD_CONST               1 ('hello world')
            4 CALL_FUNCTION            1
            6 POP_TOP

6           8 LOAD_GLOBAL              0 (print)
           10 LOAD_GLOBAL              1 (sum)
           12 LOAD_CONST               2 (1)
           14 LOAD_CONST               3 (2)
           16 CALL_FUNCTION            2
           18 CALL_FUNCTION            1
           20 POP_TOP
           22 LOAD_CONST               0 (None)
           24 RETURN_VALUE

行号,指令偏移,指令,参数,参数值(参考)

查看字节码:

sum.__code__.co_code
sum.__code__.co_varnames
sum.__code__.co_consts
sum.__code__.co_names

参考资料与拓展阅读

#662 孟晚舟回国

2021-09-25
  1. 孟晚舟 1972 年出生,今年 49 岁。
    PS:任正非 1944 年出生,今年 77 岁,28 岁时生的孟晚舟。
  2. 2018 年 12 月 1 日,华为创始人任正非长女,也是华为的 CFO,孟晚舟(46 岁)在加拿大温哥华转机的时候,被加拿大警方逮捕。事后公布的原因是应美国政府要求引渡孟晚舟。时值中美贸易战,我不懂法律,美国起诉孟晚舟和华为的理由我是不清楚,目的肯定是打击中国科技企业。
  3. 2018 年 12 月 11 日,孟晚舟保释。看到网上公布的照片,孟晚舟一直戴着一个电子脚镣。
  4. 之后长达两年半的司法程序,直到今天结束,总共历时 1028 天。

看网络上的新闻,北京时间今天凌晨,孟晚舟和美国司法部达成了什么协议,孟晚舟承认了美国政府的部分事实陈述,但是不认罪,以此为代价,美国司法部结束引渡程序,并且只要孟晚舟在 2022 年之前,年前不再触犯美国联邦刑法,其将免于美国政府的起诉。加拿大随即释放孟晚舟,允许其自由离境。
虽然会被人质疑是 “人质外交”,中国政府还是释放了两名因涉嫌间谍罪而被逮捕的加拿大人(PS: 孟晚舟事件之后被逮捕)。

我不懂新闻中提到的那些个美国法律术语,只觉得折腾这么长时间,折腾了个寂寞,到了也没有怎么样,高高拿起,轻轻放下,就这样轻描淡写的结束了。
我的理解是,这件事情的发起是由于中美贸易战,结束则是由于美国放弃了和中国 “大决战” 的策略,拜登政府不想和中国硬钢。因为司法程序已经走到这一步,再沿原来的方向走下去就是彻底撕破脸了。

#660 URI

2021-09-24

URI 与 URL

                    hierarchical part
        ┌───────────────────┴─────────────────────┐
                    authority               path
        ┌───────────────┴───────────────┐┌───┴────┐
  abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
  └┬┘   └───────┬───────┘ └────┬────┘ └┬┘           └─────────┬─────────┘ └──┬──┘
scheme  user information     host     port                  query         fragment

  urn:example:mammal:monotreme:echidna
  └┬┘ └──────────────┬───────────────┘
scheme              path

URI

URI (统一资源标识符),标识一个资源主要有两种方法,根据名称(URN),根据路径(URL)。

URI 语法:

URL Syntax

有一个问题就是注册的 URI scheme 基本上都可以认为是用于资源定位,所以 URI 和 URL 的概念就比较模糊了。

有的 scheme,比如说 data:<mediatype>[;base64],<data>,既不算 URL,也不算 URI。
有的 scheme,比如说 sms:<phone number>?<action>,我觉得是属于名称标识,但是不符合 URN 的定义(必须 urn:)。

而且实际上,就比如说 URL http://example.com/user/create,我们用来创建一个用户,如果套用万维网创建时的学院派设想,我们只能把创建这个动作理解成一个资源了。所以,我认为深入讨论这几个 “统一资源” 的概念之间到底存在什么差异没有什么意义,大致知道他们的渊源就够了:先随着 WWW 出来了 URL 的概念,然后又有人整出来一个 URN,再后来统一为 URI 标准,这几个概念本来就是混乱的,这就是现实生活,泾渭分明的概念只存在于那些教授们的理想中。
如果感兴趣可以参考 RFC 3305,这是一个 Infomational RFC,就是试图解释这几个概念,并向社区作出使用建议。

URL

URL (统一资源定位符) = 协议://认证信息@主机名:端口号/路径?查询字符串#片段

协议必须是在 IANA 注册的合法 URI scheme。

我们最熟悉的是 HTTP URL,协议是 http 或者 https,除此之外还有其他的 IANA 批准的协议,比如:

  • mailto:<address>[?<header1>=<value1>[&<header2>=<value2>]]
  • imap://[<user>[;AUTH=<type>]@]<host>[:<port>]/<command>
  • git://github.com/user/project-name.git
  • file://[host]/path or file:[//host]/path
  • view-source:<absolute-URI>

URN

URN (统一资源名称) is 资源名称(唯一标识),格式:urn:<NID>:<NSS>

  1. urn 就是 IANA 注册的众多 URI scheme 之一。
  2. 这个命名空间标识 NID 也需要在 IANA 注册。

  3. urn:isbn:0451450523

  4. urn:ietf:rfc:2648

实际上,我好像没有见过什么地方在使用 URN,更别提设想中的统一资源属性 URC 了。

就好比上面提到的两个例子,ISBN: 0451450523,RFC 2648,直接这么写就是了,按照 RFC 定义写成标准的那一串实在是没见过。

总结

URL 是 URI 中的因特网部分,用来指示网络资源位置。

Python 示例

import urllib.parse

a = urllib.parse.urlunparse('http', 'user:pass@example.com:8080', '/path', 'param1=a', 'a=1&b=2', 'Title')
# 'http://user:pass@example.com:8080/path;param1=a?a=1&b=2#Title'

b = urllib.parse.urlparse(a)
b.scheme    # 0 'http'
b.netloc    # 1 'user:pass@example.com:8080'
b.path      # 2 '/path'
b.params    # 3 'param1=a'
b.query     # 4 'a=1&b=2'
b.fragment  # 5 'Title'
b.username  #   'user'
b.password  #   'pass'
b.hostname  #   'example.com'
b.port      #   8080

print(b[2]) # '/path'

b = urllib.parse.urlparse(('pymysql+mysql://root:111111@1.1.1.1:3306/xiaorui_master?charset=utf8mb4'))
print(repr((b.scheme, b.netloc, b.path, b.params, b.query, b.fragment, b.username, b.password, b.hostname, b.port)))
('pymysql+mysql', 'root:111111@1.1.1.1:3306', '/xiaorui_master', '', 'charset=utf8mb4', '', 'root', '111111', '1.1.1.1', 3306)

参考资料与拓展阅读

#658 转载:管理者的四种不同授权风格

2021-09-23

什么是授权?

所谓授权是指管理者把由他全权负责的一项或多项任务委派给下属员工,使下属拥有相当的自主权和行动权。

授权的6个误区

  1. 授权不是全程参与,授权后管理者不要过多干涉员工的工作;
  2. 授权不是弃权,授权是将任务、权利分配下去,再做适当的监督、帮助和支持;
  3. 授权不是授责,虽然把权利、工作内容和资源分配给下属,但是责任还是在主管身上;
  4. 授权不是代理职务,并不意味着下属拥有管理者的所有职权;
  5. 授权不是分工,分工在岗位说明中已经设定,但授权工作有可能在岗位职责之外;
  6. 授权不是助理,不是让下属打杂,而是要他独立完成一项工作。

管理者的四种不同授权风格

  1. 操控型:喜欢主动监督工作的进展,经常不说明理由,只是下令照办,并且严密监督工作的进行。适合于团队经验不足。
  2. 教练型:会密切监督下属工作的进行,但通常不会详细指点下属该如何进行,只会跟下属说明所交付的任务,逐步引导。当有必要或需要时,才会提供建议或支援。适合于新参加工作的员工。
  3. 顾问型:一般会给予执行工作的人更多的主导权,对于所交付的任务通常只是做大致上的描述,并且会征询对方的意见和观点,最终取得共识,最终接受任务的个人和团队投下更多的心力。这类管理者通常告知下属,只要表明需求,就会得到协助。
  4. 协调型:只给原则性的指示和注意事项,并表达进行协助的意愿,而做事的程序和方法全由下属自己决定。在授权时,会视工作执行的能力而给予相对的权限,日后再根据工作的进度进行调整。

下面举几个《三国演义》的例子

  1. 刘备谋赖孔明,武赖关张。孔明出山第一仗前,刘备命人取了佩剑和印信,代表了所有的权利都交给了孔明。孔明出计火烧博望坡时,张飞和关羽开始并不配合,刘备在旁边使眼色,关张二人不得不听令。
    刘备在给孔明授权时表现了怎样的授权风格呢?
  2. 火烧博望坡之战,孔明给诸将下令:让关羽埋伏于豫山,让曹军先头部队过去,等南面火起,放火烧曹军粮草;让张飞埋伏于安林,等南面火起,烧曹军博望城之前屯粮的地方;让赵云在博望坡北负责诱敌深入。
    孔明给关张赵三人授权时表现了怎样的授权风格呢?
  3. 赤壁之战火烧连营的真正实施者是黄盖,苦肉计前一天晚上,周瑜见到黄盖,首先询问黄盖的意见,黄盖自己提出用火攻。周瑜又引导说火攻实施的人很重要。这时候黄盖自告奋勇,愿意用苦肉计骗取信任,然后从曹军内部火攻。
    周瑜给黄盖授权实施火烧连营的计策是怎样的授权风格呢?
  4. 东吴使美人计,实则想讨要荆州。孔明授权赵云保护刘备,并给了他三个锦囊,嘱咐他在紧急时刻依次拆看。
    孔明给赵云授权时表现了怎样的授权风格呢?

答案

  1. 协调型
  2. 操控型
  3. 顾问型
  4. 教练型

#656 正当防卫与互殴

2021-09-21

抖音上看到有人穿警服说,如果有人打你,你只要还手就是互殴,无关正当防卫。那普通人面对被打的情况该怎么正确处理?

#655 JWT 认证

2021-09-20

JWT 介绍

一句话介绍 JWT:就是通过非对称加密算法签名的方式,将部分用户信息存在客户端。

全名 JSON Web Token,简称 JWT。
内容 = 头 Headers (JSON) + 负载 Payload (JSON) + 签名 Signature
分别 Base64 编码之后,用 . 连接。

示例

JSON 实现

import base64

def jsonify(obj):
    return json.dumps(obj, ensure_ascii=False, separators=',:')

def base64urlEncoding(s):
    return base64.urlsafe_b64encode(s).rstrip(b'=').decode('ascii')

def sign(key, msg):
    return base64urlEncoding(hmac.new(key, msg, hashlib.sha256).digest())

# def verify(key, msg, sig):
#     return key.verify(msg, base64.urlsafe_b64decode(sig))

def e(obj):
    return base64urlEncoding(jsonify(header).encode('utf-8'))

def encode(key, header, payload):
    s = e(header) + '.' + e(payload)
    return s + '.' + sign(key, s)

def decode(token):
    header_str, payload_str, sig = token.split('.')
    header = json.loads(base64.urlsafe_b64decode(header_str).decode('utf-8'))
    payload = json.loads(base64.urlsafe_b64decode(payload_str).decode('utf-8'))
    sig = base64.urlsafe_b64decode(sig)

header = {
  "alg": "HS256",
  "typ": "JWT"
}
payload = {
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

signature = HMAC_SHA256(
    secret,
    base64urlEncoding(header) + '.' +
    base64urlEncoding(payload)
)
const token = base64urlEncoding(header) + '.' + base64urlEncoding(payload) + '.' + base64urlEncoding(signature)

标准字段

Claim 字段

Code Name Description
iss Issuer ..
sub Subject ..
aud Audience ..
exp Expiration Time ..
nbf Not Before ..
iat Issued At ..
jti JWT ID ..

Header 字段

Code Name Description
typ Type ..
cty Content Type ..
alg Algorithm ..
kid Key ID ..
x5c X.509 Certificate ..
x5u X.509 URL ..
x5t X.509 Thumbprint ..
jku JSON Web Key URL ..
jwk JSON Web Key ..
x5t#S256 X.509 SHA-256 Thumbprint ..

关于 JWT 的看法

  1. 相比 Session,减少了 DB/Redis 操作。
  2. 如果需要注销功能(踢下线),可以加一个黑名单,验证服务定时拉取这个黑名单
  3. 服务降级的时候,这一步可以去掉
  4. 相比加密 Cookie,不用依赖 HTTP Cookie,更加灵活,适用更多场景。
  5. 应该尽可能保证 Token 不要太长
  6. 顶多 4、5 个字段就够了
  7. JWT 和 加密 Cookie 一样,如果需要存储的信息太多,还是会让请求有点臃肿
  8. 大部分时候只需要验证身份,没有太多信息存储的需求,JWT
  9. 可以在网关那边统一校验
  10. 跨域