年薪50W+的Python程序员如何写代码

为什么要用Python写代码

没有对比就没有伤害

很多互联网和移动互联网企业对开发效率的要求高于对执行效率的要求

例子1:hello, world

C的版本:

  1. #include <stdio.h>
  2. int main() {
  3. printf("hello, world\n");
  4. return 0;
  5. }

Java的版本:

  1. class Example01 {
  2. public static void main(String[] args) {
  3. System.out.println("hello, world");
  4. }
  5. }

Python的版本:

  1. print('hello, world')

例子2:1-100求和

C的版本:

  1. #include <stdio.h>
  2. int main() {
  3. int total = 0;
  4. for (int i = 1; i <= 100; ++i) {
  5. total += i;
  6. }
  7. printf("%d\n", total);
  8. return 0;
  9. }

Python的版本:

  1. print(sum(range(1, 101)))

例子3:创建和初始化数组(列表)

Java的版本:

  1. import java.util.Arrays;
  2. public class Example03 {
  3. public static void main(String[] args) {
  4. boolean[] values = new boolean[10];
  5. Arrays.fill(values, true);
  6. System.out.println(Arrays.toString(values));
  7. int[] numbers = new int[10];
  8. for (int i = 0; i < numbers.length; ++i) {
  9. numbers[i] = i + 1;
  10. }
  11. System.out.println(Arrays.toString(numbers));
  12. }
  13. }

Python的版本:

  1. values = [True] * 10
  2. print(values)
  3. numbers = [x for x in range(1, 11)]
  4. print(numbers)

例子4:双色球随机选号

Java的版本:

  1. import java.util.List;
  2. import java.util.ArrayList;
  3. import java.util.Collections;
  4. import java.util.Scanner;
  5. class Example03 {
  6. /**
  7. * 产生[min, max)范围的随机整数
  8. */
  9. public static int randomInt(int min, int max) {
  10. return (int) (Math.random() * (max - min) + min);
  11. }
  12. /**
  13. * 输出一组双色球号码
  14. */
  15. public static void display(List<Integer> balls) {
  16. for (int i = 0; i < balls.size(); ++i) {
  17. System.out.printf("%02d ", balls.get(i));
  18. if (i == balls.size() - 2) {
  19. System.out.print("| ");
  20. }
  21. }
  22. System.out.println();
  23. }
  24. /**
  25. * 生成一组随机号码
  26. */
  27. public static List<Integer> generate() {
  28. List<Integer> redBalls = new ArrayList<>();
  29. for (int i = 1; i <= 33; ++i) {
  30. redBalls.add(i);
  31. }
  32. List<Integer> selectedBalls = new ArrayList<>();
  33. for (int i = 0; i < 6; ++i) {
  34. selectedBalls.add(redBalls.remove(randomInt(0, redBalls.size())));
  35. }
  36. Collections.sort(selectedBalls);
  37. selectedBalls.add(randomInt(1, 17));
  38. return selectedBalls;
  39. }
  40. public static void main(String[] args) {
  41. try (Scanner sc = new Scanner(System.in)) {
  42. System.out.print("机选几注: ");
  43. int num = sc.nextInt();
  44. for (int i = 0; i < num; ++i) {
  45. display(generate());
  46. }
  47. }
  48. }
  49. }

Python的版本:

  1. from random import randint, sample
  2. def generate():
  3. """生成一组随机号码"""
  4. red_balls = [x for x in range(1, 34)]
  5. selected_balls = sample(red_balls, 6)
  6. selected_balls.sort()
  7. selected_balls.append(randint(1, 16))
  8. return selected_balls
  9. def display(balls):
  10. """输出一组双色球号码"""
  11. for index, ball in enumerate(balls):
  12. print(f'{ball:0>2d}', end=' ')
  13. if index == len(balls) - 2:
  14. print('|', end=' ')
  15. print()
  16. num = int(input('机选几注: '))
  17. for _ in range(num):
  18. display(generate())

温馨提示:珍爱生命,远离任何形式的赌博。

例子5:实现一个简单的HTTP服务器。

Java的版本:

说明:JDK 1.6以前,需要通过套接字编程来实现,具体又可以分为多线程和NIO两种做法。JDK 1.6以后,可以使用com.sun.net.httpserver包提供的HttpServer类来实现。

  1. import com.sun.net.httpserver.HttpExchange;
  2. import com.sun.net.httpserver.HttpHandler;
  3. import com.sun.net.httpserver.HttpServer;
  4. import java.io.IOException;
  5. import java.io.OutputStream;
  6. import java.net.InetSocketAddress;
  7. class Example05 {
  8. public static void main(String[] arg) throws Exception {
  9. HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
  10. server.createContext("/", new RequestHandler());
  11. server.start();
  12. }
  13. static class RequestHandler implements HttpHandler {
  14. @Override
  15. public void handle(HttpExchange exchange) throws IOException {
  16. String response = "<h1>hello, world</h1>";
  17. exchange.sendResponseHeaders(200, 0);
  18. try (OutputStream os = exchange.getResponseBody()) {
  19. os.write(response.getBytes());
  20. }
  21. }
  22. }
  23. }

