python读书笔记 (五) 类

7.1 名字空间

类型是类型,实例是实例。如同 def,关键字 class 的作用是创建类型对象,类型对象很特殊,在整个进程中是单例的,是不被回收的。

>>> class User(object): pass
... 
>>> u = User()
>>> type(u)
<class '__main__.User'>
>>> u.__class__
<class '__main__.User'>

类型和实例拥有自己的名字空间

>>> User.__dict__
mappingproxy({'__dict__': <attribute '__dict__' of 'User' objects>, '__weakref__': <attribute '__weakref__' of 'User' objects>, '__doc__': None, '__module__': '__main__'})
>>> u.__dict__
{}

访问对象成员时,就从他们的名字空间查找,而非globals, locals

成员查找顺序:instance.__dict__ -> class.__dict__ -> baseclass.__dict__

7.2 字段

字段(Field)和属性(Property) 是不同的。

  •     实例字段存储在 instance.__dict__, 代表单个对象实体的状态。
  •     静态字段存储在class.__dict__,为所有同类型实例共享
  •     必须通过类型和实例对象才能访问字段。
  •     以双下划线开头的class和instance成员视为私有,会被重命名。
>>> class User(object):
...     table = 't_user'
...     def __init__(self, name, age):
...             self.name = name
...             self.age = age

>>> u1 = User('user1',20)
>>> u1.__dict__
{'name': 'user1', 'age': 20}
>>> u2 = User('user2', 30)
>>> u2.__dict__
{'name': 'user2', 'age': 30}
>>> User.__dict__
'__dict__': <attribute '__dict__' of 'User' objects>, 'table': 't_user'

访问静态字段,出来class.name,也可以使用instance.name, 按照查找规则先查找instance.__dict__, 如果没有就继续查找 class.__dict__

私有字段以双下划线开头,无论是静态还是实例成员,都被重命名:_<class>__<name>

>>> class User(object):
...     __table = 't_user'
...     def __init__(self, name, age):
...         self.name = name
...         self.__age = age
...     def __str__(self):
...         return '{0}: {1},{2}'.format(self.__table, self.name, self.__age)
... 
>>> u = User('tom', 20)

>>> u.__dict__
{'name': 'tom', '_User__age': 20}

>>> User.__dict__
mappingproxy({'_User__table': 't_user',})

有些时候即想使用私有字段,又想使用外部访问权限

  • 用重命名的格式访问_class__name
  • 只用一个下划线,仅提醒,不重命名

 

7.3 属性

属性(Property) 是 由getter,setter,deleter 几个方法构成的逻辑,属性可能直接返回字段值,也可能是动态逻辑运算的结果。

属性以装饰器或描述符实现

class User(object):
    @property
    def name(self):
        return self.__name
    @name.setter
    def name(self, value):
        self.__name = value

    @name.deleter
    def name(self):
        del self.__name

u = User()
u.name = 'TOM'
print(u.__dict__)
print(User.__dict__)
# {'_User__name': 'TOM'}
# { 'name': <property object at 0x7f2b85f7c368>,}

从class.__dict__可以看出,几个属性方法最终变成了property object。所以几个同名方法也没有报错

直接使用property() 实现属性。

class User(object):

    def __init__(self, name):
        self.__name = name

    def get_name(self):
        return self.__name

    def set_name(self, value):
        self.__name = value


    def del_name(self):
        del self.__name
    name = property(get_name, set_name, del_name)

u = User('Tom')
u.__dict__['name'] = 'user'   # 显式在 instance.__dict__创建同名实例字段
print(u.name)                 # Tom

print(u.__dict__)
print(User.__dict__)


# {'name': 'user', '_User__name': 'Tom'}

# { 'set_name': <function User.set_name at 0x7f32d1efc730>,'get_name': <function User.get_name at 0x7f32d1efc6a8>,  'name': <property object at 0x7f32d365d368>, 'del_name': <function User.del_name at 0x7f32d1efc7b8>}

区别不大,但是User.__dict__保留了 set get del 方法

属性总是比实例的同名字段优先

在instance.__dict__ 显式的设置了同名实例字段,访问 u.name 时依然是属性。

 

7.4 方法

实例方法和函数最大的区别就是self这个隐式参数

