创新互联Python教程:编程常见问题

编程常见问题

一般问题

python 有没有提供带有断点、单步调试等功能的源码级调试器?

有的。

成都创新互联致力于网站制作、成都做网站,成都网站设计,集团网站建设等服务标准化,推过标准化降低中小企业的建站的成本,并持续提升建站的定制化服务水平进行质量交付,让企业网站从市场竞争中脱颖而出。 选择成都创新互联,就选择了安全、稳定、美观的网站建设服务!

以下介绍了一些 Python 的调试器,用内置函数 breakpoint() 即可切入这些调试器中。

pdb 模块是一个简单但是够用的控制台模式 Python 调试器。 它是标准 Python 库的一部分,并且 已收录于库参考手册。 你也可以通过使用 pdb 代码作为样例来编写你自己的调试器。

The IDLE interactive development environment, which is part of the standard Python distribution (normally available as Tools/scripts/idle3), includes a graphical debugger.

PythonWin 是一种 Python IDE,其中包含了一个基于 pdb 的 GUI 调试器。PythonWin 的调试器会为断点着色,并提供了相当多的超酷特性,例如调试非 PythonWin 程序等。PythonWin 是 pywin32 项目的组成部分,也是 ActivePython 发行版的组成部分。

Eric is an IDE built on PyQt and the Scintilla editing component.

trepan3k 是一个类似 gdb 的调试器。

Visual Studio Code 是包含了调试工具的 IDE,并集成了版本控制软件。

有许多商业 Python IDE 都包含了图形化调试器。包括:

  • Wing IDE

  • Komodo IDE

  • PyCharm

是否有能帮助寻找漏洞或执行静态分析的工具?

有的。

Pylint and Pyflakes do basic checking that will help you catch bugs sooner.

静态类型检查器,例如 Mypy 、 Pyre 和 Pytype 可以检查Python源代码中的类型提示。

如何由 Python 脚本创建能独立运行的二进制程序?

如果只是想要一个独立的程序,以便用户不必预先安装 Python 即可下载和运行它,则不需要将 Python 编译成 C 代码。有许多工具可以检测程序所需的模块,并将这些模块与 Python 二进制程序捆绑在一起生成单个可执行文件。

One is to use the freeze tool, which is included in the Python source tree as Tools/freeze. It converts Python byte code to C arrays; with a C compiler you can embed all your modules into a new program, which is then linked with the standard Python modules.

它的工作原理是递归扫描源代码,获取两种格式的 import 语句,并在标准 Python 路径和源码目录(用于内置模块)检索这些模块。然后,把这些模块的 Python 字节码转换为 C 代码(可以利用 marshal 模块转换为代码对象的数组初始化器),并创建一个定制的配置文件,该文件仅包含程序实际用到的内置模块。然后,编译生成的 C 代码并将其与 Python 解释器的其余部分链接,形成一个自给自足的二进制文件,其功能与 Python 脚本代码完全相同。

下列包可以用于帮助创建控制台和 GUI 的可执行文件:

  • Nuitka (跨平台)

  • PyInstaller (Cross-platform)

  • PyOxidizer (跨平台)

  • cx_Freeze (跨平台)

  • py2app (仅限 macOS)

  • py2exe (Windows only)

是否有 Python 编码标准或风格指南?

有的。 标准库模块所要求的编码风格记录于 PEP 8 之中。

语言核心内容

变量明明有值,为什么还会出现 UnboundLocalError?

It can be a surprise to get the UnboundLocalError in previously working code when it is modified by adding an assignment statement somewhere in the body of a function.

以下代码:

 
 
 
 
  1. >>> x = 10
  2. >>> def bar():
  3. ... print(x)
  4. ...
  5. >>> bar()
  6. 10

正常工作,但是以下代码

 
 
 
 
  1. >>> x = 10
  2. >>> def foo():
  3. ... print(x)
  4. ... x += 1

results in an UnboundLocalError:

 
 
 
 
  1. >>> foo()
  2. Traceback (most recent call last):
  3. ...
  4. UnboundLocalError: local variable 'x' referenced before assignment

原因就是,当对某作用域内的变量进行赋值时,该变量将成为该作用域内的局部变量,并覆盖外部作用域中的同名变量。由于 foo 的最后一条语句为 x 分配了一个新值,编译器会将其识别为局部变量。因此,前面的 print(x) 试图输出未初始化的局部变量,就会引发错误。

