Skip to content

Latest commit

 

History

History
623 lines (464 loc) · 20.5 KB

面向对象.md

File metadata and controls

623 lines (464 loc) · 20.5 KB

1.1 面向对象

date: 2019-01-21

1.1.1 类和实例

抽象是隐藏多余细节的艺术。在面向对象的概念中,抽象的直接表现形式通常为类(Class)。虽然Python是解释型语言,但是它从设计之初开始就是一门面向对象的语言。

面向对象的抽象程度比函数要高,因为一个类既包含数据,又包含操作数据的方法。数据封装、继承和多态是面向对象的三大特点。

类(Class)和实例(Instance)是面向对象最基本,也是最重要的概念。

INDEX == True

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))
    
    @classmethod
    def new_student(cls, name, score):
        student = cls(name, score)
        return student
    
    @staticmethod
    def set_index():
        return INDEX == False
    
    @staticmethod
    def get_score():
        return Foo.print_score()

关键字class后面跟着类名,类名通常是大写字母开头,紧接着是object,表示该类从哪个类继承下来。object是所有类的基类。

使用__init__(self, ...)来初始化对象,在创建类实例时候会自动调用。类方法中第一个参数一定是self,指代实例本身。诸如self.nameself.score是这个类的属性(Property)。print_score(self)称为对象的方法(Method)。

tom = Student('Tom Simpson', 15);tom.print_score()这样定义类实例与调用类方法。

  • 实例方法

上述Student().print_score()就是实例方法。实例方法只能被实例调用。

  • 类方法

上述Student().new_student()是类方法,可以进行修改初始化的类等作用。

  • 静态方法

上述Student().set_index(), Student().get_score()是静态方法,能够进行环境变量的修改和调用类方法。

1.1.2 __slots__属性

动态语言允许在程序运行过程动态地给类(Class)加上功能。

class Student(object):
    pass

def set_score(self, score):
    self.score = score

Student.set_score = set_score

给类(Class)添加方法,对所有实例均可调用;对某个实例(Instance)添加方法,对另一个实例并不起作用。

但是当需要限制实例的属性时候,不能让其这样动态添加,就需要使用__slots__属性。对于主要是用来当成简单的数据结构的类而言,你可以通过给类添加__slots__属性来极大的减少实例所占的内存。

class Date:
    __slots__ = ['year', 'month', 'day']
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

当你定义__slots__后,Python就会为实例使用一种更加紧凑的内部表示。 实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。 在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。 使用slots一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在__slots__中定义的那些属性名。

当试图绑定不存在于__slots__中的属性时,就会得到AttributeError的错误。

1.1.3 访问控制

在类中,存在属性和方法。从以上的类定义来看,外部代码可以自由地修改一个实例的属性,这对类或实例是不安全的。

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))

>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'
>>> >>> bart._Student__name
'Bart Simpson'

双下划线开头的实例变量仍然可以通过_Student__name来访问__name变量。

Python 还使用单下划线_开头的属性和方法定义为内部实现。

1.1.4 可管理的属性

在类定义中定义私有变量时候,为了能够管理私有变量,可以在类中定义settergetter函数。

class Student(object):
    def get_score(self):
         return self._score

    def set_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
>>> s = Student()
>>> s.set_score(60) # ok!
>>> s.get_score()
60
>>> s.set_score(9999)
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!

