面向对象最重要的概念就是类(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