在上面的示例中,可以将外部作用域的变量声明为全局变量以便访问:

 
 
 
 
  1. >>> x = 10
  2. >>> def foobar():
  3. ... global x
  4. ... print(x)
  5. ... x += 1
  6. ...
  7. >>> foobar()
  8. 10

与类和实例变量貌似但不一样,其实以上是在修改外部作用域的变量值,为了提示这一点,这里需要显式声明一下。

 
 
 
 
  1. >>> print(x)
  2. 11

你可以使用 nonlocal 关键字在嵌套作用域中执行类似的操作:

 
 
 
 
  1. >>> def foo():
  2. ... x = 10
  3. ... def bar():
  4. ... nonlocal x
  5. ... print(x)
  6. ... x += 1
  7. ... bar()
  8. ... print(x)
  9. ...
  10. >>> foo()
  11. 10
  12. 11

Python 的局部变量和全局变量有哪些规则?

函数内部只作引用的 Python 变量隐式视为全局变量。如果在函数内部任何位置为变量赋值,则除非明确声明为全局变量,否则均将其视为局部变量。

起初尽管有点令人惊讶,不过考虑片刻即可释然。一方面,已分配的变量要求加上 global 可以防止意外的副作用发生。另一方面,如果所有全局引用都要加上 global ,那处处都得用上 global 了。那么每次对内置函数或导入模块中的组件进行引用时,都得声明为全局变量。这种杂乱会破坏 global 声明用于警示副作用的有效性。

为什么在循环中定义的参数各异的 lambda 都返回相同的结果?

假设用 for 循环来定义几个取值各异的 lambda(即便是普通函数也一样):

 
 
 
 
  1. >>> squares = []
  2. >>> for x in range(5):
  3. ... squares.append(lambda: x**2)

以上会得到一个包含5个 lambda 函数的列表,这些函数将计算 x**2。大家或许期望,调用这些函数会分别返回 014916。然而,真的试过就会发现,他们都会返回 16

 
 
 
 
  1. >>> squares[2]()
  2. 16
  3. >>> squares[4]()
  4. 16

这是因为 x 不是 lambda 函数的内部变量,而是定义于外部作用域中的,并且 x 是在调用 lambda 时访问的——而不是在定义时访问。循环结束时 x 的值是 4 ,所以此时所有的函数都将返回 4**2 ,即 16 。通过改变 x 的值并查看 lambda 的结果变化,也可以验证这一点。

 
 
 
 
  1. >>> x = 8
  2. >>> squares[2]()
  3. 64

为了避免发生上述情况,需要将值保存在 lambda 局部变量,以使其不依赖于全局 x 的值:

 
 
 
 
  1. >>> squares = []
  2. >>> for x in range(5):
  3. ... squares.append(lambda n=x: n**2)

以上 n=x 创建了一个新的 lambda 本地变量 n,并在定义 lambda 时计算其值,使其与循环当前时点的 x 值相同。这意味着 n 的值在第 1 个 lambda 中为 0 ,在第 2 个 lambda 中为 1 ,在第 3 个中为 2,依此类推。因此现在每个 lambda 都会返回正确结果:

 
 
 
 
  1. >>> squares[2]()
  2. 4
  3. >>> squares[4]()
  4. 16

请注意,上述表现并不是 lambda 所特有的,常规的函数也同样适用。

如何跨模块共享全局变量?

在单个程序中跨模块共享信息的规范方法是创建一个特殊模块(通常称为 config 或 cfg)。只需在应用程序的所有模块中导入该 config 模块;然后该模块就可当作全局名称使用了。因为每个模块只有一个实例,所以对该模块对象所做的任何更改将会在所有地方得以体现。 例如:

config.py:

 
 
 
 
  1. x = 0 # Default value of the 'x' configuration setting

mod.py:

 
 
 
 
  1. import config
  2. config.x = 1

main.py:

 
 
 
 
  1. import config
  2. import mod
  3. print(config.x)

Note that using a module is also the basis for implementing the singleton design pattern, for the same reason.

导入模块的“最佳实践”是什么?

