第四章 面向对象编程 -- 魔术方法2

第零章 学前准备
第一章 数据结构 – 基本数据类型
第一章 数据结构 – 字符串
第一章 数据结构 – 列表、元组和切片
第一章 数据结构 – 字典
第一章 数据结构 – 集合
第一章 – 数组、队列、枚举
第一章 数据结构 – 序列分类
第二章 控制流程
第三章 函数也是对象 – 函数定义以及参数
第三章 函数也是对象 – 高阶函数以及装饰器
第三章 函数也是对象 – lambda 表达式、可调用函数及内置函数
第四章 面向对象编程 – 自定义类、属性、方法和函数
第四章 面向对象编程–魔术方法1
第四章 面向对象编程 – 魔术方法2


第四章 面向对象编程 – 魔术方法2

4.4 特殊方法(magic method)

4.4.2 重要特殊方法调用

4.4.2.4 集合模拟
  1. object.__len__(self):Called to implement the built-in function len(). Should return the length of the object, an integer >= 0. Also, an object that doesn’t define a __bool__() method and whose __len__() method returns zero is considered to be false in a Boolean context.

  2. object.__length_hint__(self):Called to implement operator.length_hint(). Should return an estimated length for the object (which may be greater or less than the actual length). The length must be an integer >= 0. The return value may also be NotImplemented, which is treated the same as if the length_hint method didn’t exist at all. This method is purely an optimization and is never required for correctness.

    优化主要是针对迭代器来说,因为我们在将迭代器转为list时,迭代器并没有len方法,也就是不知道长度多少,在转为list时可能需要多次分配内存。但是一个迭代器常常是可以知道大小的,所以迭代器可以自己定义一个__length_hint__函数,返回“大概”的元素数量。

    New In Python 3.4. PEP 424 – A method for exposing a length hint

  3. object.__getitem__(self, key):Called to implement evaluation of self[key]. For sequence types, the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a sequence type) is up to the __getitem__() method. If key is of an inappropriate type, TypeError may be raised; if of a value outside the set of indexes for the sequence (after any special interpretation of negative values), IndexError should be raised. For mapping types, if key is missing (not in the container), KeyError should be raised.

  4. object.__setitem__(self, key, value):Called to implement assignment to self[key]. Same note as for __getitem__(). This should only be implemented for mappings if the objects support changes to the values for keys, or if new keys can be added, or for sequences if elements can be replaced. The same exceptions should be raised for improper key values as for the __getitem__() method.

  5. object.__delitem__(self, key):Called to implement deletion of self[key]. Same note as for __getitem__(). This should only be implemented for mappings if the objects support removal of keys, or for sequences if elements can be removed from the sequence. The same exceptions should be raised for improper key values as for the __getitem__() method.

  6. object.__missing__(self, key):Called by dict.__getitem__() to implement self[key] for dict subclasses when key is not in the dictionary.

  7. object.__contains__(self, item):Called to implement membership test operators(in and not in). Should return true if item is in self, false otherwise. For mapping objects, this should consider the keys of the mapping rather than the values or the key-item pairs.

    the expression x in y is equivalent to any(x is e or x == e for e in y).

4.4.2.5 迭代枚举
  1. object.__iter__(self):This method is called when an iterator is required for a container. This method should return a new iterator object that can iterate over all the objects in the container. For mappings, it should iterate over the keys of the container.

  2. object.__reversed__(self):Called (if present) by the reversed() built-in to implement reverse iteration. It should return a new iterator object that iterates over all the objects in the container in reverse order.

    If the __reversed__() method is not provided, the reversed() built-in will fall back to using the sequence protocol (__len__() and __getitem__()). Objects that support the sequence protocol should only provide __reversed__() if they can provide an implementation that is more efficient than the one provided by reversed().

  3. iterator.__next__():Called to implement the built-in function next(iterator).Return the next item from the container. If there are no further items, raise the StopIteration exception. This method corresponds to the tp_iternext slot of the type structure for Python objects in the Python/C API.

