OOP

面向对象/OOP

OOD: Object Oriented Design.

面向过程的设计支持任何语言,但是如果语言本身内置面向过程的结构,就会更容易编程.

OOP: Object Oriented Programming.

python内置OOP的结构,但是不必一定要使用类和OOP.

面向对象的两个主题就是类和类实例.

创建实例的过程叫实例化.

属性就是属于另一个对象的数据或函数元素.属性分为数据属性和函数属性.


类/Class

类是现实世界的抽象的实体以编程的形式出现,实例是这些对象的具体化.

类是一种数据结构的定义,实例是申明了一个这种类型的变量.

类的定义:

Python 3 所有类默认继承自 object,即使不写 (object) 也是新式类。建议保留 (object) 以兼容性和可读性。

class ClassName(object):
    """Doc string."""
    class_suite

类的初始化方法init(相当于构造器):

如果定义了__init__方法在实例化的时候会首先调用该方法,进行一些初始化的工作。

init方法的第一个参数必须是实例self,而且不能有return语句。

init方法一般用来设置实例属性(也就是数据属性)。

class ClassName(object):
    def __init__(self, *args, **kwargs):
        pass

特殊方法new:

如果定义了__new__方法,会在init方法之前运行,并且返回一个实例,也就是__init__的self。

new方法的第一个参数必须是类cls。并且需要返回一个实例。

new方法在object中被定义为staticmethod。

相当于析构器的特殊方法del:

__del__特殊方法要在实例对象的所有引用都被清除后才会执行。

不要在del中做与实例没有关系的事情,一般不建议实现该方法。

class ClassName(object):

    def __new__(cls, *args, **kwargs):
        ...
        return ...

    def __del__(self):
        ...

类属性

类属性分为数据属性和方法属性。

类的数据属性仅仅是定义的类的变量。

数据属性通常是静态变量, 也就是和类对象绑定, 与类的实例无关。

直接通过类名来访问类的数据属性。不建议通过实例来访问类的数据属性。

class ClassName(object):
    CONST_VARIABLE = 'value'

    def __init__(self, *args, **kwargs):
        ClassName.CONST_VARIABLE = 'new'

ClassName.CONST_VARIABLE = 'new value'

类的方法属性仅仅是一个作为类定义的一部分定义的函数, 与类的实例无关。

类中定义的方法的第一个参数是一个实例self。

方法属性必须绑定到一个实例才能被直接调用, 非绑定方法没有给出实例对象一般不能直接调用。

class ClassName(object):
    def func(self, *args, **kwargs):
        pass

Python 3 中,直接调用 ClassName.func() 会报错:TypeError: func() missing 1 required positional argument: ‘self

调用非绑定方法:

ClassName.func(ClassName()) # 除非传入实例作为第一个参数self的值
# 常用场景: 调用父类中的非绑定方法
class ClassName(BaseClass):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        ...

调用绑定方法: 自动把实例作为self传入,不用显式传入。

ClassName().func()

查看类的属性:

dir(class) # 内建函数
class.__dict__ # 类的特殊属性

类的特殊属性:

class.__name__ # class namkkk
class.__qualname__
class.__module__
class.__doc__ # 文档的特殊属性, 不会被继承.
class.__annotations

class.__bases__ # 类的父类构成的元组
class.__orig_bases__
# 新式类新增的三个特殊属性:
class.__mro__ # 返回方法解析顺序的元组, mro()
class.__subclasses__() # 返回子类的列表

class.__dict__ # 以字典的形式存储对象的属性
class.__weakref__

方法和函数的特殊属性:

'__doc__',
'__annotations__'
'__qualname__'
'__name__',
'__module__',
'__self__',
'__func__', 

实例/Instances

实例化:

ins = ClassName()

实例属性

实例属性:

实例严格来说只有数据属性(方法属性应该属于类属性),数据属性就是和某个实例相关联的数据值,这些值独立于其它实例或类,当一个实例被释放,相应的数据属性也被释放。通常通过init方法来设置实例的数据属性。

class ClassName(object):
    DATA = "in class" # 类的数据属性

    def __init__(self, default="default", *args, **kwargs):
        self.default = default # 当前实例的数据属性

区别类的数据属性和实例的数据属性。

