你好,欢迎光临我的博客!

我是一名使用Python的程序员,这是我的github。平时喜欢玩各种电子游戏,这是我的psn:CenterRight这个博客用来分享我的生活、心得以及有关计算机的技术内容。除特殊说明外均采用CC0创作协议(任何人可以自由地复制,修改,分发和演出——甚至用于商业目的,而不必署名)但是转载建议保留原文链接以让读者看到最新的版本。本站所有内容仅代表本人观点,与我的雇主无关,并且会永远保持独立性不受任何组织和公司的影响。如果我们有共同兴趣,或者你遇到了麻烦,可以通过右边的电子邮箱联系我。也欢迎留言和订阅!

《猩球崛起3》:套路式的电影

最近看了《猩球崛起3》,感觉受到了满满的套路。猩猩们在丛林里幸福地生活着,突然有一天,人类过来打他们,凯撒不想挑起战争最后还是打起来了,最后猩猩获得了胜利,猩猩们在丛林里继续幸福的生活……

最近几年的作品太多走这种套路式了。可能大家都不想冒险去搞新的世界观,新的创意。既然现有的东西能赚很多钱,为什么不稳一点捞几笔钱呢?

《变形金刚》,汽车人低调地生活在地球上,突然有一天出现了一种神秘的力量足以摧毁地球,于是汽车人不知道为什么和人类打起来了,或者汽车人之间打起来了。最后汽车人胜利了,擎天柱配着背景音乐总结了一番,然后汽车人又低调地继续生活。

《神秘海域》,我的老天爷,加上最近的这一部,同样的套路来了六次了!我从1开始玩的,德里克听说了某个地方有宝藏,于是在一个手下有很多士兵的反派的跟踪下(如果没有手下有很多反派的士兵,你在游戏里面的抢是干啥用的),一路走哪儿哪儿塌终于找到了宝藏,宝藏有时候是香格里拉有时候是别的,不过这不重要,重要的是这个宝藏一定会被反派抢走,然后反派要毁灭人类,这就是你最终的Boss,然后你杀了他,明白了这个宝藏原来被藏起来是有原因的,于是你毁了宝藏,游戏结束。完完全全同样的戏路啊!而且就连每一部都会有一个老烦的女人不断需要你救或者来救你都一模一样!

我要恨死这些套路作品了。不过好像真有一些人,只要看到汽车人变形然后打起来了就挺开心的。

话说回来,虽然有些作品除了一部又一部,不过一部比一部强。比如说《战神》解秘从来没有敷衍过,设计者绝对是花了心思的。还有漫威的系列,上个周看了《蜘蛛侠》发现真的不过,除了有些地方逻辑不太好,但是剧情,搞笑以及动作方面都挺满意的,塑造人物个性方面也很成功(话痨)。还有GTA,从一个反响一般的作品开始,慢慢的成长到了《罪恶都市》,然后《圣安地列斯》,然后《GTA5》。不一样的故事,不一样但是完整且完美的剧情。每次都有点担心,这一部游戏都已经这么好了,下一部该不会令人失望吧。

 

 

《Effective Python》小记

Effective Python 59 SPECIFIC WAYS TO WRITE BETTER PYTHON 这本书终于读完了。从这本书里学到不少经验,以及之前忽略的知识。书中部分内容也是库的内容(这么说有失公允,大部分属都会有抄库文档的嫌疑的,因为文档包含了最多的信息),也有很多内容基本上是常识,比如七八章合作和产品的东西。这些内容,看了书也不会,不看书实践一下不会也得会。是“纸上得来终觉浅”的东西。

下面按照目录记一下有用的东西。划掉的是垃圾,加粗或者颜色的是干货。

Chapter 1: Pythonic Thinking
Item 1: Know Which Version of Python You’re Using
Item 2: Follow the PEP 8 Style Guide
Item 3: Know the Differences Between bytes, str, and unicode
Item 4: Write Helper Functions Instead of Complex Expressions
Item 5: Know How to Slice Sequences
Item 6: Avoid Using start, end, and stride in a Single Slice
Item 7: Use List Comprehensions Instead of map and filter
Item 8: Avoid More Than Two Expressions in List Comprehensions
Item 9: Consider Generator Expressions for Large Comprehensions
Item 10: Prefer enumerate Over range
Item 11: Use zip to Process Iterators in Parallel
Item 12: Avoid else Blocks After for and while Loops
Item 13: Take Advantage of Each Block in try/except/else/finally