Python的版本:

  1. from http.server import HTTPServer, SimpleHTTPRequestHandler
  2. class RequestHandler(SimpleHTTPRequestHandler):
  3. def do_GET(self):
  4. self.send_response(200)
  5. self.end_headers()
  6. self.wfile.write('<h1>hello, world</h1>'.encode())
  7. server = HTTPServer(('', 8000), RequestHandler)
  8. server.serve_forever()

  1. python3 -m http.server 8000

一行Python代码可以做什么

很多时候,你的问题只需一行Python代码就能解决

  1. # 一行代码实现求阶乘函数
  2. fac = lambda x: __import__('functools').reduce(int.__mul__, range(1, x + 1), 1)
  3. # 一行代码实现求最大公约数函数
  4. gcd = lambda x, y: y % x and gcd(y % x, x) or x
  5. # 一行代码实现判断素数的函数
  6. is_prime = lambda x: x > 1 and not [f for f in range(2, int(x ** 0.5) + 1) if x % f == 0]
  7. # 一行代码实现快速排序
  8. quick_sort = lambda items: len(items) and quick_sort([x for x in items[1:] if x < items[0]]) + [items[0]] + quick_sort([x for x in items[1:] if x > items[0]]) or items
  9. # 生成FizzBuzz列表
  10. ['Fizz'[x % 3 * 4:] + 'Buzz'[x % 5 * 4:] or x for x in range(1, 101)]

设计模式从未如此简单

Python是动态类型语言,大量的设计模式在Python中被简化或弱化

思考:如何优化下面的代码。

  1. def fib(num):
  2. if num in (1, 2):
  3. return 1
  4. return fib(num - 1) + fib(num - 2)

代理模式在Python中可以通过内置的或自定义的装饰器来实现。

  1. from functools import lru_cache
  2. @lru_cache()
  3. def fib(num):
  4. if num in (1, 2):
  5. return 1
  6. return fib(num - 1) + fib(num - 2)
  7. for n in range(1, 121):
  8. print(f'{n}: {fib(n)}')

说明:通过Python标准库functools模块的lru_cache装饰器为fib函数加上缓存代理,缓存函数执行的中间结果,优化代码的性能。

单例模式在Python中可以通过自定义的装饰器或元类来实现。

  1. from functools import wraps
  2. from threading import RLock
  3. def singleton(cls):
  4. instances = {}
  5. lock = RLock()
  6. @wraps(cls)
  7. def wrapper(*args, **kwargs):
  8. if cls not in instances:
  9. with lock:
  10. if cls not in instances:
  11. instances[cls] = cls(*args, **kwargs)
  12. return instances[cls]

说明:需要实现单例模式的类只需要添加上面的装饰器即可。

原型模式在Python中可以通过元类来实现。

  1. import copy
  2. class PrototypeMeta(type):
  3. def __init__(cls, *args, **kwargs):
  4. super().__init__(*args, **kwargs)
  5. cls.clone = lambda self, is_deep=True: \
  6. copy.deepcopy(self) if is_deep else copy.copy(self)

说明:通过元类给指定了metaclass=PrototypeMeta的类添加一个clone方法实现对象克隆,利用Python标准库copy模块的copydeepcopy分别实现浅拷贝和深拷贝。

数据采集和数据分析从未如此简单

网络数据采集是Python最擅长的领域之一。

例子:获取豆瓣电影“Top250”。

  1. import random
  2. import time
  3. import requests
  4. from bs4 import BeautifulSoup
  5. for page in range(10):
  6. resp = requests.get(
  7. url=f'https://movie.douban.com/top250?start={25 * page}',
  8. headers={'User-Agent': 'BaiduSpider'}
  9. )
  10. soup = BeautifulSoup(resp.text, "lxml")
  11. for elem in soup.select('a > span.title:nth-child(1)'):
  12. print(elem.text)
  13. time.sleep(random.random() * 5)

利用NumPy、Pandas、Matplotlib可以轻松实现数据分析和可视化

年薪50W 的Python程序员如何写代码 - 图1

写出Python代码的正确姿势

用Python写代码就要写出Pythonic的代码

姿势1:选择结构的正确姿势

跨界开发者的代码:

  1. name = 'jackfrued'
  2. fruits = ['apple', 'orange', 'grape']
  3. owners = {'name': '骆昊', 'age': 40, 'gender': True}
  4. if name != '' and len(fruits) > 0 and len(owners.keys()) > 0:
  5. print('Jackfrued love fruits.')