通常请勿使用 from modulename import * 。因为这会扰乱 importer 的命名空间,且会造成未定义名称更难以被 Linter 检查出来。

请在代码文件的首部就导入模块。这样代码所需的模块就一目了然了,也不用考虑模块名是否在作用域内的问题。每行导入一个模块则增删起来会比较容易,每行导入多个模块则更节省屏幕空间。

按如下顺序导入模块就是一种好做法:

  1. standard library modules — e.g. sys, os, argparse, re

  2. third-party library modules (anything installed in Python’s site-packages directory) — e.g. dateutil, requests, PIL.Image

  3. locally developed modules

为了避免循环导入引发的问题,有时需要将模块导入语句移入函数或类的内部。Gordon McMillan 的说法如下:

当两个模块都采用 “import ” 的导入形式时,循环导入是没有问题的。但如果第 2 个模块想从第 1 个模块中取出一个名称(”from module import name”)并且导入处于代码的最顶层,那导入就会失败。原因是第 1 个模块中的名称还不可用,这时第 1 个模块正忙于导入第 2 个模块呢。

如果只是在一个函数中用到第 2 个模块,那这时将导入语句移入该函数内部即可。当调用到导入语句时,第 1 个模块将已经完成初始化,第 2 个模块就可以进行导入了。

如果某些模块是平台相关的,可能还需要把导入语句移出最顶级代码。这种情况下,甚至有可能无法导入文件首部的所有模块。于是在对应的平台相关代码中导入正确的模块,就是一种不错的选择。

只有为了避免循环导入问题,或有必要减少模块初始化时间时,才把导入语句移入类似函数定义内部的局部作用域。如果根据程序的执行方式,许多导入操作不是必需的,那么这种技术尤其有用。如果模块仅在某个函数中用到,可能还要将导入操作移入该函数内部。请注意,因为模块有一次初始化过程,所以第一次加载模块的代价可能会比较高,但多次加载几乎没有什么花费,代价只是进行几次字典检索而已。即使模块名超出了作用域,模块在 sys.modules 中也是可用的。

为什么对象之间会共享默认值?

新手程序员常常中招这类 Bug。请看以下函数:

 
 
 
 
  1. def foo(mydict={}): # Danger: shared reference to one dict for all calls
  2. ... compute something ...
  3. mydict[key] = value
  4. return mydict

第一次调用此函数时, mydict 中只有一个数据项。第二次调用 mydict 则会包含两个数据项,因为 foo() 开始执行时, mydict 中已经带有一个数据项了。

大家往往希望,函数调用会为默认值创建新的对象。但事实并非如此。默认值只会在函数定义时创建一次。如果对象发生改变,就如上例中的字典那样,则后续调用该函数时将会引用这个改动的对象。

按照定义,不可变对象改动起来是安全的,诸如数字、字符串、元组和 None 之类。而可变对象的改动则可能引起困惑,例如字典、列表和类实例等。

因此,不把可变对象用作默认值是一种良好的编程做法。而应采用 None 作为默认值,然后在函数中检查参数是否为 None 并新建列表、字典或其他对象。例如,代码不应如下所示:

 
 
 
 
  1. def foo(mydict={}):
  2. ...

而应这么写:

 
 
 
 
  1. def foo(mydict=None):
  2. if mydict is None:
  3. mydict = {} # create a new dict for local namespace

参数默认值的特性有时会很有用处。 如果有个函数的计算过程会比较耗时,有一种常见技巧是将每次函数调用的参数和结果缓存起来,并在同样的值被再次请求时返回缓存的值。这种技巧被称为“memoize”,实现代码可如下所示:

 
 
 
 
  1. # Callers can only provide two parameters and optionally pass _cache by keyword
  2. def expensive(arg1, arg2, *, _cache={}):
  3. if (arg1, arg2) in _cache:
  4. return _cache[(arg1, arg2)]
  5. # Calculate the value
  6. result = ... expensive computation ...
  7. _cache[(arg1, arg2)] = result # Store result in the cache
  8. return result

也可以不用参数默认值来实现,而是采用全局的字典变量;这取决于个人偏好。

如何将可选参数或关键字参数从一个函数传递到另一个函数?