第一张虽然是Pythonic Thinking,却提出了很多不提倡使用的Python特性。但是我觉得书中这些条条框框没有必要必须遵守。比如6,为了可读性不要在一个切片操作上用3个数字,以及12,避免使用while-else,也是为了可读性。但是我认为,用了也不一定破坏了可读性。但是初衷是好的。我觉得只要不是写的太复杂,能保持一眼看明白或者多看一眼也能看明白,甚至看三眼也看不明白但是看看注释能看明白,也是可以的。本章中我觉得3和4非常有价值,4可以极大提高可读性。从这里我们知道不一定是非要为了重用才将代码封装成函数,为了可读性进行封装也可以。毕竟,函数的名字比注释要“优雅”的多。

Chapter 2: Functions
Item 14: Prefer Exceptions to Returning None
Item 15: Know How Closures Interact with Variable Scope
Item 16: Consider Generators Instead of Returning Lists
Item 17: Be Defensive When Iterating Over Arguments
Item 18: Reduce Visual Noise with Variable Positional Arguments
Item 19: Provide Optional Behavior with Keyword Arguments
Item 20: Use None and Docstrings to Specify Dynamic Default Arguments
Item 21: Enforce Clarity with Keyword-Only Arguments

14,15,16是干货,抛出一个有时候我们希望错误打断函数运行(如果出错之后后面的代码没有价值了的话),这样更好调试。对于调用出也可以使用try-except处理,而不是罗列一些if-else。有关闭包是一个重点知识。以及迭代器,不过这里感觉也并不是太深入。有关yield我还得多花点时间研究下。后面几条基本上是为了可维护性做的一些惯例。

Chapter 3: Classes and Inheritance
Item 22: Prefer Helper Classes Over Bookkeeping with Dictionaries and Tuples
Item 23: Accept Functions for Simple Interfaces Instead of Classes
Item 24: Use @classmethod Polymorphism to Construct Objects Generically
Item 25: Initialize Parent Classes with super
Item 26: Use Multiple Inheritance Only for Mix-in Utility Classes
Item 27: Prefer Public Attributes Over Private Ones
Item 28: Inherit from collections.abc for Custom Container Types

本章是面向对象的内容。Python很好入门,但是很多人将他当做一门面向过程的语言,项目都用函数组织。其实Python是纯正的面向对象的语言。就连函数也是一等的object。目前我们公司的项目都是以函数的形式组织的,我觉得很多地方都可以使用class,重用性会更好。

Chapter 4: Metaclasses and Attributes
Item 29: Use Plain Attributes Instead of Get and Set Methods
Item 30: Consider @property Instead of Refactoring Attributes Item 31: Use Descriptors for Reusable @property Methods
Item 32: Use __getattr__, __getattribute__, and __setattr__ for Lazy Attributes
Item 33: Validate Subclasses with Metaclasses
Item 34: Register Class Existence with Metaclasses
Item 35: Annotate Class Attributes with Metaclasses

这一章比较好,主要讲了Python获取属性的过程。这里的内容我觉得大多数和Python有关的书都讲不到。关于获取属性的部分我结合文档写了这篇博客。有关metaclass的部分我总结了这篇博客。都是干货。

Chapter 5: Concurrency and Parallelism
Item 36: Use subprocess to Manage Child Processes
Item 37: Use Threads for Blocking I/O, Avoid for Parallelism
Item 38: Use Lock to Prevent Data Races in Threads
Item 39: Use Queue to Coordinate Work Between Threads
Item 40: Consider Coroutines to Run Many Functions Concurrently
Item 41: Consider concurrent.futures for True Parallelism

这章涉及并行,进程、线程、协程等概念。目前用到的不太多,所以没有深入研究。这部分内容要结合操作系统的知识。以后再深入了解吧。

Chapter 6: Built-in Modules
Item 42: Define Function Decorators with functools.wraps
Item 43: Consider contextlib and with Statements for Reusable try/finally Behavior
Item 44: Make pickle Reliable with copyreg
Item 45: Use datetime Instead of time for Local Clocks
Item 46: Use Built-in Algorithms and Data Structures
Item 47: Use decimal When Precision Is Paramount
Item 48: Know Where to Find Community-Built Modules

Chapter 7: Collaboration
Item 49: Write Docstrings for Every Function, Class, and Module
Item 50: Use Packages to Organize Modules and Provide Stable APIs
Item 51: Define a Root Exception to Insulate Callers from APIs
Item 52: Know How to Break Circular Dependencies
Item 53: Use Virtual Environments for Isolated and Reproducible Dependencies

Chapter 8: Production
Item 54: Consider Module-Scoped Code to Configure Deployment Environments
Item 55: Use repr Strings for Debugging Output Item 56: Test Everything with unittest
Item 57: Consider Interactive Debugging with pdb Item 58: Profile Before Optimizing
Item 59: Use tracemalloc to Understand Memory Usage and Leaks

