最近在看 python 一些语法知识,虽然 python 代码写了不少,但是对于一些高级语法的了解还不够深入;因此本文主要记录了一些比较生疏的知识点,主要包括了装饰器,类的特殊方法,常量类这三个方面的知识。
装饰器
装饰器本质上是一个高阶函数,以被装饰的函数为参数,并返回一个包装后的函数给被装饰函数。
装饰器的一般使用形式如下:1
2
3
def func():
pass
等价于下面的形式:1
2
3def func():
pass
func = decorator(func)
装饰器可以定义多个,离函数定义最近的装饰器先被调用,比如:1
2
3
4
def func():
pass
等价于:
1 | def func(): |
对带参数的函数进行装饰
对带参数的函数进行装饰这个需求很常见,简单来说,装饰带参数的函数时,需要将参数传递给装饰器内部需要返回的函数(也叫内嵌包装函数),也就是说内嵌包装函数的参数跟被装饰函数的参数对应,如下所示
1 | def makeitalic(func): |
可以看到,装饰器内部需要返回的函数 wrapped
带上了参数 (*args, **kwargs)
, 目的是为了适应可变参数。使用如下
1 | 'python') hello( |
带参数的装饰器
上面的例子,我们增强了函数 hello 的功能,给它的返回加上了标签 <i>...</i>
,现在,我们想改用标签 <b>...</b>
或 <p>...</p>
。是不是要像前面一样,再定义一个类似 makeitalic
的装饰器呢?其实,我们可以可以使用带参数的装饰器,简单来说,就是在原来的装饰器基础上再封装一层函数,将标签作为参数,返回一个装饰器。
1 | def wrap_in_tag(tag): |
现在,我们可以根据需要生成想要的装饰器了:
1 | makebold = wrap_in_tag('b') # 根据 'b' 返回 makebold 生成器` |
上面的形式也可以写得更加简洁:1
2
3@wrap_in_tag('b')
def hello(name):
return 'hello %s' % name
这就是带参数的装饰器,其实就是在装饰器外面多了一层包装,根据不同的参数返回不同的装饰器。
私有成员
python 不像 C++ 有 private 之类的关键字,但是可以在属性或方法的名称前面加上两个下划线 __
, 来限制用户访问对象的属性或方法。
1 | In [1]: class Animal(object): |
类方法 vs 静态方法
python 中的类有两个特殊的方法:类方法和静态方法,两个方法主要有以下特点
- 两个方法均是属于类而不是属于对象的
- 两个方法都是通过内置的装饰器定义(
@classmethod
和@staticmethod
) - 类方法可以访问类属性,静态方法则不能
如下是类方法的一个例子
1 | class A(object): |
在上面,我们使用了 classmethod
装饰方法 class_foo
,它就变成了一个类方法,class_foo
的参数是 cls
,代表类本身,当我们使用 A.class_foo()
时,cls 就会接收 A 作为参数。另外,被 classmethod
装饰的方法由于持有 cls 参数,因此我们可以在方法里面调用类的属性、方法,比如 cls.bar
上面的类方法是可以修改类的属性的,静态方法定义方式类似,但是不会改变类和实例状态;如下所示,静态方法没有 self 和 cls 参数,因此没法改变类的属性,可以把它看成是一个普通的函数,甚至可以把它写到类外面,但是有时候,类就是需要这么一类方法,如果写到外面,一是不利于类的完整性,二是不利于命名空间的整洁性。
1 | class A(object): |
那么,这两个方法该在什么时候使用呢?参考 class method vs static method in Python 如下
We generally use class method to create factory methods. Factory methods return class object ( similar to a constructor ) for different use cases.
We generally use static methods to create utility functions.
如下是个比较形象的例子
1 | # Python program to demonstrate |
魔法方法
以双下划线 __
包裹起来的方法,比如最常见的 __init__
,这些方法被称为魔法方法(magic method)或特殊方法(special method),这些方法可以给 Python 的类提供特殊功能,方便我们定制一个类。
__new__
在 Python 中,当我们创建一个类的实例时,类会先调用 __new__(cls[, ...])
来创建并返回实例,然后 __init__
方法再对该实例(self)中的变量进行初始化。
关于 __new__
和 __init__
有以下几点需要注意:
__new__
是在__init__
之前被调用的__new__
是类方法,__init__
是实例方法- 重载
__new__
方法,需要返回类的实例
一般情况下,我们不需要重载 __new__
方法。但在某些情况下,我们想控制实例的创建过程,这时可以通过重载 __new__
方法来实现。 比如说,下面的例子通过了 __new__
来实现单例模式
1 | class Singleton(object): |
__str__
& __repr__
这两个方法主要是在直接打印类时候调用的,通过下面两个例子可以比较直观地看到如何使用
1 | class Foo(object): |
可以看到,使用 print 和 str 输出的是 __str__
方法返回的内容,但如果直接显示则不能,因为这个是 __repr__
方法负责的, 如下:
1 | class Foo(object): |
__iter__
在某些情况下,我们希望实例对象可被用于 for...in
循环,这时我们需要在类中定义 __iter__
和 next
(在 Python3 中是 __next__
)方法,其中,__iter__
返回一个迭代对象,next
返回容器的下一个元素,在没有后续元素时抛出 StopIteration
异常
如下是一个斐波那契数列的例子:
1 | class Fib(object): |
__getitem__
& __setitem__
& __delitem__
有时,我们希望可以使用 obj[n]
这种方式对实例对象进行取值,比如对斐波那契数列,我们希望可以取出其中的某一项,这时我们需要在类中实现 __getitem__
方法,比如下面的例子:
1 | class Fib(object): |
类似地,__setitem__
用于设置值,__delitem__
用于删除值,让我们看下面一个例子:
1 | class Point(object): |
在上面,我们定义了一个 Point 类,它有一个属性 coordinate(坐标),是一个字典,让我们看看使用:
1 | >>> p = Point() |
__call__
我们一般使用 obj.method() 来调用对象的方法,那能不能直接在实例本身上调用呢?在 Python 中,只要我们在类中定义 __call__
方法,就可以对实例进行调用,比如下面的例子:
1 | class Point(object): |
使用如下:
1 | 3, 4) p = Point( |
__slots__
__slots__
跟前面的方法不太一样,因为这是一个类的属性,当我们创建了一个类的实例后,我们还可以给该实例绑定任意新的属性和方法,如下
1 | class Point(object): |
这样其实是违背了 OOP 的封装性的理念,而且会消耗更多的内存,为了禁止这一属性,可以使用 __slots__
来告诉 Python 只给一个固定集合的属性分配空间,对上面的代码做一点改进,如下:
1 | class Point(object): |
我们给 __slots__
设置了一个元组,来限制类能添加的属性。现在,如果想绑定一个新的属性,就会出错了
常量类
在 Python 中使用常量一般来说有以下两种方式:
- 通过命名风格来提醒使用者该变量代表的意义为常量,如常量名所有字母大写,用下划线连接各个单词,PEP8 给出的编程风格就是这样的
- 通过自定义的类实现常量功能。这要求符合命名全部为大写和值一旦绑定便不可再修改 这两个条件。下面是一种较为常见的解决办法,将常量放到同一个文件中
1 | # FileName:constant.py |
简单解释一下,对象的所有属性及属性的值都存储在 __dict__
中, 上面的 __setattr__
方法在对象每次创建新常量的时候会判断常量是否已经被定义过,如果已经定义过则 raise error,从而确保了已经创建的常量不可修改。
sys.modules[__name__] = _const()
则确保了当上面的文件被 import 时,其 module 名称(也就是 __name__
的值,当文件被运行时 __name__
的值为 __main__
, 被 import 时 __name__
的值则是 module 名称)对应的是一个 _const()
对象,从而可以直接通过其创建常量。因此,使用的方法如下
1 | import constant |
参考: