• 类和对象


    改变对象的字符串显示

    问题:

      你想改变对象实例的打印或显示输出,让它们更具可读性

    解决方案:

      要改变一个实例的字符串表示,可重新定义它的str () 和repr () 方法。例如:

     1 class Pair:
     2     def __init__(self, x, y):
     3         self.x = x
     4         self.y = y
     5 
     6     def __repr__(self):
     7         #另外一种写法 return 'Pair(%r,%r)'%(self.x, self.y )
     8         return 'Pair({0.x!r}, {0.y!r})'.format(self)
     9 
    10     def __str__(self):
    11         return '({0.x!s}, {0.y!s})'.format(self)
    12 
    13 p = Pair(3, 4)
    14 
    15 print("__str__()的输出效果:", p)
    16 
    17 print('__repr__()的输出效果:', repr(p))

    以上代码执行的结果为:

    __str__()的输出效果: (3, 4)
    __repr__()的输出效果: Pair(3, 4)

    自定义字符串的格式化

    问题:

      你想通过format() 函数和字符串方法使得一个对象能支持自定义的格式化。

    解决方案:

      为了自定义字符串的格式化,我们需要在类上面定义format () 方法。例如:

     1 _formats = {
     2     'ymd' : '{d.year}-{d.month}-{d.day}',
     3     'mdy' : '{d.month}/{d.day}/{d.year}',
     4     'dmy' : '{d.day}/{d.month}/{d.year}'
     5     }
     6 
     7 class Date:
     8     def __init__(self, year, month, day):
     9         self.year = year
    10         self.month = month
    11         self.day = day
    12 
    13     def __format__(self, format_spec):
    14         if format_spec == '':
    15             format_spec = 'ymd'
    16 
    17         fmt = _formats[format_spec]
    18         return fmt.format(d=self)
    19 
    20 d = Date(2017, 8, 7)
    21 print('默认格式的输出:', format(d))
    22 
    23 print('mdy类型的输出:', format(d, 'mdy'))
    24 
    25 print('The date is {:ymd}'.format(d))
    26 
    27 print('The date is {:mdy}'.format(d))
    28 
    29 from datetime import  date
    30 d = date(2017, 8, 7)
    31 print(format(d))
    32 
    33 print(format(d,'%A, %B %d, %Y'))
    34 
    35 print('The end is {:%d %b %Y}. Goodbye'.format(d))

    以上代码执行的结果为:

    默认格式的输出: 2017-8-7
    mdy类型的输出: 8/7/2017
    The date is 2017-8-7
    The date is 8/7/2017
    2017-08-07
    Monday, August 07, 2017
    The end is 07 Aug 2017. Goodbye

    让对象支持上下文管理协议

    问题:

      你想让你的对象支持上下文管理协议(with 语句) 

    解决方案:  

      为了让一个对象兼容with 语句,你需要实现enter () 和exit () 方法。例如,考虑如下的一个类,它能为我们创建一个网络连接:

     1 from socket import socket,AF_INET,SOCK_STREAM
     2 
     3 class LazyConnection:
     4     '''
     5     连接端
     6     '''
     7     def __init__(self, addresss, family=AF_INET, type=SOCK_STREAM):
     8         self.address = addresss
     9         self.family = family
    10         self.type = type
    11         self.sock = None
    12 
    13     def __enter__(self):
    14         if self.sock is not None:
    15             raise RuntimeError('Already connected')
    16 
    17         self.sock = socket(self.family, self.type)
    18         self.sock.connect(self.address)
    19         return self.sock
    20 
    21     def __exit__(self, exc_type, exc_val, exc_tb):
    22         self.sock.close()
    23         self.sock = None
    24 
    25 
    26 from functools import partial
    27 
    28 conn = LazyConnection(('www.python.org', 80))
    29 
    30 with conn as s:
    31     '''
    32     通过with管理上下文
    33     '''
    34     # conn.__enter__() executes: connection open
    35     s.send(b'GET /index.html HTTP/1.0
    ')
    36     s.send(b'Host: www.python.org
    ')
    37     s.send(b'
    ')
    38     resp = b''.join(iter(partial(s.recv, 8192), b'')).decode('utf-8')
    39     # conn.__exit__() executes: connection closed
    40     print(resp)

    以上代码执行的结果为:

    HTTP/1.1 301 Moved Permanently
    Server: Varnish
    Retry-After: 0
    Location: https://www.python.org/index.html
    Content-Length: 0
    Accept-Ranges: bytes
    Date: Mon, 07 Aug 2017 02:34:49 GMT
    Via: 1.1 varnish
    Connection: close
    X-Served-By: cache-nrt6134-NRT
    X-Cache: HIT
    X-Cache-Hits: 0
    X-Timer: S1502073289.426123,VS0,VE0
    Strict-Transport-Security: max-age=63072000; includeSubDomains

    总结:  

      编写上下文管理器的主要原理是你的代码会放到with 语句块中执行。当出现with语句的时候,对象的enter () 方法被触发,它返回的值(如果有的话) 会被赋值给as 声明的变量。然后,with 语句块里面的代码开始执行。最后, exit () 方法被触发进行清理工作。

      不管with 代码块中发生什么,上面的控制流都会执行完,就算代码块中发生了异常也是一样的。事实上, exit () 方法的第三个参数包含了异常类型、异常值和追溯信息(如果有的话)。exit () 方法能自己决定怎样利用这个异常信息,或者忽略它并返回一个None 值。如果exit () 返回True ,那么异常会被清空,就好像什么都没发生一样, with 语句后面的程序继续在正常执行。

    创建大量对象时节省内存方法

    问题:

      你的程序要创建大量(可能上百万) 的对象,导致占用很大的内存

    解决方案:

      对于主要是用来当成简单的数据结构的类而言,你可以通过给类添加slots 属性来极大的减少实例所占的内存。比如:

    1 class Date:
    2     __slots__ =  ['year', 'month', 'day']
    3     def __init__(self, year, month, day):
    4         self.year = year
    5         self.month = month
    6         self.day = day

    注意:

      当你定义slots 后,Python 就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。在slots 中列出的属性名在内部被映射到这个数组的指定小标上。使用slots 一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在slots中定义的那些属性名

    在类中封装属性名

    问题:

      你想封装类的实例上面的“私有”数据,但是Python 语言并没有访问控制

    解决方案: 

      Python 程序员不去依赖语言特性去封装数据,而是通过遵循一定的属性和方法命名规约来达到这个效果。第一个约定是任何以单下划线_开头的名字都应该是内部实现。比如:

     1 class A:
     2     def __init__(self):
     3         self._internal = 0
     4         self.public = 1
     5 
     6     def public_method(self):
     7         print('public method')
     8 
     9     def _internal_method(self):
    10         print('internal method')

    你还可能会遇到在类定义中使用两个下划线(__ ) 开头的命名。比如:

     1 class B:
     2     def __init__(self):
     3         self.__private = 0
     4 
     5     def __private_method(self):
     6         print('private method...外部不能调用,只能内部中调用')
     7 
     8     def public_method(self):
     9         print('公用方法调用内部的__private_method..')
    10         self.__private_method()

      使用双下划线开始会导致访问名称变成其他形式。比如,在前面的类B 中,私有属性会被分别重命名为_B__ private 和_B __private method 。这时候你可能会问这样重命名的目的是什么,答案就是继承——这种属性通过继承是无法被覆盖的。比如:

     1 class C(B):
     2     '''
     3     C类中的__private 在外部调用为_C__private
     4     C类中的__private_method在外部调用为_C__private_method
     5     '''
     6 
     7     def __init__(self):
     8         super().__init__()
     9         self.__private = 1              #这样写就不会覆盖掉B中的__private
    10 
    11         def __private_mothod(self):     #同样不会覆盖掉B中的__private_method
    12             print('C类中的private_mothd')

    总结:

      上面提到有两种不同的编码约定(单下划线和双下划线) 来命名私有属性,那么问题就来了:到底哪种方式好呢?大多数而言,你应该让你的非公共名称以单下划线开头。但是,如果你清楚你的代码会涉及到子类,并且有些内部属性应该在子类中隐藏起来,那么才考虑使用双下划线方案。

     

    创建可管理的属性

    问题:

      你想给某个实例attribute 增加除访问与修改之外的其他处理逻辑,比如类型检查或合法性验证

    解决方案:

      自定义某个属性的一种简单方法是将它定义为一个property。例如,下面的代码定义了一个property,增加对一个属性简单的类型检查:

     1 class Person:
     2     def __init__(self, first_name):
     3         self.first_name = first_name
     4 
     5     @property                                       #把方法变成一个属性,外部调用的时候,不需要加上()
     6     def first_name(self):
     7         return self._first_name
     8 
     9     @first_name.setter                              #给frist_name方法接收一个值作为self._first_name的返回
    10     def first_name(self, value):
    11         if not isinstance(value, str):
    12             raise TypeError('Expected a string')
    13         self._first_name = value
    14 
    15     @first_name.deleter                             #如果first_name属性没被创建则报错
    16     def first_name(self):
    17         raise ArithmeticError("Can't delete attribute")
    18 
    19 a = Person('demon')
    20 print(a.first_name)
    21 
    22 #a.first_name = 50          #报错raise TypeError('Expected a string')
    23 
    24 #del a.first_name           #报错ArithmeticError: Can't delete attribute

    以上代码执行的结果为:

    demon

     使用已存在的get 和set 方法基础上定义property。例如:

     1 class Person:
     2     def __init__(self, first_name):
     3         self.set_first_name(first_name)
     4 
     5     def get_first_name(self):
     6         return self._first_name
     7 
     8     def set_first_name(self, value):
     9         if not isinstance(value, str):
    10             raise TypeError('Expected a string')
    11         self._first_name = value
    12 
    13     def del_first_name(self):
    14         raise AttributeError("Can't delete attribute")
    15 
    16     name = property(get_first_name, set_first_name, del_first_name)
    17     

    调用父类方法

    问题:

      你想在子类中调用父类的某个已经被覆盖的方法 

    解决方案:

      为了调用父类(超类) 的一个方法,可以使用super() 函数,比如:

     1 class A:
     2     def spam(self):
     3         print('A.spam')
     4 
     5 class B(A):
     6     def spam(self):
     7         print('B.spam')
     8         super().spam()
     9 
    10 a = A()
    11 b = B()
    12 
    13 a.spam()            #调用A类中的spam方法
    14 print('我是分割符'.center(50, '*'))
    15 b.spam()            #先调用B类中的spam方法,然后通过super继承A类中的spam方法返回输出

    以上代码执行的结果为:

    A.spam
    **********************我是分割符***********************
    B.spam
    A.spam

    super() 函数的一个常见用法是在init () 方法中确保父类被正确的初始化了:

    1 class A:
    2     def __init__(self):
    3         self.x = 0
    4 
    5 class B(A):
    6     def __init__(self):
    7         super().__init__()
    8         self.y = 1
    9         

    super() 的另外一个常见用法出现在覆盖Python 特殊方法的代码中,比如:

     1 class Proxy:
     2     def __init__(self, obj):
     3         self._obj = obj
     4 
     5     def __getattr__(self, name):
     6         return getattr(self._obj, name)
     7 
     8     def __setattr__(self, name, value):
     9         if name.startswith('_'):
    10             super().__setattr__(name, value)            #__setattr__()实现包含一个名字检查,如果某个属性名称以_开头,就通过super调用
    11         else:
    12             setattr(self._obj, name , value)            #否则就委派给内部的_obj来处理

      在上面代码中, __setattr__ () 的实现包含一个名字检查。如果某个属性名以下划线( _) 开头,就通过super() 调用原始的__setattr__ () ,否则的话就委派给内部的代理对象self. _obj 去处理。这看上去有点意思,因为就算没有显式的指明某个类的父类, super() 仍然可以有效的工作。

    子类中扩展property

    问题:

      在子类中,你想要扩展定义在父类中的property 的功能

    解决方案:

      考虑如下的代码,它定义了一个property:

     1 class  Person:
     2     def __init__(self, name):
     3         self._name = name
     4 
     5     @property
     6     def name(self):
     7         return self._name
     8 
     9     @name.setter
    10     def name(self, value):
    11         if not isinstance(value, str):
    12             raise TypeError('Expected a string')
    13         self._name = value
    14 
    15     @name.deleter
    16     def name(self):
    17         raise AttributeError("Can't delete attribute")
    18 
    19 
    20 class SubPerson(Person):
    21     @Person.name.getter
    22     def name(self):
    23         print('Getting name')
    24         return super().name
    25 
    26     @Person.name.setter
    27     def name(self, value):
    28         print('Setting name to', value)
    29         super(SubPerson, SubPerson).name.__set__(self, value)

    创建新的类或实例属性

    问题:

      你想创建一个新的拥有一些额外功能的实例属性类型,比如类型检查 

    解决方案:

      如果你想创建一个全新的实例属性,可以通过一个描述器类的形式来定义它的功能。下面是一个例子:

     1 class Integer:
     2     def __init__(self, name):
     3         self.name = name
     4 
     5     def __get__(self, instance, cls):
     6         if instance is None:
     7             return self
     8         else:
     9             return instance.__dict__[self.name]
    10 
    11     def __set__(self, instance, value):
    12         if not isinstance(value, int):
    13             raise TypeError('Expected an int')
    14         instance.__dict__[self.name] = value
    15 
    16     def __delattr__(self, instance):
    17         del instance.__dict__[self.name]
    18 
    19 
    20 class Point:
    21     x = Integer('x')
    22     y = Integer('y')
    23 
    24     def __init__(self, x, y):
    25         self.x = x
    26         self.y = y
    27 
    28 p = Point(2, 3)
    29 print(p.x)                  #x.__get__(p, Point)
    30 print(p.y)
    31 
    32 print(Point.x)              #x.__get__(None, Point) 

    super() 的另外一个常见用法出现在覆盖Python 特殊方法的代码中,比如:

    2
    3
    <__main__.Integer object at 0x101c456a0>

    使用延迟计算属性

    问题:

      你想将一个只读属性定义成一个property,并且只在访问的时候才会计算结果。但是一旦被访问后,你希望结果值被缓存起来,不用每次都去计算

    解决方案: 

      定义一个延迟属性的一种高效方法是通过使用一个描述器类,如下所示:

     1 class lazyproperty:
     2     def __init__(self, func):
     3         self.func = func
     4 
     5     def __get__(self, instance, cls):
     6         if instance is None:
     7             return self
     8         else:
     9             value = self.func(instance)
    10             setattr(instance, self.func.__name__, value)
    11             return value
    12 
    13 
    14 import math
    15 
    16 
    17 class Circle:
    18     def __init__(self, radius):
    19         self.radius = radius
    20 
    21     @lazyproperty
    22     def area(self):
    23         print('Computing area')
    24         return math.pi * self.radius ** 2
    25 
    26     @lazyproperty
    27     def perimeter(self):
    28         print('Computing perimeter')
    29         return 2 * math.pi * self.radius
    30 
    31 
    32 c = Circle(4.0)
    33 print('radius:', c.radius)
    34 print('area:', c.area)
    35 print('perimeter:', c.perimeter)

    以上代码执行的结果为:

    radius: 4.0
    Computing area
    area: 50.26548245743669
    Computing perimeter
    perimeter: 25.132741228718345

    简化数据结构的初始化

    问题:

      你写了很多仅仅用作数据结构的类,不想写太多烦人的init () 函数

    解决方案:

      可以在一个基类中写一个公用的init () 函数:

     1 import math
     2 
     3 
     4 class Structurel:
     5     _fields = []
     6 
     7     def __init__(self, *args):
     8         if len(args) != len(self._fields):
     9             raise TypeError('Expected {} arguments'.format(len(self._fields)))
    10         for name,value in zip(self._fields, args):
    11             setattr(self, name, value)
    12 
    13 
    14 class Stock(Structurel):
    15     _fields = ['name', 'shares', 'price']
    16 
    17 
    18 class Point(Structurel):
    19     _fields = ['x', 'y']
    20 
    21 
    22 class Circle(Structurel):
    23     _fields = ['radius']
    24 
    25     def area(self):
    26         return math.pi * self.radius ** 2
    27 
    28 s = Stock('ACME', 50, 91.1)
    29 p = Point(2, 3)
    30 c = Circle(4.5)
    31 print(c.area())
    32 # c = Stock('ACME', 50)         #报错,会抛出raise的TypeError的异常

    以上代码执行的结果为:

    63.61725123519331

    定义接口或者抽象基类

    问题:

      你想定义一个接口或抽象类,并且通过执行类型检查来确保子类实现了某些特定的方法

    解决方案:

      使用abc 模块可以很轻松的定义抽象基类:

     1 from abc import ABCMeta, abstractclassmethod
     2 
     3 class IStream(metaclass=ABCMeta):
     4     @abstractclassmethod
     5     def read(self, maxbytes=-1):
     6         pass
     7 
     8     @abstractclassmethod
     9     def write(self, data):
    10         pass
    11 
    12 #抽象类的一个特点是它不能直接被实例化,比如你想像下面这样做是不行的
    13 #a = IStream()  TypeError Can't instantiate abstract class
    14 
    15 #抽象类的目的就是让别的类继承它并实现特定的抽象方法:
    16 class SocketStream(IStream):
    17     def read(self, maxbytes=-1):
    18         pass
    19 
    20     def write(self, data):
    21         pass

    实现数据模型的类型约束

    问题: 

      你想定义某些在属性赋值上面有限制的数据结构

    解决方案:

      在这个问题中,你需要在对某些实例属性赋值时进行检查。所以你要自定义属性赋值函数,这种情况下最好使用描述

     1 class Descriptor:
     2     def __init__(self, name=None, **opts):
     3         self.name = name
     4         for key,value in opts.items():
     5             setattr(self, key, value)
     6 
     7     def __set__(self, instance, value):
     8         instance.__dict__[self.name] = value
     9 
    10 
    11 class Typed(Descriptor):
    12     expected_type = type(None)
    13 
    14     def __set__(self, instance, value):
    15         if not isinstance(instance, self.expected_type):
    16             raise TypeError('expected ' + str(self.expected_type))
    17         super().__set__(instance, value)
    18 
    19 
    20 class Unsigned(Descriptor):
    21     def __set__(self, instance, value):
    22         if value <= 0:
    23             raise ValueError('Expected >= 0')
    24         super().__set__(instance, value)
    25 
    26 
    27 class MaxSized(Descriptor):
    28     def __init__(self, name=None, **opts):
    29         if 'size' not in opts:
    30             raise TypeError('missing size option')
    31         super().__init__(name, **opts)
    32 
    33     def __set__(self, instance, value):
    34         if len(value) >= self.size:
    35             raise ValueError('size must be < ' + str(self.size))
    36         super().__set__(instance, value)
    37 
    38 
    39 class Integer(Typed):
    40     expected_type = int
    41 
    42 
    43 class UnsignedInteger(Integer, Unsigned):
    44     pass
    45 
    46 
    47 class Float(Typed):
    48     expected_type = float
    49 
    50 
    51 class UnsignedFloat(Float, Unsigned):
    52     pass
    53 
    54 
    55 class String(Typed):
    56     expected_type = str
    57 
    58 
    59 class SizedString(String, MaxSized):
    60     pass
    61 
    62 
    63 class Stock:
    64     name = SizedString('name', size=8)
    65     shares = UnsignedInteger('shares')
    66     price = UnsignedFloat('price')
    67 
    68     def __init__(self, name, shares, price):
    69         self.name = name
    70         self.shares = shares
    71         self.price = price

    实现自定义容器

    问题:

      你想实现一个自定义的类来模拟内置的容器类功能,比如列表和字典。但是你不确定到底要实现哪些方法

    解决方案:

      collections 定义了很多抽象基类,当你想自定义容器类的时候它们会非常有用。比如你想让你的类支持迭代,那就让你的类继承collections.Iterable 即可:

     1 from collections import Iterable
     2 
     3 
     4 class A(Iterable):
     5 
     6     def __iter__(self):
     7         return iter()
     8 
     9 
    10 
    11 
    12 #另外一种实现的方式
    13 import bisect
    14 import collections
    15 
    16 class SortedItem(collections.Sequence):
    17     def __init__(self, initial=None):
    18         self._item = sorted(initial) if initial is not None else []
    19 
    20     def __getitem__(self, index):
    21         return self._item[index]
    22 
    23     def __len__(self):
    24         return len(self._item)
    25 
    26     def add(self, item):
    27         bisect.insort(self._item, item)                 #可以保证元素插入后还保持顺序
    28 
    29 
    30 items = SortedItem([5, 1, 3])
    31 print('列表排序的结果'.center(30, '*'))
    32 print(list(items))
    33 print('获取列表索引位置的值'.center(30, '-'))
    34 print(items[0], items[-1])
    35 print('添加元素以后的列表'.center(30, '*'))
    36 items.add(2)
    37 print(list(items))

    以上代码执行的结果为:

    ***********列表排序的结果************
    [1, 3, 5]
    ----------获取列表索引位置的值----------
    1 5
    **********添加元素以后的列表***********
    [1, 2, 3, 5]

    collections 中很多抽象类会为一些常见容器操作提供默认的实现, 这样一来你只需要实现那些你最感兴趣的方法即可。假设你的类继承自collections.MutableSequence ,如下:

     1 import collections
     2 
     3 class Item(collections.MutableSequence):
     4     def __init__(self, initial=None):
     5         self._item = initial if initial is not None else []
     6 
     7     def __getitem__(self, index):
     8         print('Getting:', index)
     9         return self._item[index]
    10 
    11     def __setitem__(self, index, value):
    12         print('Setting', index, value)
    13         self._item[index] = value
    14 
    15     def __delitem__(self, index):
    16         print('Deleting:', index)
    17         del self._item[index]
    18 
    19     def insert(self, index, value):
    20         print('Inserting:', index, value)
    21         self._item.insert(index, value)
    22 
    23     def __len__(self):
    24         return len(self._item)
    25 
    26 a = Item([1, 2, 3, 4, 5])
    27 print('长度:', len(a))
    28 
    29 a.append(10)
    30 a.append(2)
    31 print(list(a))
    32 print('2在a列表中出现的次数:', a.count(2))
    33 a.remove(10)
    34 print('删除列表中元素为10:', list(a))

    以上代码执行的结果为:

    长度: 5
    Inserting: 5 10
    Inserting: 6 2
    Getting: 0
    Getting: 1
    Getting: 2
    Getting: 3
    Getting: 4
    Getting: 5
    Getting: 6
    Getting: 7
    [1, 2, 3, 4, 5, 10, 2]
    Getting: 0
    Getting: 1
    Getting: 2
    Getting: 3
    Getting: 4
    Getting: 5
    Getting: 6
    Getting: 7
    2在a列表中出现的次数: 2
    Getting: 0
    Getting: 1
    Getting: 2
    Getting: 3
    Getting: 4
    Getting: 5
    Deleting: 5
    Getting: 0
    Getting: 1
    Getting: 2
    Getting: 3
    Getting: 4
    Getting: 5
    Getting: 6
    删除列表中元素为10: [1, 2, 3, 4, 5, 2]

    属性的代理访问

    问题:

      你想将某个实例的属性访问代理到内部另一个实例中去,目的可能是作为继承的一个替代方法或者实现代理模式

    解决方案:

      简单来说,代理是一种编程模式,它将某个操作转移给另外一个对象来实现。最简单的形式可能是像下面这样:

     1 class A:
     2     def spam(self, x):
     3         self._x = x
     4 
     5     def foo(self):
     6         pass
     7 
     8 
     9 class B1:
    10     '''简单代理'''
    11     def __init__(self):
    12         self._a = A()
    13 
    14     def spam(self, x):
    15         return self._a.spam(x)
    16 
    17     def foo(self):
    18         return self._a.foo()
    19 
    20     def bar(self):
    21         pass
    22 
    23 
    24 #如果有大量的方法需要代理,那么使用getattr () 方法或许或更好些
    25 class B2:
    26     def __init__(self):
    27         self._a = A()
    28 
    29     def bar(self):
    30         pass
    31 
    32     def __getattr__(self, name):
    33         return getattr(self._a, name)

    另外一个代理例子是实现代理模式,例如:

     1 #简单的实现代理模式
     2 class Proxy:
     3     def __init__(self, obj):
     4         self._obj = obj
     5 
     6     def __getattr__(self, name):
     7         print('getattr:', name)
     8         return getattr(self._obj, name)
     9 
    10     def __setattr__(self, name, value):
    11         if name.startswith('_'):
    12             super().__setattr__(name, value)
    13         else:
    14             print('setattr:', name, value)
    15             setattr(self._obj, name, value)
    16 
    17     def __delattr__(self, name):
    18         if name.startswith('_'):
    19             super().__delattr__(name)
    20         else:
    21             print('delattr:', name)
    22             delattr(self._obj, name)
    23 
    24 
    25 class Spam:
    26     def __init__(self, x):
    27         self.x = x
    28 
    29     def bar(self, y):
    30         print('Spam.bar:', self.x, y)
    31 
    32 
    33 #实例化Spam
    34 s = Spam(2)
    35 #实例化Proxy,并把实例化的Spam传入进去
    36 p = Proxy(s)
    37 #调用Proxy中的getattr
    38 print(p.x)
    39 #调用Proxy中的bar方法
    40 p.bar(3)
    41 #切换x值
    42 p.x = 30
    43 print(p.x)

    以上代码执行的结果为:

    getattr: x
    2
    getattr: bar
    Spam.bar: 2 3
    setattr: x 30
    getattr: x
    30

    在类中定义多个构造器

    问题:

      你想实现一个类,除了使用init () 方法外,还有其他方式可以初始化它

    解决方案:

      为了实现多个构造器,你需要使用到类方法。例如:

     1 import time
     2 
     3 
     4 class Date:
     5     '''
     6     方法一:使用类方法
     7     '''
     8     def __init__(self, year, month, day):
     9         self.year = year
    10         self.month = month
    11         self.day = day
    12 
    13     @classmethod
    14     def today(cls):
    15         t = time.localtime()
    16         return cls(t.tm_year, t.tm_mon, t.tm_mday)
    17 
    18 
    19 
    20 class NewDate(Date):
    21     pass
    22 
    23 a = Date(2017, 8, 8)
    24 b = Date.today()
    25 c = NewDate.today()     #创建并返回最终的实例

    创建不调用init 方法的实例

     问题:

      你想创建一个实例,但是希望绕过执行init () 方法

    解决方案:

      可以通过new () 方法创建一个未初始化的实例。例如考虑如下这个类:

     1 class Date:
     2     def __init__(self, year, month, day):
     3         self.year = year
     4         self.month = month
     5         self.day = day
     6 
     7 d = Date(2017, 8, 8)
     8 print('可以使用init来初始化的:', d.year)
     9 
    10 '''
    11 d1 = Date.__new__(Date)
    12 print(d1.day)
    13 #会报这个错误 AttributeError: 'Date' object has no attribute 'day'
    14 '''
    15 #解决方法如下.这个Date day 还不存在,所以你需要手动初始化
    16 data = {'year':2017, 'month':8 , 'day':8}
    17 d1 = Date.__new__(Date)
    18 for key,value in data.items():
    19     setattr(d1, key, value)
    20 
    21 print('需要手动设置并初始化的(new):', d1.day)

    以上代码执行的结果为:

    可以使用init来初始化的: 2017
    需要手动设置并初始化的(new): 8

    当我们在反序列对象或者实现某个类方法构造函数时需要绕过init () 方法来创建对象。例如,对于上面的Date 来来讲,有时候你可能会像下面这样定义一个新的构造函数today() :

     1 from time import localtime
     2 
     3 class Date:
     4     def __init__(self, year, month, day):
     5         self.year = year
     6         self.month = month
     7         self.day = day
     8 
     9     @classmethod
    10     def today(cls):
    11         d = cls.__new__(cls)
    12         t = localtime()
    13         d.year = t.tm_year
    14         d.month = t.tm_mon
    15         d.day = t.tm_mday
    16         return d
    17 
    18 d = Date(2017, 8, 8)

    让类支持比较操作

    问题:

      你想让某个类的实例支持标准的比较运算(比如>=,!=,<=,< 等),但是又不想去实现那一大丢的特殊方法

    解决方案:

      装饰器functools.total ordering 就是用来简化这个处理的。使用它来装饰一个来,你只需定义一个eq () 方法,外加其他方法( lt , le , gt , or ge ) 中的一个即可。然后装饰器会自动为你填充其它比较方法

      作为例子,我们构建一些房子,然后给它们增加一些房间,最后通过房子大小来比较它们:

     1 from  functools import total_ordering
     2 
     3 
     4 class Room:
     5     def __init__(self, name, length, width):
     6         self.name = name
     7         self.length = length
     8         self.width = width
     9         self.square_feet = self.length * self.width
    10 
    11 
    12 @total_ordering
    13 class House:
    14     def __init__(self, name, style):
    15         self.name = name
    16         self.style = style
    17         self.rooms = list()
    18 
    19     @property
    20     def living_space_footage(self):
    21         return sum(r.square_feet for r in self.rooms)
    22 
    23     def add_room(self, room):
    24         self.rooms.append(room)
    25 
    26     def __str__(self):
    27         '{}: {} square foot {}'.format(self.name,
    28                                        self.living_space_footage,
    29                                        self.style)
    30 
    31     def __eq__(self, other):
    32         return self.living_space_footage == other.living_space_footage
    33 
    34     def __lt__(self, other):
    35         return self.living_space_footage < other.living_space_footage
    36 
    37     def __gt__(self, other):
    38         return self.living_space_footage > other.living_space_footage
    39 
    40 
    41 h1 = House('h1', 'Cape')
    42 h1.add_room(Room('Master Bedroom', 14, 21))
    43 h1.add_room(Room('Living Room', 18, 20))
    44 h1.add_room(Room('Kitchen', 12, 16))
    45 h1.add_room(Room('Office', 12, 12))
    46 h2 = House('h2', 'Ranch')
    47 h2.add_room(Room('Master Bedroom', 14, 21))
    48 h2.add_room(Room('Living Room', 18, 20))
    49 h2.add_room(Room('Kitchen', 12, 16))
    50 h3 = House('h3', 'Split')
    51 h3.add_room(Room('Master Bedroom', 14, 21))
    52 h3.add_room(Room('Living Room', 18, 20))
    53 h3.add_room(Room('Office', 12, 16))
    54 h3.add_room(Room('Kitchen', 15, 17))
    55 
    56 houses = [h1, h2, h3]
    57 
    58 print('Is h1 bigger than h2?', h1 > h2) # prints True
    59 print('Is h2 smaller than h3?', h2 < h3) # prints True
    60 print('Is h2 greater than or equal to h1?', h2 >= h1) # Prints False
    61 #print('Which one is biggest?', max(houses)) # Prints 'h3: 1101-square-foot Split'
    62 #print('Which is smallest?', min(houses)) # Prints 'h2: 846-square-foot Ranch

     以上代码执行的结果为:

    Is h1 bigger than h2? True
    Is h2 smaller than h3? True
    Is h2 greater than or equal to h1? False
  • 相关阅读:
    常用网络操作命令
    C语言中的位域[转]
    状态机——一种强大的思想利器
    9030PCI CAN驱动开发点滴
    驱动开发中应该注意的事项
    java 从网络Url中下载文件
    windows pyspider 爬虫安装
    java list去重
    Java 文件分块及合并
    工程部署到linux
  • 原文地址:https://www.cnblogs.com/demon89/p/7296122.html
Copyright © 2020-2023  润新知