首页 > 编程语言 >python语法中的左值、右值和字符

python语法中的左值、右值和字符

时间:2022-12-09 20:45:53浏览次数:54  
标签:name 右值 python 左值 tsecer alias import mod

位置决定语义

在下面的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()

标签:name,右值,python,左值,tsecer,alias,import,mod
From: https://www.cnblogs.com/tsecer/p/16969956.html

相关文章

  • python基础-常用内置包
      内置包是python自带的一些功能模块,有需求时可以在自己文件中直接导入使用。 1.datetime包  python中的时间包,可以在业务开发中辅助我们处理时间信息;#datetime......
  • 教你用Python实现抽奖式随机提问
    案例介绍欢迎来的我的小院,我是霍大侠,恭喜你今天又要进步一点点了!我们来用Python编程实战案例,做一个简易的随机提问器。随机提问器主要实现随机,乱序,提取的功能。通过实战我......
  • python初步了解队列
    python初步了解队列队列是一种先入先出的数据结构单纯用列表来实现队列运用pop()函数,进行出队效率很低,因为在列表开头删除元素需要将其他元素往前移动一位.所以一般用......
  • python初步了解链表
    python数据结构——链表链表由一个个节点组成,每个节点包含自己的存储数据和下一个节点。单链表简单实现先创造一个类来表示节点与节点之间的关系classNode:def......
  • python初步了解栈
    python初步了解栈栈栈是一种后入先出的数据结构。python用列表实现堆栈非常容易,使用append函数,即可实现堆栈,pop()函数即可实现从栈顶取出元素。stack=[3,4,5]stac......
  • python序列
    python序列序列包括字符串,元组,列表序列的操作在三者中都适用,同时三者存在特定的操作操作符操作作用ain/notins判断元素是否在序列中s+t连接两个序......
  • python元组
    python元组元组具体格式如下:a=(1,2)以上两个都是元组,但是一般为了易读和方便,一般使用带小括号的方式。元组的创建:a=()x=tuple()print(type(a))print(type(x......
  • python字符串
    python学习字符串处理方法1.大小写转换函数作用str.lower()全小写str.upper()全大写str.capitalize()第一个字符大写str.swapcase()大写转小写,小......
  • python集合
    python集合集合同dict类似也由{}表示,但是他只包含键,而没有对应的值,同时元素也不能重复集合的创建只能用set():a=set()print(type(a))#<class'set'>内置方法(1)se......
  • python字典
    python字典字典由key和value组成,一个key对应一个value,且key不能重复,这样我们能通过key来访问value。我们可以通过以下两中方式创建一个空字典dic1={}dic2=dict()......