理解python面向对象编程
python类使用class
关键字定义,使用class
定义的类又可以称为类型对象
也可以说,使用class
关键字可以创建类型对象。
继承了object对象的类型对象是New-Style Class
在python3中所有类型对象都是New-Style Class
class User(object): pass
类型对象的结构体
object类的C结构体如下:
typedef struct
{
PyObject_HEAD PyObject *cl_bases; PyObject *cl_dict; PyObject *cl_name;
PyObject *cl_getattr;
PyObject *cl_setattr;
PyObject *cl_delattr;
} PyClassObject;
从上面的结构体可以看出,object类存储了所有的静态字段和方法
通过实例化object类可以得到【实例对象】
In [76]: class User(object): pass
# 实例化出实例对象u
In [77]: u = User()
# 由此可以看出u对象的ob_type是User对象
In [78]: type(u)
Out[78]: __main__.User
【实例对象】C结构体定义如下:
typedef struct
{
PyObject_HEAD
PyClassObject *in_class;
PyObject *in_dict;
PyObject *in_weakreflist;
} PyInstanceObject;
实例对象也有一个dict字段,保存的是所有的实例字段
In [79]: u.name = "sunli"
In [81]: u.__dict__
Out[81]: {'name': 'sunli'}
需要清楚实例字段和静态字段的区别,类对象的字段的查找顺序是:
instance.__dict__ -> class.__dict__ -> baseclass.__dict__
字段初始化
class User(object):
# 静态字段
__table__ = "user"
# 初始化
def __init__(self, name):
self.name = name
# 序列化
def __str__(self):
return "my name is: {0}".format(self.name)
小心得,可以看到__init__, __str__
其实是重写type对象里面__init__, __str__
函数
还记得type对象定义的操作函数有哪些么
类方法
实例方法和函数的区别是self 这个隐式参数
# 定义一个打印方法
def p(self): print(self.name)
# 给实例对象动态绑定一个方法
u.p = p
# 调用p方法
u.p(u)
使用 staticmethod、classmethod 装饰器可以构造静态方法,静态方法可以被类型对象直接调用
>>> class User(object):
... def a(): pass
...
... @staticmethod 没有参数
... def b(): pass
...
... @classmethod 有参数
... def c(cls): pass
>>> User.a
类继承
和其他语言不同的是,类继承需要显示的调用父类的初始化函数
# 继承了BaseMenu类
class Menu(BaseMenu):
def __init__(self, restaurant_id):
# 必须显示的调用父类的init函数
super(Menu, self).__init__(restaurant_id)
super 的类型参数决定了在类 mro 列表中的搜索起始位置
# 表示调用父类的__init_函数,查找的顺序是Menu类的父类
super(Menu, self).__init__(restaurant_id)
如果需要显示的调用父类的函数
class TargetUrlValidator(StringValidator):
@classmethod
def validate(cls, target_url):
# 显式调用父类函数
super(TargetUrlValidator, cls).validate(target_url)
__mro__
: 关键字记录了类的操作顺序
__bases__
: 基类列表,可直接修改来更换基类,影响 mro 顺序。
In [100]: class A(object): pass
In [101]: class B(A): pass
In [102]: B.__mro__
# 先查找B,然后查找A
Out[102]: (__main__.B, __main__.A, object)
# 记录了B的父类
In [111]: B.__bases__
Out[111]: (__main__.A,)
抽象类
象类 (Abstract Class) 法实例化,且派 类必须 "完整" 实现所有抽象成员才可创建实例。
>> from abc import ABCMeta, abstractmethod, abstractproperty
>>> class UserAbstract(object):
# 使用元类来控制,创建这个类需要检查是否实现print_id方法
... __metaclass__ = ABCMeta
... def __init__(self, uid):
... self._uid = uid
...
... @abstractmethod
... def print_id(self): pass
操作符重载
称索引器,像序列或字典类型那样操作对象。
def Items(object):
def __init__(self):
self.fields = {}
def __getitem__(self, key):
return self.fields.get(key)
def __setitem__(self, key, value):
self.fields[key] = value
def __delitem__(self, key):
del self.fields[key]
def __contains__(self, key):
return key in self.fields
getattr VS getattribute
先看看这几个法的触发时机
• getattr: 访问不存在的成员。 • setattr: 对任何成员的赋值操作。 • delattr: 删除成员操作。 • getattribute: 访问任何存在或不存在的成员,包括 dict。
不要在这几个方法里面直接访问对象成员 例如 self.key
也不要hasattr/getattr/setattr/delattr函数,例如getattr(self, key)
因为它们会被再次拦截,形成无限循环。
正确的做法是直接操作 self.__dict__['key']
。
__getattribute__
连 __dict__
都会拦截,只能使用基类的 __getattribute__
返回结果
直接使用[类名.字段]访问并不会进入__getattribute__
拦截函数
class ModelProxy(object)
def __init__(self, t_obj):
self.t_obj = t_obj
def __getattribute__(self, name):
# 使用ModelProxy.key并不会执行__getattribute__方法!!!
# 所以可以使用getattr(type(self), name, None)
# type(self)返回的是ModelProxy
# 如果使用 getattr(self, name, None)则会进入无限循环
value = getattr(type(self), name, None)
# 如果不存在则抛弃异常, 抛出异常后会自动调用调用__getattr__
if value is None:
raise AttributeError
elif callable(value):
return lambda: value(self)
else:
return value
def __getattr__(self, name):
# 使用基类的__getattribute__,为什么基类的方法可以被调用
# 是因为基类的__getattribute__并没有被重写
# 还有一种调用基类的方法 super(ModelProxy, self).__getattribute__(self, "t_obj")
t_obj = object.__getattribute__(self, "t_obj")
return getattr(t_obj, name)
这里需要明白
1. 实例对象访问属性调用的是类定义的__getattribute__
2. 类型对象(类)访问静态属性调用时元类(默认是type对象)的__getattribute__
还有常用的重载方法有__call__
描述符
python定义了__get__
, __set__
的方法的类就是描述符。