请利用函数参数列表中的标识符 *** 归集实参;结果会是元组形式的位置实参和字典形式的关键字实参。然后就可利用 *** 在调用其他函数时传入这些实参:

 
 
 
 
  1. def f(x, *args, **kwargs):
  2. ...
  3. kwargs['width'] = '14.3c'
  4. ...
  5. g(x, *args, **kwargs)

形参和实参之间有什么区别?

Parameters are defined by the names that appear in a function definition, whereas arguments are the values actually passed to a function when calling it. Parameters define what kind of arguments a function can accept. For example, given the function definition:

 
 
 
 
  1. def func(foo, bar=None, **kwargs):
  2. pass

foobarkwargsfunc 的形参。 不过在调用 func 时,例如:

 
 
 
 
  1. func(42, bar=314, extra=somevar)

42314somevar 则是实参。

为什么修改列表 ‘y’ 也会更改列表 ‘x’?

如果代码编写如下:

 
 
 
 
  1. >>> x = []
  2. >>> y = x
  3. >>> y.append(10)
  4. >>> y
  5. [10]
  6. >>> x
  7. [10]

或许大家很想知道,为什么在 y 中添加一个元素时, x 也会改变。

产生这种结果有两个因素:

  1. 变量只是指向对象的一个名称。执行 y = x 并不会创建列表的副本——而只是创建了一个新变量 y,并指向 x 所指的同一对象。这就意味着只存在一个列表对象,xy 都是对它的引用。

  2. 列表属于 mutable 对象,这意味着它的内容是可以修改的。

在调用 append() 之后,该可变对象的内容由 [] 变为 [10]。由于 x 和 y 这两个变量引用了同一对象,因此用其中任意一个名称所访问到的都是修改后的值 [10]

如果把赋给 x 的对象换成一个不可变对象:

 
 
 
 
  1. >>> x = 5 # ints are immutable
  2. >>> y = x
  3. >>> x = x + 1 # 5 can't be mutated, we are creating a new object here
  4. >>> x
  5. 6
  6. >>> y
  7. 5

可见这时 xy 就不再相等了。因为整数是 immutable 对象,在执行 x = x + 1 时,并不会修改整数对象 5,给它加上 1;而是创建了一个新的对象(整数对象 6 )并将其赋给 x (也就是改变了 x 所指向的对象)。在赋值完成后,就有了两个对象(整数对象 65 )和分别指向他俩的两个变量( x 现在指向 6y 仍然指向 5 )。

Some operations (for example y.append(10) and y.sort()) mutate the object, whereas superficially similar operations (for example y = y + [10] and sorted(y)) create a new object. In general in Python (and in all cases in the standard library) a method that mutates an object will return None to help avoid getting the two types of operations confused. So if you mistakenly write y.sort() thinking it will give you a sorted copy of y, you’ll instead end up with None, which will likely cause your program to generate an easily diagnosed error.

不过还存在一类操作,用不同的类型执行相同的操作有时会发生不同的行为:即增量赋值运算符。例如,+= 会修改列表,但不会修改元组或整数(a_list += [1, 2, 3]a_list.extend([1, 2, 3]) 同样都会改变 a_list,而 some_tuple += (1, 2, 3)some_int += 1 则会创建新的对象)。

换而言之:

  • 对于一个可变对象( list 、 dict 、 set 等等),可以利用某些特定的操作进行修改,所有引用它的变量都会反映出改动情况。

  • 对于一个不可变对象( str 、 int 、 tuple 等),所有引用它的变量都会给出相同的值,但所有改变其值的操作都将返回一个新的对象。

如要知道两个变量是否指向同一个对象,可以利用 is 运算符或内置函数 id()。

如何编写带有输出参数的函数(按照引用调用)?

