github.io 有点慢,font-awesome 有点大,
客官麻烦搬条板凳坐一下下,我马上就到!

Python进阶函数和功能_#2(杂记)

本文是近期学习中零散的一些知识点,主要是面向对象,记于此。

下面是快捷方式(点击直达)
导入模块导入模块面向对象文法属性优先级类方法类的继承面相对象比较不定参数的传递关于Python版本和函数使用

导入模块

1
2
3
4
5
6
7
# 尝试导入较老版本模块
try:
import json
except ImportError:
import simplejson as json

print(json.dumps({'python':3.6}))

回到目录


面向对象文法

1
2
3
4
5
6
7
8
9
class Person(object):
def __init__(name, gender, birth):
pass

xiaoming = Person('Xiao Ming', 'Male', '1990-1-1')

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() takes exactly 3 arguments (4 given)

这会导致创建失败或运行不正常,因为第一个参数name被Python解释器传入了实例的引用,从而导致整个方法的调用参数位置全部没有对上。

正确写法:

1
2
3
4
class Person(object):
# 第一个参数一定要是self!
def __init__(self, name, gender, birth):
pass

第一个参数一定要是 self

回到目录


属性优先级

当实例属性和类属性重名时,实例属性优先级高,它将屏蔽掉对类属性的访问。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person(object):
address = 'Earth'
def __init__(self, name):
self.name = name

p1 = Person('Bob')
p2 = Person('Alice')

print('Person.address = ' + Person.address)

p1.address = 'China'
print('p1.address = ' + p1.address)

print('Person.address = ' + Person.address)
print('p2.address = ' + p2.address)

Person.address = Earth
p1.address = China
Person.address = Earth
p2.address = Earth

我们发现,在设置了 p1.address = ‘China’ 后,p1访问 address 确实变成了 ‘China’,但是,Person.address和p2.address仍然是’Earch’,怎么回事?

原因是 p1.address = ‘China’并 没有改变 Person 的 address ,而是给 p1这个实例绑定了实例属性address ,对p1来说,它有一个实例属性address(值是’China’),而它所属的类Person也有一个类属性address,所以:

  • 访问 p1.address 时,优先查找实例属性,返回’China’。
  • 访问 p2.address 时,p2没有实例属性address,但是有类属性address,因此返回’Earth’。

回到目录


类方法

@classmethod 装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person(object):

__count = 0

@classmethod
def how_many(cls):
return cls.__count

def __init__(self, name):
Person.__count += 1
pass

print(Person.how_many())
p1 = Person('Bob')
print(Person.how_many())

回到目录


类的继承

函数 super(子类, self) 将返回当前类继承的父类,然后调用init()方法,注意self参数已在super()中传入,在init()中将隐式传递,不需要写出(也不能写)。

例如

1
2
3
4
5
6
7
8
9
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender

class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score

一定要用 super(Student, self).init(name, gender) 去初始化父类,否则,继承自 Person 的 Student 将没有 name 和 gender。

回到目录


面相对象比较

使用魔术方法,这里有一种巧妙的表达:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Student(object):

def __init__(self, name, score):
self.name = name
self.score = score

def __str__(self):
return '(%s: %s)' % (self.name, self.score)

__repr__ = __str__

def __cmp__(self, s):
if False == isinstance(s, Student):
return -1
return -cmp(self.score, s.score) or cmp(self.name, s.name)

L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]

print(sorted(L))

