位置决定语义

在下面的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语法中的左值、右值和字符的更多相关文章

  1. i++和++i以及左值,右值

    左值(LValue)和右值(RValue)的一个快捷记法是赋值运算,左值是赋值运算左边的值,右值就是右边(=,=废话).例如: int a = 5; a就是左值,5就是右值. 当然,如果真是这么个含义 ...

  2. C++ 左值 右值

    最近在研究C++ 左值 右值,搬运.收集了一些别人的资料,供自己记录和学习,若以后看到了更好的解释,会继续补充.(打“?”是我自己不明白的地方 )   参考:<Boost程序库探秘——深度解析C ...

  3. C++ 左值与右值 右值引用 引用折叠 => 完美转发

    左值与右值 什么是左值?什么是右值? 在C++里没有明确定义.看了几个版本,有名字的是左值,没名字的是右值.能被&取地址的是左值,不能被&取地址的是右值.而且左值与右值可以发生转换. ...

  4. 左值&右值

    一.引子 我们所谓的左值.右值,正确的说法应该是左值表达式.右值表达式. 因为C++的表达式不是左值就是右值. 在C中,左值指的是既能够出现在等号左边也能出现在等号右边的表达式,右值指的则是只能出现在 ...

  5. C++11之右值引用(一):从左值右值到右值引用

    C++98中规定了左值和右值的概念,但是一般程序员不需要理解的过于深入,因为对于C++98,左值和右值的划分一般用处不大,但是到了C++11,它的重要性开始显现出来. C++98标准明确规定: 左值是 ...

  6. C语言几个术语: 数据对象,左值,右值

    1. 数据对象 赋值表达式语句的目的是把值存储到内存位置上. 用于存储值的数据存储区域统称为数据对象. 2. 左值 左值是C语言的术语, 用于标识特定数据对象的名称或表达式. 对象指的是实际的数据存储 ...

  7. c++ 左值右值 函数模板

    1.先看一段代码,这就是一种函数模板的用法,但是红色的部分如果把a写成a++或者写成一个常量比如1,都是编译不过的,因为如果是a++的话,实际上首先是取得a的 值0,而0作为一个常量没有地址.写成1也 ...

  8. C和C指针小记(八)-操作符、左值右值

    1.移位操作符 移位操作符分为左移操作符(<<)和右移操纵符(>>) 对于无符号数:左右位移操作都是逻辑位移 对于有符号数:到底是采用逻辑位移还是算术位移取决于编译器.如果一个 ...

  9. 理解 ES6 语法中 yield 关键字的返回值

    在 ES6 中新增了生成器函数的语法,本文解释了生成器函数内 yield 关键字的返回值. 描述 根据语法规范,yield 关键字用来暂停和继续执行一个生成器函数.当外部调用生成器的 next() 方 ...

  10. Python 字典中一键对应多个值

    #字典的一键多值 print'方案一 list作为dict的值 值允许重复' d1={} key=1 value=2 d1.setdefault(key,[]).append(value) value ...

随机推荐

  1. 微信小程序if for

    1.控制代码的显示隐藏 1.wx:if="{{}}"判断是否需要渲染代码 <view wx:if="{{tiaojian===1}}">显示1< ...

  2. 肖sir ___性能测试____多线程

    一.理论 (一) (1)多线程是Python程序中实现多任务的一种方式(2)线程是程序执行的最小单位. (3)同属一个进程的多个线程共享进程所拥有的全部资源. (二)进程和线程对比 (1)关系对比: ...

  3. 如何实现chrome谷歌浏览器多开(独立环境 独立cookie)、改任务栏图标

    多开谷歌浏览器: 由于各种各样的原因,你可能需要在一个电脑登录某个平台,比如一个电脑登录3个公众号,或者3个知乎等等. 最简单的方案是,直接安装3个不同的浏览器,比如一个谷歌浏览器,一个火狐浏览器,一 ...

  4. WPF图片的缩放节省内存

    一.前言 正好项目用到要加载大量图片,虽然说可以使用WPF提供的自带的UI虚拟化功能,但是直接加载大量的图片到内存还是会 消耗很多的内存,而且WPF支持UI虚拟化的ListBox等容器的布局是Virt ...

  5. QT程序自动寻找依赖的DLL

    1.找到项目的生成目录,比如项目源码路径:E:\Qt\Login: 2.进入它的项目生成目录,拷贝出可执行程序,例如放置在 E:\QtApp中. 3.然后从开始菜单打开 Qt 命令行, a.输入命令 ...

  6. 微信小程序 switch

    微信小程序 switch 组件 的大小调整方法: 加上 zoom:数值   ==>数值在0到1之间

  7. desginer启动就直接卡死

    博主经验: 请不要开有道词典    请不要开有道词典         请不要开有道词典

  8. You are using pip version 8.1.2, however version 23.0 is available.You should consider upgrading via the 'pip install --upgrade pip' command.

    今天使用python2安装es模块时报错: 原因是pip(模块管理工具)版本过低,需先升级pip,再进行安装 先替换pip的镜像,默认镜像拉取慢,还可能会失败 cd ~;mkdir .pip;touc ...

  9. Python学习笔记(五)if分支语句

    一.if语法 示例: 1 money = int(input('请输入余额:')) 2 if money >= 5: 3 print('买得起!') 4 5 if True: 6 print(' ...

  10. 记一次Mybatis-Plus动态分表DynamicTableNameInnerInterceptor里无法动态替换表名的坑

    首先上源码 protected String changeTable(String sql) { ExceptionUtils.throwMpe(null == tableNameHandler, & ...