请记住,Python 中的实参是通过赋值传递的。由于赋值只是创建了对象的引用,所以调用方和被调用方的参数名都不存在别名,本质上也就不存在按引用调用的方式。通过以下几种方式,可以得到所需的效果。

  1. 返回一个元组:

       
       
       
       
    1. >>> def func1(a, b):
    2. ... a = 'new-value' # a and b are local names
    3. ... b = b + 1 # assigned to new objects
    4. ... return a, b # return new values
    5. ...
    6. >>> x, y = 'old-value', 99
    7. >>> func1(x, y)
    8. ('new-value', 100)

    这差不多是最明晰的解决方案了。

  2. 使用全局变量。这不是线程安全的方案,不推荐使用。

  3. 传递一个可变(即可原地修改的) 对象:

       
       
       
       
    1. >>> def func2(a):
    2. ... a[0] = 'new-value' # 'a' references a mutable list
    3. ... a[1] = a[1] + 1 # changes a shared object
    4. ...
    5. >>> args = ['old-value', 99]
    6. >>> func2(args)
    7. >>> args
    8. ['new-value', 100]
  4. 传入一个接收可变对象的字典:

       
       
       
       
    1. >>> def func3(args):
    2. ... args['a'] = 'new-value' # args is a mutable dictionary
    3. ... args['b'] = args['b'] + 1 # change it in-place
    4. ...
    5. >>> args = {'a': 'old-value', 'b': 99}
    6. >>> func3(args)
    7. >>> args
    8. {'a': 'new-value', 'b': 100}
  5. 或者把值用类实例封装起来:

       
       
       
       
    1. >>> class Namespace:
    2. ... def __init__(self, /, **args):
    3. ... for key, value in args.items():
    4. ... setattr(self, key, value)
    5. ...
    6. >>> def func4(args):
    7. ... args.a = 'new-value' # args is a mutable Namespace
    8. ... args.b = args.b + 1 # change object in-place
    9. ...
    10. >>> args = Namespace(a='old-value', b=99)
    11. >>> func4(args)
    12. >>> vars(args)
    13. {'a': 'new-value', 'b': 100}

    没有什么理由要把问题搞得这么复杂。

最佳选择就是返回一个包含多个结果值的元组。

如何在 Python 中创建高阶函数?

有两种选择:嵌套作用域、可调用对象。假定需要定义 linear(a,b) ,其返回结果是一个计算出 a*x+b 的函数 f(x)。 采用嵌套作用域的方案如下:

 
 
 
 
  1. def linear(a, b):
  2. def result(x):
  3. return a * x + b
  4. return result

或者可采用可调用对象:

 
 
 
 
  1. class linear:
  2. def __init__(self, a, b):
  3. self.a, self.b = a, b
  4. def __call__(self, x):
  5. return self.a * x + self.b

采用这两种方案时:

 
 
 
 
  1. taxes = linear(0.3, 2)

都会得到一个可调用对象,可实现 taxes(10e6) == 0.3 * 10e6 + 2

可调用对象的方案有个缺点,就是速度稍慢且生成的代码略长。不过值得注意的是,同一组可调用对象能够通过继承来共享签名(类声明):

 
 
 
 
  1. class exponential(linear):
  2. # __init__ inherited
  3. def __call__(self, x):
  4. return self.a * (x ** self.b)

对象可以为多个方法的运行状态进行封装:

 
 
 
 
  1. class counter:
  2. value = 0
  3. def set(self, x):
  4. self.value = x
  5. def up(self):
  6. self.value = self.value + 1
  7. def down(self):
  8. self.value = self.value - 1
  9. count = counter()
  10. inc, dec, reset = count.up, count.down, count.set

以上 inc()dec()reset() 的表现,就如同共享了同一计数变量一样。

如何复制 Python 对象?

一般情况下,用 copy.copy() 或 copy.deepcopy() 基本就可以了。并不是所有对象都支持复制,但多数是可以的。

某些对象可以用更简便的方法进行复制。比如字典对象就提供了 copy() 方法:

 
 
 
 
  1. newdict = olddict.copy()

序列可以用切片操作进行复制:

 
 
 
 
  1. new_l = l[:]

如何找到对象的方法或属性?

For an instance x of a user-defined class, dir(x) returns an alphabetized list of the names containing the instance attributes and methods and attributes defined by its class.

如何用代码获取对象的名称?

一般而言这是无法实现的,因为对象并不存在真正的名称。赋值本质上是把某个名称绑定到某个值上;defclass 语句同样如此,只是值换成了某个可调用对象。比如以下代码:

 
 
 
 
  1. >>> class A:
  2. ... pass
  3. ...
  4. >>> B = A
  5. >>> a = B()
  6. >>> b = a
  7. >>> print(b)
  8. <__main__.A object at 0x16D07CC>
  9. >>> print(a)
  10. <__main__.A object at 0x16D07CC>