>>> class User(object):
...     def print_id(self):
...             print(hex(id(self)))
... 
>>> u = User()
>>> u.print_id
<bound method User.print_id of <__main__.User object at 0x7f6124960dd8>>
>>> User.print_id
<function User.print_id at 0x7f6124967c80>

>>> u.print_id()
0x7f6124960dd8
>>> User.print_id(u)
0x7f6124960dd8

当实例调用时, 他是一个bound method,动态绑定到对象实例。当类型调用时,是一个function,必须显式的传递self参数

在方法里访问对象成员时,必须使用对象实例引用。否则被当成普通名字,按照LEGB查找。

因为所有的方法都存储在class.__dict__中,不可能出现同名key,所以方法不能重载

7.5 继承

基类所有的实例字段都存储在instance.__dict__

class User(object):
    table = 't_user'

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def test(self):
        print(self._name, self._age)

class Manager(User):
    table = 't_manage'

    def __init__(self, name, age, title):
        User.__init__(self, name, age)
        self._title = title

    def kill(self):
        print('213...')

>>> m = Manager('TOM',34, 'XCD')
>>> m.__dict__
{'_name': 'TOM', '_age': 34, '_title': 'XCD'}       # 实例包含了所有基类的字段。

>>> Manager.__dict__                                # 派生类名字空间里没有任何基类成员
{ 'kill': <function Manager.kill at 0x7f681aec3598>, 'table': 't_manage'}
>>> User.__dict__
{ 'test': <function User.test at 0x7f681aec3488>, 'table': 't_user'}

基类引用常常在__base__, 直接派生类存储在__subclasses__

>>> Manager.__base__
<class 'testdemo.User'>


>>> User.__subclasses__()
[<class 'testdemo.Manager'>]

多重继承

class A(object):
    def __init__(self, a):
        self._a = a

class B(object):
    def __init__(self, b):
        self._b = b

class C(A, B):
    def __init__(self, a, b):
        A.__init__(self, a)
        B.__init__(self, b)

>>> C.__bases__
(<class 'testdemo.A'>, <class 'testdemo.B'>)
>>> c = C(1,2)
>>> c.__dict__
{'_a': 1, '_b': 2}                # 实例名字空间包含了所有基类实例字段

多继承成员搜索mro(method resolution order):从下到上(深度优先,从派生类到基类),从左到右

mro 和我们前面提及的成员查找规则是有区别的,__mro__ 列表中并没有 instance。所以在表述时,需要注意区别

>>> C.mro()
[<class 'testdemo.C'>, <class 'testdemo.A'>, <class 'testdemo.B'>, <class 'object'>]
>>> C.__mro__
(<class 'testdemo.C'>, <class 'testdemo.A'>, <class 'testdemo.B'>, <class 'object'>)

super

super 的类型参数决定了在 mro 列表中的搜索起始位置,总是返回该参数后续类型的成员。单继
承时总是搜索参数的基类型

>>> class A(object):
...    def test(self): print "a"

>>> class B(A):
...    def test(self): print "b"

>>> class C(B):
...    def __init__(self):
...       super(C, self).test()  # 从 mro 中 C 的后续类型,也就是 B 开始查找。
...       super(B, self).test()  # 从 B 的后续类型 A 开始查找。

>>> C.__mro__
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <type 'object'>]
>>> C()
b
a
<__main__.C object at 0x101498f90>

__bases__

类型对象有两个相似的成员:

• __base__: 只读,总是返回 __bases__[0]。
• __bases__: 基类列表,可直接修改来更换基类,影响 mro 顺序。

>>> class A(object): pass
... 
>>> class B(object): pass
... 
>>> class C(B): pass
... 
>>> C.__bases__                                # 直接基类元组
(<class '__main__.B'>,)
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.B'>, <class 'object'>)

>>> C.__bases__ = (A,)                          # 通过__bases__修改基类
>>> C.__base__                                  # __base__变化A
<class '__main__.A'> 
>>> C.__mro__                                   # mro变化
(<class '__main__.C'>, <class '__main__.A'>, <class 'object'>) 

对多继承同样有效,比如调整基类顺序

>>> class C(A,B): pass

>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

>>> C.__bases__ = (B,A)             # 调整基类顺序
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

通过更换基类实现代码注入,影响既有类型的行为。事实上,我们还可以更改实例的类型。

