Python 拾遗

偶尔复习,查漏补缺。(随手写的笔记,不建议看。。)

不可变对象

不可变对象常用在 参数共享/参数传递 上,好处很多,一是可以使用字符串池来节省空间,二是该对象可以安全地共享/传递,不会造成误修改问题。

  1. numbers
  2. string
  3. tuple
  4. frozenset

a. 问题

在使用*作为重复运算符时,如果目标是一个嵌套的可变对象,就会产生令人费解的问题:

>>> a = [1,2,3]
>>> b = a * 3
>>> b
[1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> b = [a] * 3  # nested
>>> b
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
>>> b[1][1] = 4
>>> b
[[1, 4, 3], [1, 4, 3], [1, 4, 3]]

因为 * 并不是深拷贝,它只是简单地复制了 [a] 这个列表,里面的 [1,2,3] 都是同一个对象,所以改了一个,所有的都会改变。
解决方法是不要使用 * 号,改用[a.copy() for i in range(3)] 执行深拷贝。如果不需要修改,请直接使用不可变对象。

b. 奇技淫巧

  1. 序列分组(使用了浅拷贝的特性)
>>> a = 'asdfghjkl'
>>> iters = [iter(a)] * 3  # 这里是浅拷贝,列表中的三个元素都是同一个迭代器。重点就是这个
>>> parts = zip(*iters)  # 参数解包
>>> list(parts)
[('a', 's', 'd'), ('f', 'g', 'h'), ('j', 'k', 'l')]

作用域

  1. Python 中只有模块,类以及函数才会引入新的作用域,其它的代码块是不会引入新的作用域的。(而在 C/Java 中,任何一个 {} 块就构成一个局部作用域。另外 Julia 中 for/while/try-catch 都是局部作用域,但 if-else 又不是局部作用域。总之这些小差别要注意。)
  2. 局部变量可以与外部变量同名,并且在其作用域中,局部变量会覆盖掉外部变量。
    不知是出于实现简单或是性能,还是其他的原因,好像所有的语言都是这样的。其实我更希望变量的作用域覆盖会报错。
  3. 如果有函数与其他函数或变量(甚至某些保留字)同名,后定义的会覆盖掉先定义的。(这是因为 Python 中函数也是对象。而在 C/Java 中这是会报错的)

此外,还有一个小问题,先看一个例子:

>>> i = 4
>>> def f():     # 单纯的从函数作用域访问外部作用域是没问题的
...     print(i)
... 
>>> f()
4

再看一个问题举例:

>>> i = 3
>>> def f():
...     print(i)  # 这里应该是访问外部作用域
...     i = 5     # 可这里又定义了一个同名局部变量 i
... 
>>> f()   # 于是就出错了
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UnboundLocalError: local variable 'i' referenced before assignment

如果在内部作用域先访问外部作用域,再定义一个同名的局部变量,解释器就懵逼了。
如果你其实想做的是改变全局变量 i 的值,就应该在开头声明 global i. 而如果 外部变量 i 不是存在于全局作用域,而是在某个闭合作用域内的话,就该用 nonlocal i

inspect

P.S. 类比 Java 的反射,但是要简单很多

函数式

  1. map: Mapping Functions over Iterables
  2. zip:
  3. filter: Selecting Items in Iterables
  4. reduce: Combining Items in Iterables (这个现在在 functools 模块内,常和 operator 模块一起用)

生成器

  1. yield from: 从另一个生成器返回值

Built-in Functions

Built-in Functions

时间

  1. time: 时间模块,用的最多的应该就是 time.time() 了。
  2. datetime: 时间日期处理模块,支持年月日时分秒的算术运算。
  3. 性能分析:line_profile: 分析每行的运行时间。

packages

  1. __all__: 用于控制 import * 的行为。

  2. 通过字符串加载包:importlib

求值

  1. compile
    将 source 编译成 AST 对象,该对象可传给 eval 或 exec 执行。

  2. eval
    只接受单个表达式 (expression),evaluate it,并返回表达式的值。

  3. exec
    接受包含语句 (statement) 的代码段,execute it only for its side effects,无返回值。

装饰器

装饰器有两种:用函数定义的装饰器,还有用类定义的装饰器。函数装饰器最常用。

装饰器可用于装饰函数,修改函数/类的某些行为,或者将函数注册到别的地方。

1. 函数定义装饰器

@decc
def gg(xx):
    ...

# 等同于
def gg(xx)
gg = decc(gg)

带参的装饰器

@decorator(A, B)
def F(arg):
    ...

F(99)

# 等同于
def F(arg):
    ...

F = decorator(A, B)(F)      # Rebind F to result of decorator's return value
F(99)                                # Essentially calls decorator(A, B)(F)(99)

上面演示的是用函数定义的装饰器,也是最常用的装饰器。
装饰器接收的参数可以是各种各样的,下面是一个带参的装饰器:

@on_command("info")
def get_info():
    return "这就是你需要的 info"

def on_command(name: str):  # 调用此函数获得装饰器,这样就实现了带参装饰器
    def deco(func: Callable) -> Callable:  # 这个才是真正的装饰器
        # 将命令处理器注册到命令列表内
        return func  # 直接返回原函数,这样的话,多个装饰器就不会相互影响了。
    return deco

# 上面的等同于:
get_info = on_command("info")(get_info)  # on_command("info") 返回真正的装饰器

如果你的 on_command 有通用的部分,还可以将通用的部分抽离出来复用:

def _deco_maker(event_type: str) -> Callable:  # 调用这个,获取 on_xxx 的 deco_deco,
    def deco_deco(self) -> Callable:   # 这个对应 on_xxx
        def deco(func: Callable) -> Callable: # 这个才是真正的装饰器
            # do something 
            return func  # 返回原函数

        return deco

    return deco_deco

我们知道 Python 的类实际上是可以很方便的修改的,因此函数装饰器也能用于装饰类,修改类的某些行为。

def log_getattribute(cls):
    # Get the original implementation
    orig_getattribute = cls.__getattribute__

    # Make a new definition
    def new_getattribute(self, name):
        print('getting:', name)
        return orig_getattribute(self, name)

    # Attach to the class and return
    cls.__getattribute__ = new_getattribute  # 修改了被装饰类 cls 的 __getattribute__
    return cls

# Example use
@log_getattribute
class A:
    def __init__(self,x):
        self.x = x
    def spam(self):
        pass

2. 类定义装饰器

类定义装饰器和函数定义装饰器的使用方式完全一致。它也可以用于装饰函数或者类。

那么为啥还需要类定义装饰器呢?它的优势在于类是可以继承的,这样的话,就能用继承的方式定义装饰器,将通用部分定义成超类。

类定义装饰器的定义方法如下:

# PythonDecorators/entry_exit_class.py
class entry_exit(object):

    def __init__(self, f):
        self.f = f

    def __call__(self):  #关键在于这个函数,它使此类的对象变成 Callable
        print("Entering", self.f.__name__)
        self.f()
        print("Exited", self.f.__name__)

@entry_exit
def func1():
    print("inside func1()")

# 上面的装饰器相当于
func1 = entry_exit(func1)  # 从这里看的话,装饰器的行为完全一致

# 接下来调用该函数(实际上是调用了 entry_exit 对象的 call 函数)
func1()

输出结果如下:

Entering func1
inside func1()
Exited func1

OOP

  1. 调用超类方法:

    • 直接通过超类名.__init__(self,xx)调用
    • 通过super(__class__, self).__init__()调用。
      (Python3 可直接用 super().__init__()
      但是要搞清楚,super() 方法返回的是一个代理类。另外被代理的类也不一定是其超类。如果不清楚这些差别,最好还是显式用方法一最好。
  2. 抽象超类:@abstractmethod

  3. @staticmethod @classmethod 与 Java 的 static 方法对比
    python的类方法、静态方法,与java的静态方法:

    1. java 中 constants、utils 这样的静态类,对应的是python的一个模块(文件),类属性对应模块的全局属性,静态方法对应模块的函数

    2. 对于 java 中需要访问类属性的静态方法,如果它不属于第一类,应该用 @classmethod 实现它。classmethod最大的特点就是一定有一个 cls 传入。这种方法的主要用途是实现工厂函数。

    3. 对于不需要访问任何类属性,也不属于第一类的方法,应该用 @staticmathod 实现。这种方法其实完全不需要放到类里面,它就是一个独立的函数。(仍然放里面,是为了把功能类似的函数组织到一起而已。)

  4. __slots__: 属性导出,不在该列表内的属性,若存在则为只读。不存在的话,就不存在。。
    6. __getattr__: 拦截对不存在的属性的访问,可用于实现动态分配属性。

  5. __getattribute__: 和上面相同,但是它拦截对所有属性的访问,包括对已存在的属性的访问。

  6. @property: 提供对属性访问的安全检查

  7. descriptor: get set delete 控制对类的访问。(上面的 getattr 等是控制对类的属性的访问)

  8. 类构造器 __new__:在 __init__ 之前运行,它接收一个 cls 参数,然后使用它构造并返回类实例 self

  9. 类方法的 cls 即是当前类,是 type 的实例,cls.xxx<类名>.xxx 调用结果是一致的。而 self 由 __new__ 构造,是 cls 的实例。

元类 metaclasses

元类,也就是用于创建class 的 class,算是很高级的话题了(If you wonder whether you need metaclasses, you don’t )
元类的工作流程:

  1. 拦截类的创建
  2. 修改类
  3. 返回修改之后的类
    详细直接看 http://blog.jobbole.com/21351/ 吧。

查看源码

Python 很多功能是 C 写的,直接从 Pycharm 中只能看到自动生成的文档。
对一般的标准库的模块,查看方法是很简单的:直接通过 __file__ 属性就能看到。
而对于 builtins 模块,都在这个文件里。

原文地址:https://www.cnblogs.com/kirito-c/p/9129456.html