Arguably the class has a name: even though it is bound to two names and invoked through the name B the created instance is still reported as an instance of class A. However, it is impossible to say whether the instance’s name is a or b, since both names are bound to the same value.

代码一般没有必要去“知晓”某个值的名称。通常这种需求预示着还是改变方案为好,除非真的是要编写内审程序。

在 comp.lang.python 中,Fredrik Lundh 在回答这样的问题时曾经给出过一个绝佳的类比:

这就像要知道家门口的那只猫的名字一样:猫(对象)自己不会说出它的名字,它根本就不在乎自己叫什么——所以唯一方法就是问一遍你所有的邻居(命名空间),这是不是他们家的猫(对象)……

……并且如果你发现它有很多名字或根本没有名字,那也不必惊讶!

逗号运算符的优先级是什么?

逗号不是 Python 的运算符。 请看以下例子:

 
 
 
 
  1. >>> "a" in "b", "a"
  2. (False, 'a')

由于逗号不是运算符,而只是表达式之间的分隔符,因此上述代码就相当于:

 
 
 
 
  1. ("a" in "b"), "a"

而不是:

 
 
 
 
  1. "a" in ("b", "a")

对于各种赋值运算符( =+= 等)来说同样如此。他们并不是真正的运算符,而只是赋值语句中的语法分隔符。

是否提供等价于 C 语言 “?:” 三目运算符的东西?

有的。语法如下:

 
 
 
 
  1. [on_true] if [expression] else [on_false]
  2. x, y = 50, 25
  3. small = x if x < y else y

在 Python 2.5 引入上述语法之前,通常的做法是使用逻辑运算符:

 
 
 
 
  1. [expression] and [on_true] or [on_false]

然而这种做法并不保险,因为当 on_true 为布尔值“假”时,结果将会出错。所以肯定还是采用 ... if ... else ... 形式为妙。

是否可以用 Python 编写让人眼晕的单行程序?

Yes. Usually this is done by nesting lambda within lambda. See the following three examples, slightly adapted from Ulf Bartelt:

 
 
 
 
  1. from functools import reduce
  2. # Primes < 1000
  3. print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
  4. map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))))
  5. # First 10 Fibonacci numbers
  6. print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:
  7. f(x,f), range(10))))
  8. # Mandelbrot set
  9. print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+'\n'+y,map(lambda y,
  10. Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
  11. Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
  12. i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y
  13. >=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr(
  14. 64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy
  15. ))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24))
  16. # \___ ___/ \___ ___/ | | |__ lines on screen
  17. # V V | |______ columns on screen
  18. # | | |__________ maximum of "iterations"
  19. # | |_________________ range on y axis
  20. # |____________________________ range on x axis

请不要在家里尝试,骚年!

函数形参列表中的斜杠(/)是什么意思?