4.4.2.6 可调用模拟
  1. object.__call__(self[, args...]):Called when the instance is “called” as a function; if this method is defined, x(arg1, arg2, …) roughly translates to type(x).call(x, arg1, …).
    特殊属性
    属性 意义 权限
    __doc__ 该函数的文档字符串,没有则为 None;不会被子类继承。 可写
    __name__ 该函数的名称。 可写
    __qualname__ 该函数的 qualified name。3.3 新版功能. 可写
    __module__ 该函数所属模块的名称,没有则为 None。 可写
    __defaults__ 由具有默认值的参数的默认参数值组成的元组,如无任何参数具有默认值则为 None。 可写
    __code__ 表示编译后的函数体的代码对象。 可写
    __globals__ 对存放该函数中全局变量的字典的引用 --- 函数所属模块的全局命名空间。 只读
    __dict__ 命名空间支持的函数属性。 可写
    __closure__ None 或包含该函数可用变量的绑定的单元的元组。 只读
    __annotations__ 包含参数标注的字典。字典的键是参数名,如存在返回标注则为 'return'。 可写
    __kwdefaults__ 仅包含关键字参数默认值的字典。 可写
4.4.2.7 上下文管理器
  1. object.__enter__(self) :Enter the runtime context related to this object. The with statement will bind this method’s return value to the target(s) specified in the as clause of the statement, if any.

  2. object.__exit__(self, exc_type, exc_value, traceback) :Exit the runtime context related to this object. The parameters describe the exception that caused the context to be exited. If the context was exited without an exception, all three arguments will be None.

    If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.

    Note that __exit__() methods should not reraise the passed-in exception; this is the caller’s responsibility.

PEP 343 – The “with” Statement

4.4.2.8 属性管理
  1. object.__getattr__(self, name):当默认属性访问因引发 AttributeError 而失败时被调用(失败可能因为__getattribute__()方法因为name不是实例的属性或者 self 的类关系树中的属性而引发了 AttributeError或者对name特性属性调用__get__()时引发了AttributeError)。此方法应当返回(找到的)属性值或是引发一个 AttributeError 异常。

  2. object.__getattribute__(self, name):此方法会无条件地被调用以实现对类实例属性的访问。如果类还定义了 __getattr__(),则后者不会被调用,除非 __getattribute__() 显式地调用它或是引发了 AttributeError。此方法应当返回(找到的)属性值或是引发一个 AttributeError 异常。为了避免此方法中的无限递归,其实现应该总是调用具有相同名称的基类方法来访问它所需要的任何属性,例如 object.__getattribute__(self, name)

  3. object.__setattr__(self, name, value):此方法在一个属性被尝试赋值时被调用。这个调用会取代正常机制(即将值保存到实例字典)。 name 为属性名称, value 为要赋给属性的值。

    如果 __setattr__() 想要赋值给一个实例属性,它应该调用同名的基类方法,例如 object.__setattr__(self, name, value)

  4. object.__delattr__(self, name):Like __setattr__() but for attribute deletion instead of assignment. This should only be implemented if del obj.name is meaningful for the object.

  5. object.__dir__(self):此方法会在对相应对象调用 dir() 时被调用。返回值必须为一个序列。 dir() 会把返回的序列转换为列表并对其排序。返回的序列是模块中可访问名称的字符串序列

  6. 属性访问顺序:调用__getattribute__>调用数据描述符>调用当前实例对象的所属成员(若与描述符对象同名,会被覆盖)>调用类的的所属成员>调用非数据描述符>调用父类的所属成员>调用__getattr__

    PEP 562 – Module __getattr__ and __dir__

  7. __slots__:allow us to explicitly declare data members (like properties) and deny the creation of __dict__ and __weakref__ (unless explicitly declared in __slots__ or available in a parent.)

    The space saved over using __dict__ can be significant. Attribute lookup speed can be significantly improved as well.
4.4.2.9 属性描述符

