1.1 类和实例
1.2 访问限制
1.3 继承和多态
1.4 获取对象信息
1.5 实例属性和类属性
2. 面向对象高级编程
2.1 使用_slots_
2.2 使用@property
2.3 多重继承
2.4 定制类
2.5 使用枚举类
2.6 使用元类
1. 面向对象编程(OOP)
面向对象编程,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
给对象发消息实际上是调用对象对应的关联函数,称之为对象的方法(Method)
1.1 类和实例
面向对象最重要的概念就是类(Class)和实例(Insance)。
在Python中定义类用class关键字,class后紧接着是类名,类名通常是大写开头的单词,紧接着是Object。
由于类可以起到模板的作用,因此,在创建实类的时候,把一些我们认为是必须绑定的属性强制填写进去。通过定义一个特殊的_init_方法。
class Student(object):
def __init__(self, name, score): # 注意_init_第一个参数永远是self,表示创建实例的本身
self.name = name
self.score = score
数据封装:
封装数据的函数是和类本身关联起来的,我们称之为类的方法:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self): # 除了第一个参数是self外,其他和普通函数一样
print('%s: %s' % (self.name, self.score))
1.2 访问限制
如果让内部属性不被外部访问修改,可以把属性的名称的前面加上两个下划线_,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
如果外部代码想获取和修改name和score,可以给类增加get_score和set_score这样的方法。
class Student(object):
...
def get_score(self):
return self.__score
def set_score(self, score):
self.__score = score
在Python中,以双下划线_开头和以双下划线_结束的变量名,类似__xx__这样的是特殊变量,特殊变量是可以直接访问的,不是private变量。
以一个下划线开头的变量,比如_name,这样的实例变量是可以直接访问的,但是按照约定成俗的规定,意思就是”虽然我可以被直接访问,但是,请把我视为私有变量,不要随意访问”。
双下划线开头的变量,比如__name也不是一定不能被外部直接访问,可以通过_Student__name来访问__name变量。
1.3 继承和多态
在OOP程序设计中,当我们定义一个Class的时候,可以从某个现有的Class继承,新的Class类称为子类,而被继承的类称为基类,父类或者超类。
# 基类
class Animal(object):
def run(self):
print('Animal is running...')
# 子类
class Dog(Animal):
pass
继承的好处:
- 子类可以继承父类所有的方法。
子类可以自己增加方法。
当子类和父类同时拥有相同的方法时,子类覆盖了父类的方法,执行子类的方法,称之为多态。
多态的好处,就是著名的开闭原则。对拓展开放,对修改关闭。
静态语言VS动态语言
对于静态语言(Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,则无法调用run()方法。
对于动态语言(Python)来说,则不一定需要传入Animal类,我们只需要保证传入的对象一个run()方法就行。
class Timer(object):
def run(self):
print('Start...')
1.4 获取对象信息
- 判断对象类型,使用type()。
- 对于class继承关系来说,使用type()就不方便,要判断class的类型,可以使用isinstance()函数。能用type()判断的基本类型也可以用isinstance()判断。
- 如果要获得一个对象的所有属性和方法,可以使用dir()函数,返回一个包含字符串的list。
# type()判断对象类型
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>
# isinstance()判断对象类型
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
# dir()获取对象的所有属性和方法
>>> dir('ABC')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
1.5 实例属性和类属性
由于Python是动态语言,根据类创建的实例可以绑定任意属性。
在编写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,当删除实例属性的时候,再使用相同的名称,访问到的将是类属性。
>>> 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
2. 面向对象高级编程
2.1 使用_slots_
为了限制实例的属性,Python允许在定义class的时候,定义一个特殊的_slots_变量。
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
>>> 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'
使用_slots_要注意,_slots_定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。
2.2 使用@property
在绑定属性的时候,我们不能直接把属性暴露出去,为了限制属性的范围,可以通过get和set方法检查参数。
Python内置的@property装饰器就是负责把一个方法变成属性调用。
class Student(object):
@property
def score(self):
return self._score
@score.setter # 只定义getter方法,不定义setter方法就表明该属性只读
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
2.3 多重继承
通过多重继承,一个子类就可以同时获得多个子类的所有功能。
在设计类的继承关系时,通常,主线是单一继承下来的,如果需要混入”额外”的功能,通常就需要多重继承来实现。这种设计称之为”MixIn”。
我们不需要负责庞大的继承链,只有选择组合不同类的功能,就可以快速构造出所需的子类。
2.4 定制类
_str_
print打印实例的时候,会出现一堆类似<main.Student object at 0x109afb190>,为了打印好看,我们只需要定义一个_str_方法。但是直接打印的时候还是会出现类似的问题,那么再定义一个_repr_()就会与_str_()效果一样。
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
_iter_
如果一个类想被用于for…in…循环,类似list和tuple这样,就必须实现一个_iter_方法,该方法返回一个迭代对象。
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 # 返回下一个值
>>> for n in Fib():
... print(n)
...
1
1
2
3
5
...
46368
75025
_getitem_
按照下标取出元素,实现切片的功能,就要实现_getitem_方法。
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
_getattr_
正常情况下,当我们调用类的方法或属性的时候,如果不存在就会报错,为了避免这个错误,Python中的_getattr_()方法能动态返回一个属性。
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
_call_
一个对象的实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用,但是任何类只需要定义一个_call_()方法,就可以直接对实例进行调用。
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
---
>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.
通过callable()函数,我们就可以判定一个对象是否是”可调用”对象。
2.5 使用枚举类
当我们需要定义常量的时候,一个办法就是用大写常量名通过整数定义,更好的办法是为这样的枚举类型定义一个class类型,然后,每个常量是class的唯一的实例。Python提供了Enum类来实现这个功能。
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
# 这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个变量,或者枚举它的所有成员。
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
如果需要更精确的控制枚举类型,可以从Enum派生出自定义类。
from enum import Enum, unique
@unique # @unique装饰器可以帮助我们检查保证没有重复值
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
# 访问枚举类型的若干种方法
>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(day1 == Weekday.Mon)
True
>>> print(day1 == Weekday.Tue)
False
>>> print(Weekday(1))
Weekday.Mon
>>> print(day1 == Weekday(1))
True
>>> Weekday(7)
Traceback (most recent call last):
...
ValueError: 7 is not a valid Weekday
>>> for name, member in Weekday.__members__.items():
... print(name, '=>', member)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat
# 既可以根据成员名称引用枚举常量,又可以根据value的值获得枚举常量。
2.6 使用元类
class的定义是运行时动态创建的,而创建class的方法就是使用type()函数。
type()函数可以查看一个类的类型或变量的类型,既可以返回一个对象的类型,又可以创建出新的类型。
>>> def fn(self, name='world'): # 先定义函数
... print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>
除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。
metaclass,直译为元类,简单的解释就是,当我们定义了类之后,就可以根据类创建出实例,所以,先定义类,然后创建实例。
先定义metaclass,就可以创建类,最后创建实例。
感谢廖雪峰的官方网站提供的教程。Python学习笔记系列都基于廖老师的教程。