python 编程常见问题

python编程常见问题:
https://docs.python.org/zh-cn/3/faq/programming.html#programming-faq

本文想对此做一些摘要。

1 作用域问题(当你对作用域中的变量进行赋值时,该变量将成为该作用域的局部变量,并在外部作用域中隐藏任何类似命名的变量)

当你对作用域中的变量进行赋值时,该变量将成为该作用域的局部变量,并在外部作用域中隐藏任何类似命名的变量。
解决:
使用 global 、nonlocal 去显式声明变量的作用域。

代码1

a=1
def func():
    a=2
    print(a)
func()
print(a)

代码1执行结果:

2
1

代码2

a=1
def func():
    print(a)
    a=2
func()
print(a)

代码2执行结果出现错误:

UnboundLocalError: local variable 'a' referenced before assignment

https://www.runoob.com/python/python-functions.html
匿名函数
python 使用 lambda 来创建匿名函数。

lambda只是一个表达式,函数体比def简单很多。
lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
lambda函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。
虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
语法
lambda函数的语法只包含一个语句,如下:

lambda [arg1 [,arg2,…argn]]:expression

2 如何跨模块共享全局变量(规范方法是创建一个特殊模块)

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

config.py:

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

mod.py:

import config
config.x = 1

main.py:

import config
import mod
print(config.x)

请注意,出于同样的原因,使用模块也是实现Singleton设计模式的基础。

3 好习惯:函数不使用可变对象作为默认值,除非用于缓存结果值技巧

这种类型的缺陷通常会惹恼新手程序员。考虑这个函数

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

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

函数调用经常被期望为默认值创建新的对象。 但实际情况并非如此。 默认值会在函数定义时一次性地创建。 如果对象发生改变,就如本示例中的字典那样,则对函数的后续调用将会引用这个被改变的对象。

按照定义,不可变对象例如数字、字符串、元组和 None 因为不可变所以是安全的。 对可变对象例如字典、列表和类实例的改变则可能造成迷惑。

由于这一特性,在编程中应遵循的一项好习惯是不使用可变对象作为默认值。 而应使用 None 作为默认值和函数中的值,检查值为 None 的形参并创建相应的列表、字典或其他可变对象。 例如,不要这样写:

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

而要这样写:

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

这一特性有时会很有用处。 当你有一个需要进行大量耗时计算的函数时,一个常见技巧是将每次调用函数的参数和结果值缓存起来,并在同样的值被再次请求时返回缓存的值。 这称为“记忆”,具体实现方式可以是这样的:

# Callers can only provide two parameters and optionally pass _cache by keyword
def expensive(arg1, arg2, *, _cache={}):
    if (arg1, arg2) in _cache:
        return _cache[(arg1, arg2)]

    # Calculate the value
    result = ... expensive computation ...
    _cache[(arg1, arg2)] = result           # Store result in the cache
    return result

你也可以使用包含一个字典的全局变量而不使用参数默认值;这完全取决于个人偏好。

4 使用函数参数列表中的 * 和 ** 说明符收集参数;这会将位置参数作为元组,将关键字参数作为字典。

5 深拷贝浅拷贝与可变不可变对象之间的事情

下面的x和y指向同一不可变对象,执行 y = x 并不会为列表创建一个副本,只是一个引用。

x=5
y=x

下面这代码的操作的x和y就是指向不同对象的,因为x=x+1这句话分两步,第一步计算x+1得出的6是一可变对象,只能创建新的对象出来。

x=5
y=x
x=x+1

某些操作 (例如 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()。


import copy
a=[1,2,3,4]
b=copy.copy(a)
b[0]=0
print(id(a)==id(b))

# 引用
a=[1,2,3,4]
b=a
b[0]=0
print(id(a)==id(b))

a=[1,2,3,4]
b=a[:]
b[0]=0
print(id(a)==id(b))

a=[1,2,3,4]
b=a[0:3]
b[0]=0
print(id(a)==id(b))

a={"d":12,"r":23}
b=copy.copy(a)
b["d"]=0
print(id(a)==id(b))

a={"d":12,"r":23}
b=a.copy()
b["d"]=0
print(id(a)==id(b))

# 引用
a={"d":12,"r":23}
b=a
b["d"]=0
print(id(a)==id(b))


结果

False
True
False
False
False
False
True

6 编写带输出参数的函数

通过返回一个结果元组:

def func2(a, b):
    a = 'new-value'        # a and b are local names
    b = b + 1              # assigned to new objects
    return a, b            # return new values

x, y = 'old-value', 99
x, y = func2(x, y)
print(x, y)                # output: new-value 100

这几乎总是最清晰明了的解决方案。

7 使用嵌套作用域,或者使用可调用对象创建高阶函数

使用嵌套作用域

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

使用可调用对象

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

可调用对象方式的缺点是速度略慢且生成的代码略长。 但是,请注意一组可调用对象能够通过继承来共享签名:

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

对象可以封装多个方法的状态:

class counter:

    value = 0

    def set(self, x):
        self.value = x

    def up(self):
        self.value = self.value + 1

    def down(self):
        self.value = self.value - 1

count = counter()
inc, dec, reset = count.up, count.down, count.set

8 使用dir()可以获得类的属性和方法

class counter:

    value = 0

    def set(self, x):
        self.value = x

    def up(self):
        self.value = self.value + 1

    def down(self):
        self.value = self.value - 1


print(dir(counter))

返回

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'down', 'set', 'up', 'value']