A slash in the argument list of a function denotes that the parameters prior to it are positional-only. Positional-only parameters are the ones without an externally usable name. Upon calling a function that accepts positional-only parameters, arguments are mapped to parameters based solely on their position. For example, divmod() is a function that accepts positional-only parameters. Its documentation looks like this:

 
 
 
 
  1. >>> help(divmod)
  2. Help on built-in function divmod in module builtins:
  3. divmod(x, y, /)
  4. Return the tuple (x//y, x%y). Invariant: div*y + mod == x.

形参列表尾部的斜杠说明,两个形参都是仅限位置形参。因此,用关键字参数调用 divmod() 将会引发错误:

 
 
 
 
  1. >>> divmod(x=3, y=4)
  2. Traceback (most recent call last):
  3. File "", line 1, in
  4. TypeError: divmod() takes no keyword arguments

数字和字符串

如何给出十六进制和八进制整数?

要给出八进制数,需在八进制数值前面加上一个零和一个小写或大写字母 “o” 作为前缀。例如,要将变量 “a” 设为八进制的 “10” (十进制的 8),写法如下:

 
 
 
 
  1. >>> a = 0o10
  2. >>> a
  3. 8

十六进制数也很简单。只要在十六进制数前面加上一个零和一个小写或大写的字母 “x”。十六进制数中的字母可以为大写或小写。比如在 Python 解释器中输入:

 
 
 
 
  1. >>> a = 0xa5
  2. >>> a
  3. 165
  4. >>> b = 0XB2
  5. >>> b
  6. 178

为什么 -22 // 10 会返回 -3 ?

这主要是为了让 i % j 的正负与 j 一致,如果期望如此,且期望如下等式成立:

 
 
 
 
  1. i == (i // j) * j + (i % j)

那么整除就必须返回向下取整的结果。C 语言同样要求保持这种一致性,于是编译器在截断 i // j 的结果时需要让 i % j 的正负与 i 一致。

对于 i % j 来说 j 为负值的应用场景实际上是非常少的。 而 j 为正值的情况则非常多,并且实际上在所有情况下让 i % j 的结果为 >= 0 会更有用处。 如果现在时间为 10 时,那么 200 小时前应是几时? -190 % 12 == 2 是有用处的;-190 % 12 == -10 则是会导致意外的漏洞。

我如何获得 int 字面属性而不是 SyntaxError ?

Trying to lookup an int literal attribute in the normal manner gives a SyntaxError because the period is seen as a decimal point:

 
 
 
 
  1. >>> 1.__class__
  2. File "", line 1
  3. 1.__class__
  4. ^
  5. SyntaxError: invalid decimal literal

解决办法是用空格或括号将字词与句号分开。

 
 
 
 
  1. >>> 1 .__class__
  2. >>> (1).__class__

如何将字符串转换为数字?

对于整数,可使用内置的 int() 类型构造器,例如 int('144') == 144。 类似地,可使用 float() 转换为浮点数,例如 float('144') == 144.0

默认情况下,这些操作会将数字按十进制来解读,因此 int('0144') == 144 为真值,而 int('0x144') 会引发 ValueError。 int(string, base) 接受第二个可选参数指定转换的基数,例如 int( '0x144', 16) == 324。 如果指定基数为 0,则按 Python 规则解读数字:前缀 ‘0o’ 表示八进制,而 ‘0x’ 表示十六进制。

如果只是想把字符串转为数字,请不要使用内置函数 eval()。 eval() 的速度慢很多且存在安全风险:别人可能会传入带有不良副作用的 Python 表达式。比如可能会传入 __import__('os').system("rm -rf $HOME") ,这会把 home 目录给删了。

eval() 还有把数字解析为 Python 表达式的后果,因此如 eval('09') 将会导致语法错误,因为 Python 不允许十进制数带有前导 ‘0’(’0’ 除外)。

如何将数字转换为字符串?

To convert, e.g., the number 144 to the string '144', use the built-in type constructor str(). If you want a hexadecimal or octal representation, use the built-in functions hex() or oct(). For fancy formatting, see the 格式字符串字面值 and 格式字符串语法 sections, e.g. "{:04d}".format(144) yields '0144' and "{:.3f}".format(1.0/3.0) yields '0.333'.

如何修改字符串?

无法修改,因为字符串是不可变对象。 在大多数情况下,只要将各个部分组合起来构造出一个新字符串即可。如果需要一个能原地修改 Unicode 数据的对象,可以试试 io.StringIO 对象或 array 模块:

 
 
 
 
  1. >>> import io
  2. >>> s = "Hello, world"
  3. >>> sio = io.StringIO(s)
  4. >>> sio.getvalue()
  5. 'Hello, world'
  6. >>> sio.seek(7)
  7. 7
  8. >>> sio.write("there!")
  9. 6
  10. >>> sio.getvalue()
  11. 'Hello, there!'
  12. >>> import array
  13. >>> a = array.array('u', s)
  14. >>> print(a)
  15. array('u', 'Hello, world')
  16. >>> a[0] = 'y'
  17. >>> print(a)
  18. array('u', 'yello, world')
  19. >>> a.tounicode()
  20. 'yello, world'

如何使用字符串调用函数/方法?

有多种技巧可供选择。

  • 最好的做法是采用一个字典,将字符串映射为函数。其主要优势就是字符串不必与函数名一样。这也是用来模拟 case 结构的主要技巧:

       
       
       
       
    1. def a():
    2. pass
    3. def b():
    4. pass
    5. dispatch = {'go':

      本文标题:创新互联Python教程:编程常见问题
      转载来于:http://www.shufengxianlan.com/qtweb/news3/52203.html

      网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

      广告

      声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联