美文网首页
python的多种魔术方法

python的多种魔术方法

作者: 不忘初心_悟空 | 来源:发表于2020-09-21 18:05 被阅读0次

    [toc]
    定制类和魔法方法

    • new
    • str , repr
    • iter
    • getitem , setitem , delitem
    • getattr , setattr , delattr
    • call

    new

    在 Python 中,当我们创建一个类的实例时,类会先调用 new(cls[, ...]) 来创建实例,然后 init 方法再对该实例(self)进行初始化。

    关于 newinit 有几点需要注意:
    new 是在 init 之前被调用的;
    new 是类方法,init 是实例方法;
    重载 new 方法,需要返回类的实例;
    一般情况下,我们不需要重载 new 方法。但在某些情况下,我们想控制实例的创建过程,这时可以通过重载 _new 方法来实现。

    class A(object):
        _dict = dict()
    
        def __new__(cls):
            if 'key' in A._dict:
                print "EXISTS"
                return A._dict['key']
            else:
                print "NEW"
                return object.__new__(cls)
    
        def __init__(self):
            print "INIT"
            A._dict['key'] = self
    

    str & repr

    class Foo(object):
        def __init__(self, name):
            self.name = name
        def __str__(self):
            return 'Foo object (name: %s)' % self.name
        def __repr__(self):
            return 'Foo object (name: %s)' % self.name
    

    print Foo('ethan') # 使用 print
    Foo object (name: ethan)

    str(Foo('ethan')) # 使用 str
    'Foo object (name: ethan)'

    Foo('ethan') # 直接显示
    <main.Foo at 0x10c37a490>
    Foo('ethan') # 使用repr(类中实现)
    'Foo object (name: ethan)'

    iter

    在某些情况下,我们希望实例对象可被用于 for...in 循环,这时我们需要在类中定义 iter 和 next(在 Python3 中是 next)方法,其中,iter 返回一个迭代对象,next 返回容器的下一个元素,在没有后续元素时抛出 StopIteration 异常.

    看一个斐波那契数列的例子:

    class Fib(object):
        def __init__(self):
            self.a, self.b = 0, 1
    
        def __iter__(self):  # 返回迭代器对象本身
            return self      
    
        def next(self):      # 返回容器下一个元素
            self.a, self.b = self.b, self.a + self.b
            return self.a  
    

    fib = Fib()
    for i in fib:
    ... if i > 10:
    ... break
    ... print i

    getitem、setitem、delitem

    geitem 用于获取值,类似地,setitem 用于设置值,delitem 用于删除值,让我们看下面一个例子:

    class Point(object):
        def __init__(self):
            self.coordinate = {}
    
        def __str__(self):
            return "point(%s)" % self.coordinate
    
        def __getitem__(self, key):
            return self.coordinate.get(key)
    
        def __setitem__(self, key, value):
            self.coordinate[key] = value
    
        def __delitem__(self, key):
            del self.coordinate[key]
            print 'delete %s' % key
    
        def __len__(self):
            return len(self.coordinate)
    
        __repr__ = __str__
    

    在上面,我们定义了一个 Point 类,它有一个属性 coordinate(坐标),是一个字典,让我们看看使用:

    >>> p = Point()
    >>> p['x'] = 2    # 对应于 p.__setitem__('x', 2)
    >>> p['y'] = 5    # 对应于 p.__setitem__('y', 5)
    >>> p             # 对应于 __repr__
    point({'y': 5, 'x': 2})
    >>> len(p)        # 对应于 p.__len__
    2
    >>> p['x']        # 对应于 p.__getitem__('x')
    2
    >>> p['y']        # 对应于 p.__getitem__('y')
    5
    >>> del p['x']    # 对应于 p.__delitem__('x')
    delete x
    >>> p
    point({'y': 5})
    >>> len(p)
    1
    

    getattr、setattr、delattr

    当我们获取对象的某个属性,如果该属性不存在,会抛出 AttributeError 异常,比如:

    class Point(object):
        def __init__(self, x=0, y=0):
            self.x = x
            self.y = y
    
    >>> p = Point(3, 4)
    >>> p.x, p.y
    (3, 4)
    >>> p.z
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-547-6dce4e43e15c> in <module>()
    ----> 1 p.z
    
    AttributeError: 'Point' object has no attribute 'z'
    

    那有没有办法不让它抛出异常呢?当然有,只需在类的定义中加入 getattr 方法,比如:

    class Point(object):
        def __init__(self, x=0, y=0):
            self.x = x
            self.y = y
        def __getattr__(self, attr):
            if attr == 'z':
                return 0
    
    >>> p = Point(3, 4)
    >>> p.z
    0
    

    现在,当我们调用不存在的属性(比如 z)时,解释器就会试图调用 getattr(self, 'z') 来获取值,但是,上面的实现还有一个问题,当我们调用其他属性,比如 w ,会返回 None,因为 getattr 默认返回就是 None,只有当 attr 等于 'z' 时才返回 0,如果我们想让 getattr 只响应几个特定的属性,可以加入异常处理,修改 getattr 方法,如下:

    def __getattr__(self, attr):
        if attr == 'z':
            return 0
        raise AttributeError("Point object has no attribute %s" % attr)
    

    setattr, delattr

    class Point(object):
        def __init__(self, x=0, y=0):
            self.x = x
            self.y = y
    
        def __getattr__(self, attr):
            if attr == 'z':
                return 0
            raise AttributeError("Point object has no attribute %s" % attr)
    
        def __setattr__(self, *args, **kwargs):  
            print 'call func set attr (%s, %s)' % (args, kwargs)
            return object.__setattr__(self, *args, **kwargs)
    
        def __delattr__(self, *args, **kwargs):  
            print 'call func del attr (%s, %s)' % (args, kwargs)
            return object.__delattr__(self, *args, **kwargs)
    
    >>> p = Point(3, 4)
    call func set attr (('x', 3), {})
    call func set attr (('y', 4), {})
    >>> p.z
    0
    >>> p.z = 7
    call func set attr (('z', 7), {})
    >>> p.z
    7
    >>> p.w
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 8, in __getattr__
    AttributeError: Point object has no attribute w
    >>> p.w = 8
    call func set attr (('w', 8), {})
    >>> p.w
    8
    >>> del p.w
    call func del attr (('w',), {})
    >>> p.__dict__
    {'y': 4, 'x': 3, 'z': 7}
    

    call

    我们一般使用 obj.method() 来调用对象的方法,那能不能直接在实例本身上调用呢?在 Python 中,只要我们在类中定义 call 方法,就可以对实例进行调用,比如下面的例子:

    class Point(object):
        def __init__(self, x, y):
            self.x, self.y = x, y
        def __call__(self, z):
            return self.x + self.y + z
    

    使用如下:

    >>> p = Point(3, 4)
    >>> callable(p)     # 使用 callable 判断对象是否能被调用
    True
    >>> p(6)            # 传入参数,对实例进行调用,对应 p.__call__(6)
    13                  # 3+4+6
    

    slots

    在 Python 中,我们在定义类的时候可以定义属性和方法。当我们创建了一个类的实例后,我们还可以给该实例绑定任意新的属性和方法。
    看下面一个简单的例子:

    class Point(object):    
        def __init__(self, x=0, y=0):
            self.x = x
            self.y = y
    
    >>> p = Point(3, 4)
    >>> p.z = 5    # 绑定了一个新的属性
    >>> p.z
    5
    >>> p.__dict__
    {'x': 3, 'y': 4, 'z': 5}
    

    在上面,我们创建了实例 p 之后,给它绑定了一个新的属性 z,这种动态绑定的功能虽然很有用,但它的代价是消耗了更多的内存。

    因此,为了不浪费内存,可以使用 slots 来告诉 Python 只给一个固定集合的属性分配空间,对上面的代码做一点改进,如下:

    class Point(object):
        __slots__ = ('x', 'y')       # 只允许使用 x 和 y
    
        def __init__(self, x=0, y=0):
            self.x = x
            self.y = y
    

    上面,我们给 slots 设置了一个元组,来限制类能添加的属性。现在,如果我们想绑定一个新的属性,比如 z,就会出错了,如下:

    >>> p = Point(3, 4)
    >>> p.z = 5
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-648-625ed954d865> in <module>()
    ----> 1 p.z = 5
    
    AttributeError: 'Point' object has no attribute 'z'
    

    注意:
    使用 slots 有一点需要注意的是,slots 设置的属性仅对当前类有效,对继承的子类不起效,除非子类也定义了 slots,这样,子类允许定义的属性就是自身的 slots 加上父类的 slots。

    相关文章

      网友评论

          本文标题:python的多种魔术方法

          本文链接:https://www.haomeiwen.com/subject/tqfbyktx.html