Pythonic的代码:

  1. name = 'jackfrued'
  2. fruits = ['apple', 'orange', 'grape']
  3. owners = {'name': '骆昊', 'age': 40, 'gender': True}
  4. if name and fruits and owners:
  5. print('Jackfrued love fruits.')

姿势2:交换两个变量的正确姿势

跨界开发者的代码:

  1. temp = a
  2. a = b
  3. b = temp

  1. a = a ^ b
  2. b = a ^ b
  3. a = a ^ b

Pythonic的代码:

  1. a, b = b, a

姿势3:用序列组装字符串的正确姿势

跨界开发者的代码:

  1. chars = ['j', 'a', 'c', 'k', 'f', 'r', 'u', 'e', 'd']
  2. name = ''
  3. for char in chars:
  4. name += char

Pythonic的代码:

  1. chars = ['j', 'a', 'c', 'k', 'f', 'r', 'u', 'e', 'd']
  2. name = ''.join(chars)

姿势4:遍历列表的正确姿势

跨界开发者的代码:

  1. fruits = ['orange', 'grape', 'pitaya', 'blueberry']
  2. index = 0
  3. for fruit in fruits:
  4. print(index, ':', fruit)
  5. index += 1

Pythonic的代码:

  1. fruits = ['orange', 'grape', 'pitaya', 'blueberry']
  2. for index, fruit in enumerate(fruits):
  3. print(index, ':', fruit)

姿势5:创建列表的正确姿势

跨界开发者的代码:

  1. data = [7, 20, 3, 15, 11]
  2. result = []
  3. for i in data:
  4. if i > 10:
  5. result.append(i * 3)

Pythonic的代码:

  1. data = [7, 20, 3, 15, 11]
  2. result = [num * 3 for num in data if num > 10]

姿势6:确保代码健壮性的正确姿势

跨界开发者的代码:

  1. data = {'x': '5'}
  2. if 'x' in data and isinstance(data['x'], (str, int, float)) \
  3. and data['x'].isdigit():
  4. value = int(data['x'])
  5. print(value)
  6. else:
  7. value = None

Pythonic的代码:

  1. data = {'x': '5'}
  2. try:
  3. value = int(data['x'])
  4. print(value)
  5. except (KeyError, TypeError, ValueError):
  6. value = None

使用Lint工具检查你的代码规范

阅读下面的代码,看看你能看出哪些地方是有毛病的或者说不符合Python的编程规范的。

  1. from enum import *
  2. @unique
  3. class Suite (Enum):
  4. SPADE, HEART, CLUB, DIAMOND = range(4)
  5. class Card(object):
  6. def __init__(self,suite,face ):
  7. self.suite = suite
  8. self.face = face
  9. def __repr__(self):
  10. suites='♠♥♣♦'
  11. faces=['','A','2','3','4','5','6','7','8','9','10','J','Q','K']
  12. return f'{suites[self.suite.value]}{faces[self.face]}'
  13. import random
  14. class Poker(object):
  15. def __init__(self):
  16. self.cards =[Card(suite, face) for suite in Suite
  17. for face in range(1, 14)]
  18. self.current=0
  19. def shuffle (self):
  20. self.current=0
  21. random.shuffle(self.cards)
  22. def deal (self):
  23. card = self.cards[self.current]
  24. self.current+=1
  25. return card
  26. def has_next (self):
  27. if self.current<len(self.cards): return True
  28. return False
  29. p = Poker()
  30. p.shuffle()
  31. print(p.cards)

PyLint的安装和使用

Pylint是Python代码分析工具,它分析Python代码中的错误,查找不符合代码风格标准(默认使用的代码风格是 PEP 8)和有潜在问题的代码。

  1. pip install pylint
  2. pylint [options] module_or_package

Pylint输出格式如下所示。

模块名:行号:列号: 消息类型 消息

消息类型有以下几种:

  1. C - 惯例:违反了Python编程惯例(PEP 8)的代码。
  2. R - 重构:写得比较糟糕需要重构的代码。
  3. W - 警告:代码中存在的不影响代码运行的问题。
  4. E - 错误:代码中存在的影响代码运行的错误。
  5. F - 致命错误:导致Pylint无法继续运行的错误。

Pylint命令的常用参数:

  1. --disable=<msg ids>-d <msg ids>:禁用指定类型的消息。
  2. --errors-only-E:只显示错误。
  3. --rcfile=<file>:指定配置文件。
  4. --list-msgs:列出Pylint的消息清单。
  5. --generate-rcfile:生成配置文件的样例。
  6. --reports=<y_or_n>-r <y_or_n>:是否生成检查报告。

