zoukankan      html  css  js  c++  java
  • python的多种魔术方法


    定制类和魔法方法

    • 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。

  • 相关阅读:
    leetcode Super Ugly Number
    leetcode Find Median from Data Stream
    leetcode Remove Invalid Parentheses
    leetcode Range Sum Query
    leetcode Range Sum Query
    leetcode Minimum Height Trees
    hdu 3836 Equivalent Sets
    hdu 1269 迷宫城堡
    hud 2586 How far away ?
    poj 1330 Nearest Common Ancestors
  • 原文地址:https://www.cnblogs.com/changting/p/13707105.html
Copyright © 2011-2022 走看看