属性描述符是对类/对象中某个成员进行详细的管理操作。

  1. object.__get__(self, instance, owner=None):调用此方法以获取所有者类的属性(类属性访问)或该类的实例的属性(实例属性访问)。 可选的 owner 参数是所有者类而 instance 是被用来访问属性的实例,如果通过 owner 来访问属性则返回 None

    此方法应当返回计算得到的属性值或是引发 AttributeError 异常。

  2. object.__set__(self, instance, value):调用此方法以设置 instance 指定的所有者类的实例的属性为新值 value

    请注意,添加 __set__()__delete__() 会将描述器变成“数据描述器”。

  3. object.__delete__(self, instance):调用此方法以删除 instance 指定的所有者类的实例的属性。

  4. object.__set_name__(self, owner, name):在所有者类 owner 创建时被调用。描述器会被赋值给 name。如果在某个类被初次创建之后又额外添加一个描述器时,那就需要显式地调用它并且附带适当的形参:
   class A:
      pass
   descr = custom_descriptor()
   A.attr = descr
   descr.__set_name__(A, 'attr')

class NonnegativeNumberDescriptor:
    def __set_name__(self, owner, name):
        self.private_name = f"__{
      
      name}"

    def __set__(self, instance, value):
        if isinstance(value, (int, float)) and value >= 0:
            setattr(instance, self.private_name, value)
        else:
            raise ValueError(
                f'{
      
      value} must be int or float and >= 0')

    def __delete__(self, instance):
        delattr(instance, self.private_name)

    def __get__(self, instance, owner):
        return getattr(instance, self.private_name)


class Student:
    age = NonnegativeNumberDescriptor()
    score = NonnegativeNumberDescriptor()

    def __init__(self, name, age, score=0):
        self.name = name
        self.age = age
        self.score = score


class StudentWithProperty:
    def __init__(self, name, age, score=0):
        self.name = name
        self.__age = age
        self.__score = score

    @property
    def age(self):
        return self.__age

    @property
    def score(self):
        return self.__score

    @score.setter
    def score(self, score):
        if isinstance(score, (int, float)) and score >= 0:
            self.__score = score
        else:
            raise ValueError(
                f'{
      
      score} must be int or float and >= 0')


stu = Student("化学", 1)
print(stu.score)
print(stu.name)
stu.name = "政治"
print(stu.name)
stu.score = 12.1
print(stu.score)


stu = StudentWithProperty("化学", 1)
print(stu.score)
print(stu.name)
stu.name = "政治"
print(stu.name)
stu.age = 12
stu.score = -12.1
print(stu.score)
4.4.2.10 Emulating numeric types

对表达式 a+b 来说,解释器会执行以下几步操作:

  1. 如果a__add__方法,而且返回值不是NotImplemented,调用a.__add__(b),然后返回结果。
  2. 如果a没有__add__方法,或者调用__add__方法返回NotImplemented,检查b有没有__radd__方法,如果有,而且没有返回NotImplemented,调用b.__radd__(a),然后返回结果。
  3. 如果b没有__radd__方法,或者调用__radd__方法返回NotImplemented,抛出TypeError,并在错误消息中指明操作数类型不支持。

4.4.2.10.1 二元运算符
  1. object.__add__(self, other)
  2. object.__sub__(self, other)
  3. object.__mul__(self, other)
  4. object.__matmul__(self, other)
  5. object.__truediv__(self, other)
  6. object.__floordiv__(self, other)
  7. object.__mod__(self, other)
  8. object.__divmod__(self, other)
  9. object.__pow__(self, other[, modulo])
  10. object.__lshift__(self, other)
  11. object.__rshift__(self, other)
  12. object.__and__(self, other)
  13. object.__xor__(self, other)
  14. object.__or__(self, other)

调用这些方法来实现二元算术运算 (+, -, *, @, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |) 。例如,求表达式 x + y 的值,其中 x 是具有 __add__() 方法的类的一个实例,则会调用 x.__add__(y)__divmod__() 方法应该等价于使用 __floordiv__()__mod__() ,它不应该被关联到 __truediv__() 。请注意如果要支持三元版本的内置 pow() 函数,则 __pow__() 的定义应该接受可选的第三个参数。