使用Profile工具剖析你的代码性能

cProfile模块

example01.py

  1. import cProfile
  2. def is_prime(num):
  3. for factor in range(2, int(num ** 0.5) + 1):
  4. if num % factor == 0:
  5. return False
  6. return True
  7. class PrimeIter:
  8. def __init__(self, total):
  9. self.counter = 0
  10. self.current = 1
  11. self.total = total
  12. def __iter__(self):
  13. return self
  14. def __next__(self):
  15. if self.counter < self.total:
  16. self.current += 1
  17. while not is_prime(self.current):
  18. self.current += 1
  19. self.counter += 1
  20. return self.current
  21. raise StopIteration()
  22. cProfile.run('list(PrimeIter(10000))')

执行结果:

  1. 114734 function calls in 0.573 seconds
  2. Ordered by: standard name
  3. ncalls tottime percall cumtime percall filename:lineno(function)
  4. 1 0.006 0.006 0.573 0.573 <string>:1(<module>)
  5. 1 0.000 0.000 0.000 0.000 example.py:14(__init__)
  6. 1 0.000 0.000 0.000 0.000 example.py:19(__iter__)
  7. 10001 0.086 0.000 0.567 0.000 example.py:22(__next__)
  8. 104728 0.481 0.000 0.481 0.000 example.py:5(is_prime)
  9. 1 0.000 0.000 0.573 0.573 {built-in method builtins.exec}
  10. 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}

line_profiler

给需要剖析时间性能的函数加上一个profile装饰器,这个函数每行代码的执行次数和时间都会被剖析。

example02.py

  1. @profile
  2. def is_prime(num):
  3. for factor in range(2, int(num ** 0.5) + 1):
  4. if num % factor == 0:
  5. return False
  6. return True
  7. class PrimeIter:
  8. def __init__(self, total):
  9. self.counter = 0
  10. self.current = 1
  11. self.total = total
  12. def __iter__(self):
  13. return self
  14. def __next__(self):
  15. if self.counter < self.total:
  16. self.current += 1
  17. while not is_prime(self.current):
  18. self.current += 1
  19. self.counter += 1
  20. return self.current
  21. raise StopIteration()
  22. list(PrimeIter(1000))

安装和使用line_profiler三方库。

  1. pip install line_profiler
  2. kernprof -lv example.py
  3. Wrote profile results to example02.py.lprof
  4. Timer unit: 1e-06 s
  5. Total time: 0.089513 s
  6. File: example02.py
  7. Function: is_prime at line 1
  8. # Hits Time Per Hit % Time Line Contents
  9. ==============================================================
  10. 1 @profile
  11. 2 def is_prime(num):
  12. 3 86624 43305.0 0.5 48.4 for factor in range(2, int(num ** 0.5) + 1):
  13. 4 85624 42814.0 0.5 47.8 if num % factor == 0:
  14. 5 6918 3008.0 0.4 3.4 return False
  15. 6 1000 386.0 0.4 0.4 return True

memory_profiler

给需要剖析内存性能的函数加上一个profile装饰器,这个函数每行代码的内存使用情况都会被剖析。

example03.py

  1. @profile
  2. def eat_memory():
  3. items = []
  4. for _ in range(1000000):
  5. items.append(object())
  6. return items
  7. eat_memory()

安装和使用memory_profiler三方库。

  1. pip install memory_profiler
  2. python3 -m memory_profiler example.py
  3. Filename: example03.py
  4. Line # Mem usage Increment Line Contents
  5. ================================================
  6. 1 38.672 MiB 38.672 MiB @profile
  7. 2 def eat_memory():
  8. 3 38.672 MiB 0.000 MiB items = []
  9. 4 68.727 MiB 0.000 MiB for _ in range(1000000):
  10. 5 68.727 MiB 1.797 MiB items.append(object())
  11. 6 68.727 MiB 0.000 MiB return items

如何构建综合职业素养

学习总结

  1. 了解全局
  2. 确定范围
  3. 定义目标
  4. 寻找资源
  5. 创建学习计划
  6. 筛选资源
  7. 开始学习,浅尝辄止(YAGNI)
  8. 动手操作,边学边玩
  9. 全面掌握,学以致用
  10. 乐为人师,融会贯通

时间管理

  1. 提升专注力

  2. 充分利用碎片时间

  3. 使用番茄工作法

  4. 时间是怎么浪费掉的

  5. 任何行动都比不采取行动好
    年薪50W 的Python程序员如何写代码 - 图2

好书推荐

  1. 职业规划:《软技能 - 代码之外的生存指南》
  2. 吴军系列:《浪潮之巅》、《硅谷之谜》、《数学之美》、……
  3. 时间管理:《成为一个更高效的人》、《番茄工作法图解》