python语法中的左值、右值和字符
位置决定语义
在下面的python代码中,忽略掉语法错误,源码中同样一个单词tsecer在不同的位置有不同的意义
- import之后
在import之后的tsecer是作为一个简单的字面字符串来处理:这里的意思是这个tsecer不会有任何变量(及相关展开)的意义,它更类似于C语言中的字符串,也就是字面量,在源代码中看到是什么显示就是什么。
import tsecer
tsecer = tsecer + 1
- 等号右侧
这个让人不禁想起C语言中左值/右值的概念:在等号的右侧,同样的tsecer表示的并不是tsecer字符串本身,编译器需要将这个变量展开为对其具体内容的引用。由于这里只是引用,所以需要保证这个变量之前一定已经定义过。
- 等号左边
这个和右边取内容(dereference)的操作不同,这里更类似于是一个取地址的操作,也就是需要更新tsecer变量地址中的地址。或者对于赋值操作 lhs = rhs来说,它的语义是把rhs的内容存储/更新到lhs代表的地址中。
syntax tree visit
和通常的语言处理逻辑相同:python的语法树处理同样经过了符号表和语法分析,其中的符号表处理在symtable.c文件,而语法分析则在compile.c文件。这两个文件可以说是整个python语法分析最为关键的两个部分,这两个文件中的主体函数都是各种的visit函数,例如在符号表中有symtable_visit_expr函数,在语法分析中的compiler_visit_expr函数,两个函数的第二个参数都是相同的。从C++的视角来看,如果把第一个参数看做是类的话,那么它们操作的都是相同的数据类型。
symtable_visit_expr(struct symtable *st, expr_ty e)
compiler_visit_expr(struct compiler *c, expr_ty e)
import代码生成
对于import的生成,它生成的字节码只是从consts中读取内容,也就是把字后的NAME作为一个const(类似于C语言的字面字符串)处理,不会进行任何的变量展开。
static int
compiler_import(struct compiler *c, stmt_ty s)
{
/* The Import node stores a module name like a.b.c as a single
string. This is convenient for all cases except
import a.b.c as d
where we need to parse that string to extract the individual
module names.
XXX Perhaps change the representation to make this case simpler?
*/
Py_ssize_t i, n = asdl_seq_LEN(s->v.Import.names);
for (i = 0; i < n; i++) {
alias_ty alias = (alias_ty)asdl_seq_GET(s->v.Import.names, i);
int r;
PyObject *level;
level = PyLong_FromLong(0);
if (level == NULL)
return 0;
ADDOP_O(c, LOAD_CONST, level, consts);
Py_DECREF(level);
ADDOP_O(c, LOAD_CONST, Py_None, consts);
ADDOP_NAME(c, IMPORT_NAME, alias->name, names);
if (alias->asname) {
r = compiler_import_as(c, alias->name, alias->asname);
if (!r)
return r;
}
else {
identifier tmp = alias->name;
Py_ssize_t dot = PyUnicode_FindChar(
alias->name, '.', 0, PyUnicode_GET_LENGTH(alias->name), 1);
if (dot != -1) {
tmp = PyUnicode_Substring(alias->name, 0, dot);
if (tmp == NULL)
return 0;
}
r = compiler_nameop(c, tmp, Store);
if (dot != -1) {
Py_DECREF(tmp);
}
if (!r)
return r;
}
}
return 1;
}
一个全局变量的load/store为例
下面是python字节码中对于全局变量存储和读取的操作,典型的场景下这个对应的赋值操作左侧和右侧变量访问时对应的机器码。这个代码其实也很直观,如果把字典看做一个map的话,load的时候是map.find(key),而store则是map.insert(key, value);
/// @file: Python-3.6.0\Python\ceval.c
PyObject *
_PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
{
///....
TARGET(STORE_GLOBAL) {
PyObject *name = GETITEM(names, oparg);
PyObject *v = POP();
int err;
err = PyDict_SetItem(f->f_globals, name, v);
Py_DECREF(v);
if (err != 0)
goto error;
DISPATCH();
}
///....
TARGET(LOAD_GLOBAL) {
PyObject *name = GETITEM(names, oparg);
PyObject *v;
if (PyDict_CheckExact(f->f_globals)
&& PyDict_CheckExact(f->f_builtins))
{
v = _PyDict_LoadGlobal((PyDictObject *)f->f_globals,
(PyDictObject *)f->f_builtins,
name);
if (v == NULL) {
if (!_PyErr_OCCURRED()) {
/* _PyDict_LoadGlobal() returns NULL without raising
* an exception if the key doesn't exist */
format_exc_check_arg(PyExc_NameError,
NAME_ERROR_MSG, name);
}
goto error;
}
Py_INCREF(v);
}
else {
/* Slow-path if globals or builtins is not a dict */
/* namespace 1: globals */
v = PyObject_GetItem(f->f_globals, name);
if (v == NULL) {
if (!PyErr_ExceptionMatches(PyExc_KeyError))
goto error;
PyErr_Clear();
/* namespace 2: builtins */
v = PyObject_GetItem(f->f_builtins, name);
if (v == NULL) {
if (PyErr_ExceptionMatches(PyExc_KeyError))
format_exc_check_arg(
PyExc_NameError,
NAME_ERROR_MSG, name);
goto error;
}
}
}
PUSH(v);
DISPATCH();
}
///...
}
为什么 tsecer + 1 = 1会有语法错误
从虚拟机实现来看,tsecer+1这个表达式在内部也是有唯一确定的内存地址的,所以这个赋值在操作上并不是实现不了,只是编译器在语法分析的时候拒绝了这种情况。
根本的原因在于虽然tsecer+1有一个唯一的内部变量地址,但是如果不把它放到一个可以再次访问的位置(例如变量中),下次如何再访问这个修改的值呢?也就是tsecer+1这个操作不是可重入的,下次执行它返回变量的地址可能就已经变化,所以前一次赋值并没有意义。
我们通常说,python这种动态语言的变量不需要声明就可以直接使用,其实从某种角度看,它还是需要声明(至少是一个形式上)的:声明的方式就是变量必须先在赋值之类的store操作中作为左值出现。
回到正题
由于import后面的内容只是字面量(而不会展开),所以如果想通过import来动态加载一个模块无法实现,也就是下面的语法是无法达到目的的。
tsecer@harry: cat -n import_var_mod.py
1 import sys
2
3 print(sys.argv)
4
5 modname = sys.argv[1]
6 import modname
tsecer@harry: python import_var_mod.py tsecer
['import_var_mod.py', 'tsecer']
Traceback (most recent call last):
File "import_var_mod.py", line 6, in <module>
import modname
ImportError: No module named modname
tsecer@harry:
这里给出了动态加载的操作方法
import importlib
function_string = 'mypackage.mymodule.myfunc'
mod_name, func_name = function_string.rsplit('.',1)
mod = importlib.import_module(mod_name)
func = getattr(mod, func_name)
result = func()
python语法中的左值、右值和字符的更多相关文章
- i++和++i以及左值,右值
左值(LValue)和右值(RValue)的一个快捷记法是赋值运算,左值是赋值运算左边的值,右值就是右边(=,=废话).例如: int a = 5; a就是左值,5就是右值. 当然,如果真是这么个含义 ...
- C++ 左值 右值
最近在研究C++ 左值 右值,搬运.收集了一些别人的资料,供自己记录和学习,若以后看到了更好的解释,会继续补充.(打“?”是我自己不明白的地方 ) 参考:<Boost程序库探秘——深度解析C ...
- C++ 左值与右值 右值引用 引用折叠 => 完美转发
左值与右值 什么是左值?什么是右值? 在C++里没有明确定义.看了几个版本,有名字的是左值,没名字的是右值.能被&取地址的是左值,不能被&取地址的是右值.而且左值与右值可以发生转换. ...
- 左值&右值
一.引子 我们所谓的左值.右值,正确的说法应该是左值表达式.右值表达式. 因为C++的表达式不是左值就是右值. 在C中,左值指的是既能够出现在等号左边也能出现在等号右边的表达式,右值指的则是只能出现在 ...
- C++11之右值引用(一):从左值右值到右值引用
C++98中规定了左值和右值的概念,但是一般程序员不需要理解的过于深入,因为对于C++98,左值和右值的划分一般用处不大,但是到了C++11,它的重要性开始显现出来. C++98标准明确规定: 左值是 ...
- C语言几个术语: 数据对象,左值,右值
1. 数据对象 赋值表达式语句的目的是把值存储到内存位置上. 用于存储值的数据存储区域统称为数据对象. 2. 左值 左值是C语言的术语, 用于标识特定数据对象的名称或表达式. 对象指的是实际的数据存储 ...
- c++ 左值右值 函数模板
1.先看一段代码,这就是一种函数模板的用法,但是红色的部分如果把a写成a++或者写成一个常量比如1,都是编译不过的,因为如果是a++的话,实际上首先是取得a的 值0,而0作为一个常量没有地址.写成1也 ...
- C和C指针小记(八)-操作符、左值右值
1.移位操作符 移位操作符分为左移操作符(<<)和右移操纵符(>>) 对于无符号数:左右位移操作都是逻辑位移 对于有符号数:到底是采用逻辑位移还是算术位移取决于编译器.如果一个 ...
- 理解 ES6 语法中 yield 关键字的返回值
在 ES6 中新增了生成器函数的语法,本文解释了生成器函数内 yield 关键字的返回值. 描述 根据语法规范,yield 关键字用来暂停和继续执行一个生成器函数.当外部调用生成器的 next() 方 ...
- Python 字典中一键对应多个值
#字典的一键多值 print'方案一 list作为dict的值 值允许重复' d1={} key=1 value=2 d1.setdefault(key,[]).append(value) value ...
随机推荐
- 下拉刷新 get请求 post请求 onLoad
"enablePullDownRefresh": true 下拉刷新之后背景颜色 "backgroundColor": "#efefef&qu ...
- TypeError: unsupported operand type(s) for |=: 'dict' and 'dict'
原因:python3.9 支持对 dict 类型使用 |, 而较老的版本不支持 解决方案 :1. 用更新的 python 2. 把 | 操作替换成 {**d1, **d2} 来源:https://st ...
- python发送钉钉消息通用脚本
1.使用shell生成需要发送的内容. 2.调用该脚本发送文本内容python3,其中的文件 /wj/sbjk,需要改成直接需要发送的文件. [root@manager dingding]# more ...
- C++ 中的匿名函数(lambda表达式)
问题引入 使用std::sort函数对自定义类型排序时,我们需要传入一个比较函数作为参数.该比较函数只需要使用一次,但占有一个全局命名域中的名字,而且非常短,短到不需要名字就知道它的作用.这很浪费命名 ...
- 谷歌浏览器console.log打印失效问题
什么都没干,谷歌浏览器console.log就失效了,百度说如下图就能打印了,该勾选的勾选了,就是打印无效 后来发现是眼睛边上的那个输入框将打印的内容给过滤掉了,将输入框中的内容删掉就好了 Filte ...
- vscode开发vue3+ts环境搭建
一.开发环境准备 1.nvm 2.vscode 3.node 4.chome 5.git 6.npm install -g yarn tyarn 7.tyarn global add commitiz ...
- docker 创建 network 指定ip 段
docker network create -d bridge --gateway 172.22.0.1 --subnet 172.22.0.0/16 zk_default
- Mac系统中安装Nginx
一.前言 本文介绍一下,如何在Mac系统中安装Nginx,把详细过程记录下来,方便以后查看,也方便大家学习. 二.正文 1.安装 Homebrew homebrew是什么?它是Mac中的一款软件包 ...
- 关于easyocr、paddleocr、cnocr之比较
关于easyocr.paddleocr.cnocr之比较 EasyOCR 是一个使用 Java 语言实现的 OCR 识别引擎(基于Tesseract).借助几个简单的API,即能使用Java语言完成图 ...
- Linux 第十节( APACHE )
Apache 基金会,软件 http 协议 httpd 软件包 vim /etc/httpd/conf/httpd.conf //网站配置主文件 systemctl restart http ...