廖雪峰Python 类

廖雪峰Python 类

init方法

1
2
3
4
5
6
7
8
虽然实例属性可以后期动态添加,但是也可以通过__init__初始化方法绑定成员变量。self参数表示指向创建的实例

可以没有init方法

class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score

访问限制

1
2
3
4
成员变量前面加上两个下划线,变成私有属性:外部无法通过 .__属性 访问。
之所有无法通过.__属性访问的原因是,解释器会把__属性改名成其它的名字(不同的解释器有可能不同),例如:_类名__属性

通过方法操作成员变量,可以实现逻辑检查

继承和多态

1
2
3
class Animal(object):
def run(self):
print('Animal is running...')
1
2
3
4
5
6
7
重写父类方法
class Dog(Animal):
def run(self):
print('Dog is running...')

def eat(self):
print('Eating meat...')
1
java中:继承后重写父类方法要加上@override
1
2
3
4
5
6
7
8
多态的好处从下面就可以看出:无需确切地知道它的子类型,就可以放心地调用run()方法
def run_twice(animal):
animal.run()
animal.run()

调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
对扩展开放:允许新增子类;
对修改封闭:不需要修改依赖父类型的调用方函数

获取对象信息

type isinstance dir()

1
2
3
4
5
type()

isinstance()

dir()用于获得一个对象的所有属性和方法,它返回一个包含字符串的list

–len–()

1
2
3
4
5
6
__len__() 一个对象能够使用len()都是因为它的内部实现了__len__()方法

>>> len('ABC')
3
>>> 'ABC'.__len__()
3
1
类似__xxx__的属性和方法在Python中都是有特殊用途的

hasattr setattr getattr()

1
2
3
4
5
6
7
>>> class MyObject(object):
... def __init__(self):
... self.x = 9
... def power(self):
... return self.x * self.x
...
>>> obj = MyObject()
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19
1
2
3
4
5
6
7
8
9
>>> hasattr(obj, 'power') # 有方法'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81

实例属性和类属性

1
可以在创建实例时通过init方法,给实例添加实例属性,也可以在实例创建之后同过动态方式添加属性
1
2
3
4
5
6
class Student(object):
def __init__(self, name):
self.name = name

s = Student('Bob')
s.score = 90
1
2
3
类属性
class Student(object):
name = 'Student'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> class Student(object):
... name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student

实例可以访问到类属性,当实例没有和类属性同名的属性时,会访问到的是类属性。有同名的访问的是实例的。

动态添加方法和属性

实例

1
2
3
可以给类实例动态增加属性和方法

动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现
1
2
3
4
5
class Student():
pass

s = Student('tom',18)
s.grade = 99
1
2
3
4
5
6
7
8
def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25

1
2
3
4
>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score

面向对象高级编程

使用–slots–

1
2
3
__slots__用于限定给实例动态添加的属性类型

一个子类不会继承父类的__slots__
1
2
3
4
5
6
7
8
9
10
class Student(object):
__slots__ = ('name', 'age')

>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

@property

1

多重继承

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

# 大类:
class Mammal(Animal):
pass

class Bird(Animal):
pass

# 各种动物:
class Dog(Mammal):
pass

class Bat(Mammal):
pass

class Parrot(Bird):
pass

class Ostrich(Bird):
pass
1
2
3
4
5
6
7
8
9
现在,我们要给动物再加上Runnable和Flyable的功能,只需要先定义好Runnable和Flyable的类

class Runnable(object):
def run(self):
print('Running...')

class Flyable(object):
def fly(self):
print('Flying...')
1
2
3
4
5
class Dog(Mammal, Runnable):
pass

class Bat(Mammal, Flyable):
pass

定制类

–str()–

1
__str__()和java里toString()方法一样的功能,让print能够打印一个变量

–repr–()

1
__repr__()和__str()__类似,前者在命令行输入变量回车时会用,后者print变量时会用
1
2
3
4
5
6
7
8
解决办法是再定义一个__repr__()。但是通常__str__()和__repr__()代码都是一样的,所以,有个偷懒的写法:

class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__

–iter–()、–next–()

1
2
3
类似于java如果一个对象想能够用for each循环,则要实现iterable接口完成iterator()方法

如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值
斐波那契
1
2
3
4
5
6
7
8
9
10
11
12
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b

def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己

def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration()
return self.a # 返回下一个值

–getitem–()、–setitem–()、–delitem–()

1
2
3
__getitem__()是对象可以通过索引取值,和能够使用切片机制
__setitem__(),把对象视作list或dict来对集合赋值
--delitem--()用于删除某个元素。

–getattr–()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
用于解决调用某个属性时不存在的问题

class Student(object):
def __init__(self):
self.name = 'Michael'

def __getattr__(self, attr):
if attr=='score':
return 99

>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99
1
2
3
4
5
6
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
>>> s.age()
25
1
2
3
4
5
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)

–call–()、callable()

1
2
3
任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用

__call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数
1
2
3
>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.
1
那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有__call__()的类实例
1
通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。

枚举类

1
from enum import Enum

元类

1
2
3
type()可以用来在运行期间动态创建类

metaclass:略