obj1 = ClassName()
print(obj1.DATA) # "in class", 当实例没有同名的数据属性,会访问类的数据属性。
obj1.DATA = "in obj1" # 相当于给实例新建了一个数据属性,会覆盖类的数据属性。
print(obj1.DATA) # "in obj1" 访问的是实例的数据属性,覆盖了类的数据属性。
print(ClassName.DATA) # "in class" 访问类的数据属性。

查看实例属性:

instance.__dict__

实例的特殊属性:

instance.__dict__ # 以字典的形式存储对象的属性
instance.__class__ # 实例对应的类

封装/Encapsulation

封装描述了对数据/信息进行隐藏的观念,对数据属性提供接口和访问函数.

默认情况下,数据属性和类属性都是public的.类所在的模块和导入了类的其它模块都可以使用.

var # public
def method_name(self):

一个下划线开头的属性是protected,能在类本身和子类使用,类的实例可以直接访问,不可以用from module import *导入.

用于把属性限制在一个模块中.

_xxx # protected
def _xxx(self):

双下划线开头的属性是private, 只能类本身使用,类的实例不能直接访问,子类和其它类都不能使用,子类也不能覆盖.

用于把属性限制在一个类中.

__xxx # private
def __xxx(self):

系统已经定义的特殊方法,也称魔法方法.

def __xxx__(self): # 系统定义的名字

Composition

类之间的关系只有两种继承和包含.

创建复合对象时可以通过composition组合来增加功能和代码的重用性.

当类之间有显著不同,并且较小的类是较大的类所需的组件时一般使用组合.

from .company import Company
from .home import Home
class Emp(object):
    def __init__(self, *args, **kwargs):
        self.comp = Company(args)
        self.home = Home(kwargs)

继承/Inheritance

利用类的两种方式就是包装和继承.

子类和派生

对于相同的类但是有不同的功能,可以通过derivation派生来实现.

通过使用一个已经定义好的类,扩展它或者修改,而不会影响系统中使用现存类的其它代码片段.

class Father(object):
    def woman(self):
        ...

class Mother(object);
    def man(self):
        ...

class Child(Father, Mother):
    def child(self):
        ...

继承

继承描述了基类的属性如何遗传给派生类.

派生类(子类)继承自基类(父类)

python中的类需要继承一个或多个父类.

object类是所有类的父类.

子类继承了基类的属性和方法.

文档字符串__doc__是唯一的,不能继承.

一个类的__bases__属性可以查看它的父类组成的元组.不包括父类的父类.

class.__bases__ # 类的父类构成的元组

实例调用方法时,默认调用的该对象的类的本身的方法,如果该类没有实现该方法才会调用父类的方法.

class Parent(object):
    def foo(self):
        print("in parent.")

class Child(Parent):
    def foo(self):
        print("in child.")

从内置类继承

可以从内置类型继承子类,修改一些属性.

class RoundFloat(float):
    def __new__(cls, val):
       return super(RoundFloat, cls).__new__(cls, round(val, 2))

class SortedKeyDict(dict):
    def keys(self):
        return sorted(super(SortedKeyDict, self).keys())

Multiple inheritance多重继承

由于类,类型,内建类型的子类都重新架构,新的类采用MRO算法来查找子类中使用的属性.

MRO: Method Resolution Order, 方法解释顺序.采用广度优先,从左至右边,来获取在子类中的属性.

可以通过新式类的特殊属性查看子类的属性的查找顺序:

class.__mro__ # 返回方法解析顺序的元组

多继承,mro和super的用法:

super每次只调用MRO中的第一个父类,和getattr的顺序一样.并且相同的父类只调用一次.

数据属性,普通方法属性,特殊方法属性都是按照MRO顺序来查找.

https://github.com/crazy-canux/python/tree/master/python/multiple_inheritance


多态/Polymorphism

python不支持方法重载,但是可以通过对参数的判断,对不同的参数进行不同的处理。以此来实现重载的功能。

python可以重载魔法方法。

magicmethod

python类有一些可自定义的特殊方法集,它们中的一些有预定义的默认行为,一些没有,留到需要的时候去实现。

这些特殊方法是python中用来扩充类的方法。可以用来模拟标准类型或者重载操作符。

这些特殊方法都是用双下划线开头和结尾的,也被称为魔法方法。

对象创建和销毁:

__init__(self, *args, **kwargs) # 构造器,带一些可选的参数
__new__(cls, *args, **kwargs) # 构造器,带一些可选的参数,通常用来设置不可变数据类型的子类。
__del__(self) # 解构器

字符串表示:

__str__(self) # str(), print() 
obj = ClassName()
print(obj) # 默认的类的__str__会调用__repr__
# 可以通过重写__str__或__repr__来改变打印的内容

__repr__(self) # repr()
obj = ClassName()
obj # 默认的打印对象的运行时的字符串,<test.RoundFloat at 0x7fb715253e90>
# 可以通过重写__repr__()改变打印的内o

__bool__(self) # 用于bool判断真假

可调用对象的特殊方法:

__call__(self, *args) # 表示可调用的实例, callable(object) 会返回true.

class TestClass(object):
    def __call__(self, *args):
        print("Instance is callable after implement call method in class.")
        print("Args come from instance invoke is: {}".format(args))

tc = TestClass()
callable(tc) # True
tc()
tc('arg1')

实例和类的检查相关特殊方法:

__instancecheck__(self, instance) # isinstance(instance, class)
__subclasscheck__(self, subclass) # issubclass(subclass, class)

属性相关特殊方法:

__getattr__(self, name) # getattr(), 仅当属性没有在实例/类/父类的__dict__中找到才会调用.
__getattribute__(self, name)
__setattr__(self, name, value)
__delattr__(self, name)
__dir__(self) # dir()

# 描述符相关
__get__(self, instance, owner)
__set__(self, instance, value)
__delete__(self, instance)

with上下文管理特殊方法:

__enter__(self) # return self, 需要返回self
__exit__(self, exc_type, exc_value, traceback)

对象比较特殊方法:

__cmp__(self, other) # cmp()
__lt__(self, other)
__le__(self, other)
__eq__(self, other)
__ne__(self, other)
__gt__(self, other)
__ge__(self, other)

容器类型相关特殊方法:

__len__(self) # len()
__getitem__(self, key) #
__setitem__(self, key, value) #
__delitem__(self, key) # del
__reversed__(self) # reversed()
__iter__(self) # iter()
__contains__(self, item)
__missing__(self, key)

https://github.com/crazy-canux/python/blob/master/python/magicmethod/container.py

数值类型相关特殊方法:

__add__(self, other)
__sub__(self, other)
__mul__(self, other)
__div__(self, other)
__truediv__(self, other)
__floordiv__(self, other)
__mod__(self, other)
__divmod__(self, other)
__pow__(self, other[, module])
__lshift__(self, other)
__rshift__(self, other)
__and__(self, other)
__xor__(self, other)
__or__(self, other)

__rxxx__(self, other)

# 原位运算必须返回self.
__ixxx__(self, other) # self += other -> self = self + other

__neg__(self)
__pos__(self)
__abs__(self)
__invert__(self)

__complex__(self)
__int__(self)
__long__(self)
__float__(self)

__oct__(self)
__hex__(self)

__index__(self)
__coerce__(self, other)

Delegation & Wrapping

Wrapping包装就是对一个已经存在的对象增加,删除或修改已经存在的功能.

Delegation授权(代理)是Wrapping包装的一个特性,用于简化处理相关命令性功能,最大化重用代码.

实现delegation的关键在于覆盖__getattr__()特殊方法.通过调用内置函数getattr()得到一个对象的默认行为.

class Wrapper(object):
    def __init__(self, obj):
        self.__data = obj

    def __getattr__(self, attr):
        return getattr(self.__data, attr)

metaclass元类

metaclass 是"创建类的类"。正如实例是类的实例化,类也是元类的实例化。在 Python 中,类本身也是对象,而元类就是用来创建这些类对象的。

元类用来定义某些类是如何被创建的。改变类的默认行为和创建方式。

大多数情况下不需要创建元类,一般使用系统的元类的默认方式。

在执行类定义的时候,解释器必须知道这个类的元类;

class ClassName(metaclass=MetaClassName):
    pass

在执行类定义时候检查元类,元类传递三个参数到构造器:

类名
从基类继承数据的元组, __bases__
类的属性字典, __dict__

元类相关的可定制属性:

__metaclass__

定义一个元类:

class MetaClassName(type):
    def __new__(cls, name, bases, dicts):
        super().__new__(cls, name, bases, dicts)
        # 在这里做一些你希望使用该元类的类在定义时做的操作
    def __call__(cls, *args, **kwargs):
       if cls not in cls._instances:
          cls._instances[cls] = super().__call__(*args, **kwargs)
      return cls._instances[cls]

