接下来学习面向对象编程,基础的就不记录了,只记录一些Python特有的或者高级的特性。
1. 类的定义
定义类使用class关键字,后面紧跟类名(首字母大写),接着是从哪个类继承下来的(所有类最终会继承object)。
通过类名加参数就可以创建实例。可以自由的给实例绑定属性。可以把一些必须绑定的属性写在类中,通过__init__方法在初始化时将属性值进行绑定。
类中的函数与普通函数的区别是第一个参数是self,表示创建的实例本身(在调用时不需要传,Python解释器自己会把实例变量传进去)。
class Person(object): def __init__(self, name, age): self.name = name self.age = age def say_hello(self): print("Hello, my name is %s and I'm %s years old" % (self.name, self.age)) tom = Person('tom', 20) tom.say_hello() # Hello, my name is tom and I'm 20 years old
2. 访问限制
如果要让类的内部属性不被外部访问,可以在前面加上两个_,就变成了私有变量。
如果外部要访问/修改变量,可以加上get_xxx、set_xxx这样的方法。
前后都有两个_的是特殊变量,可以直接访问。
开头有一个_的变量是“约定俗成”的私有变量,外部可以访问,但是不要这么做。
开头有两个_的变量其实也是可以访问的,假设类名是Person,变量名是__name,在目前版本的Python中,外部可以通过_Person__name来访问__name变量。但是不建议这么做,除了规范外,不同版本的Python解释器会把__name改成不同的变量名。
3. 继承和多态
object是根(基类),任何类都可以追溯到object。
多态的含义(假设有一个类Animal,里面有一个run方法):
对于一个变量,我们只需要知道它是Animal类型,无需确切知道它的子类型,就可以调用run()方法,而具体调用的run()方法是作用在Animal、Dog还是Cat对象上,由运行时刻该对象的确切类型决定。即调用方只管调用,不管细节,当新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是开闭原则:
对扩展开放:允许新增Animal的子类;
对修改封闭:不需要修改依赖Animal类型的函数。
对于静态语言(如Java),如果需要传入Animal类型,则传入的对象必须是Animal类型或它的子类;而对于Python这种动态语言,不一定需要传入Animal类型,只需要保证传入的对象有一个run()方法就可以了。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
Python支持多重继承,常使用MixIn的设计模式。举个例子,Python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn和ThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。
4. 获取对象信息
type()函数可以获取对象的类型。
对于有继承关系的class来说,可以使用isinstance()函数。该函数判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。
ininstance()还支持判断一个变量是否是某些类型中的一种:
isinstance([1, 2, 3], (list, tuple))
dir()用于获取一个对象的所有属性和方法,它返回一个包含字符串的list。
方法也是属性,如果想知道哪些是方法,可以再使用callable()进行判断。
getattr()、setattr()和hasattr()用于操作对象的属性和函数,如:
# obj对象是否有属性x hasattr(obj, 'x') # 设置一个属性y setattr(obj, 'y', 20) # 获取属性y getattr(obj, 'y') # 获取属性z(如果试图获取不存在的属性,会有错误,因此可以给一个默认值) getattr(obj, 'z', 404)
5. 实例属性和类属性
给实例绑定属性的方法是通过实例变量,或者通过self变量。
而如果想给类本身绑定一个属性,可以直接在class中定义属性。
类属性实例也可以使用,不过前提是实例没有这个属性。
个人认为比较好的方式是实例属性和类属性不要使用相同的名字,另外在获取类属性的时候,通过类名来获取,而不是通过实例名来获取。
6. 使用@propery
@property装饰器可以将一个方法变为属性调用。
把一个getter方法变成属性赋值,只需要加上一个@property就可以了。这时@property本身又创建了另一个装饰器@xxx.setter,其中xxx是属性名,负责把一个setter方法变成属性赋值。
还可以只定义getter方法,这样这个属性就是一个只读属性。
class Student(object): @property def birth(self): return self._birth @birth.setter def birth(self, value): self._birth = value @property def age(self): return 2016 - self._birth s = Student() s.birth = 1990 s.birth s.age
7. 定制类
Python的class中有许多形如__xxx__这样有特殊用途的函数,可以帮助我们定制类。
__len__
定义了__len__()方法的class,可以直接作用于len()函数,即len()函数内部调用了对象的__len__()方法。
__slots__
我们可以给实例绑定一个属性,也可以给实例绑定一个方法:
def set_age(self, age): self.age = age from types import MethodType s.set_age = MethodType(set_age, s) # 其中s为某类的实例
这样绑定的方法只能是当前实例有效,如果想让所有实例都有效,可以给类绑定方法:
Person.set_age = set_age
但是,如果我们想限制实例的属性怎么办?可以在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:
class Student(object): __slots__ = ('name', 'age')
__slots__定义的属性仅对当前类实例起作用,对类不起作用,对继承的子类的实例也不起作用。除非在子类中也定义__slots__,这样子类允许定义的属性就是自身的__slots__再加上父类的__slots__。
__str__和__repr__
__str__()方法用于指定在print()对象时打印出的内容(给用户看的),而__repr__()方法用于指定直接打印的内容(给开发者看的)。当两者的内容一样时,可以在定义时直接赋值偷个懒:
class Person(object): def __str__(self): return 'str' def __repr__(self): return 'repr' # __repr__ = __str__ p = Person() print(p) # str p # repr
__iter__和__next__
如果一个类想被用于for...in循环,就必须定义一个__iter__()方法,该方法返回一个迭代对象,在遍历时通过迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
以斐波那契数列为例:
class 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 if self.a > 10: raise StopIteration() return self.a for n in Fib(): print(n) # 1 # 1 # 2 # 3 # 5 # 8
__getitem__、__setitem__和__delitem__
要能根据下标来操作对象,就需要对应定义__getitem__()、__setitem__()和__delitem()__方法。
下标的行为可能是一个int,也可能是一个切片(slice类型,还可能有step),也可能是一个可作为key的object,如str。
所以要实现这几个方法并不简单,需要考虑多种情况(list、tuple、dict)和多种用法(index、slice、key),这里就不举例了。
__getattr__
正常情况下,在调用类的某个属性或方法时,如果不存在,会报错(AttributeError),__getattr__()方法可以处理这个问题。
当定义了__getattr__()方法后,如果某个方法或属性不存在,就会调用__getattr__(self, attr)方法,第二个参数就是方法名,可以自己来处理。
利用完全动态的__getattr__,可以写出一种链式调用:
def Chain(object): def __init__(self, path = ''): self._path = path def __getattr__(self, path): return Chain("%s/%s" % (self._path, path)) def __str__(self): return self._path __repr__ = __str__ Chain().status.user.timeline.list # /status/user/timeline/list
__call__
如果想直接在实例本身上调用,可以定义__call__()方法。__call__()方法还可以定义参数。这样对实例调用好比对一个函数进行调用一样。
那么如果判断一个变量是对象还是函数呢?更多的时候,我们判断的是一个对象能否被调用,可以通过callable()函数来判断。
更多可定制方法请参见官方文档。
8. 使用枚举类
Enum可以将相关常量定义在一个class中,且class不可变,成员可直接比较。
from enum import Enum Month = Enum('Month', ('Jan', 'Feb', 'Mar')) for name, member in Month.__members__.items(): print(name, '->', member, ',', member.value) # Jan -> Month.Jan , 1 # Feb -> Month.Feb , 2 # Mar -> Month.Mar , 3
枚举值默认是从1开始的。如果要自定义值,可以从Enum派生出自定义类:
from enum import Enum, unique @unique class Weekday(Enum): Sun = 0 Mon = 1 Tue = 2 day1 = Weekday.Mon print(day1) # Weekday.Mon print(Weekday.Mon) # Weekday.Mon print(Weekday['Mon'] # Weekday.Mon print(Weekday(1)) # Weekday.Mon print(day1 == Weekday.Mon) # True
@unique装饰器可以帮助检查没有重复值。
访问枚举类型有多重方法,既可以用成员名称引用枚举常量,也可以直接根据value的值获取枚举常量。
9. 使用元类
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
type()函数可以查看一个类或变量的类型,class的类型是type,实例的类型是class。
type()函数既可以返回一个对象的类型,又可以创建出新的类型。
def fn(self, name = 'world'): pass Hello = type('Hello', (object,), dict(hello = fn)) h = Hello() h.hello() print(type(Hello)) # <class 'type'> print(type(h)) # <class '__main__.Hello'>
要创建一个class对象,type()依次传入三个参数:
class的名称;
继承的父类集合;
class的方法名称与函数绑定。
除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。
metaclass不好理解,目前还没有看懂,暂时留两个链接,后面继续深入理解:
http://blog.jobbole.com/21351/
上面的英文原文:
http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python