>>> a = A()
>>> a.__class__
<class '__main__.A'>

>>> a.__class__ = B
>>> a.__class__
<class '__main__.B'>

 

抽象类

from abc import ABCMeta, abstractmethod, abstractproperty

class User(object):
    __metaclass__ = ABCMeta   # 通过元类来控制抽象类行行为

    def __init__(self, uid):
        self._uid = uid

    @abstractmethod
    def print_id(self):
        pass

    name = abstractproperty()   # 抽象属性

class Manager(User):
    def __init__(self, uid):
        User.__init__(self, uid)

    def print_id(self):
        print(self._uid, self._name)

    name = property(lambda s: s._name, lambda s,v: setattr(s, '_name', v))

m = Manager(1)
m.name = 'TOM'
m.print_id()

 

7.7 操作符重载

__setitem__

又称索引器,像序列或字典类型那样操作对象。

class A(object):
    def __init__(self, **kwargs):
        self._data = kwargs

    def __getitem__(self, key):
        return self._data.get(key)

    def __setitem__(self, key, value):
        self._data[key] = value

    def __delitem__(self, key):
        self._data.pop(key, None)

    def __contains__(self, key):
        return key in self._data.keys()

a = A(x=1, y=2)
print(a['x'])
a['z'] = 3
print('z' in a)
del a['z']
print(a._data) 
# 1
# True
# {'x': 1, 'y': 2}

__call__

像函数那样调用对象

class A(object):
    def __call__(self, *args, **kwargs):
        print(hex(id(self)), args, kwargs)

a = A()
a(1, 2, s='hi')

__dir__

配合__slots__隐藏内部成员

class A(object):
    __slots__ = ('x', 'y')

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

    def __dir__(self):  # 必须返回list
        return ['x']

a = A(1, 2)
print(dir(a))  # ['x'] y不见了

__getattr__

这几个方法的触发时机

  • __getattr__:  访问不存在的成员
  • __setattr__:  对任何成员的赋值操作
  • __delattr__:  删除成员操作
  • __getattribute__:  访问任何存在或不存在的成员,包括 _dict__

不要在这几个方法里直接访问对象成员,也不要用 hasattr/getattr/setattr/delattr 函数,因为它们会被再次拦截,形成无限循环。正确的做法是直接操作 __dict__

而 __getattribute__ 连 __dict__ 都会拦截,只能用基类的 __getattribute__ 返回结果

class A(object):

    def __init__(self, x):
        self.x = x

    def __getattr__(self, name):
        print('getattr: ', name)
        return self.__dict__.get(name)

    def __setattr__(self, key, value):
        print('set: ', key, value)
        self.__dict__[key] = value

    def __delattr__(self, name):
        print('del: ', name)
        self.__dict__.pop(name, None)

    def __getattribute__(self, name):
        print('attr: ', name)
        return object.__getattribute__(self, name)


>>> a = A(10)                        # init里面的self.x = x被__setattr__捕获
set:  x 10 
attr:  __dict__
>>> a.x                              # 访问已存在的字段,仅被__getattribute__捕获
attr:  x
10
>>> a.y=20                           # 创建新的字段,被__setattr__ 捕获
set:  y 20
attr:  __dict__
>>> a.z                              # 访问不存在的字段被__getattr__捕获
attr:  z
getattr:  z
attr:  __dict__
>>> del a.y
del:  y
attr:  __dict__

总结:

    名字空间:访问对象成员查找顺序:instance.__dict__ -> class.__dict__ --> base.__dict__

    字段: 实例字段 (实例名字空间) 和 静态字段 (类名字空间)

    属性: 由setter, getter, deletter构成的逻辑,属性的优先级高于实例字段

    方法: 方法存放在class.__dict__, 在调用时动态绑定在实例对象上,方法不能重载是因为存放在class.__dict__ 键不能重复

    继承: 多继承成员搜索mro:从下到上(深度优先,从派生类到基类),从左到右  ,__base__: 只读,总是返回 __bases__[0]。__bases__: 基类列表,可直接修改来更换基类,影响 mro 顺序。

    操作符重载:__setitem__, __getattr__ , __call__, __dir__

转载于:https://my.oschina.net/acutesun/blog/968242

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值