前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python内置(3)exec&eval、globals&locals、input&print、5个基本类型、object

Python内置(3)exec&eval、globals&locals、input&print、5个基本类型、object

作者头像
一只大鸽子
发布2022-12-06 14:33:26
5240
发布2022-12-06 14:33:26
举报
文章被收录于专栏:Python基础、进阶与实战

所有的内置函数

compile, exec and eval

exec

代码语言:javascript
复制
x = [1, 2]
print(x)

保存为文件并运行,或者在解释器中直接运行,都会得到输出[1, 2]

除此之外,你还可以将程序作为字符串传递给内置函数exec

代码语言:javascript
复制
>>> code = '''
... x = [1, 2]
... print(x)
... '''
>>> exec(code)

exec (execute执行)的缩写。将一些Python代码作为字符串接收,并将其作为Python代码运行。默认情况下,exec将在与其余代码相同的范围内运行,这意味着它可以读取和操作变量,就像Python文件中的任何其他代码段一样。

代码语言:javascript
复制
>>> x = 5
>>> exec('print(x)')
5

exec允许您在运行时运行真正的动态代码。例如,您可以在运行时从互联网上下载Python文件,将其内容传递给exec,它将为您运行它。(但请不要这样做。

大多数情况下,你不需要使用exec。只是在某些动态行为时有用(例如在运行时创建动态类,如collections.namedtuple的行为)或者修改从Python文件读入的代码。

但是本节主要讨论的是exec如何实现动态行为的。 exec不仅接收字符串,也可以接收代码对象code object

代码对象是Python程序的“字节码”版本。它们不仅包含从Python代码生成的确切指令,而且还存储该代码段中使用的变量和常量等内容。 代码对象是从 AST(abstract syntax trees,抽象语法树)生成的,这些 AST 本身由在代码串上运行的分析器生成。 下面是一个例子: 1.首先使用ast模块从代码中生成一个AST:

代码语言:javascript
复制
>>> import ast
>>> code = '''
... x = [1, 2]
... print(x)
... '''
>>> tree = ast.parse(code)
>>> print(ast.dump(tree, indent=2))

Module(
  body=[
    Assign(
      targets=[
        Name(id='x', ctx=Store())],
      value=List(
        elts=[
          Constant(value=1),
          Constant(value=2)],
        ctx=Load())),
    Expr(
      value=Call(
        func=Name(id='print', ctx=Load()),
        args=[
          Name(id='x', ctx=Load())],
        keywords=[]))],
  type_ignores=[])

Assign 描述了x=[1, 2] Expr 描述了print(x)

tokenize 在将代码解析到 AST 之前,实际上有一个步骤:词法分析。 这是指根据Python的语法将源代码转换为令牌(token) python -m tokenize code.py

所以现在我们有一个 AST 对象。 2.我们可以使用内置函数compile将其编译为代码对象。然后,在代码对象上用exec运行它。

代码语言:javascript
复制
>>> import ast
>>> code = '''
... x = [1, 2]
... print(x)
... '''
>>> tree = ast.parse(code)
>>> code_obj = compile(tree, 'myfile.py', 'exec')
>>> exec(code_obj)
[1, 2]

但现在,我们可以研究代码对象的样子。让我们来看看它的一些属性:

代码语言:javascript
复制
>>> code_obj.co_code
b'd\x00d\x01g\x02Z\x00e\x01e\x00\x83\x01\x01\x00d\x02S\x00'
>>> code_obj.co_filename
'myfile.py'
>>> code_obj.co_names
('x', 'print')
>>> code_obj.co_consts
(1, 2, None)

可以看到变量xprint, 常数12 以及有关代码文件的更多信息。它具有直接在Python虚拟机中运行所需的所有信息,以便生成该输出。

dis Python中的dis模块可用于以人类可理解的方式可视化代码对象的内容,以帮助弄清楚Python在引擎盖下正在做什么。它接收字节码,常量和变量信息,并产生以下内容:

代码语言:javascript
复制
>>> import dis
>>> dis.dis('''
... x = [1, 2]
... print(x)
... ''')

eval

evalexec 非常类似,只是它只接受表达式(不接受语句或类似的语句集),并且不像exec ,它返回一个值,也就是表达式的结果。

代码语言:javascript
复制
>>> result = eval('1 + 1')
>>> result
2

你还可以用一条漫长详细的方式使用eval。您只需要告诉ast.parsecompile期望评估此代码的值,而不是像Python文件一样运行它。

代码语言:javascript
复制
>>> expr = ast.parse('1 + 1', mode='eval')
>>> code_obj = compile(expr, '<code>', 'eval')
>>> eval(code_obj)
2

globals 和 locals :所有东西存储的地方

尽管代码生成的code objects存储逻辑和常量,但它们不存储他们使用的变量的值。 下面用一段代码说明:

代码语言:javascript
复制
def double(number):
    return number * 2

这个函数的代码对象将存储常量2,以及变量名称number,但它显然不能包含number 的实际值,因为在函数实际运行之前不会给它。

那么,变量的值从何而来呢? 答案是Python将所有内容存储在与每个本地作用域关联的字典中。这意味着每段代码都有自己定义的“本地作用域”,该作用域在该代码内部使用locals()访问,其中包含与每个变量名称对应的值。 让我们试一下locals()

代码语言:javascript
复制
>>> value = 5
>>> def double(number):
...     return number * 2
...
>>> double(value)
10
>>> locals()
{'__name__': '__main__', '__doc__': None, '__package__': None,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
'__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>,
'value': 5, 'double': <function double at 0x7f971d292af0>}

看看最后一行:不仅value存储在localtic字典中,函数double本身也存储在那里!这就是Python存储其数据的方式。

globals非常相似,只是globals始终指向模块作用域(也称为全局作用域)。因此,对于类似下面的代码:

代码语言:javascript
复制
magic_number = 42

def function():
    x = 10
    y = 20
    print(locals())
    print(globals())

locals只包含xy ,而globals 包含magic_numberfunction本身。

input 和 print:面包和黄油

inputprint可能是您最早知道的Python的两个函数。它们看起来很直接,不是吗? input输入一行文本,然后print将其打印出来,就这么简单。对吗? inputprint可能有更多你不知道的功能。 下面是print的完整方法签名:

代码语言:javascript
复制
print(*values, sep=' ', end='\n', file=sys.stdout, flush=False)

其中*values表示任意数量参数, sep=' ' 默认用空格分隔。 如果你想改变分隔符,可以指定sep关键字,如'\n'

代码语言:javascript
复制
print(1,2,3,4, sep='\n')

end参数表示print末尾额外添加的字符,默认为换行。 如果你不希望在每次打印的末尾打印一个新行,你可以使用:end=''

代码语言:javascript
复制
>>> for i in range(10):
...     print(i, end='')

file 是想要打印到的文件,默认值为sys.stdout,打印到控制台。如果想写入文件,只需指定file关键字参数:

代码语言:javascript
复制
with open('myfile.txt', 'w') as f:
    print('Hello!', file=f)

博客介绍了一种花哨的操作,修改sys.stdout的值为某个文件,print会默认输出到文件中。为了方便恢复原始sys.stdout状态,作者还写了一个上下文管理器( context manager, 来自contextlib)方便用完后还原。 而contextlib已经定义了这个函数(上下文管理器),方便重定向stdout: from contextlib import redirect_stdout

flush是一个布尔值(True or False)。它所做的只是告诉print立即将文本写入控制台/文件,而不是将其放入缓冲区中。 这通常不会有太大区别,但是如果要将非常大的字符串打印到控制台,则可能需要将其设置True为以避免向用户显示输出时出现滞后。

现在我相信你们中的许多人都对input函数隐藏的秘密感兴趣,但没有。 input只需输入一个字符串以显示为提示符。

str, bytes, int, bool, float and complex: 5个基本类型

Python正好有6种原始数据类型(好吧,实际上只有5种,后面会说)。其中4个本质上是数字,另外2个是基于文本的。让我们先谈谈基于文本的内容,因为这会简单得多。

str是 Python 中最常见的数据类型之一。使用input方法获取用户输入会给出一个字符串,Python 中的所有其他数据类型都可以转换为字符串。这是必要的,因为所有计算机输入/输出都是文本形式的,无论是用户I/O还是文件I/O,这可能就是字符串无处不在的原因。

bytes另一方面,实际上是计算中所有I/O的基础。如果你了解计算机,你可能会知道所有数据都是以比特(bits)和字节(bytes)的形式存储和处理的——这也是终端真正工作的方式。

如果要查看inputprint下的字节:需要查看sys模块中的 I/O 缓冲区:sys.stdout.buffersys.stdin.buffer

代码语言:javascript
复制
>>> import sys
>>> print('Hello!')
Hello!
>>> 'Hello!\n'.encode()  # Produces bytes
b'Hello!\n'
>>> char_count = sys.stdout.buffer.write('Hello!\n'.encode())
Hello!
>>> char_count  # write() returns the number of bytes written to console
7

缓冲区对象接收bytes ,将这些对象直接写入输出缓冲区,并返回返回的字节数。

为了证明所有内容都只是下面的字节,让我们看另一个使用其字节打印表情符号的示例:

代码语言:javascript
复制
>>> import sys
>>> '🐍'.encode()
b'\xf0\x9f\x90\x8d'   # utf-8 encoded string of the snake emoji
>>> _ = sys.stdout.buffer.write(b'\xf0\x9f\x90\x8d')
🐍

int是另一种广泛使用的基本基元数据类型。它也是其他 2 种数据类型的最低公分母:floatcomplexcomplexfloat的超类型,而 float又是int 的超类型。

这意味着所有int 都作为floatcomplex 有效,但反过来不行。同样,所有 float也作为complex 有效。

如果你不知道,complex是Python中“复数”的实现。它们是数学中非常常见的工具。

让我们来看看它们:

代码语言:javascript
复制
>>> x = 5
>>> y = 5.0
>>> z = 5.0+0.0j
>>> type(x), type(y), type(z)
(<class 'int'>, <class 'float'>, <class 'complex'>)
>>> x == y == z  # All the same value
True
>>> y
5.0
>>> float(x)    # float(x) produces the same result as y
5.0
>>> z
(5+0j)
>>> complex(x)  # complex(x) produces the same result as z
(5+0j)

现在,我提到过一下,Python中实际上只有5种原始数据类型,而不是6种。这是因为, bool实际上不是一个原始数据类型 -- 它实际上是int

您可以通过查看这些类的mro属性来自己检查它。

mro代表“方法解析顺序”。它定义了查找在类上调用的方法的顺序。从本质上讲,方法调用首先在类本身中查找,如果它不存在,则在其父类中搜索它,然后在其父类中搜索它,一直到顶部object:。Python中的所有内容都继承自object .是的,Python中的几乎所有内容都是一个对象。

代码语言:javascript
复制
>>> int.mro()
[<class 'int'>, <class 'object'>]
>>> float.mro()
[<class 'float'>, <class 'object'>]
>>> complex.mro()
[<class 'complex'>, <class 'object'>]
>>> str.mro()
[<class 'str'>, <class 'object'>]
>>> bool.mro()
[<class 'bool'>, <class 'int'>, <class 'object'>]  # Look!

从它们的“祖先”可以看出,所有其他数据类型都不是任何东西的“子类”(除了object,它将永远存在)。而bool 继承自int

现在,在这一点上,您可能想知道“为什么?为什么boolint子类? 这主要是因为兼容性原因。从历史上看,Python中的逻辑真/假操作仅用于0表示假和1表示真。在Python版本2.2中,布尔值TrueFalse被添加到Python中,它们只是围绕这些整数值的包装器。到目前为止,事实一直保持不变。就这样。

但是,这也意味着,无论好坏,您都可以在将bool值当int用:

代码语言:javascript
复制
>>> import json
>>> data = {'a': 1, 'b': {'c': 2}}
>>> print(json.dumps(data))
{"a": 1, "b": {"c": 2}}
>>> print(json.dumps(data, indent=4))
{
    "a": 1,
    "b": {
        "c": 2
    }
}
>>> print(json.dumps(data, indent=True))
{
 "a": 1,
 "b": {
  "c": 2
 }
}

indent=True被当做indent=1

object:最基本的基类

object是所有类的基类。 object类定义了Python中对象的一些最基本的属性。诸如能够通过hash()对对象进行哈希处理等功能,能够设置属性并获取其值,能够将对象转换为字符串表示形式等等。 它通过其预定义的“魔术方法”来完成所有这些工作:

代码语言:javascript
复制
>>> dir(object)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__',
'__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__']

__getattr__方法访问属性obj.x__setattr____delattr__设置新属性和删除属性。 对象的哈希由预定义的__hash__方法生成, 对象的字符串表示形式来自__repr__

代码语言:javascript
复制
>>> object()  # This creates an object with no properties
<object object at 0x7f47aecaf210>  # defined in __repr__()
>>> class dummy(object):
...     pass
>>> x = dummy()
>>> x
<__main__.dummy object at 0x7f47aec510a0>  # functionality inherited from object
>>> hash(object())
8746615746334
>>> hash(x)
8746615722250
>>> x.__hash__()  # is the same as hash(x)
8746615722250

实际上,关于Python中的魔术方法还有很多话要说,因为它们构成了Python面向对象,鸭子类型本质的骨干。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-09-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一只大鸽子 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 所有的内置函数
    • compile, exec and eval
      • exec
      • eval
    • globals 和 locals :所有东西存储的地方
      • input 和 print:面包和黄油
        • str, bytes, int, bool, float and complex: 5个基本类型
          • object:最基本的基类
          相关产品与服务
          对象存储
          对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档