如果这些方法中的某一个不支持与所提供参数进行运算,它应该返回 NotImplemented

4.4.2.10.2 反向二元运算符
  1. object.__radd__(self, other)
  2. object.__rsub__(self, other)
  3. object.__rmul__(self, other)
  4. object.__rmatmul__(self, other)
  5. object.__rtruediv__(self, other)
  6. object.__rfloordiv__(self, other)
  7. object.__rmod__(self, other)
  8. object.__rdivmod__(self, other)
  9. object.__rpow__(self, other[, modulo])
  10. object.__rlshift__(self, other)
  11. object.__rrshift__(self, other)
  12. object.__rand__(self, other)
  13. object.__rxor__(self, other)
  14. object.__ror__(self, other)

调用这些方法来实现具有反射(交换)操作数的二元算术运算 (+, -, *, @, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |) 。这些成员函数仅会在左操作数不支持相应运算且两个操作数类型不同时被调用。例如,求表达式 x - y 的值,其中 y 是具有 __rsub__() 方法的类的一个实例,则当 x.__sub__(y) 返回 NotImplemented 时会调用 y.__rsub__(x)

4.4.2.10.3 原地操作运算符
  1. object.__iadd__(self, other)
  2. object.__isub__(self, other)
  3. object.__imul__(self, other)
  4. object.__imatmul__(self, other)
  5. object.__itruediv__(self, other)
  6. object.__ifloordiv__(self, other)
  7. object.__imod__(self, other)
  8. object.__ipow__(self, other[, modulo])
  9. object.__ilshift__(self, other)
  10. object.__irshift__(self, other)
  11. object.__iand__(self, other)
  12. object.__ixor__(self, other)
  13. object.__ior__(self, other)

调用这些方法来实现扩展算术赋值 (+=, -=, *=, @=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=) 。这些方法应该尝试进行自身操作 (修改 self ) 并返回结果 (结果应该但并非必须为 self )。如果某个方法未被定义,相应的扩展算术赋值将回退到普通方法。例如,如果 x 是具有 __iadd__() 方法的类的一个实例,则 x += y 就等价于 x = x.__iadd__(y) 。否则就如 x + y 的求值一样选择 x.__add__(y)y.__radd__(x)

4.4.2.10.4 一元运算符
  1. object.__neg__(self)
  2. object.__pos__(self)
  3. object.__abs__(self)
  4. object.__invert__(self)

调用此方法以实现一元算术运算 (-, +, abs(), ~)

4.4.2.10.5 数值转换
  1. object.__complex__(self)
  2. object.__int__(self)
  3. object.__float__(self)

调用这些方法以实现内置函数 complex() , int()float() 。应当返回一个相应类型的值。

4.4.2.10.6 index操作符
  1. object.__index__(self)

调用此方法以实现 operator.index() 以及 Python 需要无损地将数字对象转换为整数对象的场合(例如切片或是内置的 bin() , hex()oct() 函数)。 存在此方法表明数字对象属于整数类型。 必须返回一个整数。
如果未定义 __int__() , __float__()__complex__() 则相应的内置函数 int() , float()complex() 将回退为 __index__()

4.4.2.10.7 数值舍入运算
  1. object.__round__(self[, ndigits])

  2. object.__trunc__(self)

  3. object.__floor__(self)

  4. object.__ceil__(self)

调用这些方法以实现内置函数 round() 以及 math 函数 trunc() , floor()ceil() 。 除了将 ndigits 传给 __round__() 的情况之外这些方法的返回值都应当是原对象截断为 Integral (通常为 int )。

The built-in function int() falls back to __trunc__() if neither __int__() nor __index__() is defined.

4.4.3 特殊方法案例:Vector2d