第六章将build-in函数,不如文档详细。甚至有点过时。

第七八章是开发经验,可以参考一下。不过没有应用的话都是纸上谈兵。

就写这些吧,下一步准备结合源代码和文档研究一下常用的函数、数据结构和库。

 

个人抗风险基金

人活着不容易,世间的任何东西都比不上健康、幸福和自由。本文基于这个观点。

最近由于快递造成了一笔小损失,很不爽。基本每过一段时间,就会遇到各种各样的烂事,比如丢了交通卡,丢了钥匙,错过了高铁等等。然后造成自己不开心。其实过了一段时间也就过去了,想想也没什么,但是就是得经历一段不开心的时间。

所以我想到一个办法,也许可以解决这个问题。每个月可以拿出一部分钱(暂定300),这笔钱作为“对抗各种意外事故”的基金。一切意外事故造成的金钱损失都从这部分钱里面出。如果这个钱用不完,就把他捐掉。这样感觉花的不是自己的钱,相对来说感情伤害会小得多。

这个办法如果起作用有以下几个要点。

  1. 这笔钱是必须支付的。如果这个月没有用到,就把它提现,可以说就没有任何用处了。因为“不发生意外”给自己带来了收益。我们的目的是“使意外没有造成损失”,所以如果损失是注定的,那么就无所谓“意外带来的损失”了。
  2. 这笔钱必须是不给自己带来收益的。很好理解,还是为了“发生意外”与“不发生意外”没有什么大区别。所以这笔钱不可以给自己带来收益,包括请朋友吃一顿大餐带来的人情、买自己不需要的东西等等。所以最合适的方式是“不记名的捐赠”。
  3. 可以1年来结算一次或者更长。因为主要目的是以减少自己的损失为主,所以可以这样,账户金额超过1000结算一次。或者半年结算一次。防止某个月出现比较大的损失。

我跟人感觉比较靠谱。刚毕业一个月我就把“报道证”给弄丢了,但是可以找淘宝代办,他们帮你跑腿,一个月搞出来。如果有这个基金的话我肯定心安理得找淘宝了。这个月开始打算试一下。

 

认识Python的MetaClass

本文主要介绍Python的MetaClass的用法。

MetaClass也是一个class,这个class继承自type,它用于生产新的class对象。定义class的时候,就会执行到MetaClass的__new__()代码。

从这里出发,我们需要一个新的class的时候,就可以使用MetaClass定义它的行为。本文介绍MetaClass的三种用法:

  1. 定义新的class的时候,执行一些检查操作
  2. 定义新的class的时候,将新class进行注册动作
  3. 定义新的class的时候,自动修改class的属性

MetaClass可以读取和修改class的属性。

Python2和Python3的写法有所不同,上面是Python3的写法,Python2的写法如下。

用MetaClass验证

定义一个新的class相当于调用type创建一个新的class对象,所以覆盖__new__()方法可以实现一些验证的动作。

下面的代码定义了一个多边形的MetaClass,在定义新的class的时候,验证边数是否小于3,如果小于3,这不科学。

使用MetaClass对class进行注册

下面的代码,在每次定义一个新的class的时候,就将class注册到一个字典,维护对应的行为。而不必每次都手动进行维护。

修改class的属性

假设我们想要实现一个Python对象到数据库字段的对应(也就是ORM),我们可以使用前面博客提到的Descriptor,如下。

但是这里存在的问题是,first_name已经写了一次了,还要在创建Field的时候再写一次,不是有些多余吗?Field应该自动根据引用它的变量名自动命名。这可以通过MetaClass来做到。

综上,MetaClass是非常实用的,可以帮我们减少很多需要重复复制粘贴的代码。

参考:Effective Python 59 SPECIFIC WAYS TO WRITE BETTER PYTHON by Brett Slatkin, Item 33,34,35

 

理解Python对象的属性

对于Python对象的属性是如何读取的,我一直存在一些疑问。对对象的属性赋值,什么时候直接指向一个新的对象,什么时候会直接抛出AttributeError错误,什么时候会通过Descriptor?Python的descriptor是怎么工作的?如果对a.x进行赋值,那么a.x不是应该直接指向一个新的对象吗?但是如果x是一个descriptor实例,为什么不会直接指向新对象而是执行__get__方法?经过一番研究和实验尝试,我大体明白了这个过程。

__getattr__ __getattribute__和__setattr__

对于对象的属性,默认的行为是对对象的字典(即__dict__)进行get set delete操作。比如说,对a.x查找x属性,默认的搜索顺序是a.__dict__[‘x’],然后是type(a).__dict__[‘x’],然后怼type(a)的父类(metaclass除外)继续查找。如果查找不到,就会执行特殊的方法。

