Python编程无师自通 — 专业程序员的养成

源码组织非常乱,聊胜于无: https://www.epubit.com/bookDetails?id=N2005

这本书读起来非常轻松.

第一部分编程简介

第1 章 概述

乔什 (Josh Waitzkin ),在《学习的艺术》一书中回忆了他如何反向学习国际象棋。他没有和其他人一样研究开局,而是从学习象棋残局(棋盘上只剩下少数几个棋子〉开始。这样做让他对国际象棋有了更深的理解,并赢得了多次大赛冠军。

先学习如何编程再学习理论的方法更高效,因为你会拥有了解背后原理的强烈驱动。有实践经验, 才能对理论有更深刻的理解, 因为理论就是从实践中总结出来的.

不管你从事什么工作, 编程都有助于你的职业发展。学习编程将给你自己赋能. 学会编程后,你就可以坐下来自己实现,而不需要依赖他人。

学会编程,将把你从重复性工作中解放出来。

即使你的目标不是成为软件工程师,科学和金融等领域的岗位对那些拥有编程经验的人也更友好。

人们对程序员有一些常见的误解,比如程序员都得擅长数学。这是错误的印象,不过编程确实是一件困难的工作。 实际上,数学是比编程更牛逼的存在, 是真正实现抽象思考的工具, 编程只是工具罢了.

第2 章 起步

“ 一名优秀的程序员,在穿越羊行道时也会确认双向的来车情况。” ------ 道格拉斯·林德( Doug Linder )

第3章 编程概论

“这是我能想到的,唯一可以让我既当工程师又做艺术家的工作。它要求具备极其续密的技术思维,因为你必须要完成精确的思考,这点我很喜欢。另一方面,它又鼓励你肆意挥洒自己的创意,只有你想不到没有你做不到的。” ---- 安迪·赫兹菲尔德( Andy Hertzfeld )

只有在代码中执行特别操作,或者代码不清晰易懂的情况下,才需要写注释。尽量少写注释一一不要每行代码都写注释,有特殊情况才需要。

现在可以把对象看作拥有3 个属性的数据值:唯一标识( identity ), 数据类型和 值。

  • 对象的唯一标识,指的是其在计算机内存中的地址,该地址不会变化。
  • 对象的数据类型是对象所属的数据类别,这决定了对象的属性,也不会变化。
  • 对象的值是其表示的数据, 例如数字2 的值即为2 。

数据类型为NoneType 的对象,其值永远为None ,用来表示数据缺失。

从下面的例子可以看出, 变量名不变的情况下, 变量名可能会指向不同类型不同值的不同对象.

>>> a = 2
>>> id(a)
140722191402704
>>> a = 3
>>> id(a)
140722191402736
>>> a = 2.5
>>> id(a)
2130623232336
>>> a = None
>>> type(a)
<class 'NoneType'>
>>> id(a)
140722191173760

python3中默认的除法结果是小数, 地板除法使用 //:

>>> 4/2
2.0
>>> 5/2
2.5
>>> 5//2
2

运算顺序( order of operation ),指的是数学计算中对表达式求值的一套规则。可使 用PEMAS 方法,帮助记忆数学公式的运算顺序:

  • 括号( parentheses )
  • 指数( exponents )、
  • 乘法( multiplication )、除法( division )
  • 加法( addition )和减法( subtraction )。

第4 章 函数

可以在程序的任何地方对全局变量进行写操作,但是在局部作用域中需稍加注意:必须明确使用global 关键字,并在其后写上希望使用的变量。

x = 1
y = 2

def somewhateFunc():
    global x
    x = 3
    print("x in func:" ,x)
    y= 4
    print("y in func:" ,y)

somewhateFunc()
print("x outof func:" ,x)
print("y outof func:" ,y)
'''
x in func: 3
y in func: 4
x outof func: 3
y outof func: 2
'''

不要在except 语句中使用try 语句定义的变量,因为异常可能是在变量定义之前发生的

a = 1
b = 2
c = 0

try:
    r = a/c

    d = 0
    r = b/d
except ZeroDivisionError:
    print(c,d, 'can not be zero')
'''
Traceback (most recent call last):
  File "C:/Users/yanglei/Desktop/temp.py", line 6, in <module>
    r = a/c
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:/Users/yanglei/Desktop/temp.py", line 11, in <module>
    print(c,d, 'can not be zero')
NameError: name 'd' is not defined
'''

在编写函数时, 在函数顶部留下注释来解释每个参数应该为何种数据类型,是比较好的做法。这些注释被称为文档字符串( docstring )。文档字符串用于解释函数的功能,记录所需的参数类型。

def add(x , y) :
    """
    返回x + y 的值
    : param x : int .
    :param y : int .
    :return : int , x 与y 之和
    """
    return x + y

print(help(add))
'''
Help on function add in module __main__:

add(x, y)
    返回x + y 的值
    : param x : int .
    :param y : int .
    :return : int , x 与y 之和
'''

第5 章 容器

“愚者困惑,智者提问。” ---- 本杰明·迪斯雷利( Benjamin Disraeli)

方法与函数一样,可执行代码井返回结果。不同的是,只有在对象上才能调用方法。同样也可以传递参数给方法。

"Hello". replace("o",'@')

字符串、列表和元组都是可迭代的( iterable )。如果可以使用循环访问对象中的每一个元素,那么该对象是可迭代的,被称为可迭代对象。可迭代对象中的每一个元素都有一个索引( index ),即表示元素在可法代对象中位置的数字。

即使元组中只有一个元素,也需要在该元素的后面加上逗号。只有这样, Python 才能将其与其他为了表示运算顺序而放在圆括号中的数字标记进行区分。

>>> t1 = (1,)
>>> t1
(1,)
>>> i = (1)
>>> i
1
>>> t2 = ('hell0',)
>>> t2
('hell0',)
>>> s = ('hell0')
>>> s
'hell0'

第6 章 字符串操作

“理论上,理论和实践没有区别。但实践上,是有区别的。” ---- 简·范德斯奈普特( Jan L. A. van de Snepscheut )

  • 用字符串的capitalize 方法:首字母大写
  • 可使用format 方法创建新字符串,该方法会把字符串中的“{}”替换为传入的字符串或变量
  • 可使用index 方法,获得字符串中某个字符第一次出现的索引。如果index 方法没有找到匹配的结果,会报异常.如果不确定是否有匹配的结果,可使用异常处理的方法
>>> "four score and ...".capitalize()
'Four score and ...'
>>> name = "Jerry"
>>> s = "{} and {} is good friends".format("Tom",name)
>>> s
'Tom and Jerry is good friends'

第7 章 循环

"百分之八十的成功只是出席" ---- 伍迪· 艾伦( Woody Allen ) (这里强调坚持,不要停下来)

由于访问可迭代对象中索引和元素是很常见的操作, Python 提供了一个专门的enumerate 函数:

aString = "hello world!"
aList = ["hello","world"]

for i,v in enumerate(aString):
    print(i,v)
'''
0 h
1 e
2 l
3 l
4 o
5  
6 w
7 o
8 r
9 l
10 d
11 !
'''

for i,v in enumerate(aList):
    print(i,v)
'''
0 hello
1 world
'''

第8 章 模块

“坚韧与志气在任何时代都会带来奇迹。” ---- 乔治·华盛顿( George Washington )

程序员将大型程序分割成多个包含Python 代码的文件,也被称为模块( module )。可以将模块理解为python文件(不一定是源代码文件).

python3 的内置模块列表: https://docs.python.org/3/py-modindex.html

import statistics
import keyword 

nums = [1, 5 , 33 , 12 , 46, 33 , 2]

# 计算均值,中间数,众数
print(statistics.mean(nums))
print(statistics.median(nums))
print(statistics.mode(nums))
'''
18.857142857142858
12
33
'''

# 使用内置模块检查字符串是不是Python 关键字
print(keyword.iskeyword('for'))
print(keyword.iskeyword('For'))
'''
True
False
'''

第9 章 文件

“我坚信,自我教育是唯一的教育形式。” ---- 艾萨克·阿西莫夫( Isaac Asimov )

open 函数会返回一个叫文件对象( file object )的对象, 可用来读/写文件。

Python 有一个内置模块支持处理csv 文件:

  • 使用with 语句打开csv 文件,获取文件对象f
  • 调用csv 模块的 writer 的方法,将f 和分隔符作为参数, 返回一个csv对象 csv_f
  • csv_f 对象调用 writerow 方法, writerow 方法接受列表为参数向 f写入行
  • writerow 方法只创建一行数据,因此要创建两行数据必须调用该方法两次。
  • 读取方法类似
import csv

with open("temp.csv","w",newline='') as f:
    # newline='' 可以解决多行之间插入了空行的问题
    csv_f = csv.writer(f,delimiter=",")
    csv_f.writerow([1,2,3])
    csv_f.writerow([4,5,6])
    csv_f.writerow([7,8,9,10])

'''
temp.csv 文件内容
1,2,3
4,5,6
7,8,9,10
'''

# 读取文件
with open("temp.csv","r") as f:
    csv_f = csv.reader(f,delimiter=",")
    for row in csv_f:
        print(row)

'''
['1', '2', '3']
['4', '5', '6']
['7', '8', '9', '10']
'''

第 10 章 综合练习

“我所学到的一切,都是从书本上得来的。” ---- 亚伯拉罕·林肯( Abraham Lincoln )

第11 章 练习

“练习成就不了完美。多练习会产生髓磷脂,是髓磷脂让你做到完美。” ---- 丹尼尔·科伊尔( Daniel Coyle)

第二部分面对对象编程简介

第12 章 编程范式

“只有两种编程语言:大家抱怨的和没人用的。” ---- 本贾尼·斯特劳斯特鲁普(Bjarme Stroustrup )

所以使用人的多少可以作为评判一种编程语言是否成功的重要维度.

编程范式( programming paradigm ) ,即编程风格。当前有许多不同的编程范式。要达到专业程序员水平,则需要学习面向对象编程或函数式编程范式。

不同编程范式之间的根本区别之一,就是对状态( state )的处理。状态,是程序运行时其内部变量的值。全局状态( global state )是程序运行时其内部全局变量的值。

过程式编程

本书第一部分的程序,使用的是过程式编程( procedural programming ) : 这种编程风格要求你编写一系列步骤来解决问题,每步都会改变程序的状态。在过程式编程中,写的是“先做这个,再做那个”这样的代码。

在过程式编程时,我们将数据存储在全局变量中,并通过函数进行处理。

由于我们将程序的状态都保存在全局变量中,如果程序慢慢变大就会碰到问题。因为随着程序规模扩大,可能会在多个函数中使用全局变量,我们很难记录都有哪些地方对一个全局变量进行了修改。

这种编程方式也会有副作用( side effects ),其中之一就是会改变全局变量的状态。使用过程式编程时,经常会碰到意料之外的副作用,比如意外递增某个变量两次。这些问题促使了面向对象编程和函数式编程的出现.

函数式编程

函数式编程( functional programming )源自拉姆达运算( lambda calculus ):世界上最小的通用编程语言(由数学家阿隆佐·邱奇发明)。

函数式编程通过消除全局状态,解决了过程式编程中出现的问题。函数式程序员依靠的是不使用或不改变全局状态的函数,他们唯一使用的状态就是传给函数的参数。一个函数的结果通常被继续传给另一个函数。因此,这些程序员通过函数之间传递状态,避免了全局状态的问题,也因此消除了由此带来的副作用和其他问题。

函数式编程的专业术语很多,有人下过一个还算精简的定义:“函数式代码有一个特征:没有副作用。它不依赖当前函数之外的数据,也不改变当前函数之外的数据。”并给出了一个带副作用的函数。

函数式编程的一个优点,在于它消除了所有由全局状态引发的错误(函数式编程中不存在全局状态〉。但是也有缺点,即部分问题更容易通过状态进行概念化。例如,设计一个包含全局状态的人机界面,比设计没有全局状态的人机界面要更简单。如果你要写一个程序,通过按钮改变用户看到画面的可见状态,用全局状态来编写该按钮会更简单。你可以创建一个全局变量,值为True 时画面可见,值为Fa l se 时则不可见。如果不使用全局状态,设计起来就比较困难。

a = 0

def increment():
    # 该函数有副作用
    # 因为它依赖函数之外的数据,并改变了当前函数之外的数据: 递增了全局变量的值
    global a
    a += 1

def increment2(a):
    # 该函数没有副作用,因为它没有依赖或修改自身之外的数据。
    return a + 1

面向对象编程

面向对象( object-oriented )编程范式也是通过消除全局状态来解决过程式编程引发的问题,但并不是用函数,而是用对象来保存状态。在面向对象编程中,类( class )定义了一系列相互之间可进行交互的对象。类是程序员对类似对象进行分类分组的一种手段。

每个对象都是类的实例。如果定义了一个叫Orange 的类,然后创建两个Or ange 对象,那么每个对象都是Orange 类的实例: 它们的数据类型相同,都是Orange 。

对象和实例这两个术语可以替换使用。在定义类时, 该类的所有实例是类似的: 都拥有类中定义的属性,如颜色或种类, 但是每个实例的具体属性值是不一样的。

根据惯例, Python 中的类名都是以大写字母开头,且采用驼峰命名法, 即如果类名由多个单词组成,每个单词的第一个字母都应该大写,如LikeThis ,而不是用下划线分隔(函数的命令惯例)。

类中的代码主体可以是一个单一语句,或一个叫方法( method ) 的复合语句。

方法类似于函数,但因为是在类中定义的, 只能在类创建的对象上调用方法(如本书第一部分中在字符串上调用.upper())

方法的命名则遵循函数命名规则,都是小写,并用下划线分隔。

方法的定义方式与函数定义方式相同,但有两处区别: 一是必须在类的内部定义方法, 二是必须接受至少一个参数(特殊情况除外)。按照惯例, 方法的第一个参数总是被命名为self 。创建方法时,至少要定义一个参数,因为在对象上调用方法时, Python会自动将调用方法的对象作为参数传入。

可使用self 定义实例变量( instance variable ) : 属于对象的变量。如果创建多个对象,各自都有不同的实例变量值。通过语法self.[变量名] = [变量值] 定义实例变量。通常是在特殊方法init (代表初始化)中定义实例变量,创建对象时Python 会调用该方法。

class Orange:
    def __init__ (self, w, c):
        self. weight = w
        self. color = c
        print ( "Created ! ")

o = Orange(3,'red')
print(o.weight,o.color)
'''
Created ! 
3 red
'''

双下划线包围的方法(如 init ),被称为魔法方法( magic method ),即Python 用于创建对象等特殊用途的方法。

创建新Orange 对象的语法,与调用函数的语法类似:[ 类名]([ 参数] ), 将[类名]替换为想要用来创建对象的类的名称,[ 参数]替换为川江接受的参数即可。这里不用传入self 参数, Python 会自动传入。创建新对象的过程, 也被称为创建类的实例。

面向对象编程有多个优点:

  • 鼓励代码复用,从而减少了开发和维护的时间;
  • 还鼓励拆解问题,使代码更容易维护。

但有一个缺点便是编写程序时要多下些功夫,因为要做很多的事前规划和设计。这实际上会促使编程者对要解决的问题本身做更深入抽象的思考,并不能算缺点.

练习

定义类的时候, 容易忘掉 self.

import math

# 1.定义一个 Apple 的类,创建4 个实例变量, 表示苹果的4 种属性。
class Apple:
    def __init__(self,size,color,weight,place_of_production):
        self.size= size
        self.color=color
        self.weight=weight
        self.place_of_production=place_of_production
apple = Apple(10,'red','0.3kg','Yantai')
print(apple.size,apple.color,apple.weight,apple.place_of_production)  # 10 red 0.3kg Yantai

# 2. 定义一个叫Circle 的类, 创建area 方法计算其面积。然后创建一个Circle
# 对象,调用其area 方法,打印结果。可使用Python 内置的math 模块中的pi 函数。

class Circle:
    def __init__(self,r):
        self.r = r

    def area(self):
        print('The radius of the circle is {}, the area of the circle is {}'.format(self.r,math.pi*self.r*self.r))

c = Circle(1)
c.area()
c2 = Circle(12)
c2.area()
'''
The radius of the circle is 1, the area of the circle is 3.141592653589793
The radius of the circle is 12, the area of the circle is 452.3893421169302
'''

'''
3. 定义一个叫Triangle 的类, 创建area 方法计算并返回面积。然后创建一个
Triangle 对象,调用其area 方法, 打印结果。
已知三角形三边a,b,c,则
(海伦公式)(p = (a + b + c)/2)
S=sqrt[p(p-a)(p-b)(p-c)]
'''
class Triangle:
    def __init__(self,a,b,c):
        self.a, self.b,self.c = a,b,c

    def area(self):
        p = (self.a + self.b + self.c)/2
        print("The length of the three sides of the triangle is {} {} {}, the area of the triangle is {}".format(self.a, self.b,self.c,math.sqrt(p*(p-self.a)*(p-self.b)*(p-self.c))))

t = Triangle(3,4,5)
t.area()
# The length of the three sides of the triangle is 3 4 5, the area of the triangle is 6.0

第13 章 面向对象编程的四大支柱

“优良设计创造价值的速度,快于其增加成本的速度。” ---- 托马斯. c. 盖勒( Thomas C.Gale )

面向对象编程有四大概念: 封装、抽象、多态和继承。它们共同构成了面向对象编程的四大支柱。编程语言必须同时支持这4 个概念,才能被认为是一门面向对象编程的语言,如Python 、Java 和Ruby 。

封装

封装( encapsulation )包含两个概念。

  • 第一个概念是在面向对象编程中,对象将变量(状态)和方法(用来改变状态或执行涉及状态的计算)集中在一个地方即对象本身。
  • 封装包含的第二个概念,指的是隐藏类的内部数据,以避免客户端( client ) 代码(即类外部的代码〉直接进行访问。

许多编程语言允许程序员定义私有变量( private variable )和私有方法( private method )来解决这个问题: 对象可以访问这些变量和方法,但是客户端代码不行。私有变量和方法适用于如下场景:

  • 有一个类内部使用的方法或变量, 并且希望后续调整代码实现(或保留选项的灵活),但不想让任何使用该类的人依赖这些方法或变量,因为后续代码可能会调整(到时会导致客户端代码无法执行) 。
  • 私有变量是封装包含的第二个概念的一种范例; 私有变量隐藏了类的内部数据,避免客户端代码直接访问。公有变量( public variabl e ) 则相反,它是客户端代码可以直接访问的变量。

Python 中没有私有变量,所有的变量都是可以公开访问的。Python 通过另一种方法解决了私有变量应对的问题:使用命名约定。在Python 中,如果有调用者不应该访问的变量或方法,则应在名称前加下划线。Python 程序员看见某个方法或变量以下划线开头时,就会知道它们不应该被使用(不过实际仍然是可以使用的) 。

class PublicPrivateExample:
    def __init__(self):
        self.public = "safe"
        self._unsafe = "unsafe"
        '''编写客户端代码的程序员看到上述代码后,会知道变量self.public 是可以安全
        使用的, 但是不应该使用变量self._unsafe ,因为其以下划线开头。如果非要使用,
        后续可能会有风险。维护上述代码的程序员,没有义务一直保留self . unsafe ,因为
        调用者本不应该访问该变量。'''

    def public_method(self):
        # clients can use this
        pass

    def _unsafe_method(self):
        # clients shouldn't use this
        pass

抽象

抽象( abstraction )指的是 “剥离事物的诸多特征,使其只保留最基本的特质”的过程。 在面向对象编程中,使用类进行对象建模时就会用到抽象的技巧。

假设要对人进行建模。人的特征很复杂:头发和眼睛颜色不同,还有身高、体重、种族、性别等诸多特征。如要创建一个类代表人,有一些细节可能与要解决的问题并不相关。举个例子,我们创建一个Person类,但是忽略其眼睛颜色和身高等特征,这就是在进行抽象。 Person 对象是对人的抽象,代表的是只具备解决当前问题所需的基本特征的人。

多态

多态( polymorphism )指的是“为不同的基础形态(数据类型)提供相关接口的能力”。接口,指的是函数或方法。

'''
print 函数为字符串、整数和浮点数这3 种不同的数据类型提供了相同的接口。
我们不必定义并调用3 个不同的函数,只需要调用print 函数即可支
持所有数据类型。
'''
print("Hello, World!")
print(200)
print(200.1)

继承

编程语境中的继承( inheritance ),与基因继承类似。在基因继承中, 子女会从父母那继承眼睛颜色等特征。类似地,在创建类时,该类也可以从另一个类那里继承方法和 变量。被继承的类,称为父类( parent class );继承的类则被称为子类( child class )

子类与其他类没有区别,它可以定义新的方法和变量,不会影响父类。

当子类继承父类的方法时,我们可以定义一个与继承的方法名称相同的新方法,从 而覆盖父类中的方法。子类改变从父类中继承方法的实现能力, 被称为方法覆盖( method overriding )

组合

介绍完面向对象编程的4 个支柱之后,这里再介绍一个更重要的概念:组合 (composition ) 。通过组合技巧,将一个对象作为变量保存在另一个对象中,可以模拟“拥有”关系。

class Dog():
    def __init__(self,
                 name,
                 breed,
                 owner):
        self.name = name
        self.breed = breed
        self.owner = owner


class Person():
    def __init__(self, name):
        self.name = name

aPerson = Person("Hilton Wang")

# 在创建Dog 对象时将Person 对象作为owner 参数传入
# 这样, aDog 对象 "Stanley"就有了一位主人,即名叫 "Hilton Wang" 的 Person对象,保存在其实例变量owner 中。
aDog = Dog("Stanley","Bulldog",aPerson)

print("{}'s breed is {}, and it's owner's name is {}".format(aDog.name,aDog.breed,aDog.owner.name)) 
# Stanley's breed is Bulldog, and it's owner's name is Hilton Wang

组合关系表示: 定义对象的属性时, 属性可以为其他对象类型,并在实例化时传入该数据类型对应的实例即可.

第14 深入面向对象编程

“视代码如诗词,勿要做无所谓的堆砌。” ---- 伊利亚·多尔曼( Ilya Dorman )

本章将学习与面向对象编程相关的其他概念。

类变量与实例变量

在Python 中,类即对象。这个理念源自引领了面向对象编程风潮的S malltalk 语言。

Python 中的每个类,都是type 类的一个实例对象

class Person:
    pass

print(type(Person))
print(Person)
'''
<class 'type'>
<class '__main__.Person'>
'''

类中有两种类型的变量:

  • 类变量( class variable )
    • 类变量属于Python 为每个类定义创建的对象,以及类本身创建的对象。
    • 类变量的定义方式与普通变量相同(但是必须在类内部定义〉,可以通过类对象访问,也可以通过使用类创建的对象访问。访问方式与实例变量(变量名前面加self.) 的访问方式相同。
    • 类变量可以在不使用全局变量的情况下,在类的所有实例之间共享数据。
  • 实例变量( instance variable ) :,通过语法self . [变量名] =[ 变量值]定义。实例变量属于对象
# Thank you so much for purchasing my book! Feel free to contact me at cory[at]theselftaughtprogrammer.io.
# If you are enjoying it, please consider leaving a review on Amazon :). Keep up the hard work!


class Rectangle():
    recs = [] # 在 __init__ 方法之外定义的类变量

    def __init__(self, w, l):
        self.width = w
        self.len = l
        '''每创建一个Rectangle 对象,
        init 方法中的代码就会向recs 列表中添加一个
        由新对象宽度和长度组成的元组。
        这样,每当新创建一个Rectangle 对象时,就会被自动添加到recs 列表。
        通过使用类变量,即可在不使用全局变量的情况下,
        做到了在类创建的不同实例之间共享数据。
        '''
        self.recs.append((self.width,
                          self.len))

    def print_size(self):
        print("""{} by {}
              """.format(self.width,
                         self.len))

r1 = Rectangle(10, 24)
print(Rectangle.recs)

r2 = Rectangle(20, 40)
print(Rectangle.recs)

r3 = Rectangle(100, 200)
print(Rectangle.recs)
'''
[(10, 24)]
[(10, 24), (20, 40)]
[(10, 24), (20, 40), (100, 200)]
'''

魔法方法

Python 中所有的类,均继承自一个叫object 的父类, 也继承了父类object中定义的所谓 魔法方法

如何理解 Python 中所有的类,均继承自一个叫object 的父类。: object没有父类

class A:
    pass

class B(A):
    pass


print(type(B))      # <class 'type'>
print(type(object)) # <class 'type'>
print(type(type))   # <class 'type'>

# 函数issubclass(A,B),该作用是判断继承关系,判断A是否继承B
# 函数issubclass(A,B)  并不能标明 A,B 是否是父子关系

print(issubclass(B,A))           # True
print(issubclass(B,object))      # True
print(issubclass(A,object))      # True
print(issubclass(type,object))   # True
print(issubclass(type,type))     # True
print(issubclass(object,object)) # True

print(issubclass(object,type))   # False

# 使用 __bases__ 方法才能找到自己的父类

print(A.__bases__)
print(B.__bases__)
print(type.__bases__)
print(object.__bases__)
'''
(<class 'object'>,)
(<class '__main__.A'>,)
(<class 'object'>,)
()
'''
  • 打印一个对象时, Python 调用了其从Object 继承的魔法方法 __repr__, 并打印 __repr__方法返回的结果。我们可以覆盖继承来 __repr__方法,以改变打印结果。
  • 如果两个对象是相同的对象,关键宇is 返回True ,反之则返回False 。
class Dog:
    def __init__(self,name):
        self.name=name

class Dog2:
    def __init__(self,name):
        self.name=name

    def __repr__(self):
        return self.name

d1 = Dog('Doggie')
print(d1)

d2 = Dog2('Doggie')   # <__main__.Dog object at 0x0000027AAE567880>
print(d2)             # Doggie

d3 = d2

print(d3 is d2) # True
print(d3 is d1) # False

第15 章 综合练习

“代码跑起来我们再聊。” ---- 沃德· 坎宁汉( 明Tard Cunningham )

第三部分 编程工具简介

第16 章 Bash

“除了计算机编程外,我想不出还有其他让我感兴趣的工作。我可以无中生有地创造出精美的范式和结构,在此过程中也解决了无数的小谜团。” ---- 皮特·范德林登( Peter Van Der Linden )

命令行接口是一种可以支持用户输入指令,并由操作系统执行的程序。Bash 则是大部分类UNIX操作系统都具备的命令行接口实现。

命令行是除了编码之外, 其他工作的“控制中心”。

命令行、包管理器、正则表达式和版本控制,这些都是程序员工具库中的核心成员。

笔者所共事过的团队中, 没有成员不擅长使用这些工具。当你以编程为职业时-,也需要做到可以熟练使用上述工具。笔者花了很长时间才做到这点,也很后悔当初没有更早地学习如何使用这些工具。

Bash 与Python 的shell 类似, 可以向其中输入命令(类似Python 中的函数〉。然后输入空格,以及传入命令的参数,按下回车键后, Bash 就会返回结果。windows中可以使用 PowerShell.

  • echo 命令类似Python 中的print 函数。
  • 可通过上下箭头按键, 查看在Bash 中近期执行的命令。如需查看所有最近命令的列表, 可使用命令history 查看
  • 在使用Bash 时,其必然是会位于某个目录中。可使用命令pwd (print working directory) 来打印当前所在的目录的名称
  • 命令支持一个叫旗标( flag )的概念,可以改变命令的执行方式。旗标对于命令来说,是一些值为True 或False 的执行选项。一个命令的所有旗标默认置为False 。如果向命令中添加一个旗标, Bash 将把该旗标的值设为True ,命令的执行方式也会随之改变。在旗标的名称前面加一个( - )或两个连字符(--),即可将旗标置为True 。旗标就是命令行选项呗.
  • 操作系统和很多程序都会将数据保存在隐藏文件中。隐藏文件指的是默认不会展示给用户的文件,因为修改隐藏文件会影响依赖这些文件的程序。隐藏文件的名称以英文句点开头, 如.hidden 。在ls 命令后加上旗标 -a (表示所有文件),即可查看隐藏文件。
  • 在类UNIX 操作性系统中,竖直线“|” 被称为管道C pipe )。可使用管道将一个命令的输出,传入另一个命令作为输入。例如,可使用ls 命令的输出, 作为less 命令的输入(需确保当前目录不是空目录) :执行结果是一个用less 程序打开的文本文件,内容为l s 命令的输出. windows 下可以试试 ls | echo
  • 环境变量( environment variable )是保存在操作系统中的变量, 程序可通过这些变量获取当前运行环境的相关数据,如运行程序的计算机的名称, 或运行程序的用户的名称。使用语法export [变量名]= [变量值] ,即可在Bash 中新建一个环境变量。如需在Bash中引用环境变量, 必须在其名称前加一个美元符号。这样创建的环境变量只能存在于当前的Bash 窗口。将环境变量添加到类UNIX 操作系统使用的一个隐藏文件中,可使得环境变量持久存在。该隐藏文件位于home 目录下,名为.profile. 只要.profile 文件中包含该变量,即可持久性地使用。从.profile 中移除相关内容,即可删除变量。
  • 操作系统支持多用户使用。用户指的是使用操作系统的人。每个用户都分配了用户名和密码,可用来登录和使用操作系统。每个用户还有对应的权限: 能够执行的操作范 围。使用命令whoami 可打印操作系统用户的名称
  • 由于安全原因,我们通常不会以根用户身份登录系统。在需要以根用户权限执行命令时,可在命令前加上sudo (superuser do 的简称) 。sudo 可在不影响操作系统安全性的前提下,让我们以根用户的身份执行命令。

可以在window上安装 WSL 2, 参考 https://docs.microsoft.com/zh-cn/windows/wsl/about :

WSL 2 是适用于 Linux 的 Windows 子系统体系结构的一个新版本,它支持适用于 Linux 的 Windows 子系统在 Windows 上运行 ELF64 Linux 二进制文件。 它的主要目标是 提高文件系统性能,以及添加 完全的系统调用兼容性。

安装 WSL2:

1.以管理员身份打开 PowerShell 并运行:

dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart

dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

重新启动 计算机,以完成 WSL 安装并更新到 WSL 2

  1. 下载 Linux 内核更新包并安装

  2. 将 WSL 2 设置为默认版本

以管理员身份打开 PowerShell 并运行:

wsl --set-default-version 2

4.安装所选的 Linux 分发:打开 Microsoft Store,并选择你偏好的 Linux 分发版。这里选择Ubuntu

5.使 WSL 2 成为你的默认体系结构,以管理员身份打开 PowerShell 并运行:

wsl --set-default-version 2

使用windows下的 unbutu:

cd /mnt/c/Users/username/Desktop/

第17 章正则表达式

“代码胜于雄辩。” ---- 林纳斯·托瓦兹( Linus Torvalds )

许多编程语言和操作系统都支持正则表达式( regular expression ):定义搜索模式的一组字符串。正则表达式可用于检索文件或其他数据中是否存在指定的复杂模式。例如,可使用正则表达式匹配文件中所有的数字。本章将学习如何定义正则表达式,将其传入类UNlX 操作系统以用来检索文件的grep 命令。该命令会返回文件中与指定模式匹配的文本。我们还将学习在Python 中使用正则表达式检索字符串。

windows命令行:

python -c "import this" >> TheZenOfPython.txt

参数 c 告诉Python 传入的字符串中包含有Python 代码。然后Python 会执行传入的代码。Python 执行import this 之后,将打印The Zen of Python, 并写入 TheZenOfPython.txt

TheZenOfPython.txt文本:

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

中文译文:

Python之禅 by Tim Peters

优美胜于丑陋
明了胜于晦涩
简洁胜于复杂
复杂胜于凌乱
扁平胜于嵌套
间隔胜于紧凑
可读性很重要

即使假借特例的实用性之名,也不可违背这些规则
不要包容所有错误,除非你确定需要这样做
当存在多种可能,不要尝试去猜测
而是尽量找一种,最好是唯一一种明显的解决方案
民然这并不容易,因为你不是Python之父
做也许好过不做,但不假思索就动手还不如不做
如果你无法向人描述你的方案,那肯定不是一个好方案
命名空间是一种绝妙的理念---我们应当多加利用

如果是在 WLS2 下使用 grep 处理windows下的文件, 需要转文件编码为 utf-8:

  • 使用file命令查看文件编码
  • 使用iconv 进行编码转换, 作为 grep的输入

grep 命令接受两个参数: 一个正则表达式和检索正则表达式中定义模式的文件路径。使用正则表达式进行最简单的模式匹配,就是简单匹配,即一个字符串匹配单词中相同的字符串, 可以加上参数 -i 忽略大小写

$ file TheZenOfPython.txt
TheZenOfPython.txt: Little-endian UTF-16 Unicode text, with CRLF line terminators
$ iconv -f UTF-16  TheZenOfPython.txt -t utf-8 | grep Beautiful
Beautiful is better than ugly.
$ iconv -f UTF-16  TheZenOfPython.txt -t utf-8 | grep beautiful
$ iconv -f UTF-16  TheZenOfPython.txt -t utf-8 | grep -i  beautiful
Beautiful is better than ugly.

可通过内置模块re 在Python 中使用正则表达式。re 模块提供了一个叫findall 的方法,将正则表达式和目标文本作为参数传入,该方法将以列表形式返回文本中与正 则表达式匹配的所有元素:

shell 中可以用 [[:digit:]] 匹配数字:

$ iconv -f UTF-16  TheZenOfPython.txt -t utf-8 | grep [[:digit:]]
digit test: 5.2

python示例:

import re

content = '''The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
digit test: 5.2'''

matches = re.findall('than',content)

print(matches) 
# 只会找出匹配的关键字
# ['than', 'than', 'than', 'than', 'than', 'than', 'than', 'than']

for line in content.split('\n'):
    if len(re.findall('than',line))>0:
        print(line)

'''
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Now is better than never.
Although never is often better than *right* now.
'''


# 将 re.IGNORECASE 作为第3 个参数传入findall ,可以让其忽略大小写
print(re.findall('beautiful',content)) # []
print(re.findall('beautiful',content,re.IGNORECASE)) # ['Beautiful']

# 可使用补字符号〈创建一个正则表达式, 表示只有模式位于行的起始位置时才匹配成功
for line in content.split('\n'):
    if len(re.findall('in',line,re.IGNORECASE))>0:
        print(line)

'''
In the face of ambiguity, refuse the temptation to guess.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
'''

for line in content.split('\n'):
    if len(re.findall('^in',line,re.IGNORECASE))>0:
        print(line)
'''
In the face of ambiguity, refuse the temptation to guess.
'''

print('\n')

# 匹配多个字符a,b,c [abc]
for line in content.split('\n'):
    if len(re.findall('[abcde]r',line,re.IGNORECASE))>0:
        print(line)

'''
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Special cases aren't special enough to break the rules.
Errors should never pass silently.
There should be one-- and preferably only one --obvious way to do it.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
Namespaces are one honking great idea -- let's do more of those!
'''


# 在Python 中使用 `\d` 匹配数字
for line in content.split('\n'):
    if len(re.findall('\d',line,re.IGNORECASE))>0:
        print(line)

'''
digit test: 5.2
'''

第18 章 包管理器

包( package ) 是“ 打包”好用来发布的软件,它包括组成实际程序的所有文件,以及相关的元数据 metadata : 有关软件名称、版本号和依赖( dependencies )等数据。依赖指的是程序正常运行时所需要依赖的程序。我们可使用包管理器下载并安装程序。包管理器会下载包相关的所有依赖程序。

  • 可使用命令pip freeze 查看己经安装了哪些包
  • 可以使用pip uninstall 包名称 卸载程序

我们最好将Python 包安装在虚拟环境( virutal environment )中,而不是直接都安装在site-packages 目录下。虚拟环境可以将不同编程项目所需的包分隔开来。

第19 章 版本控制

我拒绝做计算机能够胜任的事情。---- 奥林· 施福尔( Olin Shivers )

与他人(或整个团队) 一起进行某个项目的开发时,项目成员都需要对代码库 codebase 进行修改并保持同步。版本控制系统的设计初衷,就是帮助项目成员之间更好地进行协作。Git 和SVN 是两个流行的版本控制系统。

代码仓库( repository )是Git 等版本控制系统创建的一种数据结构,用来记录编程项目中所有的变动。

  • 命令 git remote - v可打印本地代码仓库推送和拉取代码的目标URL 链接。

第20 章 融会贯通

“神话和传说的魔力在我们这一代成真。只要在键盘上敲下正确的咒语,显示屏就像是活了过来,里面都是以前不可能存在或发生的事情。” ---- 费德里克·布鲁克斯( Frederick Brooks )

第四部分 计算机科学简介

第21 章数据结构

“我从心底认为,优秀的程序员与平庸的程序员之间的区别,是在于认为自己的代码重要还是数据结构更加重要。平庸的程序员眼里只有代码,优秀的程序员则关注数据结 构及之前的关系。” ---- 林纳斯· 托瓦兹( Linus Torvalds )

数据结构 data structure 是用来存储和组织信息的一种形式,对于编程来说是至关重要的,大多数编程语言也都自带了数据结构。本书前面的章节已经介绍了如何使用 Python 中自带的列表、元组和字典等多个数据结构。

描述问题是解决问题的第一步, 数据结构是描述问题的一种方式, 这是数据结构如此重要的根本原因.

栈( stack )是一种数据结构。与列表类似,我们可以向战中添加或移除元素,但是不同的地方是,只能添加或移除最后一个元素。

  • 将元素从棋中移除,被称为出枝( popping )
  • 将元素放回战中,被称为入枝( pushing )
  • 这种最后一个放入的元素被第一个取出的数据结构,也被称为先进后出 CIFO型数据结构: Last in, first out。
  • 栈可用来逆转可迭代对象,因为所有放入栈中的元素都会逆序取出。所以可以用pop方法逆转python中的序列对象.

队列( queue )也是一种数据结构。与列表也很相像,可以从中添加和移除元素。队列与校也有类似的地方,因为只能按照特定的顺序添加和移除元素。与找不同的是,队 列是一个先进先出( FIFO )的数据结构: 第一个添加的元素也是第一个移除的元素。

可以将FIFO 数据结构想象成一队等待购买电影票的人。队伍中的第一个人是第一个买到票的,第二个人是第二个买到票的,以此类推。

第22 章算法

“算法,就像一张菜谱。” ---- 瓦辛· 拉提夫( Waseem Latif)

算法( algorithm )是解决问题的一系列步骤。

经典算法是对经典问题的抽象.

面试中经常会问到的问题一-FizzBuzz:编写一个程序,打印从1 到100 之间的数字。碰到3 的倍数时,不打印数字,而是打印" Fizz ”; 碰到5 的倍数时,则打印" Buzz ”; 如果是3 和5 共同的倍数,则打印” FizzBuzz ” 。

  • 检查 15
  • 检查 3
  • 检查 5
for i in range(1,101):
    if i%15==0:
        print(i,'FizzBuzz')
    elif i%3==0:
        print(i,'Fizz')
    elif i%5==0:
        print(i,'Buzz')
'''
3 Fizz
5 Buzz
6 Fizz
9 Fizz
10 Buzz
12 Fizz
15 FizzBuzz
18 Fizz
20 Buzz
21 Fizz
24 Fizz
25 Buzz
27 Fizz
30 FizzBuzz
33 Fizz
35 Buzz
36 Fizz
39 Fizz
40 Buzz
42 Fizz
45 FizzBuzz
48 Fizz
50 Buzz
51 Fizz
54 Fizz
55 Buzz
57 Fizz
60 FizzBuzz
63 Fizz
65 Buzz
66 Fizz
69 Fizz
70 Buzz
72 Fizz
75 FizzBuzz
78 Fizz
80 Buzz
81 Fizz
84 Fizz
85 Buzz
87 Fizz
90 FizzBuzz
93 Fizz
95 Buzz
96 Fizz
99 Fizz
100 Buzz
'''

递归式算法( recursive algorithm)则是通过调用自身的函数来实现。任何可以迭代式解决的问题,都可以递归式地解决;但是,有时候递归算法是更加优雅的解决方案。

递归的3 个条件:

  • l. 递归算法必须有终止条件。
    1. 递归算法必须改变自己的状态,不断靠近终止条件。
    1. 递归算法必须递归地不断调用自己。
def factorial(n):
    """计算整数n的阶乘, 假设n不等于0"""
    result = 1
    for i in range(1,n+1):
        result *= i

    return result

print(factorial(10)) # 3628800

def factorial2(n):
    # 递归方式实现

    # 终止条件
    if n==1:
        return n
    else:
        # 调用自己
        return n*factorial2(n-1)

print(factorial2(10)) # 3628800

递归需要对问题的循环遍历有相对高层次的抽象理解.

题库挑战: https://leetcode-cn.com/

第五部分找到工作

第23 章 最佳编程实践

写代码时,每次都要告诉自己:最后负责维护代码的,会是一个知道你住在哪的变态暴力狂。---- 约翰·伍德( John Woods )

生产代码( production code )是用户使用的产品中的代码。将软件部署到生产环境( production )后,就意味着用户可以公开访问了。本章将介绍几个普遍的编程原则,有助于大家编写可部署于生产环境的代码。这些原则大多源自 The Pragmatic Programmer(程序员修炼之道从小工到专家) 这本书,读完这本书后我的代码质量大幅提升。

1.写代码是最后的手段:不要重复别人 DROC (Dont’t Repeat Other's code)

作为一名软件工程师,你在工作时应尽量少写代码。碰到问题时,你首先想到的不应该是“我怎么解决这个问题”,而是“其他人是不是已经解决了这个问题,我能使用他 们的方案吗?”如果你自己去解决一个常见的问题,很有可能别人已经有了解决方案。先在网上检索解决办法,只有在确定没人解决过该问题之后,才开始自己动手解决。

2.DRY

DRY 是不要重复自己( Dont’t Repeat Yourself) 的简称,指的是不要在程序中编写重复的或是基本相同的代码。正确的做法是将代码封装至函数中,后续可重复使用。

3.正交性

正交性( Orthogonality )是《The Pragmatic Programmer》中提倡并普及的另一个重要编程原则。

亨特和托马斯认为, “该术语已经被用来表示某种独立性或解耦化。如果两个或多个事物之间的变化不会相互影响, 那么它们之间就存在正交性。在设计优良的系统中, 数据库代码与用户界面之间是正交的; 调整用户界面不会影响数据库, 替换数据库也不会改变用户界面。”

实践中请牢记,“ A 不应该影响B ” 。假设我们有两个模块module_a 和module_b , module_a 不应对module_b 中的内容进行修改,反之亦然。

如果设计的系统中A 会影响到B , 而B 又影响C , 很快就会失去控制, 系统将变得无法管理。

4.每个数据都只应保存在一处

假设手上有一个数据,我们只需要将其存储在一个地方。例如,我们正在开发用 来处理手机号码的软件, 其中有两个函数要使用地区编号的列表, 这里要确保程序中 只有一个地区编号列表,而不是为每个函数重复创建。正确的做法是创建一个保存地 区编号的全局变量。更好的解决方案则是将信息保存在文件或数据库中。

5.函数只做一件事

我们写的每个函数应该只做一件事。如果发现函数太长, 请检查其是否在完成多个 任务。将函数限制为只完成一个任务有很多好处。首先,代码可读性增强,因为函数名 称可以直接说明其功能。如果代码出错, 调试也将更加方便,因为每个函数只负责一个 特定的任务,我们可以快速隔离并调试问题函数。

用许多知名程序员的话来说: “软件的复杂性大多源自试图两件事当一件事做。”

6.若起费时间过长,你的做法很可能就是错的

如果你不是在处理非常复杂的问题, 比如处理大数据,但是程序却要花很长时间才 能加载,这时可以认为你的做法很有可能错了。

7.第一次就要用最佳的方法完成

在编程时你可能会这样想: “我知道有一个更好的做法, 但是我己经开始编码了, 不 想回头重写。” 那我建议你停止编码,改用更好的方法来完成。

多学习, 使用业界的最佳实践省心省力.

8.遵循惯例

学习新编程语言的惯例,能够提升阅读用该语言编写的代码的速度。PEP8 是一系 列编写Python 代码的指南,强烈建议阅读,可前往查看:

https://www.python.org/dev/peps/pep-0008/

9.使用强大的IDE

spyder 不错, 本书作者推荐的是 PyCharm.

10.记录日志

记录日志 logging 指的是在软件运行时记录数据的做法。我们可通过日志来协助 程序调试, 更好地了解程序运行时的状态。

程序出错时,我们不希望没有感知----我们应该记录下相关信息, 方便以后核查。

记录日志也有助于收集和分析信息。例如, 可以搭建一个Web 服务器来记录数据,包括每次收到请求的日期和时间。我们可以将所有的日志记录在数据库中, 编写程序分析其中的数据, 并生成图表展示访问网站的人次。

博客作者亨瑞克· 沃纳( Henrik Warne )在博客中写过这样一段话: “伟大程序员与平庸程序员的区别之一, 就是伟大的程序员会做日志记录, 使得出错时的调试变得更简单。”

Python 自带了一个logging 日志模块, 支持在控制台或文件中记录日志。 可查看教程, 学习如何使用自带的logging 模块。

https://docs.python.org/3/library/logging.html

11.测试

程序测试指的是检查程序是否“达到了设计和开发要求,对各类输入返回正确的结 果,功能执行耗时在可接受范围,可用性足够高,可在目标环境下安装和运行, 并且实 现了相关利益方所期待的效果。”为了进行程序测试,程序员要额外编写程序。 在生产环境中, 测试是必须完成的。对于计划部署在生产环境的程序,我们应当认 为在没有编写测试之前都是不完整的。但是,如果是一个不会再使用的临时程序,测试 可能有些浪费时间。如果编写的是其他人也将使用的程序,则应该编写测试。很多知名 程序员都曾说过:“ 未经测试的代码就是漏洞百出的代码。”

使用Python 自带的unittest 模块的教程:

https://docs.python.org/3/library/unittest.html

12.代码审查

在代码审查 code review 时,同事会阅读你的代码井提供反馈。建议尽可能多地进行代码审查,尤其对于自学成才的程序员来说。即使你遵守了本章中所列的所有最佳 实践, 也有可能存在错误的做法。你需要有经验的程序员对你的代码进行检查,指出所犯的错误,这样才有可能解决。

Code Review 是一个专注于代码审查的程序员社区。任何人都可以登入该网站, 提交代码。社区的其他成员会审查代码,并反馈做得好的地方以及可以改进的地方。网站 地址为:

https://codereview.stackexchange.com/

13.安全

对于自学的程序员来说,安全是一个很容易忽视的问题。在面试时也很少会被问到 安全问题, 在学习编程时我们也不会去考虑安全问题。但是, 在实际工作中,我们需要 对自己代码的安全性负直接责任。本节将给出几个提高代码安全性的建议。

  • 我们在本书中已经学习了使用sudo 命令以根用户的身份执行命令。非必要情况下, 务必不要在命令行使用sudo 执行命令,因为如果有黑客侵入程序的话,将会获得根访问权限。
  • 如果你是服务器管理员, 还应该禁止根用户登录。每个黑客都会盯着根账号,在攻击系统时是首要选择的目标。
  • 总是假设用户的输入是恶意的。部分恶意攻击的发生,就是利用了可接受用户输入的程序漏洞,因此我们要假设所有的用户输入都是恶意的,并以此为依据进行编码。
  • 另一个提高代码安全性的策略, 是最小化你的攻击面积( attack surface ),即黑客可从程序中提取数据或攻击系统的相关区域。通过最小化攻击面积,可以减少程序出现漏洞的可能性。最小化攻击面积的几种常见做法包括:
    • 避免保存敏感信息,
    • 赋予用户最低的访问权限,
    • 尽可能少用第三方库(代码量越小、漏洞越少),剔除不再使用的功能代码(代码量越小、漏洞越少〉等。

**避免以根用户身份登录系统,不要信任用户输入,以及最小化攻击面积,是确保程序安全性的几个重要手段。但这还只是提升安全性的一小部分。我们应该试着从黑客的角度进行思考。他们会如何利用你的代码?这样可以帮助我们找到之前可能忽略的漏洞。

有关安全的话题非常大,不是本书所能涵盖的,因此建议大家时刻思考并学 习如何提升安全性。布鲁斯· 舒奈尔( Bruce Schneier )对此的总结十分精辟:

“ 安全是一种思维状态。”

第24 章 第一份编程王作

“请注意, 在‘现实世界’里,演讲者的诉求永远是听众不要挑战其默认的假设条件。” ---- 艾兹格·W· 边科斯彻 ( Edsger W.Dijkstra )

专注某一个感兴趣的编程领域,成为该领域内的专家。专注一个编程方向会使得求职更容易。

Web 开发和移动开发是两个非常流行的编程方向,各自包含两个细分领域: 前端和 后端。应用的前端是用户可见的部分,如Web 应用的图形界面;后端是用户看不见的地 方,是向前端提供数据的部分。

Python 官网提供了一个Python 工作列表, 可以先从这里找起。先查看几个岗位的要求以及所使用的技术,了解需要学习l那些内容才能成功竞争该岗位:

https://www.python.org/jobs/

通过外包积累初期经验:在Upwork 等类似网站创建账号,试着申请规模较小的编程工作。我建议去找确实有编程外包需求的朋友,让他们在Upwork 等网站注册账号,然后正式雇用你完成任务。这样后续可以给你很不锚的评价。其他人看到你至少成功完成了一项工作之后,被雇佣的概率就会提高,因为你己经成功建立了可信度。

大部分编程面试聚焦两个主题:数据结构和算法。要想顺利通过编程面试,你需要精通这两个领域。这也会帮助你成为一名更优秀的程序员。强烈建议使用LeetCode 网站练习,别人在面试中问的问题,在这个网站上都可以找到答案。

第25 章 团队协作

“没有优秀的团队,无法打造出优秀的软件。大部分的软件团队看上去就像内部不和谐的家庭。” ---- 吉姆·麦卡锡( Jim McCarthy )

由于主要依靠自学,因此你可能会习惯独自编程。但是在加入公司后,你需要学习如何进行团队协作。即使你自己创业, 最终也要招聘其他程序员,这时也要学会团队协作。编程是一项团队工作,与其他集体项目一样,都需要处理好与同事之间的关系。

1.掌握基础

公司聘用你,是认为你应该掌握了本书中所介绍的能力。仅仅通读完本书还不够,还需要掌握其中的概念。

如果同事经常要帮助你熟悉基础,这将极大降低他们对你的信任程度。

2.提问前请先搜索

作为团队中的新同事,你会有很多需要学习的地方,需要学会提问。提问是学习的一种很好的方式。但是在提问之前, 请确认提的问题是合适的。建议只有在自己己经花费几分钟时间了解无法解答之后,再去提问。如果问了太多自己本该轻松解决的问题,可能会惹同事厌烦。

更好的提问方式是: 学习研究了相关问题后,能够和同事交流探讨, 能够相互学习, 这也是能够提升整个团队能力的方式.

第26 章 更多学习资料

“最优秀的程序员比一般优秀的程序员,不只是好一丁半点。不论用什么标准来衡量,他们都比后者优秀太多:认知创新能力、工作效率、设计原创性或问题解决能力都是如此。” ---- 兰道尔· 斯特若斯( Randall E.Stross )

Medium 上有位工程师写了一篇名为《ABC: Always Be Coding 》(生命不息, 编程不止)的文章, 文章标题就是核心思想: 生命不息,编程不止。

经典书籍

有一些编程书籍是必读书目。《程序员修炼之道》《设计模式》 《代码大全》《编译原理》,以及MIT 出版社出版的《算法导论》均为程序员必读书目。

设计模式是本书中没有涉及的一个重要领域。

另外,作者强烈推荐一个免费的互动式算法入门教程,名为《Problem Solving with Data Structures and Algorithms 》,这本书比《算法导论》更加容易理解。

在线课堂

在线编程课堂也是提升编程技巧的一种方式。作者列出的值得推荐的课程:

https://www.theselftaughtprogrammer.io/courses

骇客新闻

骇客新闻( Hacker News ) 是技术孵化器Y Combinator 推出的一个用户新闻分享平台,能够帮助大家及时掌握最新的趋势和技术:

https://news.ycombinator.com/

第27 章 下一步

“热爱你所掌握的行业,知足常乐。” ---- 马库斯· 奥勒留斯( Marcus Aurelius )

下一步你应该怎么做?要学习数据结构和算法, 请前往LeetCode 网站,进行算法练习,然后寻找更多的练习机会。在本章中,作者将分享一些有关如何持续提升程序员技能的思考。

找到导师

导师能够帮助你将编程能力提升一个档次。学习编程的一个困难在于,有太多你做 得不够好的地方,但是你自己不知道。我前面也提到,可以通过代码审查来发现不足。 导师可以和你一起做代码审查,帮助你优化编码过程,推荐优秀的书籍, 教会你之前不 理解的编程概念。

加深理解

编程领域中有一个叫“黑盒”的概念,指的是某个你在使用,但是并不了解其工作原理的东西。刚开始编程时,对你来说一切都是黑盒。提升编程能力的一个最好的方式,就是打开碰到的每个黑盒,努力掌握其原理。有位朋友曾经跟我说,他自己的一个重大的“啊哈”时刻, 就是意识到命令行本身其实就是一个程序。打开黑盒,就是我所说的加深理解。

所有领域的底层都是相通的, 吸收的东西越多, 越有助于自己理解新的东西.这也是下面提到的 "讨论程序员如何提升能力的帖子。获赞数最高的答案出人意料:做编程以外的事情。"

撰写本书加深了我对编程的理解。有一些我认为己经理解的概念,在写书时才发现无法清楚地解释给读者。我必须要加深自己的理解。不要停留在一个答案, 寻找所有能发现的解释。敢于在网络上提问, 接纳不同的观点。

另外一种加深理解的方法,便是亲自去开发希望获得更深理解的东西。不懂版本控制?那就试着业余时间自己开发一个简易的版本控制系统。投入精力去完成此类项目是值得的,可以有效地提升你对相关话题的理解。

其他建议

我曾在论坛上看到讨论程序员如何提升能力的帖子。获赞数最高的答案出人意料:做编程以外的事情。后来的经验告诉我,这句话是对的。

读完丹尼尔·科伊勒的《The Talent Code 》提升了我的编程能力, 因为他教会了我掌握任何一个技能所需要的方法论。时刻注意编程领域之外的动态, 注重吸收那些有助于编程的知识。

我要给你的最后一个建议,是尽可能多花时间阅读其他人的代码。这也是程序员自我提升的最好办法之一。在学习过程中,记得保持写代码和读代码之间的平衡。刚开始时,阅读其他人的代码会有些困难,但是要坚持,因为你可以从其他程序员身上学到宝贵的财富。

实践出真知.

© Licensed under CC BY-NC-SA 4.0

在认识一切事物之后,人才能认识自己,因为事物仅仅是人的界限。——尼采

发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!