class ClassName(metaclass=MetaClassName):
    def __init__(self, value):
        self.value = value
    ...

https://github.com/crazy-canux/python/tree/master/python/metaclass

使用场景:

  • 单例模式
  • 自动注册类
  • 属性验证和转
  • ORM模型创建
  • 抽象基类和接口检查

abstractmethod

抽象方法,类似于java的interface.

最简单的抽象方法:

# 如果子类没有实现同名的该方法,就会抛出异常.
def base_method(self):
    raise NotImplementedError

或者使用abc标准库来实现:

https://github.com/crazy-canux/python/tree/master/python/psl/myabc.py


descriptors描述符

普通对象访问(set/get/delete)属性的优先级:

obj.__dict__['attr'] # 先访问实例对象
obj.__class__.__dict__['attr'] # 再访问类对象
obj.__class__.__base__.__dict__['attr'] # 接着访问基类的对象,不包括metaclass.
__getattr__ # 如果实现了的话,优先级最低

描述符是一个对象,它定义了当另一个对象的属性被访问时应该如何处理。任何定义了 get()、set() 或 delete() 方法的类都是描述符。

__get__(self, obj, type=None) # 返回一个属性的值
__set__(self, obj, value) # 设置一个属性的值,返回None
__delete__(self, obj) # 属性的引用递减,返回None

描述符是数据property,class,staticmethod,classmethod, 以及super的机制.

data descriptor:定义了__get__和__set__的对象是数据描述符, 主要用于数据属性.

non data descriptor:仅仅定义了__get__的对象是非数据描述符,主要用于方法属性.

如果实例的字典(obj.dict)具有与数据描述符相同名称的条目,则数据描述符优先。

如果实例的字典(obj.dict)具有与非数据描述符相同名称的条目,则字典条目优先。

class DescriptorName(object):
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, typ):
        print('__get__', instance, typ)
        return self.name

    def __set__(self, instance, value):
        print('__set__', instance, value)
        self.name = value

class TestClass(object):
    name = DescriptorName('canux')

tc = TestClass()
print(tc.name) # __get__(tc, type(tc))被调用
print(TestClass.name) # __get__(None, TestClass)被调用
tc.__dict__['name'] = 'test' # 无效
tc.name = 'test' # __set__被调用
TestClass.name = 'test' # 仅仅是重新定义类的属性,覆盖了描述符
# 此时tc.__dict__有同名属性,如果定义了__set__
print(tc.name) # __get__被调用,属性已经修改
print(TestClass.name) # __get__被调用,属性已经修改
# 如果没有定义__set__,就是调用的tc.__dict__里面的.

描述符访问属性的优先级:

数据描述符(__set__, __get__)
# 对于访问实例属性obj.__getattribute__调用方式:type(obj).__dict__['attr'].__get__(obj, type(obj))
# 对于访问类属性class.__getattribute__调用方式:ClassName.__dict__['attr'].__get__(None, ClassName)
instance.__dict__
非数据描述符(__get__)
__getattr__ # 如果实现了的话,在描述符中优先级最低

描述符是由__getattribute__特殊方法调用,覆盖该方法可以防止描述符自动调用.

obj.__getattribute__和class.__getattribute__的调用方式不同.

描述符的三个特殊方法一般是通过属性访问自动调用.

函数和方法的描述符:

在属性访问期间函数包括了__get__方法用于绑定方法.因此函数和方法是非数据描述符.

class TClass(object):
    def __get__(self, obj, typ=None):
        return types.MethodType(self, obj, typ)


    def tmethod(self, args):
        return args

class Foo(object):
    @Tclass
    def bar(self):
        print('in bar')

obj = TClass()
TClass.__dict__['tmethod'] # function __main__.f
TClass.tmethod # unbound method TClass.tmethod
obj.tmethod # bound method TClass.tmethod of <__main__.TClass object at 0x7f8a4f084c10>

obj.function(*args) -> function(obj, *args)
Class.function(*args) -> function(*args)

property

property属性是一种有用的特殊类型的描述符. 也是descriptor的主要用途.

property(fget=None, fset=None, fdel=None, doc=None) # 返回一个property类型的对象