__getattr__只有在当对象的属性找不到的时候被调用。

__getattribute__ 每次都会调用这个方法拿到对象的属性,即使对象存在。

__setattr__每次在对象设置属性的时候都会调用。

判断对象的属性是否存在用的是内置函数hasattrhasattr是C语言实现的,看了一下源代码,发现自己看不懂。不过搜索顺序和本节开头我说的一样。以后再去研究下源代码吧。

总结一下,取得一个对象的属性,默认的行为是:

  1. 查找对象的__dict__
  2. 如果没有,就查找对象的class的__dict__,即type(a).__dict__['x']
  3. 如果没有,就查找父类class的__dict__
  4. 如果没有,就执行__getattr__(如果定义了的话)
  5. 否则就抛出AttributeError

对一个对象赋值,默认的行为是:

  1. 如果定义了__set__方法,会通过__setattr__赋值
  2. 否则会更新对象的__dict__

但是,如果对象的属性是一个Descriptor的话,会改变这种默认行为。

Python的Descriptor

对象的属性可以通过方法来定义特殊的行为。下面的代码,Homework.grade可以像普通属性一样使用。

但是,如果有很多这样的属性,就要定义很多setter和getter方法。于是,就有了可以通用的Descriptor。

Descriptor是Python的内置实现,一旦对象的某个属性是一个Descriptor实例,那么这个对象的读取和赋值将会使用Descriptor定义的相关方法。如果对象的__dict__和Descriptor同时有相同名字的,那么Descriptor的行为会优先。

实现了__get__()__set__()方法的叫做data descriptor,只定义了__get__()的叫做non-data descriptor(通常用于method,本文后面有相应的解释)。上文提到,data descriptor优先级高于对象的__dict__但是non-data descriptor的优先级低于data descriptor。上面的代码删掉__set__()将会是另一番表现。

如果需要一个“只读”的属性,只需要将__set__()抛出一个AttributeError即可。只定义__set__()也可以称作一个data descriptor。

调用关系

对象和类的调用有所不同。

对象的调用在object.__getattribute__()b.x转换成type(b).__dict__['x'].__get__(b, type(b)),然后引用的顺序和上文提到的那样,首先是data descriptor,然后是对象的属性,然后是non-data descriptor。

对于类的调用,由type.__getattribute__()B.x转换成B.__dict__['x'].get(None, B)。Python实现如下:

需要注意的一点是,Descriptor默认是由__getattribute__()调用的,如果覆盖__getattribute__()将会使Descriptor失效。

Function,ClassMethod和StaticMethod

看起来这和本文内容没有什么关系,但其实Python中对象和函数的绑定,其原理就是Descriptor。

在Python中,方法(method)和函数(function)并没有实质的区别,只不过method的第一个参数是对象(或者类)。Class的__dict__中把method当做function一样存储,第一个参数预留出来作为self。为了支持方法调用,function默认有一个__get__()实现。也就是说,所有的function都是non-data descriptor,返回bound method(对象调用)或unbound method(类调用)。用纯Python实现,如下。

bound和unbound method虽然表现为两种不同的类型,但是在C源代码里,是同一种实现。如果第一个参数im_self是NULL,就是unbound method,如果im_self有值,那么就是bound method。

总结:Non-data descriptor提供了将函数绑定成方法的作用。Non-data descriptor将obj.f(*args)转化成f(obj, *args),klass.f(*args)转化成f(*args)。如下表。

Transformation Called from an Object Called from a Class
function f(obj, *args) f(*args)
staticmethod f(*args) f(*args)
classmethod f(type(obj), *args) f(klass, *args)

可以看到,staticmethod并没有什么转化,和function几乎没有什么差别。因为staticmethod的推荐用法就是将逻辑相关,但是数据不相关的functions打包组织起来。通过函数调用、对象调用、方法调用都没有什么区别。staticmethod的纯python实现如下。

classmethod用于那些适合通过类调用的函数,例如工厂函数等。与类自身的数据有关系,但是和实际的对象没有关系。例如,Dict类将可迭代的对象生成字典,默认值为None。

classmethod的纯Python实现如下。

最后的话

一开始只是对对象的属性有些疑问,查来查去发现还是官方文档最靠谱。然后认识了Descriptor,最后发现这并不是少见的trick,而是Python中的最常见对象——function时时刻刻都在用它。从官方文档中能学到不少东西呢。另外看似平常、普通的东西背后,可能蕴含了非常智慧和简洁的设计。

相关阅读

  1. hasattr的陷阱
  2. Effective Python: Item 31
  3. Descriptor HowTo Guide