需要特别注意的是,python 3.x 中不再支持 cmp() 函数,取而代之使用:
lt() for sorting, eq() with hash(), and other rich comparisons as needed. (If you really need the cmp() functionality, you could use the expression (a > b) - (a < b) as the equivalent for cmp(a, b).

回到目录


不定参数的传递

对于Person类的定义:

1
2
3
4
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender

希望除了 name和gender 外,可以提供任意额外的关键字参数,并绑定到实例,请修改 Person 的 init() 定 义,完成该功能。

例程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person(object):

def __init__(self, name, gender, **kw):
self.name = name
self.gender = gender
# Python 2.7 中 iteritems 独立封装,返回一个迭代器;但是3.*版本整合进了字典内建函数 items。
for k, v in kw.items():
setattr(self, k, v)


p = Person('Bob', 'Male', age=18, course='Python')
print(p.age)
print(p.course)
# Python 2.7 中 filter 对象可以直接输出,但是在3.*版本需要强制转化成 list 对象,否则输出 <filter object at ...>
print(list(filter(lambda x: x[0] != '_', dir(p))))

  • **kw 以字典的形式接受参数表
  • *args 以元组的方式接受参数表
  • 关于Python版本和函数使用(链接

回到目录


魔术方法

__repr__:调试时输出的字符串
__str__:用户使用时输出的字符串
__cmp__:sorted等函数调用时返回结果,可以配合 cmp() 函数等使用
__len__:(略)
__add____sub____mul____div__

__slots__

由于Python是动态语言,任何实例在运行期都可以动态地添加属性。

如果要限制添加的属性,例如,Student类只允许添加 name、gender和score 这3个属性,就可以利用Python的一个特殊的 __slots__ 来实现。

顾名思义,__slots__ 是指一个类允许的属性列表:

1
2
3
4
5
6
class Student(object):
__slots__ = ('name', 'gender', 'score')
def __init__(self, name, gender, score):
self.name = name
self.gender = gender
self.score = score

现在,对实例进行操作:

1
2
3
4
5
6
7
>>> s = Student('Bob', 'male', 59)
>>> s.name = 'Tim' # OK
>>> s.score = 99 # OK
>>> s.grade = 'A'
Traceback (most recent call last):
...
AttributeError: 'Student' object has no attribute 'grade'

__slots__的目的是限制当前类所能拥有的属性,如果不需要添加任意动态的属性,使用slots也能节省内存。

在继承中使用slot

假设Person类通过slots定义了name和gender,请在派生类Student中通过slots继续添加score的定义,使Student类可以实现name、gender和score 3个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person(object):

__slots__ = ('name', 'gender')

def __init__(self, name, gender):
self.name = name
self.gender = gender

class Student(Person):

__slots__ = ('score',)
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score

s = Student('Bob', 'male', 59)
s.name = 'Tim'
s.score = 99
print(s.score)

call

在Python中,函数其实是一个对象:

1
2
3
4
5
>>> f = abs
>>> f.__name__
'abs'
>>> f(-123)
123

由于 f 可以被调用,所以,f 被称为可调用对象。

所有的函数都是可调用对象。

一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法call()。

我们把 Person 类变成一个可调用对象:

1
2
3
4
5
6
7
8
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender

def __call__(self, friend):
print('My name is %s...' % self.name)
print('My friend is %s...' % friend)

现在可以对 Person 实例直接调用:

1
2
3
4
>>> p = Person('Bob', 'male')
>>> p('Tim')
My name is Bob...
My friend is Tim...

单看 p(‘Tim’) 你无法确定 p 是一个函数还是一个类实例,所以,在Python中,函数也是对象,对象和函数的区别并不显著。

例程(打印斐波那契数列):

1
2
3
4
5
6
7
8
9
10
11
class Fib(object):
def __call__(self,num):
L = [0,1]
i = 2
while i < num:
L.append(L[i-2]+L[i-1])
i=i+1
return L

f = Fib()
print f(10)

回到目录


get()/set() 及 @property 装饰器

考察 Student 类:

1
2
3
4
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score

当我们想要修改一个 Student 的 scroe 属性时,可以这么写:

1
2
s = Student('Bob', 59)
s.score = 60

但是也可以这么写:

1
s.score = 1000

显然,直接给属性赋值无法检查分数的有效性。

如果利用两个方法:

1
2
3
4
5
6
7
8
9
10
class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
def get_score(self):
return self.__score
def set_score(self, score):
if score < 0 or score > 100:
raise ValueError('invalid score')
self.__score = score

这样一来,s.set_score(1000) 就会报错。

这种使用 get/set 方法来封装对一个属性的访问在许多面向对象编程的语言中都很常见。

但是写 s.get_score() 和 s.set_score() 没有直接写 s.score 来得直接。

有没有两全其美的方法?——有。

因为Python支持高阶函数,在函数式编程中我们介绍了装饰器函数,可以用装饰器函数把 get/set 方法“装饰”成属性调用:

1
2
3
4
5
6
7
8
9
10
11
12
class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
@property
def score(self):
return self.__score
@score.setter
def score(self, score):
if score < 0 or score > 100:
raise ValueError('invalid score')
self.__score = score

注意: 第一个score(self)是get方法,用@property装饰,第二个score(self, score)是set方法,用@score.setter装饰,@score.setter是前一个@property装饰后的副产品。

现在,就可以像使用属性一样设置score了:

1
2
3
4
5
6
7
8
>>> s = Student('Bob', 59)
>>> s.score = 60
>>> print s.score
60
>>> s.score = 1000
Traceback (most recent call last):
...
ValueError: invalid score

说明对 score 赋值实际调用的是 set方法。

回到目录


关于Python版本和函数使用

map、filter 和 reduce
这三个函数号称是函数式编程的代表。在 Python3.x 和 Python2.x 中也有了很大的差异。

首先我们先简单的在 Python2.x 的交互下输入 map 和 filter,看到它们两者的类型是 built-in function (内置函数):

1
2
3
4
>>> map
<built-in function map>
>>> filter
<built-in function filter>

它们输出的结果类型都是列表:

1
2
3
4
>>> map(lambda x:x *2, [1,2,3])
[2, 4, 6]
>>> filter(lambda x:x %2 ==0,range(10))
[0, 2, 4, 6, 8]

但是在Python 3.x中它们却不是这个样子了:

1
2
3
4
5
6
7
8
>>> map
<class 'map'>
>>> map(print,[1,2,3])
<map object at 0x10d8bd400>
>>> filter
<class 'filter'>
>>> filter(lambda x:x % 2 == 0, range(10))
<filter object at 0x10d8bd3c8>

首先它们从函数变成了类,其次,它们的返回结果也从当初的列表成了一个可迭代的对象, 我们尝试用 next 函数来进行手工迭代:

1
2
3
4
5
6
7
8
9
>>> f =filter(lambda x:x %2 ==0, range(10))
>>> next(f)
0
>>> next(f)
2
>>> next(f)
4
>>> next(f)
6

对于比较高端的 reduce 函数,它在 Python 3.x 中已经不属于 built-in 了,被挪到 functools 模块当中。

另外,关于更多 Python 2.x 和 Python 3.x 中的不同,可以参看 这里,希望有时间我可以仔细翻译一下。

回到目录




————  EOF  ————