但是上述的调用略显复杂,没有直接用属性简单。并且还需要对实例进行除了访问和修改之外的其他逻辑处理(比如类型检测和合法性验证),上面的方法就不大适用了。

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

    @property
    def score(self):
        return self._score

    @score.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

    @score.deleter
    def score(self):
        raise AttributeError("Can't delete attribute")
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!
>>> del s.score
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
AttributeError: can`t delete attribute
>>>

上述代码中有三个相关联的方法,这三个方法的名字都必须一样。 第一个方法是一个getter函数,它使得score成为一个属性。 其他两个方法给score属性添加了setterdeleter函数。 需要强调的是只有在score属性被创建后, 后面的两个装饰器@score.setter@score.deleter才能被定义。

在实现一个property的时候,底层数据会存储在self._score中。在__init__()方法中设置了self.score,就自动调用setter方法,这个方法里面会进行参数的检查,否则就是直接访问self._score了。

还能在已存在的getset方法基础上定义property

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

    def get_score(self):
         return self._score

    def set_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

    def del_score(self):
        raise AttributeError("Can't delete attribute")

    name = property(get_score, set_score, del_score)

1.1.5 调用父类方法

为了调用父类的方法,可以使用super()函数。

class A:
    def spam(self):
        print('A.spam')

class B(A):
    def spam(self):
        print('B.spam')
        super().spam()

super()常见用法就是调用__init__()方法来确保父类被正确初始化。

class Base:
    def __init__(self):
        print('Base.__init__')

class A(Base):
    def __init__(self):
        super().__init__()
        print('A.__init__')

class B(Base):
    def __init__(self):
        super().__init__()
        print('B.__init__')

class C(A,B):
    def __init__(self):
        super().__init__()
        print('C.__init__')
>>> c = C()
Base.__init__
B.__init__
A.__init__
C.__init__
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class '__main__.Base'>, <class 'object'>)
>>>

从上面的代码中,可以看出Python处理继承关系时候,会计算出一个所谓的方法解析顺序(MRO)列表。这个列表就是上述 C.__mro__。其遵循三条原则:

  • 子类会先于父类被检查

  • 多个父类会根据它们在列表中的顺序被检查

  • 如果对下一个类存在两个合法的选择,选择第一个父类

看下面这段代码

>>> class A:
>>>     def spam(self):
>>>         print('A.spam')
>>>         super().spam()
>>> a = A()
>>> a.spam()
A.spam
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 4, in spam
AttributeError: 'super' object has no attribute 'spam'
>>>
>>> class B:
...     def spam(self):
...         print('B.spam')
...
>>> class C(A,B):
...     pass
...
>>> c = C()
>>> c.spam()
A.spam
B.spam
>>>

在类A定义完后直接调用这个类A会报错,而在多继承情况下,类A的super().spam()确没有报错,这个跟类C的MRO列表有关。类A的super().spam()实际调用的是与其完全无关的类B的spam()方法。

1.1.6 定制类

要改变一个实例的字符串表示,可重新定义它的__str__(self)__repr__(self)方法。

class Pair:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return 'Pair({0.x!r}, {0.y!r})'.format(self)

    def __str__(self):
        return '({0.x!s}, {0.y!s})'.format(self)

为了自定义字符串的格式化,我们需要在类上面定义__format__()方法。

_formats = {
    'ymd' : '{d.year}-{d.month}-{d.day}',
    'mdy' : '{d.month}/{d.day}/{d.year}',
    'dmy' : '{d.day}/{d.month}/{d.year}'
    }

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __format__(self, code):
        if code == '':
            code = 'ymd'
        fmt = _formats[code]
        return fmt.format(d=self)

为了让一个对象兼容 with 语句,你需要实现__enter__(self)__exit__(self)方法。

from socket import socket, AF_INET, SOCK_STREAM

class LazyConnection:
    def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
        self.address = address
        self.family = family
        self.type = type
        self.sock = None

    def __enter__(self):
        if self.sock is not None:
            raise RuntimeError('Already connected')
        self.sock = socket(self.family, self.type)
        self.sock.connect(self.address)
        return self.sock

    def __exit__(self, exc_ty, exc_val, tb):
        self.sock.close()
        self.sock = None

from functools import partial

conn = LazyConnection(('www.python.org', 80))
# Connection closed
with conn as s:
    # conn.__enter__() executes: connection open
    s.send(b'GET /index.html HTTP/1.0\r\n')
    s.send(b'Host: www.python.org\r\n')
    s.send(b'\r\n')
    resp = b''.join(iter(partial(s.recv, 8192), b''))
    # conn.__exit__() executes: connection closed

1.1.7 元类和抽象基类

  • 元类

Python处处皆对象,Python中有“名字空间”的概念,这类似于其他编程语言中的作用域。在Python中,名字空间底层由一个dict实现,变量名就是字典中的键,而变量引用的对象就是字典中键对应的值。

Python中最为特殊的是,类型也是一个对象,包括Object在内的所有类型,都是对象实例。

深入理解python之对象系统

类型的对象的类型叫做元类,元类默认是一个type类对象,或者某个继承自type的类对象。最后,type类对象的类型是type自身。

正常情况下,我们经常使用object来派生一个类,即:

class Foo(object):
    pass

实际上,Python最终是调用了type来创建类,即我们常用来判断对象类型的type来创建类。

它的创建方式如下:

Foo = type('Foo', (object, ), {})    # 使用 type 创建了一个类对象

其中:

  1. 第一个参数是类名;

  2. 第二个参数是元祖类型,代表所有的父类;

  3. 第三个参数是字典,定义属性和方法。

元类(metaclass)是用来创建类(对象)的可调用对象,即控制类的创建行为。

如下举例:

  1. 定义一个元类
class PrefixMetaclass(type): # 从 type 继承,表示其是用来创建类的
    def __new__(cls, name, bases, attrs):
        '''
        cls:当前准备创建的类
        name:类的名字
        bases:类的父类集合
        attrs:类的属性和方法,是一个字典
        '''
        # 给所有属性和方法前面加上前缀 my_
        _attrs = (('my_' + name, value) for name, value in attrs.items())  

        _attrs = dict((name, value) for name, value in _attrs)  # 转化为字典
        _attrs['echo'] = lambda self, phrase: phrase  # 增加了一个 echo 方法

        return type.__new__(cls, name, bases, _attrs)  # 返回创建后的类
  1. 定制类
class Foo(metaclass=PrefixMetaclass):
    name = 'foo'
    def bar(self):
        print('bar')
  1. 使用
>>> f = Foo()
>>> f.name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'name'
>>> f.my_name
'foo'
>>> f.bar()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'
>>> f.my_bar()
bar
>>> f.echo('hello metaclass')
'hello metaclass'

可以看到,Foo 原来的属性 name 已经变成了 my_name,而方法 bar 也变成了 my_bar

typeObject的关系:

Python的世界中,Object是父子关系的顶端,所有的数据类型的父类都是它;type是类型实例关系的顶端,所有对象都是它的实例的。

它们两个的关系可以这样描述:Objecttype的实例(typeObject的父类),typeObject的子类,因此type也是type自身的实例。

type与Object

  • 抽象基类

抽象基类主要定义了基本类和最基本的抽象方法,可以为子类定义共有的接口,且不需要具体实现。

抽象基类提供了逻辑和实现解耦的能力。

能以最精简的方式展示代码之间的逻辑关系,让模块之间依赖清晰简单;

针对抽象基类,只需要关注其方法和描述,而不需要考虑过多的其他逻辑。

Python使用 abc 模块实现抽象基类:

from abc import ABCMeta, abstractmethod

class IStream(metaclass=ABCMeta):
    @abstractmethod
    def read(self, maxbytes=-1):
        pass

    @abstractmethod
    def write(self, data):
        pass

它的一个特点就是不能直接被实例化。抽象基类目的就是让别的类继承它并实现特定的抽象方法。

还有实现特定的接口,例如检测类型是否为特定类型的接口:

def serialize(obj, stream):
    if not isinstance(stream, IStream):
        raise TypeError('Expected an IStream')
    pass
  • 抽象类和接口类的区别和联系
  1. 抽象类: 规定了一系列的方法,并规定了必须由继承类实现的方法。由于有抽象方法的存在,所以抽象类不能实例化。

  2. 接口类:与抽象类很相似,表现在接口中定义的方法,必须由引用类实现,但他与抽象类的根本区别在于用途:与不同个体间沟通的规则。

  3. 区别和关联:

    • 接口是抽象类的变体,接口中所有的方法都是抽象的,而抽象类中可以有非抽象方法,抽象类是声明方法的存在而不去实现它的类;
    • 接口可以继承,抽象类不行;
    • 接口定义方法,没有实现的代码,而抽象类可以实现部分方法;
    • 接口中基本数据类型为static而抽象类不是。

1.1.8 魔术方法

Python类使用两个下划线定义的函数统称为魔术方法。这些方法有特殊的用途,有的不需要我们自己定义,有的则通过一些简单的定义可以实现比较神奇的功能。

下面只做基本的汇总,展开叙述后续再补充。

  • 基本定制
C.__init__(self[, arg1, ...]) 构造器带一些可选的参数C.__new__(self[, arg1, ...]) 构造器带一些可选的参数);通常用在设置不变数据类型的子类
C.__del__(self) 解构器
C.__str__(self) 可打印的字符输出内建str()及print 语句
C.__repr__(self) 运行时的字符串输出内建repr()  ‘‘   操作符
C.__unicode__(self)b Unicode 字符串输出内建unicode()
C.__call__(self, *args) 表示可调用的实例
C.__nonzero__(self) 为object 定义False 内建bool() (从2.2 版开始C.__len__(self) “ ” 长度可用于类);内建len()
  • 对象值比较
C.__cmp__(self, obj) 对象比较内建cmp()
C.__lt__(self, obj) and 小于/小于或等于对应<<=操作符
C.__le__(self,obj)
C.__gt__(self, obj) and 大于/大于或等于对应>>=操作符
C.__ge__(self,obj)
C.__eq__(self, obj) and 等于/不等于对应==,!=<>操作符
C.__ne__(self,obj)
  • 属性操作
C.__getattr__(self, attr) 获取属性内建getattr();仅当属性没有找到时调用
C.__setattr__(self, attr, val) 设置属性
C.__delattr__(self, attr) 删除属性
C.__getattribute__(self, attr) 获取属性内建getattr();总是被调用
C.__get__(self, attr) (描述符获取属性
C.__set__(self, attr, val)  (描述符设置属性
C.__delete__(self, attr)  (描述符删除属性
  • 数值计算
C.__*add__(self, obj) +操作符
C.__*sub__(self, obj) -操作符
C.__*mul__(self, obj) *操作符
C.__*div__(self, obj) /操作符
C.__*truediv__(self, obj)  True /操作符
C.__*floordiv__(self, obj)  Floor //操作符
C.__*mod__(self, obj) 取模/取余%操作符
C.__*divmod__(self, obj) 除和取模内建divmod()
C.__*pow__(self, obj[, mod]) 乘幂内建pow();**操作符
C.__*lshift__(self, obj) 左移位<<操作符

C.__*rshift__(self, obj) 右移>>操作符
C.__*and__(self, obj) 按位与&操作符
C.__*or__(self, obj) 按位或|操作符
C.__*xor__(self, obj) 按位与或^操作符

C.__neg__(self) 一元负
C.__pos__(self) 一元正
C.__abs__(self) 绝对值内建abs()
C.__invert__(self) 按位求反~操作符

C.__complex__(self, com) 转为complex(复数);内建complex()
C.__int__(self) 转为int;内建int()
C.__long__(self) 转为long内建long()
C.__float__(self) 转为float内建float()

C.__oct__(self) 八进制表示内建oct()
C.__hex__(self) 十六进制表示内建hex()
C.__coerce__(self, num) 压缩成同样的数值类型内建coerce()
C.__index__(self)g 在有必要时,压缩可选的数值类型为整型比如用于切片索引等等
  • 序列操作
C.__len__(self) 序列中项的数目
C.__getitem__(self, ind) 得到单个序列元素
C.__setitem__(self, ind,val) 设置单个序列元素
C.__delitem__(self, ind) 删除单个序列元素

C.__getslice__(self, ind1,ind2) 得到序列片断
C.__setslice__(self, i1, i2,val) 设置序列片断
C.__delslice__(self, ind1,ind2) 删除序列片断
C.__contains__(self, val) f 测试序列成员内建in 关键字
C.__*add__(self,obj) 串连+操作符
C.__*mul__(self,obj) 重复*操作符
C.__iter__(self)  创建迭代类内建iter()
  • 映射操作
C.__len__(self) mapping 中的项的数目
C.__hash__(self) 散列(hash)函数值
C.__getitem__(self,key) 得到给定键(key)的值
C.__setitem__(self,key,val) 设置给定键(key)的值
C.__delitem__(self,key) 删除给定键(key)的值
C.__missing__(self,key) 给定键如果不存在字典中则提供一个默认值