一直有不要把兴趣和事业混为一谈的说法,我仔细看过,都很有道理。
想象过一些人痛心疾首的望着我说,“你在这有天分,你不做是在浪费你的才华!”这时我就呵呵一笑,“我的才华还有很多,不差浪费这一点”。
好在我的兴趣不需要什么卓越的智商和天分,就是门手艺而已。而手艺是需要磨练的,没时间磨练还搞什么手艺。
好比你爱上一个姑娘,要是两情相悦最好。可要是单恋一枝花,你也没什么办法。
离开舒适区,回到起点,做喜欢的事情,要是摔到头破血流–也没什么办法不是。
王子子
一直有不要把兴趣和事业混为一谈的说法,我仔细看过,都很有道理。
想象过一些人痛心疾首的望着我说,“你在这有天分,你不做是在浪费你的才华!”这时我就呵呵一笑,“我的才华还有很多,不差浪费这一点”。
好在我的兴趣不需要什么卓越的智商和天分,就是门手艺而已。而手艺是需要磨练的,没时间磨练还搞什么手艺。
好比你爱上一个姑娘,要是两情相悦最好。可要是单恋一枝花,你也没什么办法。
离开舒适区,回到起点,做喜欢的事情,要是摔到头破血流–也没什么办法不是。
最近太忙,实在没时间充电了,翻出一篇一年前的学习笔记先顶一下,虽然当今机器学习框架已经封装的非常好了,但是了解何时使用和算法里发生了什么也是很重要的。
用python的文本处理能力将文档切分成词向量,对文档进行分类和过滤,最终将结果转换成人可以理解的信息。
优点:在数据较少的情况下仍然有效,可以处理多类别问题。
缺点:对于输入数据的准备方式较为敏感。
使用数据类型:标称型数据。
朴素贝叶斯的一般流程:
1.收集数据:可以使用任何方法。
2.准备数据:需要数值型或者布尔型数据。
3.分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果更好。
4.训练算法:计算不同的独立特征的条件概率。
5.测试算法:计算错误率。
6.使用算法:一个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴素贝叶斯分类器。
决策树和朴素贝叶斯的使用情况分类:
决策树适合标准的离散化数据,并且对数值型数据的处理难以处理。
朴素贝叶斯的要求相对较低。
|
|
朴素贝叶斯算法是充分的利用概率原理,用概率的方法分类一个对象的所属类别,对多个类型的对象都可以使用。
算法首先收集了所有训练的词组(数字),通过对总体(比如说文章)的的判断训练,让机器清楚每个的词组出现在总体中分类的概率,用对数的方式防止向下溢出,最终整合概率进行分类。
处理命令行参数可以使用argsparse,也推荐更方便更高级的docopt进行处理。
docopt是根据常见的帮助信息定义了一套领域特定语言(DSL),并通过这个DSL Parser参数生成处理命令行参数的代码。
pandas作为python三大科学运算库之一的使用。
使用Beautifulsoup更好
序列化,简单来说就是把内存中的数据结构在不丢失其身份和类型信息的情况下转成对象的文本或二进制表示的过程。同类支持序列化的模块有pickle,json,marshal和shelve。
pickle是最通用的序列化模块,我们应该优先使用c语言实现的cPickle,速度比pickle快1000倍,区别是cPickle不能被继承。
pickle主要通过dump和load两种方法序列化与反序列化(存储与读取)
|
|
因为python-message的消息订阅默认是全局性的,所以有可能产生名字冲突。
所谓状态模式,就是当一个对象的内在状态改变时允许改变其行为,但这个对象看起来像是改变了其类。
简单的状态模式有其缺点:
这时候我们可以使用 Python-state 来解决。
|
|
@statefule装饰器重载了被修饰的类的__getattr__()从而使得 People 的实例能够调用当前状态类的方法,同时被修饰的类的实例是带有状态的,能够使用curr()查询当前状态,也可以使用switch()进行状态切换,默认的状态是通过类定义的 default 属性标识,default = True的类成为默认状态。
状态类 Workday 和 Weekend 继承自 State 类,从其派生的子类可以使用__begin__和__end___状态转换协议,自定义进入和离开当前状态时对宿主的初始化和清理工作。
下面是一个真实业务的例子:
|
|
Python 中一切皆对象,在新式类中,object 是所有内建类型的基类,用户自定义的类可以继承自 object 也可继承自内建类型。
|
|
新式类支持 property 和描述符特性,作为新式类的祖先,Object 类还定义了一些特殊方法:new()、init()、delattr()、getattribute()、setattr()、hash()、repr()、str()等。
|
|
运行结果:
|
|
从结果中我们可以看出,程序输出了__new__()调用所产生的输出,并抛出了异常。于是我们知道,原来__new__()才是真正创建实例,是类的构造方法,而__init__()是在类的对象创建好之后进行变量的初始化。上面程序抛出异常是因为在__new__()中没有显式返回对象,a1此时为None,当去访问实例属性时就抛出了异常。
根据官方文档,我们可以总结以下几点:
分别来看例子加深理解:
|
|
在 Python 中所谓的变量其实都是名字,这些名字指向一个或多个 Python 对象。这些名字都存在于一个表中(命名空间),我们称之为局部变量,调用locals()可以查看:
|
|
Python 中的作用域分为:
Python 3 中引入了 nonlocal 关键字:
|
|
在类中当定义实例方法的时候需要将第一个参数显式声明为self, 而调用时不需要传入该参数, 我们通过self.x访问实例变量, self.m()访问实例方法:
|
|
运行结果:
|
|
从中可以发现, self 表示实例对象本身, 即 SelfTest 类的对象在内存中的地址. self 是对对象 st 本身的引用, 我们在调用实例方法时也可以直接传入实例对象: SelfTest.display(st). 同时 self 或 cls 并不是 Python 的关键字, 可以替换成其它的名称。
Python 中为什么需要 self 呢:
Python 属于一级对象语言, 我们有好几种方法可以引用类方法:
|
|
Python 的哲学是:显示优于隐式(Explicit is better than implicit)。
古典类与新式类所采取的 MRO (Method Resolution Order, 方法解析顺序) 的实现方式存在差异。
古典类是按照多继承申明的顺序形成继承树结构, 自顶向下采用深度优先的搜索顺序. 而新式类采用的是 C3 MRO 搜索方法, 在新式类通过__mro__得到 MRO 的搜索顺序, C3 MRO 的算法描述如下:
假定,C1C2…CN 表示类 C1 到 CN 的序列,其中序列头部元素(head)=C1,序列尾部(tail)定义 = C2…CN;
C 继承的基类自左向右分别表示为 B1,B2…BN
L[C] 表示 C 的线性继承关系,其中 L[object] = object。
算法具体过程如下:
L[C(B1…BN)] = C + merge(L[B1] … L[BN], B1 … BN)
其中 merge 方法的计算规则如下:在 L[B1]…L[BN],B1…BN 中,取 L[B1] 的 head,如果该元素不在 L[B2]…L[BN],B1…BN 的尾部序列中,则添加该元素到 C 的线性继承序列中,同时将该元素从所有列表中删除(该头元素也叫 good head),否则取 L[B2] 的 head。继续相同的判断,直到整个列表为空或者没有办法找到任何符合要求的头元素(此时,将引发一个异常)。
菱形继承是我们在多继承设计的时候需要尽量避免的一个问题。
|
|
.操作符封装了对实例属性和类属性两种不同属性进行查找的细节。
但是如果是访问方法呢:
|
|
根据通过实例访问属性和根据类访问属性的不同,有以下两种情况:
一种是通过实例访问,比如代码 obj.x,如果 x 是一个描述符,那么 getattribute() 会返回 type(obj).dict[‘x’].get(obj, type(obj)) 结果,即:type(obj) 获取 obj 的类型;type(obj).dict[‘x’] 返回的是一个描述符,这里有一个试探和判断的过程;最后调用这个描述符的 get() 方法。
另一个是通过类访问的情况,比如代码 cls.x,则会被 getattribute()转换为 cls.dict[‘x’].get(None, cls)。
描述符协议是一个 Duck Typing 的协议,而每一个函数都有 get 方法,也就是说其他每一个函数都是描述符。所有对属性, 方法进行修饰的方案往往都用到了描述符, 如classmethod, staticmethod, property等, 以下是property的参考实现:
|
|
以上两种方法可以对实例属性进行获取和拦截:
但访问不存在的实例属性时,会由内部方法__getattribute__()抛出一个 AttributeError 异常,也就是说只要涉及实例属性的访问就会调用该方法,它要么返回实际的值,要么抛出异常。详情请参考。
那么__getattr__()在什么时候调用呢:
当这两个方法同时被定义的时候,要么在__getattribute__()中显式调用,要么触发AttributeError异常,否则__getattr__()永远不会被调用。
我们知道 property 也能控制属性的访问,如果一个类中如果定义了 property、getattribute()以及__getattr__()来对属性进行访问控制,会最先搜索__getattribute__()方法,由于 property 对象并不存在于 dict 中,因此并不能返回该方法,此时会搜索 property 中的get()方法;当 property 中的set()方法对属性进行修改并再次访问 property 的get()方法会抛出异常,这时会触发__getattr__()的调用。
getattribute()总会被调用,而__getattr__()只有在__getattribute__()中引发异常的情况下调用。
++i 合法,但是无效
对于打开的资源我们记得关闭它,如文件、数据库连接等,Python 提供了一种简单优雅的解决方案:with。
with的实现得益于一个称为上下文管理器(context manager)的东西,它定义程序运行时需要建立的上下文,处理程序的进入和退出,实现了上下文管理协议,即对象中定义了__enter__()和__exit__(),任何实现了上下文协议的对象都可以称为一个上下文管理器:
包含with语句的代码块执行过程如下:
|
|
Python 还提供 contextlib 模块,通过 Generator 实现,其中的 contextmanager 作为装饰器来提供一种针对函数级别上的上下文管理器,可以直接作用于函数/对象而不必关心__enter__()和__exit__()的实现。
python 的 else 子句在循环正常结束和循环条件不成立时被执行,由 break 语句中断时不执行,同样,我们可以利用这颗语法糖作用在 while 和 try…except 中。
异常处理的几点原则:
1. 注意异常的粒度,不推荐在 try 中放入过多的代码。 2. 谨慎使用单独的 except 语句处理所有异常,最好能定位具体的异常。
3. 注意异常捕获的顺序,在适合的层次处理异常,Python 是按内建异常类的继承结构处理异常的,所以推荐的做法是将继承结构中子类异常在前抛出,父类异常在后抛出。
4. 使用更为友好的异常信息,遵守异常参数的规范。
当 finally 执行完毕时,之前临时保存的异常将会再次被抛出,但如果 finally 语句中产生了新的异常或执行了 return 或 break 语句,那么临时保存的异常将会被丢失,从而异常被屏蔽。 在实际开发中不推荐 finally 中使用 return 语句进行返回。
(None被判断为False,但是空集不等于None)
类型FalseTrue布尔False (与0等价)True (与1等价)字符串“”( 空字符串)非空字符串,例如 " “,”blog“数值0, 0.0非0的数值,例如:1, 0.1, -1, 2容器[], (), {}, set()至少有一个元素的容器对象,例如:[0], (None,), [’’]NoneNone非None对象。
>>> id(None)
10743840
>>> a = None
>>> id(a)
10743840
>>> l = []
>>> if l is not None: # 判断逻辑 l 不为空
... print('l is {}'.format(l))
... else:
... print('l is empty')
...
l is []
>>> if l: # #3 正确的判断形式
... print('Do something...')
... else:
... print('Do other thing...')
...
Do other thing...
执行中会调用__nonzero__()来判断自身对象是否为空并返回0/1或True/False,如果没有定义该方法,Python 将调用__len__()进行判断,返回 0 表示为空。如果一个类既没有定义__len__()又没有定义__nonzero__(),该类实例用 if 判断为True。
连接字符串使用join将使程序性能更佳,原因是使用每次使用 + 都需要格外分一块内存去存储结果。
format方法总结 使用 format 格式化字符串有以下好处:
Python 中一切皆对象,每个对象都有一个唯一的标识符(id)、类型(type)和值。数字、字符串、元组属于不可变对象,字典、列表、字节数组属于可变对象。
默认参数在初始化时仅仅被评估一次,以后直接使用第一次评估的结果,course 指向的是 list 的地址,每次操作的实际上是 list 所指向的具体列表,所以对于可变对象的更改会直接影响原对象。
最好的方法是传入None作为默认参数,在创建对象的时候动态生成列表。
>>> list1 = ['a', 'b', 'c']
>>> list2 = list1
>>> list1.append('d')
>>> list2
['a', 'b', 'c', 'd']
>>> list3 = list1[:] # 可变对象的切片操作相当于浅拷贝
>>> list3.remove('a')
>>> list3
['b', 'c', 'd']
>>> list1
['a', 'b', 'c', 'd']
使用列表解析、字典解析、元组解析等替代for循环
解析式有以下好处:
正确的说法是传对象(call by object)或传对象的引用(call-by-object-reference),函数参数在传递过程中将整个对象传入,对可变对象的修改在函数外部以及内部都可见,对不可变对象的”修改“往往是通过生成一个新对象然是赋值实现的。
其中就是默认参数如果是可变对象,在调用者和被调用者之间是共享的。
所以默认值使用可以使用数字、字符串、元组。
不可以使用字典、列表、字节数组。
import time
# 对当前系统时间进行处理
def report(when=time.time): # 而不是when=time.time()
pass
原因如下:
1. 使用过于灵活,导致函数签名不够清晰,存在多种调用方式
2. 使用*args和**kw简化函数定义就意味着函数可以有更好的实现方法
使用场景:
1. 为函数添加一个装饰器
2. 参数数目不确定
3. 实现函数的多态或子类需要调用父类的某些方法时
(str方法面向用户更为友好,repr解释更加清晰)
总结几点:
(需要返回类的实例时,或需要动态生成对应类的类变量,使用classmethod,方法不跟实例与类相关(不适用self和cls),定义为静态方法(工具方法))
调用类方法装饰器的修饰器的方法,会隐式地传入该对象所对应的类,可以动态生成对应的类的类变量,同时如果我们期望根据不同的类型返回对应的类的实例,类方法才是正确的解决方案。
反观静态方法,当我们所定义的方法既不跟特定的实例相关也不跟特定的类相关,可以将其定义为静态方法,这样使我们的代码能够有效地组织起来,提高可维护性。
当然,也可以考虑定义一个模块,将一组的方法放入其中,通过模块来访问。
# 小技巧:Python 遇到未闭合的小括号会自动将多行代码拼接为一行
>>> s = ('SELECT * '
... 'FROM table '
... 'WHERE field="value"')
>>> s
'SELECT * FROM table WHERE field="value"'
# Python2 中使用 basestring 正确判断一个变量是否是字符串
# 性质判断
isalnum() isalpha() isdigit() islower() isupper() isspace() istitle()
# 查找替换
startswith(prefix[, start[, end]]) endswith(suffix[, start[, end]]) # prefix参数可以接收 tuple 类型的实参
count(sub[, start[, end]]) find(sub[, start[, end]]) index(sub[, start[, end]])
rfind(sub[, start[, end]]) rindex(sub[, start[, end]]) replace(old, new[, count]) # count是指的替换次数,不指定就全部替换
# 切分
partition(sep) rpartition(sep) splitlines([keepends]) split([sep, [, maxsplit]]) rsplit([sep[, maxsplit]]) # partition 返回一个3个元素的元组对象
# 变形
lower() upper() capitalize() swapcase() title()
# 删减填充
strip([chars]) lstrip([chars]) rstrip([chars]) # 没有提供chars默认是空白符,由string.whitespace 常量定义
center(width[, fillchar]) ljuct(width[, fillchar]) rjust(width[, fillchar])
zfill(width) expandtabs([tabszie])
下面来介绍一些易混淆的地方:
>>> ' hello world'.split()
['hello', 'world']
>>> ' hello world'.split(' ')
['', '', 'hello', 'world']
>>> 'hello wORld'.title()
'Hello World'
>>> import string
>>> string.capwords(' hello world!')
'Hello World!'
>>> string.whitespace
' \t\n\r\x0b\x0c'
(sort方法是原地操作,sorted是复制操作,不需要保留源列表用sort)
# 函数原型
sorted(iterable[, cmp[, key[, reverse]]]) # 返回一个排序后的列表
s.sort([cmp[, key[, reverse]]]) # 直接修改原列表,返回为None
>>> persons = [{'name': 'Jon', 'age': 32}, {'name': 'Alan', 'age': 50}, {'name': 'Bob', 'age': 23}]
>>> sorted(persons, key=lambda x: (x['name'], -x['age']))
[{'name': 'Alan', 'age': 50}, {'name': 'Bob', 'age': 23}, {'name': 'Jon', 'age': 32}]
>>> a = (1, 2, 4, 2, 3)
>>> sorted(a)
[1, 2, 2, 3, 4]
所以如果实际过程中需要保留原有列表,可以使用sorted()。sort()不需要复制原有列表,消耗内存较小,效率较高。同时传入参数key比传入参数cmp效率要高,cmp传入的函数在整个排序过程中会调用多次,而key针对每个元素仅作一次处理。
(对可变对象需要真正意义上的复制时使用copy.deepcopy,这种需求情况还是比较少见)
浅拷贝(shallow copy):构造一个新的复合对象并将从原对象中发现的引用插入该对象中。工厂函数、切片操作、copy 模块中的 copy 操作都是浅拷贝
深拷贝(deep copy):针对引用所指向的对象继续执行拷贝,因此产生的对象不受其它引用对象操作的影响。深拷贝需要依赖 copy 模块的 deepcopy() 操作
在 python 中,标识一个对象唯一身份的是:对象的id(内存地址),对象类型,对象值,而浅拷贝就是创建一个具有相同类型,相同值但不同id的新对象。因此使用浅拷贝的典型使用场景是:对象自身发生改变的同时需要保持对象中的值完全相同,比如 list 排序:
def sorted_list(olist, key=None):
copied_list = copy.copy(olist)
copied_list.sort(key=key)
return copied_list
a = [3, 2, 1] # [3, 2, 1]
b = sorted_list(a) # [1, 2, 3]
深拷贝不仅仅拷贝了原始对象自身,也对其包含的值进行拷贝,它会递归的查找对象中包含的其他对象的引用,来完成更深层次拷贝。因此,深拷贝产生的副本可以随意修改而不需要担心会引起原始值的改变:
>>> a = [1, 2]
>>> b = [a, a]
>>> b
[[1, 2], [1, 2]]
>>> from copy import deepcopy
>>> c = deepcopy(b)
>>> id(b[0]) == id(c[0])
False
>>> id(b[0]) == id(b[1])
True
>>> c
[[1, 2], [1, 2]]
>>> c[0].append(3)
>>> c
[[1, 2, 3], [1, 2, 3]]
使用 copy 和 deepcopy 可以完成对一个对象拷贝的定制。
(需要计数统计时,使用Counter)
常见的计数统计可以使用dict、defaultdict、set和list,不过 Python 提供了一个更优雅的方式:
>>> from collections import Counter
>>> some_data = {'a', '2', 2, 3, 5, 'c', '7', 4, 5, 'd', 'b'}
>>> Counter(some_data)
Counter({'7',: 1, 2: 1, 3: 1, 4: 1, 5: 1, '2': 1, 'b': 1, 'a': 1, 'd': 1, 'c': 1})
Counter 类属于字典类的子类,是一个容器对象,用来统计散列对象,支持+、-、&、|,其中&和|分别返回两个 Counter 对象各元素的最小值和最大值。
# 初始化
Counter('success')
Counter(s=3, c=2, e=1, u=1)
Counter({'s': 3, 'c': 2, 'u': 1, 'e': 1})
# 常用方法
list(Counter(some_data).elements()) # 获取 key 值
Counter(some_data).most_common(2) # 前 N 个出现频率最高的元素以及对应的次数
(Counter(some_data))['y'] # 访问不存在的元素返回 0
c = Counter('success')
c.update('successfully') # 更新统计值
c.subtract('successfully') # 统计数相减,允许为0或为负
(啥程序都需要配置,要搞懂配置库)
几乎所有的应用程序都会读取配置文件,ini是一种比较常见的文件格式:
[section1]
option1=0
Python 提供标准库 ConfigParser 来支持它:
import ConfigParser
conf = ConfigParser.ConfigParser()
conf.read('example.conf')
print(conf.get('section1', 'in_default'))
再来看个SQLAlchemy配置文件的例子:
[DEFAULT]
conn_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 = example
import ConfigParser
conf = ConfigParser.ConfigParser()
conf.read('format.conf')
print(conf.get('db1', 'conn_str'))
print(conf.get('db2', 'conn_str'))
对于大多数事情来说,失败是常态,无法掌控事情的发展是常态,缺乏安全感的弊端就是恐惧面对这些。问题是越不去面对,挫折固然少了,无法掌控的事情却更多了。要学会坦然面对无法掌控的事情,磨练出更优秀的自己。