建议34:str()和repr()的区别
1)两者之间的目标不同:str()主要面向用户,其目的是可读性,返回形式为用户友好性和可读性都较强的字符串类型;而repr()面向的是python解释器,或者说开发人员,期目的是准确性,其返回值表示python解释器内部的含义,常作为编程人员debug用途。
2)在解释器中直接输入a时默认调用repr()函数,而pring(a)则调用str()函数。
3)repr()的返回值一般可以用eval()函数来还原对象,通常来说有如下等式:
obj == eval(repr(obj))
4)这两个方法分别调用内建的__str__()和__repr__()方法,一般来说在类中都应该定义__repr__()方法,而__str__()方法则可选,当可读性比准确性更为重要的时候应该考虑定义__str__()方法。如果类中没有定义__str__()方法,则默认会使用__repr__()方法的结果来返回对象的字符串表示形式。用户实现__repr__()方法的时候最好保证其返回值可以用eval()方法使对象重新还原。
建议39:使用Counter进行计数统计
1)使用dict进行计数统计
2)使用defaultdict
from collections import defaultdict some_data = ['a', '2', '4', 'b', 'b', '5', '4', 'a', 'a', '9', '0', 'c', '5'] conut_frq = defaultdict(int) for item in some_data: conut_frq[item] += 1 print(conut_frq)
3)使用set和list
使用Counter:
from collections import Counter some_data = ['a', '2', '4', 'b', 'b', '5', '4', 'a', 'a', '9', '0', 'c', '5'] print(Counter(some_data))
建议40:深入掌握ConfigParser
getboolean()函数:将配置项的值转换为布尔值,0,no,false,off都会被黑底为False;1,yes,true,on则都被黑底为True,其他值都会导致抛出ValueError异常。
[DEFAULT]配置:当读取的配置项不在指定的节里面,ConfigParser就会到[DEFAULT]节中查找。
[DEFAULT] con_str = %(dbn)s://%(user)s:%(pw)s@%(host)s:%(port)s/%(db)s dbn = mysql user = root host = localhost port = 3306 [db1] user = aaa pw = ppp db = example [db2] host = 192.168.0.110 pw = www db = example1
import configparser conf = configparser.ConfigParser() conf.read('format.conf') print(conf.get('db1', 'con_str')) print(conf.get('db2', 'con_str'))
mysql://aaa:ppp@localhost:3306/example
mysql://root:www@192.168.0.110:3306/example1
通过get方法,格式化con_str的参数是不同的
建议52:用发布订阅模式实现松耦合
发布订阅模式的优点是发布者与订阅者松散的耦合,双方不需要知道对方的存在。由于主题是被关注的,发布者和订阅者可以对系统拓扑毫无所知。无论对方是否存在,发送者和订阅者都可以继续正常操作。要实现这个模式,就需要有一个中间代理人,在实现中一般被称为Broker,它维护这发布者和订阅者的关系:订阅者把感兴趣的主题告诉它,而发布者的信息也通过它路由到各个订阅者处。
假定你在编写一个非常牛X的程序库,姑且为它取名为 foo,里面有一个函数叫 bar,你就想啊,这么牛X的一个函数,肯定要写一下 log 啊,所以你就写了以下代码:
def bar(): print 'Haha, Calling bar().' do_sth()
你高高兴兴发了版本,大家都过得很好。过了几天,公司的另一个项目组听闻牛人您写了个库叫 foo,非常好用,就拿去用了。当天,快下班的时候,你被拖去救火,因为出 Bug 了呀。你查看了很久日志,都没有发现他们调用 bar() 的痕迹,一问,原来他们是用 logging 的,标准输出在做 Daemon 的时候被重定向到 /dev/null 去了……。
好吧,你忍。但没法忍啊,你们原来的项目又不用 logging,你在程序库里引入 logging 谁来初始化它呢?就算你引入了 logging,你们项目获取 logger 可能是用 logging.getLogger('prjA'),另一个项目可能是用 logging.getLogger('prjB'),日后还有新项目呢,想到这个你就蛋疼了。忍痛割爱,把 print 语句给删除掉?你又怕日后出了问题你自己都找不到 Bug 那还不是自己加班自己苦……。
这个时候,不妨让 python-message 来帮你手,轻松改一下 bar() 函数
import message LOG_MSG = ('log', 'foo') def bar(): messeage.pub(LOG_MSG, 'Haha, Calling bar().') do_sth()
而在你的项目中,只需要在项目开始处加上这样的代码:
import message import foo def handle_foo_log_msg(txt): print txt message.sub(foo.LOG_MSG, handle_foo_log_msg)
而很类似地,在另一个项目 prjA 里,你可以把 handle_foo_log_msg() 稍作修改:
def handle_foo_log_msg(txt): import logging logging.debug(txt)
在另一个 prjB 里则可能是这样:
import logging logger = logging.getLogger("prjB") def handle_foo_log_msg(txt): logger.debug(txt)
通过sub('greet', hello, front=True),可以将后sub的函数先被调用
订阅/发布模式是观察者模式的超集,它不关注消息是谁发布的,也不关心消息由谁处理。但有时我们也希望某个自己的类也能更方便的订阅/发布消息,也就是想退化为观察者模式,python-message同样提供支持。
建议53:用状态模式美化代码
所谓状态模式,就是当一个对象的内在状态改变是允许改变其行为,但这个对象看起来像是改变了其类,状态模式主要用于控制一个对象状态的条件表达式过于复杂的情况,其可把状态的判断逻辑转移到表示不同状态的一系列类中,进而把复杂的判断逻辑简化。
建议55:__init__()不是构造方法
class A(object): def __new__(cls, *args, **kwargs): print(cls) print(args) print(kwargs) print("------------------") isinstance = object.__new__(cls) print(isinstance) # return super(A, cls).__new__(cls) def __init__(self, a, b): print("init gets called") print("self is ", self) self.a, self.b = a, b a = A(1, 2) print(a.a) print(a.b)
__new__()才是构造方法,__new__()方法一般需要返回类的对象,当返回类的对象是将会自动调用__init__()方法进行初始化,如果没有对象返回,则__init__()方法不会被调用。__init__()方法不需要显示返回,默认为None,否则会在运行时抛出TypeError。
实例创建的时候使用__new__()方法,实例初始化的时候使用__init()方法。
一般情况下不需要覆盖__new__()方法,但当子类继承自不可变类型,如str、int、unicode或者tuple的时候,往往需要覆盖该方法。
当需要覆盖__new__()和__init__()方法的时候这两个方法的参数必须保持一致,如果不一致将导致异常。
建议57:为什么需要self参数
import math def len(point): print(point.right_angle_sideX, point.right_angle_sideY) return math.sqrt(point.right_angle_sideX ** 2 + point.right_angle_sideY ** 2) class RTriangle(object): def __init__(self, right_angle_sideX, right_angle_sideY): self.right_angle_sideX = right_angle_sideX self.right_angle_sideY = right_angle_sideY RTriangle.len = len rt = RTriangle(3, 4) print(rt.len())
建议58:理解MRO与多继承
在古典类中,MRO搜索采用简单的自左至右的深度优先方法,即按照多继承申明的顺序形式继承树结构,自顶向下采用深度优先搜索顺序,当找到所需要的属性或方法的时候就停止搜索;
在新式类中,采用的是C3 MRO搜索方法,广度优先的方法
建议60:区别__getattr__()和__getattribute__()方法
__getattr__()和__getattribute__()方法都可以用做实例属性的获取和拦截,__getattr__()适用于未定义的属性,即该属性在实例中以及对应的类的基类以及祖先类中都不存在,而__getattribute__()对于所有的访问都有会调用该方法。__getattribute__()仅应用于新式类,
class A(object): def __init__(self, name): self.name = name a = A("allin") print(a.name) print(a.test)
当访问一个不存在的实例属性的时候就会抛出AttributeError异常,这个异常是由内部方法__getattribute__(self, name)抛出的,因为__getattribute__()会被无条件调用,也就是说只要涉及实例属性的访问就会调用该方法,它要么返回实际的值,要么抛出异常。我们在上面例子中添加__getattr__()方法:
class A(object): def __init__(self, name): self.name = name def __getattr__(self, item): print("calling __getattr__: ", item) a = A("allin") print(a.name) print(a.test)
这次程序没有抛出异常,而调用了__getattr__()方法,实际上__getattr__()方法仅如下情况才被调用:
属性不在实例的__dict__中;
属性不在基类以及祖先类的__dict__中;
触发AttributeError异常时。
需要特别注意的是两个方法同时被定义的时候,要么在__getattribute__()中显示调用,要么触发AttributeError异常,否则__getattr__()永远不会被调用,两个方法都是Object类中定义的默认方法,当用户需要覆盖这些方法时有以下几点注意事项:
1)避免无穷递归:上面例子如果添加__getattribute__()就会无穷递归,因为属性的访问调用的是覆盖了的__getattribute__()方法,而该方法中self.__dict__[attr]又调用__getattribute__()
2)访问未定义的属性
例子:
class A(object): _c = "test" def __init__(self): self.x = None @property def a(self): print("using property to access attribute") if self.x is None: print("return value") return "a" else: print("error occured") raise AttributeError @a.setter def a(self, value): self.x = value def __getattr__(self, item): print("using __getattr__ to access attribute") print("attribute name: ", item) return "b" def __getattribute__(self, item): print("using __getattribute__ to access attribute") return object.__getattribute__(self, item) a1 = A() print(a1.a) print("-" * 50) a1.a = 1 print(a1.a) print("-" * 50) print(A._c)
说明:第一部分访问a1.a时会先调用__getattribute__()方法,然后进入a方法,当判断self.x的时候又一次访问属性,又一次调用__getattribute__()方法,...
第二部分打印 error occured 后会调用__getattr__()方法....
第三部分是为了说明调用类属性不会调用__getattribute__()和_getattr__()方法。