9 逗号在 Python 中不是运算符,“a” in “b”, "a"是(“a” in “b”), “a”

10 三目 “?:” small = x if x < y else y

11 函数参数列表中的斜杠(/)是什么意思?函数参数列表中的斜杠表示在它之前的形参是仅限位置形参。

在形参列表末尾的斜杠意味着两个形参都是仅限位置形参。 因此,附带关键字参数调用 divmod() 将会导致报错:

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

12 十六进制和八进制整数表示 0x22 0o22

13 字符串转换为数字用eval()不安全

用自带的int() float(),eval()太慢,而且eval()能执行python表达式不安全。

13 如何修改字符串?

无法修改,因为字符串是不可变对象。 在大多数情况下,你应该使用你想要的各种部分来构造一个新字符串。 但是,如果你想要一个可以原地修改 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'

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

有多种技巧可供选择。

最好的做法是使用一个将字符串映射到函数的字典。 这一技巧的主要优势在于字符串不必与函数名称一致。 这也是用于模拟其他语言中 case 结构的主要技巧:

def a():
    pass

def b():
    pass

dispatch = {'go': a, 'stop': b}  # Note lack of parens for funcs

dispatch[get_input()]()  # Note trailing parens to call function

使用内置函数 getattr()

import foo
getattr(foo, 'bar')()
请注意 getattr() 可用于任何对象,包括类、类实例、模块等等。

在标准库中多次使用了这个技巧,例如:

class Foo:
    def do_foo(self):
        ...

    def do_bar(self):
        ...

f = getattr(foo_instance, 'do_' + opname)
f()

使用 locals() 或 eval() 来解析出函数名:

def myFunc():
    print("hello")

fname = "myFunc"

f = locals()[fname]
f()

f = eval(fname)
f()

注意:使用 eval() 速度慢而且危险。 如果你不能绝对掌控字符串的内容,别人将能传入可被解析为任意函数直接执行的字符串。

15 将多个字符串连接在一起的最有效方法是什么?

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

要连接多个 str 对象,通常推荐的用法是将它们放入一个列表中并在结尾处调用 str.join() :

my_strings="abcdefg"
chunks = []
for s in my_strings:
    chunks.append(s)
print(chunks)

result = ''.join(chunks)
print(result)

16 如何以相反的顺序迭代序列?

使用 reversed() 内置函数,这是Python 2.4中的新功能:

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

这不会修改您的原始序列,而是构建一个反向顺序的新副本以进行迭代。

在 Python 2.3 里,您可以使用扩展切片语法:

for x in sequence[::-1]:
    ...  # do something with x ...

17 如何从列表中删除重复项?

有关执行此操作的许多方法的详细讨论,请参阅 Python Cookbook:

https://code.activestate.com/recipes/52560/

如果您不介意重新排序列表,请对其进行排序,然后从列表末尾进行扫描,删除重复项:

if mylist:
    mylist.sort()
    last = mylist[-1]
    for i in range(len(mylist)-2, -1, -1):
        if last == mylist[i]:
            del mylist[i]
        else:
            last = mylist[i]

如果列表的所有元素都可以用作设置键(即:它们都是 hashable ),这通常会更快:

mylist = list(set(mylist))

这会将列表转换为集合,从而删除重复项,然后返回到列表中。

如果序列时不可哈希的,想要去除重复项,需要对上述代码稍作修改:

# example2.py
#
# Remove duplicate entries from a sequence while keeping order
def dedupe(items, key=None):
  seen = set()
  for item in items:
    val = item if key is None else key(item)
    if val not in seen:
      yield item
      seen.add(val)
if __name__ == '__main__':
  a = [ 
    {'x': 2, 'y': 3},
    {'x': 1, 'y': 4},
    {'x': 2, 'y': 3},
    {'x': 2, 'y': 3},
    {'x': 10, 'y': 15}
    ]
  print(a)
  print(list(dedupe(a, key=lambda a: (a['x'],a['y']))))

18 如何创建多维列表?

可以使用列表推导式:

w, h = 2, 3
A = [[None] * w for i in range(h)]

19 如何按其他列表中的值对一个列表进行排序?

将它们合并到元组的迭代器中,对结果列表进行排序,然后选择所需的元素。

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

最后一步的替代方案是:

>>>
>>> result = []
>>> for p in pairs: result.append(p[1])

如果你觉得这个更容易读懂,那么你可能更喜欢使用这个而不是前面的列表推导。然而,对于长列表来说,它的速度几乎是原来的两倍。为什么?首先, append() 操作必须重新分配内存,虽然它使用了一些技巧来避免每次都这样做,但它仍然偶尔需要这样做,而且代价相当高。第二,表达式 “result.append” 需要额外的属性查找。第三,必须执行所有这些函数调用会降低速度。

20 实例方法、静态方法和类方法区别及其应用场景

https://blog.csdn.net/helloxiaozhe/article/details/79940321

# coding:utf-8


class Foo(object):
    """类三种方法语法形式"""

    def instance_method(self):
        print("是类{}的实例方法,只能被实例对象调用".format(Foo))

    @staticmethod
    def static_method():
        print("是静态方法")

    @classmethod
    def class_method(cls):
        print("是类方法")

foo = Foo()
foo.instance_method()
foo.static_method()
foo.class_method()
print('----------------')
Foo.static_method()
Foo.class_method()
发布了105 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/x1131230123/article/details/104449836