python 语法杂记 -- 装饰器,类的特殊方法,常量类
最近在看 python 一些语法知识,虽然 python 代码写了不少,但是对于一些高级语法的了解还不够深入;因此本文主要记录了一些比较生疏的知识点,主要包括了装饰器,类的特殊方法,常量类这三个方面的知识。
装饰器
装饰器本质上是一个高阶函数,以被装饰的函数为参数,并返回一个包装后的函数给被装饰函数。
装饰器的一般使用形式如下:1
2
3
def func():
pass
等价于下面的形式:1
2
3def func():
pass
func = decorator(func)
装饰器可以定义多个,离函数定义最近的装饰器先被调用,比如:1
2
3
4
def func():
pass
等价于:1
2
3
4def func():
pass
func = decorator_one(decorator_two(func))
对带参数的函数进行装饰
对带参数的函数进行装饰这个需求很常见,简单来说,装饰带参数的函数时,需要将参数传递给装饰器内部需要返回的函数 (也叫内嵌包装函数),也就是说内嵌包装函数的参数跟被装饰函数的参数对应,如下所示1
2
3
4
5
6
7
8
9
10
11
12
13def makeitalic(func):
def wrapped(*args, **kwargs):
ret = func(*args, **kwargs)
return '<i>' + ret + '</i>'
return wrapped
def hello(name):
return 'hello %s' % name
def hello2(name1, name2):
return 'hello %s, %s' % (name1, name2)
可以看到,装饰器内部需要返回的函数 wrapped
带上了参数 (*args, **kwargs)
, 目的是为了适应可变参数。使用如下1
2
3
4'python') hello(
'<i>hello python</i>'
'python', 'java') hello2(
'<i>hello python, java</i>'
带参数的装饰器
上面的例子,我们增强了函数 hello 的功能,给它的返回加上了标签 <i>...</i>
,现在,我们想改用标签 <b>...</b>
或 <p>...</p>
。是不是要像前面一样,再定义一个类似 makeitalic
的装饰器呢?其实,我们可以可以使用带参数的装饰器,简单来说,就是在原来的装饰器基础上再封装一层函数,将标签作为参数,返回一个装饰器。1
2
3
4
5
6
7
8def wrap_in_tag(tag):
def decorator(func):
def wrapped(*args, **kwargs):
ret = func(*args, **kwargs)
return '<' + tag + '>' + ret + '</' + tag + '>'
return wrapped
return decorator
现在,我们可以根据需要生成想要的装饰器了:1
2
3
4
5
6
7makebold = wrap_in_tag('b') # 根据 'b' 返回 makebold 生成器`
@makebold
def hello(name):
return 'hello %s' % name
>>> hello('world')
'<b>hello world</b>'
上面的形式也可以写得更加简洁:1
2
3@wrap_in_tag('b')
def hello(name):
return 'hello %s' % name
这就是带参数的装饰器,其实就是在装饰器外面多了一层包装,根据不同的参数返回不同的装饰器。
私有成员
python 不像 C++ 有 private 之类的关键字,但是 可以在属性或方法的名称前面加上两个下划线 __
, 来限制用户访问对象的属性或方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19In [1]: class Animal(object):
...: def __init__(self, name):
...: self.__name = name
...: def greet(self):
...: print ('Hello, I am %s.' % self.__name)
...:
In [2]: a = Animal("dog")
In [3]: a.__name
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-5-5d5520ef9fe0> in <module>()
----> 1 a.__name
AttributeError: 'Animal' object has no attribute '__name'
In [4]: a.greet()
Hello, I am dog.
类方法 vs 静态方法
python 中的类有两个特殊的方法:类方法和静态方法,两个方法主要有以下特点
- 两个方法均是属于类而不是属于对象的
- 两个方法都是通过内置的装饰器定义(
@classmethod
和@staticmethod
) - 类方法可以访问类属性,静态方法则不能
如下是类方法的一个例子1
2
3
4
5
6
7
8
9
10class A(object):
bar = 1
def class_foo(cls):
print 'Hello, ', cls
print cls.bar
# 直接通过类来调用方法 A.class_foo()
Hello, <class '__main__.A'>
1
在上面,我们使用了 classmethod
装饰方法 class_foo
,它就变成了一个类方法,class_foo
的参数是 cls
,代表类本身,当我们使用 A.class_foo()
时,cls 就会接收 A 作为参数。另外,被 classmethod
装饰的方法由于持有 cls 参数,因此我们可以在方法里面调用类的属性、方法,比如 cls.bar
上面的类方法是可以修改类的属性的,静态方法定义方式类似,但是不会改变类和实例状态;如下所示,静态方法没有 self 和 cls 参数,因此没法改变类的属性,可以把它看成是一个普通的函数,甚至可以把它写到类外面,但是有时候,类就是需要这么一类方法,如果写到外面,一是不利于类的完整性,二是不利于命名空间的整洁性。1
2
3
4
5
6
7
8
9
10class A(object):
def static_foo():
print 'Hello'
a = A()
a.static_foo()
Hello
A.static_foo()
Hello
那么,这两个方法该在什么时候使用呢?参考 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27# Python program to demonstrate
# use of class method and static method.
from datetime import date
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# a class method to create a Person object by birth year.
def fromBirthYear(cls, name, year):
return cls(name, date.today().year - year)
# a static method to check if a Person is adult or not.
def isAdult(age):
return age > 18
person1 = Person('mayank', 21)
person2 = Person.fromBirthYear('mayank', 1996)
print person1.age
print person2.age
# print the result
print Person.isAdult(22)
魔法方法
以双下划线 __
包裹起来的方法,比如最常见的 __init__
,这些方法被称为魔法方法(magic method)或特殊方法(special method), 这些方法可以给 Python 的类提供特殊功能,方便我们定制一个类。
__new__
在 Python 中,当我们创建一个类的实例时,类会先调用 __new__(cls[, ...])
来创建并返回实例,然后 __init__
方法再对该实例(self)中的变量进行初始化。
关于 __new__
和 __init__
有以下几点需要注意:
__new__
是在__init__
之前被调用的__new__
是类方法,__init__
是实例方法- 重载
__new__
方法,需要返回类的实例
一般情况下,我们不需要重载 __new__
方法。但在某些情况下,我们想控制实例的创建过程,这时可以通过重载 __new__
方法来实现。 比如说,下面的例子通过了 __new__
来实现单例模式1
2
3
4
5
6
7
8class Singleton(object):
_instance = None
def __new__(cls, *args, **kw):
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(cls, *args, **kw)
return cls._instance
class MyClass(Singleton):
a = 1
__str__
& __repr__
这两个方法主要是在直接打印类时候调用的,通过下面两个例子可以比较直观地看到如何使用1
2
3
4
5
6
7
8
9
10
11
12
13
14class Foo(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Foo object (name: %s)' % self.name
print Foo('ethan') # 使用 print
Foo object (name: ethan)
>>>
str(Foo('ethan')) # 使用 str
'Foo object (name: ethan)'
>>>
'ethan') # 直接显示 Foo(
<__main__.Foo at 0x10c37a490>
可以看到,使用 print 和 str 输出的是 __str__
方法返回的内容,但如果直接显示则不能,因为这个是 __repr__
方法负责的,如下:1
2
3
4
5
6
7
8class Foo(object):
def __init__(self, name):
self.name = name
def __repr__(self):
return 'Foo object (name: %s)' % self.name
'ethan') Foo(
'Foo object (name: ethan)'
__iter__
在某些情况下,我们希望实例对象可被用于 for...in
循环,这时我们需要在类中定义 __iter__
和 next
(在 Python3 中是 __next__
)方法,其中,__iter__
返回一个迭代对象,next
返回容器的下一个元素,在没有后续元素时抛出 StopIteration
异常
如下是一个斐波那契数列的例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Fib(object):
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self): # 返回迭代器对象本身
return self
def next(self): # 返回容器下一个元素
self.a, self.b = self.b, self.a + self.b
return self.a
fib = Fib()
for i in fib:
if i > 10:
break
print i
...
1
1
2
3
5
8
__getitem__
& __setitem__
& __delitem__
有时,我们希望可以使用 obj[n]
这种方式对实例对象进行取值,比如对斐波那契数列,我们希望可以取出其中的某一项,这时我们需要在类中实现 __getitem__
方法,比如下面的例子:1
2
3
4
5
6
7
8
9
10class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in xrange(n):
a, b = b, a + b
return a
fib = Fib()
0], fib[1], fib[2], fib[3], fib[4], fib[5] fib[
(1, 1, 2, 3, 5, 8)
类似地,__setitem__
用于设置值,__delitem__
用于删除值,让我们看下面一个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Point(object):
def __init__(self):
self.coordinate = {}
def __getitem__(self, key):
return self.coordinate.get(key)
def __setitem__(self, key, value):
self.coordinate[key] = value
def __delitem__(self, key):
del self.coordinate[key]
print 'delete %s' % key
def __len__(self):
return len(self.coordinate)
在上面,我们定义了一个 Point 类,它有一个属性 coordinate(坐标),是一个字典,让我们看看使用:1
2
3
4
5
6
7
8
9
10>>> p = Point()
>>> p['x'] = 2 # 对应于 p.__setitem__('x', 2)
>>> p['y'] = 5 # 对应于 p.__setitem__('y', 5)
>>> len(p) # 对应于 p.__len__
2
>>> p['x'] # 对应于 p.__getitem__('x')
2
>>> del p['x'] # 对应于 p.__delitem__('x')
>>> len(p)
1
__call__
我们一般使用 obj.method () 来调用对象的方法,那能不能直接在实例本身上调用呢?在 Python 中,只要我们在类中定义 __call__
方法,就可以对实例进行调用,比如下面的例子:1
2
3
4
5class Point(object):
def __init__(self, x, y):
self.x, self.y = x, y
def __call__(self, z):
return self.x + self.y + z
使用如下:1
2
3
4
53, 4) p = Point(
callable(p) # 使用 callable 判断对象是否能被调用
True
6) # 传入参数,对实例进行调用,对应 p.__call__(6) p(
13 # 3+4+6
__slots__
__slots__
跟前面的方法不太一样,因为这是一个类的属性,当我们创建了一个类的实例后,我们还可以给该实例绑定任意新的属性和方法,如下1
2
3
4
5
6
7
8
9
10
11class Point(object):
def __init__(self, x=0, y=0):
self.x = x
self.y = y
3, 4) p = Point(
5 # 绑定了一个新的属性 p.z =
p.z
5
p.__dict__
{'x': 3, 'y': 4, 'z': 5}
这样其实是违背了 OOP 的封装性的理念,而且会消耗更多的内存,为了禁止这一属性,可以使用 __slots__
来告诉 Python 只给一个固定集合的属性分配空间,对上面的代码做一点改进,如下:1
2
3
4
5
6class Point(object):
__slots__ = ('x', 'y') # 只允许使用 x 和 y
def __init__(self, x=0, y=0):
self.x = x
self.y = y
我们给 __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
2import constant
print(constant.MAX_COUNT)
参考:
class method vs static method in Python Data model Python 之旅