快乐学习
前程无忧、中华英才非你莫属!

Python问题解决系列

前言

当你学完Python,可以使用老师模板化的代码,完成一些特定领域的功能,完全没有问题,当你接触新需求的时候,感觉自己的Python 白学了,写写代码,哇,报错 了,抓头,写写代码,哇出来的效果不是我想要的,小朋友你是否有很多问号?

总结Python容易产生问号的大全参考于Python社区。

问题1:不要直接在函数中使用外部变量

x = 10
def foo():
    x += 1

#报错信息:
UnboundLocalError: local variable 'x' referenced before assignment

#报错原因:
#在Python中,仅在函数内引用的变量是隐式全局变量。
#如果在函数体内的任何位置为变量赋值,则除非明确声明为全局,否则将其视为局部值。

#解决:
x = 10
def foo():
    global x
    x += 1

#使用面向对象编程
a =2
class F:
    def foo(self):
        global a
        a = a + 2
        print(a)
if __name__ == '__main__':
    F().foo()

问题2:跨模块共享全局变量

config.py:
x = 0 #  “x”配置设置的默认值

mod.py:
import config
config.x = 1

main.py:
import config
import mod
print(config.x)
# 请注意,出于同样的原因,使用模块也是实现Singleton设计模式的基础

问题3:将可选或关键字参数传递给另一个函数

def g(x, *args, **kwargs):
    print(x,kwargs)

def f(x, *args, **kwargs):
    kwargs['width'] = '14.3c'
    g(x, *args, **kwargs)

if __name__ == '__main__':
    f(1)

问题4:为什么更改列表 'y' 也会更改列表 'x'?

x = []
y = x
y.append(10)
y # [10]
x # [10]

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

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

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

在调用 append() 之后,这个可变对象的内容由 [] 变为 [10]。 由于两个变量都指向同一对象,因此使用任何一个名称所访问到的都是修改后的值 [10]。

如果我们改为将不可变对象赋值给 x:

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

我们可以看到在此情况下 x 和 y 就不再相等了。

这是因为整数是 immutable 对象,当我们执行 x = x + 1 时我们并不是改变了 5 这个对象的值;而是创建了一个新的对象 (整数 6) 并将其赋值给 x (也就是改变了 x 所指向的对象)。

在赋值之后我们就有了两个对象 (整数 6 和 5) 以及分别指向它们的两个变量 (x 现在指向 6 而 y 仍然指向 5)。

某些操作 (例如 y.append(10) 和 y.sort()) 是改变原对象,而看上去相似的另一些操作 (例如 y = y + [10] 和 sorted(y)) 则是创建新对象。

通常在 Python 中 (以及在标准库的所有代码中) 会改变原对象的方法将返回 None 以帮助避免混淆这两种不同类型的操作。 因此如果你错误地使用了 y.sort() 并期望它将返回一个经过排序的 y 的副本,你得到的结果将会是 None,这将导致你的程序产生一个容易诊断的错误。

但是,还存在一类操作,不同的类型执行相同的操作会有不同的行为:那就是增强赋值运算符。
例如,+= 会原地改变列表,但不会改变元组或整数 (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()。

问题5:复制对象?

一般来说,通常情况下请尝试 copy.copy() 或 copy.deepcopy()。 不是所有对象都可以复制,但多数都是可以的。
某些对象可以方便地复制。 例如字典具有 copy() 方法:


newdict = olddict.copy()
序列可以通过切片来复制:
new_l = l[:]

问题6:-22 // 10返回-3?

这主要是为了让 i % j 的正负与 j 一致,如果你想要这样的结果,并且又想要:
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 则是会导致意外的漏洞。

问题7:找到对象的方法和属性

对于一个用户自定义类的实例 x,dir(x) 将返回一个按字母顺序排序的包含实例属性和方法及其类所定义的属性名称的列表

问题8:将字符串和数字相互转换

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

要将数字144转换为字符串“ 144”,请使用内置类型构造函数str()

问题9:指定位置修改字符串

因为字符串是不可变的。在大多数情况下,您应该简单地从要组装字符串的各个部分中构造一个新字符串。但是,如果您需要一个具有修改就地unicode数据能力的io.StringIO对象,请尝试使用一个对象或array 模块:

import io
s = "Hello, world"
sio = io.StringIO(s)
sio.getvalue() # 'Hello, world'

sio.seek(7) # 返回 7
sio.write("there!") # 返回6
sio.getvalue() # 'Hello, there!

import array
a = array.array('u', s)
print(a) # array('u', 'Hello, world')

a[0] = 'y'
print(a) # array('u', 'yello, world')
a.tounicode() # 'yello, world'

问题10:使用字符串调用函数/方法?

有各种各样的技术。

最好是使用将字符串映射到函数的字典。该技术的主要优点是字符串不需要与函数名称匹配。这也是用于模拟案例构造的主要技术:

def a():
    pass
def b():
    pass

dispatch = {'go': a, 'stop': b}  # 注意 funcs 缺少括号
dispatch['go']()  # 注意调用函数的尾随括号

#使用内置功能getattr():
import foo
getattr(foo, 'bar')()

问题11:高效字符串连接

str和bytes对象是不可变的,因此将许多字符串连接在一起效率不高,因为每次连接都会创建一个新对象。在一般情况下,总运行时成本在总字符串长度中是二次方的。

要累积许多str对象,建议的惯用法是将它们放入列表中并str.join()在最后调用:

chunks = []
for s in my_strings:
    chunks.append(s)
result = ''.join(chunks)

要累积许多bytes对象,建议的惯用法是bytearray使用就位串联(+=运算符)扩展对象:

result = bytearray()
for b in my_bytes_objects:
    result += b

问题12:元组和列表之间转换

类型构造函数tuple(seq)将任何序列(实际上是任何可迭代的)转换为具有相同顺序的相同项的元组,
把元组转换成列表,使用list函数即可。

问题13:负索引

Python序列用正数和负数索引。对于正数,0是第一个索引,1是第二个索引,依此类推。
对于负索引,-1是最后一个索引,-2是倒数第二个(倒数第二个)索引,依此类推。认为seq[-n]与相同seq[len(seq)-n]。使用负索引可能非常方便。例如S[:-1],除最后一个字符外的所有字符串,这对于从字符串中删除结尾的换行符很有用。

问题14:相反的顺序遍历序列

for x in reversed(sequence):
... # do something with x ..

问题15:从列表中删除重复项

mylist = list(set(mylist))

问题16:根据条件删除列表中多个元素

与删除重复项一样,使用删除条件明确地反向进行迭代是一种可能。但是,使用带隐式或显式正向迭代的切片替换更容易,更快捷。这是三个变体:

mylist[:] = filter(keep_function, mylist)
mylist[:] = (x for x in mylist if keep_condition)
mylist[:] = [x for x in mylist if keep_condition]

问题17:将方法应用于一系列对象

# 使用列表理解:
result = [obj.method() for obj in mylist]

问题18:按另一个列表中的值对一个列表排序

list1 = ["what", "I'm", "sorting", "by"]
list2 = ["something", "else", "to", "sort"]
pairs = sorted(zip(list1, list2))
# [("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
result = [x[1] for x in pairs]
print(result) # ['else', 'sort', 'to', 'something']

问题19: 循环中使用lambda表达式的问题

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

这将为您提供包含5个可以计算的lambda的列表x**2。你可能会想到的是,被调用时,他们将返回,分别为0,1, 4,9,和16。但是,当您实际尝试时,您会看到它们全部返回16:

发生这种情况是因为x它不是lambda的局部变量,而是在外部作用域中定义的,并且在调用lambda时将对其进行访问,而不是在定义时进行访问。在循环结束时,值为x is 4,因此所有函数现在都返回4**2,即16。您还可以通过更改的值来验证这一点,x并查看lambda的结果如何变化:

为了避免这种情况,您需要将值保存在lambda局部变量中,以使它们不依赖于global的值x:

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

for i in squares:
    print(i())
打赏
赞(1) 打赏
未经允许不得转载:同乐学堂 » Python问题解决系列

特别的技术,给特别的你!

联系QQ:1071235258QQ群:710045715

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫打赏

微信扫一扫打赏

error: Sorry,暂时内容不可复制!