面向对象最重要的概念就是类(Class)和实例(Instance)。 Class 是抽象出来的模板,比如Student类。
Instance 是根据 Class 创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同,互不影响。
后面跟着类名(通常大写字母开头),然后是“(object)”,表示从object类继承下来。
注意:所有类的最终类都是object类,所以默认就继承它。
注意:__init__
方法的第一个参数永远是 self,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到 self,因为 self 就指向创建的实例本身。
注意:如果定义了 __init__
方法,在创建实例的时候,就不能传入空的参数了,必须传入与 __init__
方法匹配的参数,但 self 不需要传,Python解释器自己会把实例变量传进去。
注意:和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
与静态语言不同,Python允许对对象变量绑定任何数据,也就是说,对于两个对象变量,虽然它们都是同一个 Class 的不同对象,但拥有的变量名称都可能不同。
有些时候,你会看到以一个下划线开头的实例变量名,比如 _name
,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
class Person(object):
# __init__方法的第一个参数永远是self,表示创建的实例本身,有了init方法,就不能传递空参。
def __init__(self, name, age):
# 以两个下划线__开头的表示私有变量,外部不能访问。
# 注意:__XX__ 两个下划线开头,两个下划线结束的变量,是py的特殊变量,我们不要这么定义。
self.__name = name
self.__age = age
# 类的属性,外部可访问。
self.score = score
# class 的方法,获取 class 的属性。
def print_person(self):
print('%s:%s' % (self.__name, self.__age))
if __name__ == '__main__':
# 创建 class 的 对象。
a = Person('Chris', 30)
a.print_person()
# abc.set_name('Born') # 为对象添加属性,私有属性不能绑定
# abc.age = '123' # 为对象添加属性,私有属性不能绑定
定制默认函数
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' % self.name
__repr__ = __str__
# 如果不自定义 __str__ 方法,打印对象会显示的内容如下:
print(Student('Michael')) # <__main__.Student object at 0x109afb190>
# 如果不定义 __repr__ 方法,打印引用会显示的内容如下:通常__str__()和__repr__()代码都是一样的
s = Student('Michael')
s
<__main__.Student object at 0x109afb310>
## **Class属性和Object属性**
# -*- coding: utf-8 -*-
class Animal(object):
# 类属性:定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到
name = 'Animal'
def __init__(self, name):
# 给实例绑定属性,是通过实例变量,或者通过 self 变量:
self.name = name
def run(self):
print('Animal is running...')
# 我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法
def __len__(self):
return 100
if __name__ == '__main__':
s = Animal('Dog')
print(s.name) # Dog,实例属性会覆盖类属性。
del s.name
print(s.name) # Animal 实例属性删掉之后,就会自动去找类属性。
# 在编写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
Copy
## **多重继承**
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。
但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为MixIn。
为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixIn和FlyableMixIn。类似的,你还可以定义出肉食动物CarnivorousMixIn和植食动物HerbivoresMixIn,让某个动物同时拥有好几个MixIn:
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
Copy
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
Python自带的很多库也使用了MixIn。举个例子,Python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn和ThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。
比如,编写一个多进程模式的TCP服务,定义如下:
class MyTCPServer(TCPServer, ForkingMixIn):
pass
Copy
编写一个多线程模式的UDP服务,定义如下:
class MyUDPServer(UDPServer, ThreadingMixIn):
pass
Copy
如果你打算搞一个更先进的协程模型,可以编写一个CoroutineMixIn:
class MyTCPServer(TCPServer, CoroutineMixIn):
pass
Copy
## **继承和多态**
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Animal(object):
"""docstring for Animal"""
def run(self):
print('Running')
# 继承自 Animal 类
class Dog(Animal):
# 子类和父类都有 run 方法,总会调用子类的 run 方法,这就是继承的另外一个好处"多态",重写父类方法。
def run(self):
print('Dog is running!')
# 除了可以继承父类的方法,还可以定义自己的方法
def eat(self):
print('Dog eat meat....')
aaa = 88 # 定义类属性
if __name__ == '__main__':
# 继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
# 动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。
dog = Dog()
Copy
## **call** **方法**
一个对象可以有自己的属性和方法,当我们调用对象的方法时,我们用 instance.method() 来调用。能不能直接在对象本身上调用呢?在Python中,答案是肯定的。
任何类,只需要定义一个 __call__() 方法,就可以直接对实例进行调用
通过内置函数 callable,我们就可以判断一个“对象”是不是可调用的。
# -*- coding: utf-8 -*-
class Student(object):
def __init__(self):
self.name = 'Chris'
#
def __call__(self, *args, **kwargs):
print('My name is %s.' % self.name)
if __name__ == '__main__':
pass
s1 = Student()
s1()
print(callable(s1))
Copy
## **getattr** **方法**
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错 AttributeError 。
错误信息很清楚地告诉我们,没有找到 age 这个 attribute。
要避免这个错误,除了可以加上一个 age 属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性。
当调用不存在的属性时,比如 age,Python解释器会试图调用 __getattr__(self, 'score') 来尝试获得属性,这样,我们就有机会返回 score 的值,注意,只有在没有找到属性的情况下,才调用 __getattr__
此外,注意到任意调用如 [s.xxx](http://s.xxx) 都会返回 None ,这是因为我们定义的 __getattr__ 默认返回就是None。
要让 class 只响应特定的几个属性,我们就要按照约定,raise AttributeError的错误。
# -*- coding: utf-8 -*-
class Student(object):
def __init__(self):
self.name = 'Chris'
def __getattr__(self, item):
if item == 'age':
return 18
# 按照约定,返回错误信息。
raise AttributeError('\'Student\' object has no attribute \'%s\'' % item)
if __name__ == '__main__':
pass
s1 = Student()
print(s1.name)
print(s1.age1) # AttributeError: 'Student' object has no attribute 'age'
Copy
## **iter** **方法**
如果一个类想被用于for ... in循环,类似 list 或 tuple 那样,就必须实现一个 __iter__() 方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的 __next__() 方法拿到循环的下一个值,直到遇到 StopIteration 错误时退出循环。
# -*- coding: utf-8 -*-
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 # 返回下一个值
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if __name__ == '__main__':
pass
# 有 __iter__ 方法,就可以应用到 for in 的句式中。
for fib in Fib():
pass
# Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素
print(Fib()[5])
# TypeError: 'Fib' object is not subscriptable
# 要表现得像list那样按照下标取出元素,需要实现__getitem__()方法
# 但是list有个神奇的切片方法,对于Fib却报错。原因是__getitem__()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断。
# 然后切片对象 slice 的参数也需要进行判断。
# 所以要 定制一个 Class 还有很多内容需要完善。
Copy
## **len** **方法**
Python 的内置函数 len 可以返回对象的长度,实际上就是调用该对象的 __len__()方法
我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法
# -*- coding: utf-8 -*-
class Animal(object):
def __init__(self, name):
self.name = name
def run(self):
print('Animal is running...')
# 根据需要定义我们自己的 len 方法。
def __len__(self):
return 100
Copy
## **solts** **属性**
正常情况下,当我们定义了一个 class,创建了一个 class 的对象后,我们可以给该对象绑定任何属性和方法,这就是动态语言的灵活性。
在定义 class 的时候,定义一个特殊的 __slots__ 变量,来限制该 class 的对象能动态添加的属性。
使用 __slots__ 要注意, __slots__ 定义的属性仅对当前类对象起作用,对继承它的子类的对象是不起作用的。除非在子类中也定义 __slots__ ,这样,子类实例允许定义的属性就是自身的 __slots__ 加上父类的 __slots__ 。
# -*- coding: utf-8 -*-
class Animal(object):
def run(self):
print('Animal is running...')
# 限制当前类,尽可以动态添加 age 和 sex 属性,不能添加除此之外的其他属性。
__slots__ = ('age', 'sex')
if __name__ == '__main__':
s = Animal()
# 动态给对象绑定一个属性
s.name = 'Dog'
# 动态给对象绑定一个方法
def set_age(self, age):
self.age = age
from types import MethodType
# 给对象绑定一个方法
s.set_age = MethodType(set_age, s)
s.set_age(12)
print(s.age)
# 但是,给一个实例绑定的方法,对另一个实例是不起作用的。
def set_sex(self, sex):
self.sex = sex
# 给class绑定方法后,所有对象都可以调用。
Animal.set_sex = MethodType(set_sex, Animal)
s.set_sex('Man')
print(s.sex)
Copy
## **str** **方法**
在打印变量的时候显示我们自定义的内容。
# -*- coding: utf-8 -*-
class Student(object):
def __init__(self, name):
self.name = name
# 自定义打印内容。
def __str__(self):
return 'Student Object (name = %s)' % self.name
# 终端键入变量后直接回车的时候,会打印内存地址,通常我们把它和 str 函数一起设置。
__repr__ = __str__
if __name__ == '__main__':
pass
s = Student('Chris')
print(s) # 直接打印对象:Student Object (name = Chris)
s # 直接在终端键入:<__main__.Student at 0x28cbf076b88>
Copy