diff --git a/Jax_code/Day1/day1_code.py b/Jax_code/Day1/day1_code.py new file mode 100644 index 000000000..9839e63bc --- /dev/null +++ b/Jax_code/Day1/day1_code.py @@ -0,0 +1 @@ +import this \ No newline at end of file diff --git a/Jax_code/Day10/Code.py b/Jax_code/Day10/Code.py new file mode 100644 index 000000000..4c80df115 --- /dev/null +++ b/Jax_code/Day10/Code.py @@ -0,0 +1,43 @@ +import tkinter +import tkinter.messagebox + + +def main(): + flag = True + + # 修改标签上的文字 + def change_label_text(): + nonlocal flag + flag = not flag + color, msg = ('red', 'Hello, world!')\ + if flag else ('blue', 'Goodbye, world!') + label.config(text=msg, fg=color) + + # 确认退出 + def confirm_to_quit(): + if tkinter.messagebox.askokcancel('温馨提示', '确定要退出吗?'): + top.quit() + + # 创建顶层窗口 + top = tkinter.Tk() + # 设置窗口大小 + top.geometry('240x160') + # 设置窗口标题 + top.title('小游戏') + # 创建标签对象并添加到顶层窗口 + label = tkinter.Label(top, text='Hello, world!', font='Arial -32', fg='red') + label.pack(expand=1) + # 创建一个装按钮的容器 + panel = tkinter.Frame(top) + # 创建按钮对象 指定添加到哪个容器中 通过command参数绑定事件回调函数 + button1 = tkinter.Button(panel, text='修改', command=change_label_text) + button1.pack(side='left') + button2 = tkinter.Button(panel, text='退出', command=confirm_to_quit) + button2.pack(side='right') + panel.pack(side='bottom') + # 开启主事件循环 + tkinter.mainloop() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/Jax_code/Day10/Note.md b/Jax_code/Day10/Note.md new file mode 100644 index 000000000..e69de29bb diff --git a/Jax_code/Day11/Code.py b/Jax_code/Day11/Code.py new file mode 100644 index 000000000..53ecfc01a --- /dev/null +++ b/Jax_code/Day11/Code.py @@ -0,0 +1,16 @@ +from math import sqrt + +def is_prime(n): + assert n > 0, f'Prime number ain\'t negative' + for factor in range(2, int(sqrt(n) + 1)): + if n % factor == 0: + return False + return True if n != 1 else False + +def main(): + number = int(input("Input a number:\n")) + print(is_prime(number)) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/Jax_code/Day11/Note.md b/Jax_code/Day11/Note.md new file mode 100644 index 000000000..bc5b0c1ab --- /dev/null +++ b/Jax_code/Day11/Note.md @@ -0,0 +1,49 @@ +### 读写文本文件 + +在使用`open`函数时指定好文件名并将文件模式设置为`'w'`即可。注意如果需要对文件内容进行追加式写入,应该将模式设置为`'a'` + + +| JSON | Python | +| ------------------- | ------------ | +| object | dict | +| array | list | +| string | str | +| number (int / real) | int / float | +| true / false | True / False | +| null | None | + +| Python | JSON | +| -------------------------------------- | ------------ | +| dict | object | +| list, tuple | array | +| str | string | +| int, float, int- & float-derived Enums | number | +| True / False | true / false | +| None | null | + +json模块主要有四个比较重要的函数,分别是: + +- `dump` - 将Python对象按照JSON格式序列化到文件中 +- `dumps` - 将Python对象处理成JSON格式的字符串 +- `load` - 将文件中的JSON数据反序列化成对象 +- `loads` - 将字符串的内容反序列化成Python对象 + +这里出现了两个概念,一个叫序列化,一个叫反序列化。自由的百科全书[维基百科](https://zh.wikipedia.org/)上对这两个概念是这样解释的:“序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换为可以存储或传输的形式,这样在需要的时候能够恢复到原先的状态,而且通过序列化的数据重新获取字节时,可以利用这些字节来产生原始对象的副本(拷贝)。与这个过程相反的动作,即从一系列字节中提取数据结构的操作,就是反序列化(deserialization)”。 + +目前绝大多数网络数据服务(或称之为网络API)都是基于[HTTP协议](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE)提供JSON格式的数据,关于HTTP协议的相关知识,可以看看阮一峰老师的[《HTTP协议入门》](http://www.ruanyifeng.com/blog/2016/08/http.html),如果想了解国内的网络数据服务,可以看看[聚合数据](https://www.juhe.cn/)和[阿凡达数据](http://www.avatardata.cn/)等网站,国外的可以看看[{API}Search](http://apis.io/)网站。下面的例子演示了如何使用[requests](http://docs.python-requests.org/zh_CN/latest/)模块(封装得足够好的第三方网络访问模块)访问网络API获取国内新闻,如何通过json模块解析JSON数据并显示新闻标题,这个例子使用了[天行数据](https://www.tianapi.com/)提供的国内新闻数据接口,其中的APIKey需要自己到该网站申请。 + +```Python +import requests +import json + + +def main(): + resp = requests.get('http://api.tianapi.com/guonei/?key=APIKey&num=10') + data_model = json.loads(resp.text) + for news in data_model['newslist']: + print(news['title']) + + +if __name__ == '__main__': + main() +``` \ No newline at end of file diff --git a/Jax_code/Day12/Code.py b/Jax_code/Day12/Code.py new file mode 100644 index 000000000..1cd0480c0 --- /dev/null +++ b/Jax_code/Day12/Code.py @@ -0,0 +1,20 @@ +import re + +pattern = re.compile(r'(?<=\D)1[34578]\d{9}(?=\D)') +sentence = ''' +重要的事情说8130123456789遍,我的手机号是13512346789这个靓号, +不是15600998765,也是110或119,王大锤的手机号才是15600998765。 +''' +# 查找所有匹配并保存到一个列表中 +mylist = re.findall(pattern, sentence) +print(mylist) +print('--------华丽的分隔线--------') +# 通过迭代器取出匹配对象并获得匹配的内容 +for temp in pattern.finditer(sentence): + print(temp.group()) +print('--------华丽的分隔线--------') +# 通过search函数指定搜索位置找出所有匹配 +m = pattern.search(sentence) +while m: + print(m.group()) + m = pattern.search(sentence, m.end()) \ No newline at end of file diff --git a/Jax_code/Day12/Note.md b/Jax_code/Day12/Note.md new file mode 100644 index 000000000..195e01832 --- /dev/null +++ b/Jax_code/Day12/Note.md @@ -0,0 +1,60 @@ +#### 正则表达式 +| 符号 | 解释 | 示例 | 说明 | +| ------------------ | ----------------------------------------- | ---------------- | -------------------------------------------------- | +| . | 匹配任意字符 | b.t | 可以匹配bat / but / b#t / b1t等 | +| \\w | 匹配字母/数字/下划线 | b\\wt | 可以匹配bat / b1t / b_t等
但不能匹配b#t | +| \\s | 匹配空白字符(包括\r、\n、\t等) | love\\syou | 可以匹配love you | +| \\d | 匹配数字 | \\d\\d | 可以匹配01 / 23 / 99等 | +| \\b | 匹配单词的边界 | \\bThe\\b | | +| ^ | 匹配字符串的开始 | ^The | 可以匹配The开头的字符串 | +| $ | 匹配字符串的结束 | .exe$ | 可以匹配.exe结尾的字符串 | +| \\W | 匹配非字母/数字/下划线 | b\\Wt | 可以匹配b#t / b@t等
但不能匹配but / b1t / b_t等 | +| \\S | 匹配非空白字符 | love\\Syou | 可以匹配love#you等
但不能匹配love you | +| \\D | 匹配非数字 | \\d\\D | 可以匹配9a / 3# / 0F等 | +| \\B | 匹配非单词边界 | \\Bio\\B | | +| [] | 匹配来自字符集的任意单一字符 | [aeiou] | 可以匹配任一元音字母字符 | +| [^] | 匹配不在字符集中的任意单一字符 | [^aeiou] | 可以匹配任一非元音字母字符 | +| * | 匹配0次或多次 | \\w* | | +| + | 匹配1次或多次 | \\w+ | | +| ? | 匹配0次或1次 | \\w? | | +| {N} | 匹配N次 | \\w{3} | | +| {M,} | 匹配至少M次 | \\w{3,} | | +| {M,N} | 匹配至少M次至多N次 | \\w{3,6} | | +| \| | 分支 | foo\|bar | 可以匹配foo或者bar | +| (?#) | 注释 | | | +| (exp) | 匹配exp并捕获到自动命名的组中 | | | +| (?<name>exp) | 匹配exp并捕获到名为name的组中 | | | +| (?:exp) | 匹配exp但是不捕获匹配的文本 | | | +| (?=exp) | 匹配exp前面的位置 | \\b\\w+(?=ing) | 可以匹配I'm dancing中的danc | +| (?<=exp) | 匹配exp后面的位置 | (?<=\\bdanc)\\w+\\b | 可以匹配I love dancing and reading中的第一个ing | +| (?!exp) | 匹配后面不是exp的位置 | | | +| (?a.\*?b | 将正则表达式应用于aabab,前者会匹配整个字符串aabab,后者会匹配aab和ab两个字符串 | +| +? | 重复1次或多次,但尽可能少重复 | | | +| ?? | 重复0次或1次,但尽可能少重复 | | | +| {M,N}? | 重复M到N次,但尽可能少重复 | | | +| {M,}? | 重复M次以上,但尽可能少重复 | | | + +> **说明:** 如果需要匹配的字符是正则表达式中的特殊字符,那么可以使用\\进行转义处理,例如想匹配小数点可以写成\\.就可以了,因为直接写.会匹配任意字符;同理,想匹配圆括号必须写成\\(和\\),否则圆括号被视为正则表达式中的分组。 + +### Python对正则表达式的支持 + +Python提供了re模块来支持正则表达式相关操作,下面是re模块中的核心函数。 + +| 函数 | 说明 | +| -------------------------------------------- | ------------------------------------------------------------ | +| compile(pattern, flags=0) | 编译正则表达式返回正则表达式对象 | +| match(pattern, string, flags=0) | 用正则表达式匹配字符串 成功返回匹配对象 否则返回None | +| search(pattern, string, flags=0) | 搜索字符串中第一次出现正则表达式的模式 成功返回匹配对象 否则返回None | +| split(pattern, string, maxsplit=0, flags=0) | 用正则表达式指定的模式分隔符拆分字符串 返回列表 | +| sub(pattern, repl, string, count=0, flags=0) | 用指定的字符串替换原字符串中与正则表达式匹配的模式 可以用count指定替换的次数 | +| fullmatch(pattern, string, flags=0) | match函数的完全匹配(从字符串开头到结尾)版本 | +| findall(pattern, string, flags=0) | 查找字符串所有与正则表达式匹配的模式 返回字符串的列表 | +| finditer(pattern, string, flags=0) | 查找字符串所有与正则表达式匹配的模式 返回一个迭代器 | +| purge() | 清除隐式编译的正则表达式的缓存 | +| re.I / re.IGNORECASE | 忽略大小写匹配标记 | +| re.M / re.MULTILINE | 多行匹配标记 | + +> **说明:** 上面提到的re模块中的这些函数,实际开发中也可以用正则表达式对象的方法替代对这些函数的使用,如果一个正则表达式需要重复的使用,那么先通过compile函数编译正则表达式并创建出正则表达式对象无疑是更为明智的选择。 + +### 爬虫专家必备 正则表达式 \ No newline at end of file diff --git a/Jax_code/Day13/Code.py b/Jax_code/Day13/Code.py new file mode 100644 index 000000000..e69de29bb diff --git a/Jax_code/Day13/Note.md b/Jax_code/Day13/Note.md new file mode 100644 index 000000000..e69de29bb diff --git a/Jax_code/Day14/Code.py b/Jax_code/Day14/Code.py new file mode 100644 index 000000000..e69de29bb diff --git a/Jax_code/Day14/Note.md b/Jax_code/Day14/Note.md new file mode 100644 index 000000000..e69de29bb diff --git a/Jax_code/Day15/Code.py b/Jax_code/Day15/Code.py new file mode 100644 index 000000000..e69de29bb diff --git a/Jax_code/Day15/Note.md b/Jax_code/Day15/Note.md new file mode 100644 index 000000000..e69de29bb diff --git a/Jax_code/Day2/Code.py b/Jax_code/Day2/Code.py new file mode 100644 index 000000000..aea6d5229 --- /dev/null +++ b/Jax_code/Day2/Code.py @@ -0,0 +1,3 @@ +a = int(input("a = \n")) +b = int(input("b = \n")) +print('%d + %d = %d' % (a, b, a+b)) \ No newline at end of file diff --git a/Jax_code/Day2/Note.md b/Jax_code/Day2/Note.md new file mode 100644 index 000000000..5edeb9741 --- /dev/null +++ b/Jax_code/Day2/Note.md @@ -0,0 +1,45 @@ +#### 变量命名 + +对于每个变量我们需要给它取一个名字,就如同我们每个人都有属于自己的响亮的名字一样。在Python中,变量命名需要遵循以下这些必须遵守硬性规则和强烈建议遵守的非硬性规则。 + +- 硬性规则: + - 变量名由字母(广义的Unicode字符,不包括特殊字符)、数字和下划线构成,数字不能开头。 + - 大小写敏感(大写的`a`和小写的`A`是两个不同的变量)。 + - 不要跟关键字(有特殊含义的单词,后面会讲到)和系统保留字(如函数、模块等的名字)冲突。 +- PEP 8要求: + - 用小写字母拼写,多个单词用下划线连接。 + - 受保护的实例属性用单个下划线开头(后面会讲到)。 + - 私有的实例属性用两个下划线开头(后面会讲到)。 + +#### 强制转换 +- `int()`:将一个数值或字符串转换成整数,可以指定进制。 +- `float()`:将一个字符串转换成浮点数。 +- `str()`:将指定的对象转换成字符串形式,可以指定编码。 +- `chr()`:将整数转换成该编码对应的字符串(一个字符)。 +- `ord()`:将字符串(一个字符)转换成对应的编码(整数)。 + +#### 简单的计算和转换 +a = int(input('a = ')) +b = int(input('b = ')) +print('%d + %d = %d' % (a, b, a + b)) + +#### 运算符 + +Python支持多种运算符,下表大致按照优先级从高到低的顺序列出了所有的运算符,运算符的优先级指的是多个运算符同时出现时,先做什么运算然后再做什么运算。除了我们之前已经用过的赋值运算符和算术运算符,我们稍后会陆续讲到其他运算符的使用。 + +| 运算符 | 描述 | +| ------------------------------------------------------------ | ------------------------------ | +| `[]` `[:]` | 下标,切片 | +| `**` | 指数 | +| `~` `+` `-` | 按位取反, 正负号 | +| `*` `/` `%` `//` | 乘,除,模,整除 | +| `+` `-` | 加,减 | +| `>>` `<<` | 右移,左移 | +| `&` | 按位与 | +| `^` `\|` | 按位异或,按位或 | +| `<=` `<` `>` `>=` | 小于等于,小于,大于,大于等于 | +| `==` `!=` | 等于,不等于 | +| `is` `is not` | 身份运算符 | +| `in` `not in` | 成员运算符 | +| `not` `or` `and` | 逻辑运算符 | +| `=` `+=` `-=` `*=` `/=` `%=` `//=` `**=` `&=` `|=` `^=` `>>=` `<<=` | (复合)赋值运算符 | \ No newline at end of file diff --git a/Jax_code/Day3/Code.py b/Jax_code/Day3/Code.py new file mode 100644 index 000000000..321671c73 --- /dev/null +++ b/Jax_code/Day3/Code.py @@ -0,0 +1,9 @@ +x = float(input('x = ')) +if x > 1: + y = 3 * x - 5 +else: + if x >= -1: + y = x + 2 + else: + y = 5 * x + 3 +print('f(%.2f) = %.2f' % (x, y)) \ No newline at end of file diff --git a/Jax_code/Day3/Note.md b/Jax_code/Day3/Note.md new file mode 100644 index 000000000..0846177e7 --- /dev/null +++ b/Jax_code/Day3/Note.md @@ -0,0 +1 @@ +#### Nothing hard to memorize \ No newline at end of file diff --git a/Jax_code/Day4/Code.py b/Jax_code/Day4/Code.py new file mode 100644 index 000000000..33b452403 --- /dev/null +++ b/Jax_code/Day4/Code.py @@ -0,0 +1,25 @@ +import random + +# %% +answer = random.randint(1, 101) +counter = 0 + +while True: + counter += 1 + guess = input("What's your guess: \n") + try: + if int(guess) == answer: + print(f"Bingo!\nYou've taken {counter} times to guess.") + + break + elif int(guess) < answer: + print("Bigger!\n") + elif int(guess) > 100: + print("Too Big! Smaller than 100.\n") + else: + print("Smaller!\n") + except: + print('input not valid, wasted a chance\n') + + +# %% diff --git a/Jax_code/Day4/Note.md b/Jax_code/Day4/Note.md new file mode 100644 index 000000000..a7399fd58 --- /dev/null +++ b/Jax_code/Day4/Note.md @@ -0,0 +1 @@ +#### No big deal \ No newline at end of file diff --git a/Jax_code/Day5/Code.py b/Jax_code/Day5/Code.py new file mode 100644 index 000000000..fdac0bd60 --- /dev/null +++ b/Jax_code/Day5/Code.py @@ -0,0 +1,33 @@ +from random import randint + +money = 1000 +while money > 0: + print('你的总资产为:', money) + needs_go_on = False + while True: + debt = int(input('请下注: ')) + if 0 < debt <= money: + break + first = randint(1, 6) + randint(1, 6) + print('玩家摇出了%d点' % first) + if first == 7 or first == 11: + print('玩家胜!') + money += debt + elif first == 2 or first == 3 or first == 12: + print('庄家胜!') + money -= debt + else: + needs_go_on = True + while needs_go_on: + needs_go_on = False + current = randint(1, 6) + randint(1, 6) + print('玩家摇出了%d点' % current) + if current == 7: + print('庄家胜') + money -= debt + elif current == first: + print('玩家胜') + money += debt + else: + needs_go_on = True +print('你破产了, 游戏结束!') \ No newline at end of file diff --git a/Jax_code/Day5/Note.md b/Jax_code/Day5/Note.md new file mode 100644 index 000000000..89bb73230 --- /dev/null +++ b/Jax_code/Day5/Note.md @@ -0,0 +1 @@ +#### Still no big deal \ No newline at end of file diff --git a/Jax_code/Day6/Code.py b/Jax_code/Day6/Code.py new file mode 100644 index 000000000..eb072320b --- /dev/null +++ b/Jax_code/Day6/Code.py @@ -0,0 +1,28 @@ +# %% +from regex import B +from sympy import N +import Code2 + +print("Lee called") + +# %% +def foo(): + global a + a = 200 #全局作用域 + b = 100 #嵌套作用域 + + def bar(): + nonlocal b + global c + b = 200 + c = 200 + bar() + print(b) + +if __name__ == '__main__': + a = 100 + c = 100 + + foo() + print(a, c) +# %% diff --git a/Jax_code/Day6/Code2.py b/Jax_code/Day6/Code2.py new file mode 100644 index 000000000..ad79ce553 --- /dev/null +++ b/Jax_code/Day6/Code2.py @@ -0,0 +1,6 @@ +def foo(): + print("Foo called") + + +if __name__ == '__main__': + print("Bar called") \ No newline at end of file diff --git a/Jax_code/Day6/Note.md b/Jax_code/Day6/Note.md new file mode 100644 index 000000000..05b1c103d --- /dev/null +++ b/Jax_code/Day6/Note.md @@ -0,0 +1,82 @@ +### 函数的参数 + +函数是绝大多数编程语言中都支持的一个代码的"构建块",但是Python中的函数与其他语言中的函数还是有很多不太相同的地方,其中一个显著的区别就是Python对函数参数的处理。在Python中,函数的参数可以有默认值,也支持使用可变参数,所以Python并不需要像其他语言一样支持[函数的重载](https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E9%87%8D%E8%BD%BD),因为我们在定义一个函数的时候可以让它有多种不同的使用方式,下面是两个小例子。 + +```Python + +def add(a=0, b=0, c=0): + """三个数相加""" + return a + b + c + +print(add()) +print(add(1)) +print(add(1, 2)) +print(add(1, 2, 3)) +# 传递参数时可以不按照设定的顺序进行传递 +print(add(c=50, a=100, b=200)) +``` + +### 用模块管理函数 + +如果项目是由多人协作进行团队开发的时候,团队中可能有多个程序员都定义了名为`foo`的函数,那么怎么解决这种命名冲突呢?答案其实很简单,Python中每个文件就代表了一个模块(module),我们在不同的模块中可以有同名的函数,在使用函数的时候我们通过`import`关键字导入指定的模块就可以区分到底要使用的是哪个模块中的`foo`函数,代码如下所示。 + +需要说明的是,如果我们导入的模块除了定义函数之外还有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码,事实上我们可能并不希望如此,因此如果我们在模块中编写了执行代码,最好是将这些执行代码放入如下所示的条件中,这样的话除非直接运行该模块,if条件下的这些代码是不会执行的,因为只有直接执行的模块的名字才是"\_\_main\_\_"。 + +`module3.py` + +```Python +def foo(): + pass + +def bar(): + pass + +# __name__是Python中一个隐含的变量它代表了模块的名字 +# 只有被Python解释器直接执行的模块的名字才是__main__ +if __name__ == '__main__': + print('call foo()') + foo() + print('call bar()') + bar() +``` + +### 变量的作用域 + +Python查找一个变量时会按照“局部作用域”、“嵌套作用域”、“全局作用域”和“内置作用域”的顺序进行搜索 + +我们可以使用`global`关键字来指示`foo`函数中的变量`a`来自于全局作用域,如果全局作用域中没有`a`,那么下面一行的代码就会定义变量`a`并将其置于全局作用域。同理,如果我们希望函数内部的函数能够修改嵌套作用域中的变量,可以使用`nonlocal`关键字来指示变量来自于嵌套作用域,请大家自行试验。 + +```Python +def foo(): + global a + a = 200 #全局作用域 + b = 100 #嵌套作用域 + + def bar(): + nonlocal b + global c #全局作用域 + b = 200 + c = 200 + bar() + print(b) + +if __name__ == '__main__': + a = 100 + c = 100 + + foo() + print(a, c) + +``` + +在实际开发中,我们应该尽量减少对全局变量的使用,因为全局变量的作用域和影响过于广泛,可能会发生意料之外的修改和使用,除此之外全局变量比局部变量拥有更长的生命周期,可能导致对象占用的内存长时间无法被[垃圾回收](https://zh.wikipedia.org/wiki/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%B8))。事实上,减少对全局变量的使用,也是降低代码之间耦合度的一个重要举措,同时也是对[迪米特法则](https://zh.wikipedia.org/zh-hans/%E5%BE%97%E5%A2%A8%E5%BF%92%E8%80%B3%E5%AE%9A%E5%BE%8B)的践行。减少全局变量的使用就意味着我们应该尽量让变量的作用域在函数的内部,但是如果我们希望将一个局部变量的生命周期延长,使其在定义它的函数调用结束后依然可以使用它的值,这时候就需要使用[闭包](https://zh.wikipedia.org/wiki/%E9%97%AD%E5%8C%85_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)),这个我们在后续的内容中进行讲解。 + +```Python +def main(): + # Todo: Add your code here + pass + + +if __name__ == '__main__': + main() +``` \ No newline at end of file diff --git a/Jax_code/Day7/Code.py b/Jax_code/Day7/Code.py new file mode 100644 index 000000000..92bc79e32 --- /dev/null +++ b/Jax_code/Day7/Code.py @@ -0,0 +1,199 @@ +s1 = 'hello ' * 3 +print(s1) # hello hello hello +s2 = 'world' +s1 += s2 +print(s1) # hello hello hello world +print('ll' in s1) # True +print('good' in s1) # False +str2 = 'abc123456' +# 从字符串中取出指定位置的字符(下标运算) +print(str2[2]) # c +# 字符串切片(从指定的开始索引到指定的结束索引) +print(str2[2:5]) # c12 +print(str2[2:]) # c123456 +print(str2[2::2]) # c246 +print(str2[::2]) # ac246 +print(str2[::-1]) # 654321cba +print(str2[-3:-1]) # 45 + +#%% +str1 = 'hello, world!' +# 通过内置函数len计算字符串的长度 +print(len(str1)) # 13 +# 获得字符串首字母大写的拷贝 +print(str1.capitalize()) # Hello, world! +# 获得字符串每个单词首字母大写的拷贝 +print(str1.title()) # Hello, World! +# 获得字符串变大写后的拷贝 +print(str1.upper()) # HELLO, WORLD! +# 从字符串中查找子串所在位置 +print(str1.find('or')) # 8 +print(str1.find('shit')) # -1 +# 与find类似但找不到子串时会引发异常 +# print(str1.index('or')) +# print(str1.index('shit')) +# 检查字符串是否以指定的字符串开头 +print(str1.startswith('He')) # False +print(str1.startswith('hel')) # True +# 检查字符串是否以指定的字符串结尾 +print(str1.endswith('!')) # True +# 将字符串以指定的宽度居中并在两侧填充指定的字符 +print(str1.center(50, '*')) +# 将字符串以指定的宽度靠右放置左侧填充指定的字符 +print(str1.rjust(50, ' ')) +str2 = 'abc123456' +# 检查字符串是否由数字构成 +print(str2.isdigit()) # False +# 检查字符串是否以字母构成 +print(str2.isalpha()) # False +# 检查字符串是否以数字和字母构成 +print(str2.isalnum()) # True +str3 = ' jackfrued@126.com ' +print(str3) +# 获得字符串修剪左右两侧空格之后的拷贝 +print(str3.strip()) + +# %% +def fib(n): + a, b = 0, 1 + for _ in range(n): + a, b = b, a + b + yield a + + +def main(): + for val in fib(20): + print(val) + + +if __name__ == '__main__': + main() + +# %% +t = ('骆昊', 38, True, '四川成都') +# 遍历元组中的值 +for member in t: + print(member) +# 重新给元组赋值 +# t[0] = '王大锤' # TypeError +# 变量t重新引用了新的元组原来的元组将被垃圾回收 +t = ('王大锤', 20, True, '云南昆明') +print(t) +# 将元组转换成列表 +person = list(t) +print(person) +# 列表是可以修改它的元素的 +person[0] = '李小龙' +person[1] = 25 +print(person) +# 将列表转换成元组 +fruits_list = ['apple', 'banana', 'orange'] +fruits_tuple = tuple(fruits_list) +print(fruits_tuple) +#%% +import sys + +list2 = [] +tuple2 = () +str = 'Magical Line' +print(sys.getsizeof(list2)) +print(str.center(20, '*')) +print(sys.getsizeof(tuple2)) + +# %% +set1 = {1, 2, 3, 3, 3, 2} +print(set1) +print('Length =', len(set1)) +# 创建集合的构造器语法(面向对象部分会进行详细讲解) +set2 = set(range(1, 10)) +set3 = set((1, 2, 3, 3, 2, 1)) +print(set2, set3) +# 创建集合的推导式语法(推导式也可以用于推导集合) +set4 = {num for num in range(1, 100) if num % 3 == 0 or num % 5 == 0} +print(set4) + +set2.update([10, 11]) +set2.discard(5) +print(set2) +# +# %% +import os +import time + + +def main(): + content = '北京欢迎你为你开天辟地…………' + while True: + # 清理屏幕上的输出 + os.system('cls') # os.system('clear') + print(content) + # 休眠200毫秒 + time.sleep(0.2) + content = content[1:] + content[0] + + +if __name__ == '__main__': + main() +# %% +def main(): + persons = [True] * 30 + counter, index, number = 0, 0, 0 + while counter < 15: + if persons[index]: + number += 1 + if number == 9: + persons[index] = False + counter += 1 + number = 0 + index += 1 + index %= 30 + for person in persons: + print('基' if person else '非', end='') + + +if __name__ == '__main__': + main() +# %% +import os + + +def print_board(board): + print(board['TL'] + '|' + board['TM'] + '|' + board['TR']) + print('-+-+-') + print(board['ML'] + '|' + board['MM'] + '|' + board['MR']) + print('-+-+-') + print(board['BL'] + '|' + board['BM'] + '|' + board['BR']) + + +def main(): + init_board = { + 'TL': ' ', 'TM': ' ', 'TR': ' ', + 'ML': ' ', 'MM': ' ', 'MR': ' ', + 'BL': ' ', 'BM': ' ', 'BR': ' ' + } + begin = True + while begin: + curr_board = init_board.copy() + begin = False + turn = 'x' + counter = 0 + os.system('clear') + print_board(curr_board) + while counter < 9: + move = input('轮到%s走棋, 请输入位置: ' % turn) + if curr_board[move] == ' ': + counter += 1 + curr_board[move] = turn + if turn == 'x': + turn = 'o' + else: + turn = 'x' + os.system('clear') + print_board(curr_board) + choice = input('再玩一局?(yes|no)') + begin = choice == 'yes' + + +if __name__ == '__main__': + main() +# %% diff --git a/Jax_code/Day7/Note.md b/Jax_code/Day7/Note.md new file mode 100644 index 000000000..1254127eb --- /dev/null +++ b/Jax_code/Day7/Note.md @@ -0,0 +1,300 @@ +### 使用字符串 + +```Python +s1 = 'hello, world!' +s2 = "hello, world!" +# 以三个双引号或单引号开头的字符串可以折行 +s3 = """ +hello, +world! +""" +print(s1, s2, s3, end='') +``` + +Python为字符串类型提供了非常丰富的运算符,我们可以使用`+`运算符来实现字符串的拼接,可以使用`*`运算符来重复一个字符串的内容,可以使用`in`和`not in`来判断一个字符串是否包含另外一个字符串(成员运算),我们也可以用`[]`和`[:]`运算符从字符串取出某个字符或某些字符(切片运算),代码如下所示。 + +```Python +s1 = 'hello ' * 3 +print(s1) # hello hello hello +s2 = 'world' +s1 += s2 +print(s1) # hello hello hello world +print('ll' in s1) # True +print('good' in s1) # False +str2 = 'abc123456' +# 从字符串中取出指定位置的字符(下标运算) +print(str2[2]) # c +# 字符串切片(从指定的开始索引到指定的结束索引) +print(str2[2:5]) # c12 +print(str2[2:]) # c123456 +print(str2[2::2]) # c246 +print(str2[::2]) # ac246 +print(str2[::-1]) # 654321cba +print(str2[-3:-1]) # 45 +``` + +在Python中,我们还可以通过一系列的方法来完成对字符串的处理,代码如下所示。 + +```Python +str1 = 'hello, world!' +# 通过内置函数len计算字符串的长度 +print(len(str1)) # 13 +# 获得字符串首字母大写的拷贝 +print(str1.capitalize()) # Hello, world! +# 获得字符串每个单词首字母大写的拷贝 +print(str1.title()) # Hello, World! +# 获得字符串变大写后的拷贝 +print(str1.upper()) # HELLO, WORLD! +# 从字符串中查找子串所在位置 +print(str1.find('or')) # 8 +print(str1.find('shit')) # -1 +# 与find类似但找不到子串时会引发异常 +# print(str1.index('or')) +# print(str1.index('shit')) +# 检查字符串是否以指定的字符串开头 +print(str1.startswith('He')) # False +print(str1.startswith('hel')) # True +# 检查字符串是否以指定的字符串结尾 +print(str1.endswith('!')) # True +# 将字符串以指定的宽度居中并在两侧填充指定的字符 +print(str1.center(50, '*')) +# 将字符串以指定的宽度靠右放置左侧填充指定的字符 +print(str1.rjust(50, ' ')) +str2 = 'abc123456' +# 检查字符串是否由数字构成 +print(str2.isdigit()) # False +# 检查字符串是否以字母构成 +print(str2.isalpha()) # False +# 检查字符串是否以数字和字母构成 +print(str2.isalnum()) # True +str3 = ' jackfrued@126.com ' +print(str3) +# 获得字符串修剪左右两侧空格之后的拷贝 +print(str3.strip()) +``` + +和字符串一样,列表也可以做切片操作,通过切片操作我们可以实现对列表的复制或者将列表中的一部分取出来创建出新的列表,代码如下所示。 + +```Python +fruits = ['grape', 'apple', 'strawberry', 'waxberry'] +fruits += ['pitaya', 'pear', 'mango'] +# 列表切片 +fruits2 = fruits[1:4] +print(fruits2) # apple strawberry waxberry +# 可以通过完整切片操作来复制列表 +fruits3 = fruits[:] +print(fruits3) # ['grape', 'apple', 'strawberry', 'waxberry', 'pitaya', 'pear', 'mango'] +fruits4 = fruits[-3:-1] +print(fruits4) # ['pitaya', 'pear'] +# 可以通过反向切片操作来获得倒转后的列表的拷贝 +fruits5 = fruits[::-1] +print(fruits5) # ['mango', 'pear', 'pitaya', 'waxberry', 'strawberry', 'apple', 'grape'] +``` + +下面的代码实现了对列表的排序操作。 + +```Python +list1 = ['orange', 'apple', 'zoo', 'internationalization', 'blueberry'] +list2 = sorted(list1) +# sorted函数返回列表排序后的拷贝不会修改传入的列表 +# 函数的设计就应该像sorted函数一样尽可能不产生副作用 +list3 = sorted(list1, reverse=True) +# 通过key关键字参数指定根据字符串长度进行排序而不是默认的字母表顺序 +list4 = sorted(list1, key=len) +print(list1) +print(list2) +print(list3) +print(list4) +# 给列表对象发出排序消息直接在列表对象上进行排序 +list1.sort(reverse=True) +print(list1) +``` + +### 生成式和生成器 + +我们还可以使用列表的生成式语法来创建列表,代码如下所示。 + +```Python +f = [x for x in range(1, 10)] +print(f) +f = [x + y for x in 'ABCDE' for y in '1234567'] +print(f) +# 用列表的生成表达式语法创建列表容器 +# 用这种语法创建列表之后元素已经准备就绪所以需要耗费较多的内存空间 +f = [x ** 2 for x in range(1, 1000)] +print(sys.getsizeof(f)) # 查看对象占用内存的字节数 +print(f) +# 请注意下面的代码创建的不是一个列表而是一个生成器对象 +# 通过生成器可以获取到数据但它不占用额外的空间存储数据 +# 每次需要数据的时候就通过内部的运算得到数据(需要花费额外的时间) +f = (x ** 2 for x in range(1, 1000)) +print(sys.getsizeof(f)) # 相比生成式生成器不占用存储数据的空间 +print(f) +for val in f: + print(val) +``` + +除了上面提到的生成器语法,Python中还有另外一种定义生成器的方式,就是通过`yield`关键字将一个普通函数改造成生成器函数。下面的代码演示了如何实现一个生成[斐波拉切数列](https://zh.wikipedia.org/wiki/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97)的生成器。所谓斐波拉切数列可以通过下面[递归](https://zh.wikipedia.org/wiki/%E9%80%92%E5%BD%92)的方法来进行定义: + +![$${\displaystyle F_{0}=0}$$](./res/formula_6.png) + +![$${\displaystyle F_{1}=1}$$](./res/formula_7.png) + +![$${\displaystyle F_{n}=F_{n-1}+F_{n-2}}({n}\geq{2})$$](./res/formula_8.png) + +![](./res/fibonacci-blocks.png) + +```Python +def fib(n): + a, b = 0, 1 + for _ in range(n): + a, b = b, a + b + yield a + + +def main(): + for val in fib(20): + print(val) + + +if __name__ == '__main__': + main() +``` + +### 使用元组 + +Python中的元组与列表类似也是一种容器数据类型,可以用一个变量(对象)来存储多个数据,不同之处在于元组的元素不能修改,在前面的代码中我们已经不止一次使用过元组了。顾名思义,我们把多个元素组合到一起就形成了一个元组,所以它和列表一样可以保存多条数据。下面的代码演示了如何定义和使用元组。 + +```Python +# 定义元组 +t = ('骆昊', 38, True, '四川成都') +print(t) +# 获取元组中的元素 +print(t[0]) +print(t[3]) +# 遍历元组中的值 +for member in t: + print(member) +# 重新给元组赋值 +# t[0] = '王大锤' # TypeError +# 变量t重新引用了新的元组原来的元组将被垃圾回收 +t = ('王大锤', 20, True, '云南昆明') +print(t) +# 将元组转换成列表 +person = list(t) +print(person) +# 列表是可以修改它的元素的 +person[0] = '李小龙' +person[1] = 25 +print(person) +# 将列表转换成元组 +fruits_list = ['apple', 'banana', 'orange'] +fruits_tuple = tuple(fruits_list) +print(fruits_tuple) +``` + +这里有一个非常值得探讨的问题,我们已经有了列表这种数据结构,为什么还需要元组这样的类型呢? + +1. 元组中的元素是无法修改的,事实上我们在项目中尤其是[多线程](https://zh.wikipedia.org/zh-hans/%E5%A4%9A%E7%BA%BF%E7%A8%8B)环境(后面会讲到)中可能更喜欢使用的是那些不变对象(一方面因为对象状态不能修改,所以可以避免由此引起的不必要的程序错误,简单的说就是一个不变的对象要比可变的对象更加容易维护;另一方面因为没有任何一个线程能够修改不变对象的内部状态,一个不变对象自动就是线程安全的,这样就可以省掉处理同步化的开销。一个不变对象可以方便的被共享访问)。所以结论就是:如果不需要对元素进行添加、删除、修改的时候,可以考虑使用元组,当然如果一个方法要返回多个值,使用元组也是不错的选择。 +2. 元组在创建时间和占用的空间上面都优于列表。我们可以使用sys模块的getsizeof函数来检查存储同样的元素的元组和列表各自占用了多少内存空间,这个很容易做到。我们也可以在ipython中使用魔法指令%timeit来分析创建同样内容的元组和列表所花费的时间,下图是我的macOS系统上测试的结果。 + +### 使用集合 + +Python中的集合跟数学上的集合是一致的,不允许有重复元素,而且可以进行交集、并集、差集等运算。 + +![](./res/python-set.png) + +可以按照下面代码所示的方式来创建和使用集合。 + +```Python +# 创建集合的字面量语法 +set1 = {1, 2, 3, 3, 3, 2} +print(set1) +print('Length =', len(set1)) +# 创建集合的构造器语法(面向对象部分会进行详细讲解) +set2 = set(range(1, 10)) +set3 = set((1, 2, 3, 3, 2, 1)) +print(set2, set3) +# 创建集合的推导式语法(推导式也可以用于推导集合) +set4 = {num for num in range(1, 100) if num % 3 == 0 or num % 5 == 0} +print(set4) +``` + +向集合添加元素和从集合删除元素。 + +```Python +set1.add(4) +set1.add(5) +set2.update([11, 12]) +set2.discard(5) +if 4 in set2: + set2.remove(4) +print(set1, set2) +print(set3.pop()) +print(set3) +``` + +集合的成员、交集、并集、差集等运算。 + +```Python +# 集合的交集、并集、差集、对称差运算 +print(set1 & set2) +# print(set1.intersection(set2)) +print(set1 | set2) +# print(set1.union(set2)) +print(set1 - set2) +# print(set1.difference(set2)) +print(set1 ^ set2) +# print(set1.symmetric_difference(set2)) +# 判断子集和超集 +print(set2 <= set1) +# print(set2.issubset(set1)) +print(set3 <= set1) +# print(set3.issubset(set1)) +print(set1 >= set2) +# print(set1.issuperset(set2)) +print(set1 >= set3) +# print(set1.issuperset(set3)) +``` + +> **说明:** Python中允许通过一些特殊的方法来为某种类型或数据结构自定义运算符(后面的章节中会讲到),上面的代码中我们对集合进行运算的时候可以调用集合对象的方法,也可以直接使用对应的运算符,例如`&`运算符跟intersection方法的作用就是一样的,但是使用运算符让代码更加直观。 + +### 使用字典 + +字典是另一种可变容器模型,Python中的字典跟我们生活中使用的字典是一样一样的,它可以存储任意类型对象,与列表、集合不同的是,字典的每个元素都是由一个键和一个值组成的“键值对”,键和值通过冒号分开。下面的代码演示了如何定义和使用字典。 + +```Python +# 创建字典的字面量语法 +scores = {'骆昊': 95, '白元芳': 78, '狄仁杰': 82} +print(scores) +# 创建字典的构造器语法 +items1 = dict(one=1, two=2, three=3, four=4) +# 通过zip函数将两个序列压成字典 +items2 = dict(zip(['a', 'b', 'c'], '123')) +# 创建字典的推导式语法 +items3 = {num: num ** 2 for num in range(1, 10)} +print(items1, items2, items3) +# 通过键可以获取字典中对应的值 +print(scores['骆昊']) +print(scores['狄仁杰']) +# 对字典中所有键值对进行遍历 +for key in scores: + print(f'{key}: {scores[key]}') +# 更新字典中的元素 +scores['白元芳'] = 65 +scores['诸葛王朗'] = 71 +scores.update(冷面=67, 方启鹤=85) +print(scores) +if '武则天' in scores: + print(scores['武则天']) +print(scores.get('武则天')) +# get方法也是通过键获取对应的值但是可以设置默认值 +print(scores.get('武则天', 60)) +# 删除字典中的元素 +print(scores.popitem()) +print(scores.popitem()) +print(scores.pop('骆昊', 100)) +# 清空字典 +scores.clear() +print(scores) +``` \ No newline at end of file diff --git a/Jax_code/Day8/Code.py b/Jax_code/Day8/Code.py new file mode 100644 index 000000000..d6f36972e --- /dev/null +++ b/Jax_code/Day8/Code.py @@ -0,0 +1,19 @@ +class Test: + + def __init__(self, foo): + self.__foo = foo + + def __bar(self): + print(self.__foo) + print('__bar') + + +def main(): + test = Test('hello') + # AttributeError: 'Test' object has no attribute '__bar' + test._Test__bar() + + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Jax_code/Day8/Note.md b/Jax_code/Day8/Note.md new file mode 100644 index 000000000..0fafbcc7e --- /dev/null +++ b/Jax_code/Day8/Note.md @@ -0,0 +1,29 @@ +### 访问可见性问题 + +对于上面的代码,有C++、Java、C#等编程经验的程序员可能会问,我们给`Student`对象绑定的`name`和`age`属性到底具有怎样的访问权限(也称为可见性)。因为在很多面向对象编程语言中,我们通常会将对象的属性设置为私有的(private)或受保护的(protected),简单的说就是不允许外界访问,而对象的方法通常都是公开的(public),因为公开的方法就是对象能够接受的消息。在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头,下面的代码可以验证这一点。 +```Python +class Test: + + def __init__(self, foo): + self.__foo = foo + + def __bar(self): + print(self.__foo) + print('__bar') + + +def main(): + test = Test('hello') + # AttributeError: 'Test' object has no attribute '__bar' + test.__bar() + # AttributeError: 'Test' object has no attribute '__foo' + print(test.__foo) + + +if __name__ == "__main__": + main() +``` + +### 面向对象的支柱 + +面向对象有三大支柱:封装、继承和多态。后面两个概念在下一个章节中进行详细的说明,这里我们先说一下什么是封装。我自己对封装的理解是"隐藏一切可以隐藏的实现细节,只向外界暴露(提供)简单的编程接口"。我们在类中定义的方法其实就是把数据和对数据的操作封装起来了,在我们创建了对象之后,只需要给对象发送一个消息(调用方法)就可以执行方法中的代码,也就是说我们只需要知道方法的名字和传入的参数(方法的外部视图),而不需要知道方法内部的实现细节(方法的内部视图)。 diff --git a/Jax_code/Day9/Code.py b/Jax_code/Day9/Code.py new file mode 100644 index 000000000..576c42bab --- /dev/null +++ b/Jax_code/Day9/Code.py @@ -0,0 +1,137 @@ +class Person(object): + + def __init__(self, name, age): + self._name = name + self._age = age + + # 访问器 - getter方法 + @property + def name(self): + return self._name + + # 修改器 - setter方法 + @name.setter + def name(self, name): + self._name = name + # 访问器 - getter方法 + @property + def age(self): + return self._age + + + def play(self): + if self._age <= 16: + print('%s正在玩飞行棋.' % self._name) + else: + print('%s正在玩斗地主.' % self._name) + + +def main(): + person = Person('王大锤', 12) + person.play() + person.name = 'jax' + person.play() + # person.name = '白元芳' # AttributeError: can't set attribute + + +if __name__ == '__main__': + main() + +# %% +from math import sqrt + + +class Triangle(object): + + def __init__(self, a, b, c): + self._a = a + self._b = b + self._c = c + + @staticmethod + def is_valid(a, b, c): + return a + b > c and b + c > a and a + c > b + + def perimeter(self): + return self._a + self._b + self._c + + def area(self): + half = self.perimeter() / 2 + return sqrt(half * (half - self._a) * + (half - self._b) * (half - self._c)) + + +def main(): + a, b, c = 3, 4, 5 + # 静态方法和类方法都是通过给类发消息来调用的 + if Triangle.is_valid(a, b, c): + t = Triangle(a, b, c) + print(t.perimeter()) + # 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数 + # print(Triangle.perimeter(t)) + print(t.area()) + # print(Triangle.area(t)) + else: + print('无法构成三角形.') + + +if __name__ == '__main__': + main() +# %% +from math import sqrt + + +class Triangle(object): + + def __init__(self, a, b, c): + self._a = a + self._b = b + self._c = c + + @staticmethod + def is_valid(a, b, c): + return a + b > c and b + c > a and a + c > b + + def perimeter(self): + return self._a + self._b + self._c + +def main(): + a, b, c = 3, 4, 5 + # 静态方法和类方法都是通过给类发消息来调用的 + print(Triangle.is_valid(a, b, c)) + +if __name__ == '__main__': + main() +# %% +from math import sqrt + + +class Triangle(object): + + def __init__(self, a, b, c): + self._a = a + self._b = b + self._c = c + + + @classmethod + def create_basic(cls): + return cls(1,2,3) + + @staticmethod + def is_valid(a, b, c): + return a + b > c and b + c > a and a + c > b + + def perimeter(self): + return self._a + self._b + self._c + +def main(): + a, b, c = 3, 4, 5 + # 静态方法和类方法都是通过给类发消息来调用的 + print(True if Triangle.is_valid(a, b, c) else False) + triangle = Triangle.create_basic() + print(triangle.perimeter()) + +if __name__ == '__main__': + main() +# %% diff --git a/Jax_code/Day9/Note.md b/Jax_code/Day9/Note.md new file mode 100644 index 000000000..47aa0809c --- /dev/null +++ b/Jax_code/Day9/Note.md @@ -0,0 +1,346 @@ +### @property装饰器 + +之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我们没有办法检查赋给属性的值是否有效。我们之前的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便,代码如下所示。 + +```Python +class Person(object): + + def __init__(self, name, age): + self._name = name + self._age = age + + # 访问器 - getter方法 + @property + def age(self): + return self._age + + # 修改器 - setter方法 + @age.setter + def age(self, age): + self._age = age + +def main(): + person = Person('王大锤', 12) + person.age = 22 + # person.name = '白元芳' # AttributeError: can't set attribute + + +if __name__ == '__main__': + main() +``` + +### \_\_slots\_\_魔法 + +我们讲到这里,不知道大家是否已经意识到,Python是一门[动态语言](https://zh.wikipedia.org/wiki/%E5%8A%A8%E6%80%81%E8%AF%AD%E8%A8%80)。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义\_\_slots\_\_变量来进行限定。需要注意的是\_\_slots\_\_的限定只对当前类的对象生效,对子类并不起任何作用。 + +```Python +class Person(object): + + # 限定Person对象只能绑定_name, _age和_gender属性 + __slots__ = ('_name', '_age', '_gender') + + def __init__(self, name, age): + self._name = name + self._age = age + +``` + +### 静态方法和类方法 + +之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示。 + +```Python +from math import sqrt + + +class Triangle(object): + + def __init__(self, a, b, c): + self._a = a + self._b = b + self._c = c + + @classmethod + def create_basic(cls): + return cls(1,2,3) + + @staticmethod + def is_valid(a, b, c): + return a + b > c and b + c > a and a + c > b + + def perimeter(self): + return self._a + self._b + self._c + +def main(): + a, b, c = 3, 4, 5 + # 静态方法和类方法都是通过给类发消息来调用的 + print(True if Triangle.is_valid(a, b, c)) + triangle = Triangle.create_basic() + print(triangle.perimeter()) #6 + + +if __name__ == '__main__': + main() +``` + +### 继承和多态 + +刚才我们提到了,可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,对应的原则称之为[里氏替换原则](https://zh.wikipedia.org/wiki/%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8D%A2%E5%8E%9F%E5%88%99)。下面我们先看一个继承的例子。 + + +```Python + def __init__(self, name, age, grade): + super().__init__(name, age) + self._grade = grade +``` + +子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。 + +```Python +from abc import ABCMeta, abstractmethod + +class Pet(object, metaclass=ABCMeta): + """宠物""" + + def __init__(self, nickname): + self._nickname = nickname + + @abstractmethod + def make_voice(self): + """发出声音""" + pass + +class Dog(Pet): + """狗""" + + def make_voice(self): + print('%s: 汪汪汪...' % self._nickname) + +def main(): + pet = Cat('凯蒂') + pet.make_voice() + +if __name__ == '__main__': + main() +``` + +在上面的代码中,我们将`Pet`类处理成了一个抽象类,所谓抽象类就是不能够创建对象的类,这种类的存在就是专门为了让其他类去继承它。Python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过`abc`模块的`ABCMeta`元类和`abstractmethod`包装器来达到抽象类的效果,如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)。上面的代码中,`Dog`和`Cat`两个子类分别对`Pet`类中的`make_voice`抽象方法进行了重写并给出了不同的实现版本,当我们在`main`函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)。 + + +#### 案例2:扑克游戏。 +```Python +import random + + +class Card(object): + """一张牌""" + + def __init__(self, suite, face): + self._suite = suite + self._face = face + + @property + def face(self): + return self._face + + @property + def suite(self): + return self._suite + + def __str__(self): + if self._face == 1: + face_str = 'A' + elif self._face == 11: + face_str = 'J' + elif self._face == 12: + face_str = 'Q' + elif self._face == 13: + face_str = 'K' + else: + face_str = str(self._face) + return '%s%s' % (self._suite, face_str) + + def __repr__(self): + return self.__str__() + + +class Poker(object): + """一副牌""" + + def __init__(self): + self._cards = [Card(suite, face) + for suite in '♠♥♣♦' + for face in range(1, 14)] + self._current = 0 + + @property + def cards(self): + return self._cards + + def shuffle(self): + """洗牌(随机乱序)""" + self._current = 0 + random.shuffle(self._cards) + + @property + def next(self): + """发牌""" + card = self._cards[self._current] + self._current += 1 + return card + + @property + def has_next(self): + """还有没有牌""" + return self._current < len(self._cards) + + +class Player(object): + """玩家""" + + def __init__(self, name): + self._name = name + self._cards_on_hand = [] + + @property + def name(self): + return self._name + + @property + def cards_on_hand(self): + return self._cards_on_hand + + def get(self, card): + """摸牌""" + self._cards_on_hand.append(card) + + def arrange(self, card_key): + """玩家整理手上的牌""" + self._cards_on_hand.sort(key=card_key) + + +# 排序规则-先根据花色再根据点数排序 +def get_key(card): + return (card.suite, card.face) + + +def main(): + p = Poker() + p.shuffle() + players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')] + for _ in range(13): + for player in players: + player.get(p.next) + for player in players: + print(player.name + ':', end=' ') + player.arrange(get_key) + print(player.cards_on_hand) + + +if __name__ == '__main__': + main() +``` + +#### 案例3:工资结算系统。 + +```Python +""" +某公司有三种类型的员工 分别是部门经理、程序员和销售员 +需要设计一个工资结算系统 根据提供的员工信息来计算月薪 +部门经理的月薪是每月固定15000元 +程序员的月薪按本月工作时间计算 每小时150元 +销售员的月薪是1200元的底薪加上销售额5%的提成 +""" +from abc import ABCMeta, abstractmethod + + +class Employee(object, metaclass=ABCMeta): + """员工""" + + def __init__(self, name): + """ + 初始化方法 + + :param name: 姓名 + """ + self._name = name + + @property + def name(self): + return self._name + + @abstractmethod + def get_salary(self): + """ + 获得月薪 + + :return: 月薪 + """ + pass + + +class Manager(Employee): + """部门经理""" + + def get_salary(self): + return 15000.0 + + +class Programmer(Employee): + """程序员""" + + def __init__(self, name, working_hour=0): + super().__init__(name) + self._working_hour = working_hour + + @property + def working_hour(self): + return self._working_hour + + @working_hour.setter + def working_hour(self, working_hour): + self._working_hour = working_hour if working_hour > 0 else 0 + + def get_salary(self): + return 150.0 * self._working_hour + + +class Salesman(Employee): + """销售员""" + + def __init__(self, name, sales=0): + super().__init__(name) + self._sales = sales + + @property + def sales(self): + return self._sales + + @sales.setter + def sales(self, sales): + self._sales = sales if sales > 0 else 0 + + def get_salary(self): + return 1200.0 + self._sales * 0.05 + + +def main(): + emps = [ + Manager('刘备'), Programmer('诸葛亮'), + Manager('曹操'), Salesman('荀彧'), + Salesman('吕布'), Programmer('张辽'), + Programmer('赵云') + ] + for emp in emps: + if isinstance(emp, Programmer): + emp.working_hour = int(input('请输入%s本月工作时间: ' % emp.name)) + elif isinstance(emp, Salesman): + emp.sales = float(input('请输入%s本月销售额: ' % emp.name)) + # 同样是接收get_salary这个消息但是不同的员工表现出了不同的行为(多态) + print('%s本月工资为: ¥%s元' % + (emp.name, emp.get_salary())) + + +if __name__ == '__main__': + main() +``` \ No newline at end of file