"""
A two-dimensional vector class
    >>> v1 = Vector2d(3, 4)
    >>> print(v1.x, v1.y)
    3.0 4.0
    >>> x, y = v1
    >>> x, y
    (3.0, 4.0)
    >>> v1
    Vector2d(3.0, 4.0)
    >>> v1_clone = eval(repr(v1))
    >>> v1 == v1_clone
    True
    >>> print(v1)
    (3.0, 4.0)
    >>> octets = bytes(v1)
    >>> octets
    b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
    >>> abs(v1)
    5.0
    >>> bool(v1), bool(Vector2d(0, 0))
    (True, False)

    Test of ``.frombytes()`` class method:
    >>> v1_clone = Vector2d.frombytes(bytes(v1))
    >>> v1_clone
    Vector2d(3.0, 4.0)
    >>> v1 == v1_clone
    True

    Tests of ``format()`` with Cartesian coordinates:
    >>> format(v1)
    '(3.0, 4.0)'
    >>> format(v1, '.2f')
    '(3.00, 4.00)'
    >>> format(v1, '.3e')
    '(3.000e+00, 4.000e+00)'

    Tests of the ``angle`` method::
    >>> Vector2d(0, 0).angle()
    0.0
    >>> Vector2d(1, 0).angle()
    0.0
    >>> epsilon = 10**-8
    >>> abs(Vector2d(0, 1).angle() - math.pi/2) < epsilon
    True
    >>> abs(Vector2d(1, 1).angle() - math.pi/4) < epsilon
    True

    Tests of ``format()`` with polar coordinates:
    >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS
    '<1.414213..., 0.785398...>'
    >>> format(Vector2d(1, 1), '.3ep')
    '<1.414e+00, 7.854e-01>'
    >>> format(Vector2d(1, 1), '0.5fp')
    '<1.41421, 0.78540>'

    Tests of `x` and `y` read-only properties:
    >>> v1.x, v1.y
    (3.0, 4.0)
    >>> v1.x = 123
    Traceback (most recent call last):
    ...
    AttributeError: can't set attribute

    Tests of hashing:
    >>> v1 = Vector2d(3, 4)
    >>> v2 = Vector2d(3.1, 4.2)
    >>> hash(v1), hash(v2)
    (7, 384307168202284039)
    >>> len(set([v1, v2]))
    2
"""
from array import array
import math


class Vector2d:
    __slots__ = ('__x', '__y')
    typecode = 'd'

    def __init__(self, x, y):
        self.__x = float(x)
        self.__y = float(y)

    @property
    def x(self):
        return self.__x

    @property
    def y(self):
        return self.__y

    def __iter__(self):
        return (i for i in (self.x, self.y))

    def __repr__(self):
        class_name = type(self).__name__
        return '{}({!r}, {!r})'.format(class_name, *self)

    def __str__(self):
        return str(tuple(self))

    def __bytes__(self):
        return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self)))

    def __eq__(self, other):
        return tuple(self) == tuple(other)

    def __hash__(self):
        return hash(self.x) ^ hash(self.y)

    def __abs__(self):
        return math.hypot(self.x, self.y)

    def __bool__(self):
        return bool(self.x or self.y)

    def __add__(self, other):
        if len(other) == 2:
            return Vector2d(self.x+other.x, self.y+other.y)
        else:
            return NotImplemented

    def __mul__(self, scalar):
        return Vector(self.x*scalar, self.y*scalar)

    def angle(self):
        return math.atan2(self.y, self.x)

    def __format__(self, fmt_spec=''):
        if fmt_spec.endswith('p'):
            fmt_spec = fmt_spec[:-1]
            coords = (abs(self), self.angle())
            outer_fmt = '<{}, {}>'
        else:
            coords = self
            outer_fmt = '({}, {})'
        components = (format(c, fmt_spec) for c in coords)
        return outer_fmt.format(*components)

    @classmethod
    def frombytes(cls, octets):
        typecode = chr(octets[0])
        memv = memoryview(octets[1:]).cast(typecode)
        return cls(*memv)


if __name__ == "__main__":
    import doctest
    doctest.testmod()

猜你喜欢

转载自blog.csdn.net/qq_31654025/article/details/132780903