所有的内置函数
compile, exec and eval
exec
x = [1, 2]
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)
[1, 2]
但现在,我们可以研究代码对象的样子。让我们来看看它的一些属性:
>>> code_obj.co_code
b'dx00dx01gx02Zx00ex01ex00x83x01x01x00dx02Sx00'
>>> code_obj.co_filename
'myfile.py'
>>> code_obj.co_names
('x', 'print')
>>> code_obj.co_consts
(1, 2, None)
可以看到变量x
和print
, 常数1
和2
以及有关代码文件的更多信息。它具有直接在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.parse
和compile
期望评估此代码的值,而不是像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
只包含x
和y
,而globals
包含magic_number
和 function
本身。
input 和 print:面包和黄油
input
和print
可能是您最早知道的Python的两个函数。它们看起来很直接,不是吗? input
输入一行文本,然后print
将其打印出来,就这么简单。对吗? input
和print
可能有更多你不知道的功能。 下面是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)的形式存储和处理的——这也是终端真正工作的方式。
如果要查看input
和print
下的字节:需要查看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 种数据类型的最低公分母:float
和complex
。 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
。
现在,在这一点上,您可能想知道“为什么?为什么bool
是int
子类? 这主要是因为兼容性原因。从历史上看,Python中的逻辑真/假操作仅用于0表示假和1表示真。在Python版本2.2中,布尔值True
和False
被添加到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
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/237533.html