通过上面的descriptor的普通方式实现纯pytho写的property:

class Property(object):
    def __init__(self, fget, fset, fdelete):
        self.fget = fget
        self.fset = fset
        self.fdelete = fdelete

    def __get__(self, obj, typ=None):
        return self.fget(obj)

    def __set__(self, obj, val):
        self.fset(obj, val)

    def __delete__(self, obj):
        self.fdelete(obj)

class Foo(object):
    def fget(self):
        print 'fget called'

    def fset(self, val):
        print 'fset called'

    def fdelete(self):
        print 'fdelete called'

    bar = Property(fget, fset, fdelete)

通过装饰器@property来实现:

class Person(object):
    def __init__(self):
        self._email = None

    @property
    def email(self):
        return self._email

    @email.setter
    def email(self, value):
        m = re.match('\W+@\W+\.\W+', value)
        if not m:
            raise Exception('email not valid')
        self._email = value

    @email.deleter
    def email(self):
        del self._email

https://github.com/crazy-canux/python/tree/master/python/descriptor


super

因为同名的方法子类会覆盖父类,在子类中调用父类的同名方法可以通过super内置函数。

super()方法实际是一个构造器。自动找到基类方法,同时传入self参数。

super().__init__() # 不需要传递参数

对于单继承, super用来调用父类同名方法。

class Child(Parent):
    def foo(self):
        Parent.foo(self) # 可以手动调用父类同名的方法,调用非绑定方法,传入self参数。

class Child(Parent):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs) 
        ...

    def foo(self, *args, **kwargs):
        super().foo(*args, **kwargs) 
        ...

对于多继承,super用法参考上面的多继承.

常用场景:

  • 调用父类构造函数
  • 扩展父类方法
  • 多重继承场景
  • 属性设置场景
  • 合作式继承
  • 类方法中使用
  • 静态方法中使用

super()返回的对象有一个用于调用Descriptor的定制__getattribute__()方法.

super(B, obj).method() ->
obj.__class__.__mro__ ->
A.__dict__['method'].__get__(obj, B)

https://rhettinger.wordpress.com/2011/05/26/super-considered-super/

classmethod

classmethod 是一个内置装饰器,用于将方法转换为类方法。类方法的特点是:

  • 第一个参数是类本身 cls,而不是实例 self
  • 可以通过类或实例调用
  • 可以访问类属性,但不能直接访问实例属性
  • 常用于创建备选构造函数

类方法通常用于替代类构造函数.

class ClassName(object):
    @classmethod
    def demo_cm(cls, *args, **kwargs):
        ...

# 可以通过类来调用, 也就是可以直接调用非绑定方法.自动传入类作为第一个参数.
ClassName.demo_cm(args, kwargs)
# 也可以通过实例来调用, 自动传入类作为第一个参数
ClassName().demo_cm(args, kwargs) // not recommend

描述符相关:

obj.function(*args) -> function(type(obj), *args)
Class.function(*args) -> function(Class, *args)

常用场景:

  • 备选构造函数
  • 工厂方法模式
  • 访问和修改类属性
  • 配置和设置相关方o
  • 继承中的类方法

staticmethod

staticmethod 是一个内置装饰器,用于将方法转换为静态方法。静态方法的特点是:

  • 不接收隐式的第一个参数(不需要 self 或 cls)
  • 可以通过类或实例调……
  • 不能访问类或实例的属性
  • 行为类似普通函数,只是在类的命名空间中

例子:

class ClassName(object):
    @staticmethod
    def demo_sm():
        ...

ClassName.demo_sm()
ClassName().demo_sm()

描述符相关:

obj.function(*args) -> function(*args)
Class.function(*args) -> function(*args)

常用场景:

  • 工具函数
  • 验证和转换函数
  • 常量和配置相关
  • 工厂方法辅助函数

Class Decorators

类装饰器比函数装饰器更灵活,高内聚,封装性等优点.

类装饰器用于装饰一个类.

def deco_name(cls):
    class WrapperName(cls, ...):
        def __init__(self, *args, **kwargs):
            cls.__init__()
            ....__init__()
            ...
    return WrapperName

@deco_name
class ClassName(object):
    def __init__(self, *args, **kwargs):
        ...
    ...

https://github.com/crazy-canux/python/blob/master/python/decorator/class_decorator.py


Designed by Canux