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

所有的内置函数

compile, exec and eval

exec

x = [12]
print(x)

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

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

>>> code = '''
... x = [1, 2]
... print(x)
... '''

>>> exec(code)

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

>>> 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:

>>> 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运行它。

>>> import ast
>>> code = '''
... x = [1, 2]
... print(x)
... '''

>>> tree = ast.parse(code)
>>> code_obj = compile(tree, 'myfile.py''exec')
>>> exec(code_obj)
[12]

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

>>> code_obj.co_code
b'dx00dx01gx02Zx00ex01ex00x83x01x01x00dx02Sx00'
>>> code_obj.co_filename
'myfile.py'
>>> code_obj.co_names
('x''print')
>>> code_obj.co_consts
(12None)

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

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

>>> import dis
>>> dis.dis('''
... x = [1, 2]
... print(x)
... '''
)

eval

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

>>> result = eval('1 + 1')
>>> result
2

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

>>> expr = ast.parse('1 + 1', mode='eval')
>>> code_obj = compile(expr, '<code>''eval')
>>> eval(code_obj)
2

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

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

def double(number):
    return number * 2

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

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

>>> 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始终指向模块作用域(也称为全局作用域)。因此,对于类似下面的代码:

magic_number = 42

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

locals只包含xy ,而globals 包含magic_number和 function本身。

input 和 print:面包和黄油

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

print(*values, sep=' ', end='n', file=sys.stdout, flush=False)

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

print(1,2,3,4, sep='n')

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

>>> for i in range(10):
...     print(i, end='')

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

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.buffer和 sys.stdin.buffer

>>> 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 ,将这些对象直接写入输出缓冲区,并返回返回的字节数。

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

>>> import sys
>>> '🐍'.encode()
b'xf0x9fx90x8d'   # utf-8 encoded string of the snake emoji
>>> _ = sys.stdout.buffer.write(b'xf0x9fx90x8d')
🐍

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

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

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

让我们来看看它们:

>>> 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中的几乎所有内容都是一个对象。

>>> 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用:

>>> 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()对对象进行哈希处理等功能,能够设置属性并获取其值,能够将对象转换为字符串表示形式等等。 它通过其预定义的“魔术方法”来完成所有这些工作:

>>> 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__

>>> 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面向对象,鸭子类型本质的骨干。

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


原文始发于微信公众号(一只大鸽子):Python内置(3)exec&eval、globals&locals、input&print、5个基本类型、object

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/237533.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!