編寫程序 addr-manage.py 實(shí)現(xiàn)通訊錄管理系統(tǒng),通訊錄包含若干聯(lián)系人,每個(gè)聯(lián)系人包括:姓名、地址、電話 3 項(xiàng)內(nèi)容。程序提供 4 項(xiàng)基本功能:增加聯(lián)系人: 用戶輸入姓名、地址、電話等信息,將信息保存在一個(gè)列表中列出聯(lián)系人: 打印輸出所有聯(lián)系人的信息查詢聯(lián)系人: 用戶輸入聯(lián)系人姓名,打印輸出該聯(lián)系人的信息刪除聯(lián)系人: 用戶輸入聯(lián)系人姓名,從通訊錄中刪除該聯(lián)系人通過命令行界面實(shí)現(xiàn)以上功能,程序 addr-manage.py 運(yùn)行時(shí)首先打印一個(gè)菜單,如下所示:C:\> python addr-manage.py1. create person2. list all persons3. query person4. delete person5. quitEnter a number(1-5): 總共有 5 個(gè)選項(xiàng),用戶輸入對(duì)應(yīng)的數(shù)字選擇相應(yīng)的功能,如下表所示:數(shù)字選項(xiàng)功能描述1. create person增加聯(lián)系人2. list all persons列出聯(lián)系人3. query person查詢聯(lián)系人4. delete person刪除聯(lián)系人5. quit退出通訊錄程序
編寫程序 addressBook.py 實(shí)現(xiàn)通訊錄管理系統(tǒng),通訊錄包含若干聯(lián)系人,每個(gè)聯(lián)系人包括:姓名、地址、電話 3 項(xiàng)內(nèi)容。程序提供 4 項(xiàng)基本功能:增加聯(lián)系人: 用戶輸入姓名、地址、電話等信息,將信息保存在一個(gè)列表中列出聯(lián)系人: 打印輸出所有聯(lián)系人的信息查詢聯(lián)系人: 用戶輸入聯(lián)系人姓名,打印輸出該聯(lián)系人的信息刪除聯(lián)系人: 用戶輸入聯(lián)系人姓名,從通訊錄中刪除該聯(lián)系人通過命令行界面實(shí)現(xiàn)以上功能,程序 addressBook.py 運(yùn)行時(shí)首先打印一個(gè)菜單,如下所示:C:\> python addressBook.py1. create person2. list all persons3. query person4. delete person5. quitEnter a number(1-5): 總共有 5 個(gè)選項(xiàng),用戶輸入對(duì)應(yīng)的數(shù)字選擇相應(yīng)的功能,如下表所示:數(shù)字選項(xiàng)功能描述1. create person增加聯(lián)系人2. list all persons列出聯(lián)系人3. query person查詢聯(lián)系人4. delete person刪除聯(lián)系人5. quit退出通訊錄程序
可以用如下修飾符來實(shí)現(xiàn)僅在按下相應(yīng)按鍵時(shí)才觸發(fā)鼠標(biāo)或鍵盤事件的監(jiān)聽器。.ctrl:.alt:.shift:.meta:注意:在 Mac 系統(tǒng)鍵盤上,meta 對(duì)應(yīng) command 鍵 (?)。在 Windows 系統(tǒng)鍵盤 meta 對(duì)應(yīng) Windows 徽標(biāo)鍵 (?)。在 Sun 操作系統(tǒng)鍵盤上,meta 對(duì)應(yīng)實(shí)心寶石鍵 (◆)。在其他特定鍵盤上,尤其在 MIT 和 Lisp 機(jī)器的鍵盤、以及其后繼產(chǎn)品,比如 Knight 鍵盤、space-cadet 鍵盤,meta 被標(biāo)記為“META”。在 Symbolics 鍵盤上,meta 被標(biāo)記為“META”或者“Meta”。例如:<!-- Alt + C --><input @keyup.alt.67="clear"><!-- Ctrl + Click --><div @click.ctrl="doSomething">Do something</div>Vue提供了.exact修飾符,實(shí)現(xiàn)單獨(dú)的系統(tǒng)按鍵的事件。例如:<!-- 即使 Alt 或 Shift 被一同按下時(shí)也會(huì)觸發(fā) --><button @click.ctrl="onClick">A</button><!-- 有且只有 Ctrl 被按下的時(shí)候才觸發(fā) --><button @click.ctrl.exact="onCtrlClick">A</button><!-- 沒有任何系統(tǒng)修飾符被按下的時(shí)候才觸發(fā) --><button @click.exact="onClick">A</button>
在實(shí)際開發(fā)中,解析邏輯通??梢詥为?dú)提出一個(gè)類似 Utils 的工具類,這樣可以提供給各方使用。本節(jié)放在 MainActivity 中完成,獲取 Button 后在onClick()方法中調(diào)用parse()方法進(jìn)行 Json 數(shù)據(jù)解析。最后將解析結(jié)果放到 TextView 上展示:package com.emercy.myapplication;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.widget.TextView;import org.json.JSONArray;import org.json.JSONObject;public class MainActivity extends Activity { public static final String JSON_STRING = "{\"Engineers\":[{\"skill\":\"Android\",\"language\":\"Java\",\"years\":\"5\"},{\"skill\":\"iOS\",\"language\":\"Object C\",\"years\":\"2\"},{\"skill\":\"Server\",\"language\":\"php\",\"years\":\"8\"}]}"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.parse).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { parse(JSON_STRING); } }); } private void parse(String jsonStr) { TextView textView = (TextView) findViewById(R.id.json); try { JSONObject engineers = new JSONObject(jsonStr); JSONArray array = engineers.getJSONArray("Engineers"); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < array.length(); i++) { JSONObject engineer = array.getJSONObject(i); String skill = engineer.getString("skill"); String language = engineer.getString("language"); int years = engineer.getInt("years"); stringBuilder.append("Engineer ").append(i) .append(": skill is ").append(skill) .append("; language is ").append(language) .append("; years is ").append(years).append("\n"); } textView.setText(stringBuilder.toString()); } catch (Exception e) { e.printStackTrace(); } }}核心解析邏輯都在parse()方法中,拿到 Json String 之后,首先從中去除“工程師”數(shù)組,然后遍歷數(shù)組一次解析“skill”、“l(fā)anguage”、“years”字段,挨個(gè)生成好“工程師”對(duì)象,然后拼接成結(jié)果文本展示到 TextView 上。編譯后點(diǎn)擊解析,結(jié)果如下:
前面講解雙向一對(duì)多的時(shí)候,也提到了級(jí)聯(lián)刪除。最大的印象就是,如果雙方都打開了級(jí)聯(lián)刪除,刪除時(shí)就如同推倒了多米諾骨牌的第一張牌,整個(gè)數(shù)據(jù)鏈都會(huì)刪除。多對(duì)多關(guān)聯(lián)比一對(duì)多關(guān)聯(lián)多了一張中間表,在進(jìn)行級(jí)聯(lián)刪除的時(shí)候,到底會(huì)發(fā)生什么事情?在此也有必要拿出來說一說。為了不讓事情的發(fā)展如山崩一樣不可控制,先打開學(xué)生類的級(jí)聯(lián)操作功能:private Set<Course> courses=new HashSet<Course>(); @ManyToMany(targetEntity = Course.class,cascade=CascadeType.ALL) @JoinTable(name = "score", joinColumns = @JoinColumn(name = "stuId", referencedColumnName = "stuId"), inverseJoinColumns = @JoinColumn(name = "courseId", referencedColumnName = "courseId")) public Set<Course> getCourses() { return courses; }這里使用 CascadeType.ALL。來一段測(cè)試實(shí)例,刪除剛才添加的 HibernateTemplate 同學(xué)。他會(huì)說我好悲慘,才進(jìn)來沒有多久。HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>(); hibernateTemplate.template(new Notify<Student>() { @Override public Student action(Session session) { // 查詢學(xué)生 Student student =(Student)session.get(Student.class, new Integer(23)); session.delete(student); return null; } });無驚無喜,一切按照預(yù)先的設(shè)想進(jìn)行。刪除學(xué)生時(shí),中間表中與此學(xué)生有關(guān)聯(lián)的信息,也就是說此學(xué)生選修的課程信息也自動(dòng)被刪除了。但是,會(huì)有一個(gè)想法,如果刪除課程,則中間表中記錄的與此課程有關(guān)的信息是否會(huì)自動(dòng)刪除呢?OK!開始行動(dòng)之前,可別忘記在課程類中打開級(jí)聯(lián)操作選項(xiàng):嘿嘿!現(xiàn)在兩邊的級(jí)聯(lián)操作功能都已經(jīng)打開。private Set<Student> students=new HashSet<Student>(); @ManyToMany(targetEntity = Student.class, mappedBy = "courses",cascade=CascadeType.ALL) public Set<Student> getStudents() { return students; }打開后,執(zhí)行刪除 C 課程的實(shí)例,誰讓 C 不好學(xué)了。HibernateTemplate<Course> hibernateTemplate = new HibernateTemplate<Course>(); hibernateTemplate.template(new Notify<Course>() { @Override public Course action(Session session) { // 查詢學(xué)生 Course course =(Course)session.get(Course.class, new Integer(2)); session.delete(course); return null; } });這只是一個(gè)很簡(jiǎn)單的代碼,但是卻發(fā)生如雪崩一樣的事件。到底發(fā)生了什么事情?大家進(jìn)入 MySql 看看就知道了。3張表中空空如也,所有數(shù)據(jù)都沒有了。就如同前面講解一對(duì)多的級(jí)聯(lián)刪除一樣。同樣適用于多對(duì)多關(guān)聯(lián)映射之中。因兩邊都已經(jīng)打開了級(jí)聯(lián),刪除操作如同無法控制的壞情緒,刪除課程時(shí),以中間表為連接,反復(fù)來往于三張表,把相關(guān)信息全部刪除。所以,使用級(jí)聯(lián)時(shí)一定要小心,否則,小心臟真的有點(diǎn)受不了。
gunicorn 是一個(gè) Unix 上被廣泛使用的高性能的 Python WSGI UNIX HTTP Server。和大多數(shù)的 Web 框架兼容,并具有實(shí)現(xiàn)簡(jiǎn)單,輕量級(jí),高性能等特點(diǎn)。用它部署 Flask/Django 這樣的 Python Web 項(xiàng)目再合適不過了。它的安裝和使用都十分方便,安裝直接在虛擬環(huán)境下執(zhí)行: pip install gunicorn -i https://pypi.tuna.tsinghua.edu.cn/simple,使用有兩種方式:直接使用:# 參數(shù)說明:# -b: 啟動(dòng)綁定ip和端口,0.0.0.0 是指運(yùn)行外面的所有機(jī)器訪問# -w: 啟動(dòng) worker 進(jìn)程數(shù)# --daemon: 后臺(tái)啟動(dòng)(django-manual) [root@server first_django_app]# gunicorn first_django_app.wsgi -b 0.0.0.0:8888 --daemon -w 4配置文件使用:(django-manual) [root@server first_django_app]# cat gunicorn_config.py# gunicorn_config.pyimport loggingimport logging.handlersfrom logging.handlers import WatchedFileHandlerimport osimport multiprocessingbind = '0.0.0.0:8888'# backlog: 服務(wù)器中在pending狀態(tài)的最大連接數(shù),即client處于waiting的數(shù)目。超過這個(gè)數(shù)目, client連接會(huì)得到一個(gè)errorbacklog = 512timeout = 30 workers = multiprocessing.cpu_count() * 2threads = 2loglevel = 'info' access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s %(f)s" "%(a)s"' accesslog = "/var/log/django-manual/gunicorn_access.log" errorlog = "/var/log/django-manual/gunicorn_error.log"(django-manual) [root@server first_django_app]# gunicorn first_django_app.wsgi -c gunicorn_config.py --daemon關(guān)閉進(jìn)程:# 簡(jiǎn)單粗暴的關(guān)閉方式(django-manual) [root@server first_django_app]# killall gunicorn
下面我們列舉了幾個(gè)常用的重要函數(shù),上一節(jié)已經(jīng)講述了 count () , sum () , mean () 函數(shù),這里我們將不再講述:函數(shù)名說明 min 計(jì)算最小值 max 計(jì)算最大值 cumsum 計(jì)算累計(jì)總和 describe 計(jì)算匯總統(tǒng)計(jì)下面我們通過具體的代碼數(shù)據(jù)實(shí)例看一下各個(gè)函數(shù)的應(yīng)用。1. min() 和 max() 函數(shù)這兩個(gè)函數(shù)分別用于計(jì)算指定數(shù)據(jù)集中的最小值和最大值,默認(rèn)的 axis=0 ,按列進(jìn)行計(jì)算。# 導(dǎo)入pandas包import pandas as pd# 初始化數(shù)據(jù)df1=pd.DataFrame([[96,92],[85,None],[69,90]], index=[['語文','數(shù)學(xué)','英語']], columns=[['期中考試','期末考試']])print(df1)# --- 輸出結(jié)果 --- 期中考試 期末考試語文 96 92數(shù)學(xué) 85 89英語 69 90# min 求數(shù)據(jù)集的最小值df1.min()# --- 輸出結(jié)果 ---期中考試 69期末考試 89# max 求數(shù)據(jù)集的最大值df1.max()# --- 輸出結(jié)果 ---期中考試 96期末考試 92# 結(jié)果解析:通過 min() 和 max() 函數(shù)的操作,我們可以看到求出數(shù)據(jù)集中的最大值和最小值,默認(rèn)是按列計(jì)算,也可以通過參數(shù) axis=1 設(shè)置按行計(jì)算;# max 求數(shù)據(jù)集的最大值 axis=1df1.max(axis=1)# --- 輸出結(jié)果 ---語文 96數(shù)學(xué) 89英語 902. cumsum() 函數(shù)該函數(shù)用于計(jì)算累計(jì)數(shù)值總和,默認(rèn)的 axis=0 ,按列進(jìn)行計(jì)算,通過數(shù)據(jù)處理效果要區(qū)分和 sum () 函數(shù)的區(qū)別。# 導(dǎo)入pandas包import pandas as pd# 初始化數(shù)據(jù)df1=pd.DataFrame([[96,92,89,94],[85,89,91,90],[69,90,89,88]], index=[['語文','數(shù)學(xué)','英語']], columns=[['月考1','月考2','月考3','月考4']])print(df1)# --- 輸出結(jié)果 --- 月考1 月考2 月考3 月考4語文 96 92 89 94數(shù)學(xué) 85 89 91 90英語 69 90 89 88# cumsum 計(jì)算累計(jì)總和,默認(rèn)是按列計(jì)算df1.cumsum()# --- 輸出結(jié)果 --- 月考1 月考2 月考3 月考4語文 96 92 89 94數(shù)學(xué) 181 181 180 184英語 250 271 269 272# sum 計(jì)算總和,默認(rèn)是按列計(jì)算df1.sum()# --- 輸出結(jié)果 ---月考1 250月考2 271月考3 269月考4 272# 結(jié)果解析:cumsum() 函數(shù)是累計(jì)求和,默認(rèn)是在列上計(jì)算,可以看到各個(gè)月考的行數(shù)據(jù)“數(shù)學(xué)”是“語文”加“數(shù)學(xué)”成績的和,“英語”則是“語文”加“數(shù)學(xué)”加“英語”的和。而 sum() 函數(shù)則是計(jì)算每列數(shù)據(jù)的總和,對(duì)各行數(shù)據(jù)沒有影響。3. describe() 函數(shù)該函數(shù)用于計(jì)算一些統(tǒng)計(jì)數(shù)據(jù),提供數(shù)據(jù)集的基本信息,包括范圍、大小、波動(dòng)趨勢(shì)等待,用于進(jìn)一步數(shù)據(jù)的分析,該函數(shù)用于計(jì)算列數(shù)據(jù)。該函數(shù)有三個(gè)核心參數(shù):參數(shù)名說明 percentile 可選參數(shù),要包含在輸出中的百分位數(shù),在 0-1 之間,默認(rèn) [.25, .5, .75]include 可選參數(shù),包括的不同數(shù)據(jù)類型列表,默認(rèn)只計(jì)算數(shù)值型,當(dāng)為 all 時(shí)匯總的是所有列的數(shù)據(jù);當(dāng)為 object 時(shí),匯總的是字符串列的數(shù)據(jù);當(dāng)為 number 時(shí),匯總的是數(shù)字列的數(shù)據(jù)。exclude 可選參數(shù),排除的數(shù)據(jù)類型列表下面我們通過代碼操作數(shù)據(jù)集看一下詳細(xì)的使用方法:# 導(dǎo)入pandas包import pandas as pd# 初始化數(shù)據(jù)df1=pd.DataFrame([[96,92,89,94,'A級(jí)'],[85,89,91,90,'C級(jí)'],[69,90,89,88,'B級(jí)']], index=[['語文','數(shù)學(xué)','英語']], columns=[['月考1','月考2','月考3','月考4','表現(xiàn)級(jí)別']])print(df1)# --- 輸出結(jié)果 --- 月考1 月考2 月考3 月考4 表現(xiàn)級(jí)別語文 96 92 89 94 A級(jí)數(shù)學(xué) 85 89 91 90 C級(jí)英語 69 90 89 88 B級(jí)# describe 函數(shù)df1.describe()# --- 輸出結(jié)果 --- 月考1 月考2 月考3 月考4count 3.000000 3.000000 2.000000 3.000000mean 83.333333 90.333333 90.000000 90.666667std 13.576941 1.527525 1.414214 3.055050min 69.000000 89.000000 89.000000 88.00000025% 77.000000 89.500000 89.500000 89.00000050% 85.000000 90.000000 90.000000 90.00000075% 90.500000 91.000000 90.500000 92.000000max 96.000000 92.000000 91.000000 94.000000# 結(jié)果解析:可以看到 describe() 函數(shù)統(tǒng)計(jì)分析后,默認(rèn)的是只計(jì)算數(shù)值型,包含了多種分析信息,count:數(shù)據(jù)數(shù)量,mean:平均值,std:標(biāo)準(zhǔn)差,min:最小值,25%,50%,75%:對(duì)應(yīng)的百分位上的分位數(shù)(計(jì)算方式為最大值減去最小值乘以對(duì)應(yīng)的百分位,再加上最小值),max:最大值。這些統(tǒng)計(jì)項(xiàng)能清晰的讓我們了解一組數(shù)據(jù)集的數(shù)據(jù)情況,進(jìn)而選擇合適的分析模型進(jìn)行分析。# describe 設(shè)置include=‘a(chǎn)ll’df1.describe(include='all')# --- 輸出結(jié)果 --- 月考1 月考2 月考3 月考4 表現(xiàn)級(jí)別count 3.000000 3.000000 3.000000 3.000000 3unique NaN NaN NaN NaN 3top NaN NaN NaN NaN C級(jí)freq NaN NaN NaN NaN 1mean 83.333333 90.333333 89.666667 90.666667 NaNstd 13.576941 1.527525 1.154701 3.055050 NaNmin 69.000000 89.000000 89.000000 88.000000 NaN25% 77.000000 89.500000 89.000000 89.000000 NaN50% 85.000000 90.000000 89.000000 90.000000 NaN75% 90.500000 91.000000 90.000000 92.000000 NaNmax 96.000000 92.000000 91.000000 94.000000 NaN# 結(jié)果解析:通過設(shè)置 include='all' describe() 函數(shù)計(jì)算所有列的數(shù)據(jù),并且針對(duì)字符串型的數(shù)據(jù)會(huì)有 unique:不同的值有多少;top:出現(xiàn)頻率最高的;freq:重復(fù)的次數(shù)。# describe 排除數(shù)值列 exclude=‘number’df1.describe(exclude='number')# --- 輸出結(jié)果 --- 表現(xiàn)級(jí)別count 3unique 3top C級(jí)freq 1# 結(jié)果解析:通過設(shè)置 exclude 排除數(shù)值列,可以看到最后的描述性統(tǒng)計(jì)只有字符串列的數(shù)據(jù)。
import 'reflect-metadata' // 元數(shù)據(jù)的命令式定義,定義對(duì)象或?qū)傩缘脑獢?shù)據(jù)Reflect.defineMetadata(metadataKey, metadataValue, target)Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey) // 檢查對(duì)象或?qū)傩缘脑玩溕鲜欠翊嬖谠獢?shù)據(jù)鍵let result = Reflect.hasMetadata(metadataKey, target)let result = Reflect.hasMetadata(metadataKey, target, propertyKey) // 檢查對(duì)象或?qū)傩允欠翊嬖谧约旱脑獢?shù)據(jù)鍵let result = Reflect.hasMetadata(metadataKey, target)let result = Reflect.hasMetadata(metadataKey, target, propertyKey) // 獲取對(duì)象或?qū)傩栽玩溕显獢?shù)據(jù)鍵的元數(shù)據(jù)值let result = Reflect.getMetadata(metadataKey, target)let result = Reflect.getMetadata(metadataKey, target, propertyKey) // 獲取對(duì)象或?qū)傩缘淖约旱脑獢?shù)據(jù)鍵的元數(shù)據(jù)值let result = Reflect.getOwnMetadata(metadataKey, target)let result = Reflect.getOwnMetadata(metadataKey, target, propertyKey) // 獲取對(duì)象或?qū)傩栽玩溕系乃性獢?shù)據(jù)鍵let result = Reflect.getMetadataKeys(target)let result = Reflect.getMetadataKeys(target, propertyKey) // 獲取對(duì)象或?qū)傩缘乃凶约旱脑獢?shù)據(jù)鍵let result = Reflect.getOwnMetadataKeys(target)let result = Reflect.getOwnMetadataKeys(target, propertyKey) // 從對(duì)象或?qū)傩灾袆h除元數(shù)據(jù)let result = Reflect.deleteMetadata(metadataKey, target)let result = Reflect.deleteMetadata(metadataKey, target, propertyKey) // 通過裝飾器將元數(shù)據(jù)應(yīng)用于構(gòu)造函數(shù)@Reflect.metadata(metadataKey, metadataValue)class C { // 通過裝飾器將元數(shù)據(jù)應(yīng)用于方法(屬性) @Reflect.metadata(metadataKey, metadataValue) method() { }}
其實(shí)框架這個(gè)詞并不是 Web 開發(fā)領(lǐng)域所首創(chuàng),他最早出現(xiàn)在軟件開發(fā)行業(yè)中,一開始 “框架” 是這樣被定義的:框架是一種提供了可重用的公共結(jié)構(gòu)的技術(shù),為構(gòu)建新的應(yīng)用程序提供了極大的便利。例如:在桌面應(yīng)用程序開發(fā)的領(lǐng)域,微軟公司的 Visual Studio C++ 為應(yīng)用程序生成框架,基于 VC++ 應(yīng)用程序框架可以大大地提升桌面程序的開發(fā)效率。而隨著 Web 開發(fā)項(xiàng)目的復(fù)雜度的日益提升,軟件開發(fā)中的框架技術(shù)被引入到 Web 開發(fā)領(lǐng)域。Web 開發(fā)框架是用于進(jìn)行 Web 開發(fā)的一套軟件架構(gòu),Web 框架為 Web 應(yīng)用程序提供了基礎(chǔ)的功能。開發(fā)人員在 Web 框架的基礎(chǔ)上實(shí)現(xiàn)自己的業(yè)務(wù)邏輯,基于 Web 框架開發(fā)應(yīng)用,開發(fā)人員只需要專注應(yīng)用的業(yè)務(wù)邏輯,非業(yè)務(wù)邏輯的基礎(chǔ)功能則由框架提供,從而提升開發(fā)效率。
Python 的變量名稱具有如下規(guī)則:2.3.1 變量名由字母、數(shù)字、下劃線組成合法的變量名稱如下:abcABCabc123abc_xyzabc__xyz,該命名包含了2根下劃線__init__,該命名包含了2根下劃線不合法的變量名稱如下:a$bc,該命名包含了字符$ab/c,該命名包含了字符/2.3.2 數(shù)字不能作為變量名開頭合法的變量名稱如下:abc123不合法的變量名稱如下:123abc2.3.4 不能以 Python 中的關(guān)鍵字命名在 Python 中,具有特殊功能的標(biāo)識(shí)符稱為關(guān)鍵字。關(guān)鍵字是 Python 語言自己已經(jīng)使用的了,不允許開發(fā)者自己定義和關(guān)鍵字相同名字的變量。下圖列出了 Python 中所有的關(guān)鍵字。Python 中的關(guān)鍵字在 Python 的交互模式中,輸入’False = 123’,定義變量 False,初值為 123,輸出結(jié)果如下:>>> False = 123 File "<stdin>", line 1SyntaxError: can't assign to keywordFalse 是 Python 的關(guān)鍵字,因此提示語法錯(cuò)誤:“SyntaxError: can’t assign to keyword”。
使用張量進(jìn)行矩陣運(yùn)算的條件為:兩個(gè)張量形狀除了最后兩個(gè)維度外的所有形狀必須相同;兩個(gè)張量形狀最后兩個(gè)維度需要符合 a * b 與 b * c的的格式。在 TensorFlow 之中我們可以通過tf.matmul函數(shù)進(jìn)行運(yùn)算,具體示例如下:a = tf.random.normal([3,4,5])b = tf.random.normal([3,5,6])print(tf.matmul(a, b))其中a與b是固定形狀的隨機(jī)張量,因?yàn)閮烧叩谝痪S形狀相同,而最后兩維形狀符合矩陣相乘的格式,因此可以進(jìn)行矩陣運(yùn)算,得到的結(jié)果為:tf.Tensor([[[-0.41255787 0.2539668 -0.70357645 0.02980493 0.5546258 0.5286447 ] [ 0.7544514 1.2061275 -0.8299564 -0.61776394 -2.0845695 0.55285174] [ 4.9162273 0.23087293 0.6157658 -0.3430875 -3.9511528 0.2734207 ] [-0.8638447 -0.48060232 -1.4220456 0.35801342 2.505946 2.7356615 ]] [[ 2.260117 2.338372 -3.4372165 -0.2901526 0.12752411 -0.23638 ] [ 0.14264594 -1.9469845 -5.1910253 2.5343626 -4.1282463 1.295904 ] [ 0.5720302 1.6685274 2.1391735 -1.8491768 2.8305507 -1.1663995 ] [-0.8750653 -3.5349839 -2.7755249 2.5702014 -3.525653 0.08906344]] [[ 0.04434888 2.0841029 0.06953187 -2.3450966 -1.5517069 0.83987266] [ 2.0700073 1.5478165 -0.07335746 -0.36860508 0.46835172 1.861287 ] [-3.5253298 -1.5082629 -1.6806324 -1.2718723 -1.378425 -1.1990058 ] [ 0.88312423 1.0631399 2.6772838 -1.0774231 -1.8299285 0.89358884]]], shape=(3, 4, 6), dtype=float32)可以看到,我們的張量已經(jīng)進(jìn)行了矩陣的運(yùn)算,并切形狀為我們期待的結(jié)果。
字符串函數(shù)是專門用來進(jìn)行字符串操作的。C 語言提供了一個(gè)標(biāo)準(zhǔn)的函數(shù)庫 string.h 。在這個(gè)函數(shù)庫中大致存在了 22 個(gè)字符串的函數(shù)。我們這里所介紹的字符串函數(shù)是來自于這個(gè)標(biāo)準(zhǔn)函數(shù)庫中比較常用的的一部分函數(shù)。除了這個(gè)函數(shù)庫,還會(huì)有第三方的函數(shù)庫提供的字符串的一些函數(shù)。這些不在本教程的討論范圍內(nèi)。常用的字符串函數(shù)包含一下幾個(gè):序號(hào)函數(shù)功能1strlen(str1)獲取 str1 字符串的長度2strcpy(str1,str2)將 str2 中的內(nèi)容復(fù)制到 str1 中3strcat(str1,str2)將 str2 連接到 str1 的后面4strcmp(str1,str2)比較兩個(gè)字符串,如果兩個(gè)字符串一致則返回 0;如果 str1 大于 str2 則返回正數(shù);如果 str1 小于 str2 則返回負(fù)數(shù)5strchr(str1,shar1)在 str1 中查找字符 char1 第一次出現(xiàn)的位置,返回該位置的指針|6|strstr(str1.str2)|在 str1 中查找字符串 str2 第一次出現(xiàn)的位置,返回該位置的指針
但是如果不使用 IDE ,而使用鍵盤敲擊注釋內(nèi)容,每次都要敲擊 4 次鍵盤才能完成一次注釋,對(duì)于一些簡(jiǎn)短的注釋來說是比較麻煩的。因此在 C++ 語言設(shè)計(jì)的時(shí)候,就出現(xiàn)了使用 // 來進(jìn)行單行注釋的方法。請(qǐng)注意,這里編譯器只會(huì)忽略 // 后面到本行行末的部分。也就是只能注釋一行。這與使用 /* */ 方式注釋可以同時(shí)注釋多行的方式有所不同。這種注釋的方法優(yōu)缺點(diǎn)顯而易見。與上面的傳統(tǒng)方式優(yōu)勢(shì)互補(bǔ)。Tips:需要特別注意的是,對(duì)于幾十年前的老舊的編譯器,這種注釋方式會(huì)引起錯(cuò)誤。當(dāng)然,最近十年的編譯器已經(jīng)可以支持這種新的注釋標(biāo)準(zhǔn)了。# include <stdio.h>int main(){ // 聲明變量 int x,y,z; return 0; // 返回值}將兩種注釋方式混合使用的例子。/*使用第一種方式注釋*/# include <stdio.h>int main(){ // 使用第二種方式注釋 int x,y,z; return 0;}
Tips:示例程序在 GCC 7.5.0 下測(cè)試通過,請(qǐng)大家使用支持 C99 標(biāo)準(zhǔn)的編譯器。# include <stdio.h># include <stdbool.h>int main(){ bool x,y,z; x=false; y=true; printf("x = %d; y = %d\n",x,y); z=x||y; printf("x || y = %d\n",z); z=x&&y; printf("x && y = %d\n",z); printf("!x = %d\n",!x); printf("!y = %d\n",!y); return 0;}運(yùn)行結(jié)果x = 0; y = 1x || y = 1x && y = 0!x = 1!y = 0從實(shí)際的程序可以看到,和我們上一節(jié)中介紹的表格中的運(yùn)算結(jié)果是一致的。如果不使用布爾類型,直接使用整數(shù)會(huì)怎樣?下面我就通過實(shí)際的程序來看一下結(jié)果。# include <stdio.h>int main(){ int x,y,z; x=0; y=10; printf("x = %d; y = %d\n",x,y); z=x||y; printf("x || y = %d\n",z); z=x&&y; printf("x && y = %d\n",z); printf("!x = %d\n",!x); printf("!y = %d\n",!y); return 0;}運(yùn)行結(jié)果x = 0; y = 10x || y = 1x && y = 0!x = 1!y = 0雖然由布爾類型變?yōu)榱苏麛?shù)類型,但是最后的結(jié)果是一致的。這也就是 C 語言在出現(xiàn)布爾類型之前經(jīng)常用到的形式。
特殊字符是一些有特殊含義的字符,例如的 ab*c 中的 *,* 之前的字符是 b,* 表示匹配 0 個(gè)或者多個(gè) 字符 b。下表列出了正則表達(dá)式中的特殊字符:特殊字符描述\t制表符\f換頁符\n換行符\r回車符\s匹配任意空白字符,等價(jià)于 [\t\n\r\f]\S匹配任意非空字符\d匹配任意數(shù)字,等價(jià)于 [0-9]\D匹配任意非數(shù)字^匹配字符串的開頭$匹配字符串的末尾.匹配任意字符\b匹配一個(gè)單詞邊界,在單詞的開頭或者末尾匹配xcd ef’\B匹配非單詞邊界[…]用來表示一組字符[^…]不在[]中的字符re*匹配 0 個(gè)或多個(gè)正則表達(dá)式re+匹配 1 個(gè)或多個(gè)正則表達(dá)式re?匹配 0 個(gè)或 1 個(gè)正則表達(dá)式re{n}匹配 n 個(gè)正則表達(dá)式re{n,m}匹配 n 到 m 個(gè)正則表達(dá)式a | b匹配 a或 b(re)對(duì)正則表達(dá)式分組并記住匹配的文本
確定安裝成功后我們創(chuàng)建一個(gè) minimal.py 文件,并寫入下面的代碼:from flask import Flaskapp = Flask(__name__)首先,導(dǎo)入類 flask.Flask,實(shí)例化創(chuàng)建一個(gè) Flask 應(yīng)用,第一個(gè)參數(shù)是 Flask 應(yīng)用的名稱。__name__ 是一個(gè)標(biāo)識(shí) Python 模塊的名字的變量:如果當(dāng)前模塊是主模塊,那么此模塊名字就是 __main__;如果當(dāng)前模塊是被 import 的,則此模塊名字為文件名。@app.route('/')def hello_world(): return '<b>Hello World</b>'然后,定義函數(shù) hello_world,它返回一段 html 文本。app.route(’/’) 返回一個(gè)裝飾器,裝飾器來為函數(shù) hello_world 綁定對(duì)應(yīng)的 URL,當(dāng)用戶在瀏覽器訪問這個(gè) URL 的時(shí)候,就會(huì)觸發(fā)這個(gè)函數(shù),獲取返回值。if __name__ == '__main__': app.run()如果當(dāng)前模塊是主模塊,則變量 __name__ 為 '__main__,此時(shí)調(diào)用 run() 方法啟動(dòng) Flask 應(yīng)用。運(yùn)行該程序,在瀏覽器中輸入 localhost:5000,瀏覽器顯示如下:在控制臺(tái)中,F(xiàn)lask 應(yīng)用輸出如下:$ python3 hello.py * Serving Flask app "hello" (lazy loading) * Environment: production Use a production WSGI server instead. * Debug mode: off * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)127.0.0.1 - - [20/Jul/2020 08:26:47] "GET / HTTP/1.1" 200 -127.0.0.1 - - [20/Jul/2020 08:26:47] "GET /favicon.ico HTTP/1.1" 404 -這樣,我們就已經(jīng)搭建好了一個(gè)最簡(jiǎn)單的 Flask 應(yīng)用。
在 Kotlin 中使用in運(yùn)算符來檢測(cè)某個(gè)數(shù)字是否在指定的區(qū)間內(nèi),區(qū)間表達(dá)式具有操作符形式"…"的rangeTo函數(shù)輔助in和!in組合而成。區(qū)間可以支持任何可以比較的類型,對(duì)于原生的整型,內(nèi)部有一個(gè)優(yōu)化的實(shí)現(xiàn)。比如以下幾種使用場(chǎng)景:1. 檢測(cè)某個(gè)數(shù)字是否在指定區(qū)間內(nèi):val x = 10val y = 9if (x in 1..y + 1) {//表示x是否在1~y+1范圍內(nèi)。 println("fits in range")}2. 檢測(cè)某個(gè)數(shù)字是否在指定區(qū)間外:val list = listOf("a", "b", "c")if (-1 !in 0..list.lastIndex) { println("-1 is out of range")}if (list.size !in list.indices) { println("list size is out of valid list indices range too")}3. 區(qū)間迭代:for(x in 1..10){//相當(dāng)于 x >= 1 && x <= 10 println(x)}//或者fun printList(num: Int) { for (i in 1..num) {//相當(dāng)于 i >= 1 && i <= num print(i) }}//或者使用until 函數(shù)排除結(jié)束元素for (i in 1 until 10) {// i in [1, 10) 排除了 10,相當(dāng)于 i >= 1 && i < 10 println(i)} 4. 數(shù)列迭代 :for(x in 1..100 step 2){//遞增數(shù)列迭代,每次間隔步長是2;1,3,5,7... println(x)}for(x in 9 downTo 0 step 3){//遞減數(shù)列迭代,每次間隔步長是3;9,6,3,0 println(x)}
如果是想基于基本的 API 方式登錄,我們會(huì)面臨兩大難點(diǎn):手機(jī)驗(yàn)證碼校驗(yàn) ,如下圖所示:起點(diǎn)網(wǎng)站登錄手機(jī)發(fā)送驗(yàn)證碼滑動(dòng)驗(yàn)證碼校驗(yàn),如下圖所示:起點(diǎn)網(wǎng)站登錄滑動(dòng)驗(yàn)證碼繞過這些校驗(yàn)的方法超過了本教程的知識(shí)范圍,故我們不再次詳細(xì)討論。好在起點(diǎn)網(wǎng)支持自動(dòng)登錄過程,也就是 Cookie 登錄:起點(diǎn)網(wǎng)支持自動(dòng)登錄第一次手動(dòng)登錄起點(diǎn),選擇自動(dòng)登錄后,起點(diǎn)網(wǎng)站返回的 Cookie 信息就會(huì)保存至本地。下次再訪問起點(diǎn)網(wǎng)時(shí),通過請(qǐng)求帶上該 Cookie 信息就能正確識(shí)別用戶,實(shí)現(xiàn)自動(dòng)登錄過程。Cookie 存在本地,就存在被代碼讀取的可能。通常而言,我們來使用 Python 中的 browsercookie 庫可以獲取瀏覽器的 cookie,目前它只支持 Chrome 和 FireFox 兩種瀏覽器。不過對(duì)于 Chrome 80.X 版本的瀏覽器,其中的 cookie 信息被加密了,我們無法按照早期的操作進(jìn)行 cookie 讀取。不過網(wǎng)上這個(gè)博客給出了一個(gè)解密 Cookie 的代碼,我們拿過來簡(jiǎn)單改造下,做成一個(gè)輔助模塊:# 參考文檔:https://blog.csdn.net/u012552769/article/details/105001108import sqlite3import urllib3import osimport jsonimport sysimport base64from cryptography.hazmat.backends import default_backendfrom cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modesurllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)def dpapi_decrypt(encrypted): import ctypes import ctypes.wintypes class DATA_BLOB(ctypes.Structure): _fields_ = [('cbData', ctypes.wintypes.DWORD), ('pbData', ctypes.POINTER(ctypes.c_char))] p = ctypes.create_string_buffer(encrypted, len(encrypted)) blobin = DATA_BLOB(ctypes.sizeof(p), p) blobout = DATA_BLOB() retval = ctypes.windll.crypt32.CryptUnprotectData( ctypes.byref(blobin), None, None, None, None, 0, ctypes.byref(blobout)) if not retval: raise ctypes.WinError() result = ctypes.string_at(blobout.pbData, blobout.cbData) ctypes.windll.kernel32.LocalFree(blobout.pbData) return resultdef aes_decrypt(encrypted_txt): with open(os.path.join(os.environ['LOCALAPPDATA'], r"Google\Chrome\User Data\Local State"), encoding='utf-8', mode="r") as f: jsn = json.loads(str(f.readline())) encoded_key = jsn["os_crypt"]["encrypted_key"] encrypted_key = base64.b64decode(encoded_key.encode()) encrypted_key = encrypted_key[5:] key = dpapi_decrypt(encrypted_key) nonce = encrypted_txt[3:15] cipher = Cipher(algorithms.AES(key), None, backend=default_backend()) cipher.mode = modes.GCM(nonce) decryptor = cipher.decryptor() return decryptor.update(encrypted_txt[15:])def chrome_decrypt(encrypted_txt): if sys.platform == 'win32': try: if encrypted_txt[:4] == b'x01x00x00x00': decrypted_txt = dpapi_decrypt(encrypted_txt) return decrypted_txt.decode() elif encrypted_txt[:3] == b'v10': decrypted_txt = aes_decrypt(encrypted_txt) return decrypted_txt[:-16].decode() except WindowsError: return None else: raise WindowsErrordef get_cookies_from_chrome(domain, key_list): sql = f'SELECT name, encrypted_value as value FROM cookies where host_key like "%{domain}%"' filename = os.path.join(os.environ['USERPROFILE'], r'AppData\Local\Google\Chrome\User Data\default\Cookies') con = sqlite3.connect(filename) con.row_factory = sqlite3.Row cur = con.cursor() cur.execute(sql) cookie_dict = {} for row in cur: if row['value'] is not None: name = row['name'] value = chrome_decrypt(row['value']) if value is not None and name in key_list: cookie_dict[name] = value return cookie_dictTips:上述這段代碼不用糾結(jié)細(xì)節(jié),前面函數(shù)的主要是替 get_cookies_from_chrome() 函數(shù)服務(wù)的,而該函數(shù)的輸入要搜索的網(wǎng)站以及提取相應(yīng)網(wǎng)站 cookie 信息中的某個(gè)具體字段,返回相應(yīng)的結(jié)果。本人 Python 3.8.2 安裝的是 win32 版本,該段代碼親測(cè)有效。來看看起點(diǎn)中文網(wǎng)給讀者生成的 cookie 數(shù)據(jù),我們調(diào)用上面的獲取 cookie 信息的代碼來從中提取相應(yīng)數(shù)據(jù):起點(diǎn)的cookie信息print(get_cookies_from_chrome('qidian.com', '_csrfToken'))print(get_cookies_from_chrome('qidian.com', 'e1'))print(get_cookies_from_chrome('qidian.com', 'e2'))執(zhí)行上述代碼我們可以得到如下結(jié)果:PS C:\Users\spyinx> & "D:/Program Files (x86)/python3/python.exe" c:/Users/spyinx/Desktop/test_cookie.py{'_csrfToken': 'YJklLmhMNpEfuSmqZZGaK72D4sUVJty52gyKwXXX'}{'e1': '%7B%22pid%22%3A%22qd_p_qidian%22%2C%22eid%22%3A%22qd_A08%22%2C%22l1%22%3A1%7D'}{'e2': '%7B%22pid%22%3A%22qd_p_qidian%22%2C%22eid%22%3A%22qd_A10%22%2C%22l1%22%3A1%7D'}這說明我們通過前面的代碼能爭(zhēng)取獲取到起點(diǎn)網(wǎng)保存在 Chrome 瀏覽器中的 cookie 信息。因此,前面的代碼將作為我們讀取起點(diǎn)用戶登錄 Cookie 的重要輔助模塊。Tips:這個(gè)測(cè)試只能在裝了 Chrome 瀏覽器的 Windows 系統(tǒng)上進(jìn)行測(cè)試,或者是 Linux 的桌面版我們首先來創(chuàng)建一個(gè)起點(diǎn)爬蟲:PS C:\Users\Administrator\Desktop> scrapy startproject qidian_spider接下里我們來看看要提取的我的書架的信息:我的書架書籍信息對(duì)應(yīng)的 items.py 的內(nèi)容如下:# https://docs.scrapy.org/en/latest/topics/items.htmlimport scrapyclass QidianSpiderItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() # 分組 category = scrapy.Field() # 小說名 name = scrapy.Field() # 最新章節(jié) latest_chapter = scrapy.Field() # 作者 author = scrapy.Field() # 更新時(shí)間 update_time = scrapy.Field() # 閱讀進(jìn)度 progress_status = scrapy.Field()接下來,在爬蟲部分需要請(qǐng)求該頁面然后提取相應(yīng)的數(shù)據(jù),我們的爬蟲代碼如下:"""獲取用戶書架數(shù)據(jù)"""import jsonfrom urllib import parsefrom scrapy import Requestfrom scrapy.spiders import Spiderfrom .get_cookie import get_cookies_from_chromefrom ..items import QidianSpiderItemclass BookCaseSpider(Spider): name = "bookcase" # 構(gòu)造函數(shù) def __init__(self): # 最重要的就是這個(gè)獲取起點(diǎn)的cookie數(shù)據(jù)了,這里保存了之前用戶登錄的cookie信息 self.cookie_dict = get_cookies_from_chrome( "qidian.com", ["_csrfToken", "e1", "e2", "newstatisticUUID", "ywguid", "ywkey"] ) def start_requests(self): url = "https://my.qidian.com/bookcase" # http請(qǐng)求時(shí)附加上cookie信息 yield Request(url=url, cookies=self.cookie_dict) def parse(self, response): item = QidianSpiderItem() books = response.xpath('//table[@id="shelfTable"]/tbody/tr') for book in books: category = book.xpath('td[@class="col2"]/span/b[1]/a[1]/text()').extract_first() name = book.xpath('td[@class="col2"]/span/b[1]/a[2]/text()').extract_first() latest_chapter = book.xpath('td[@class="col2"]/span/a/text()').extract_first() update_time = book.xpath('td[3]/text()').extract_first() author = book.xpath('td[@class="col4"]/a/text()').extract_first() progress_status = book.xpath('td[@class="col5"]/a/text()').extract_first() item['category'] = category item['name'] = name item['latest_chapter'] = latest_chapter item['update_time'] = update_time item['author'] = author item['progress_status'] = progress_status print(f'get item = {item}') yield item最重要的方法就是那個(gè)獲取 cookie 信息的方法了,正是靠著這個(gè) cookie,我們才能獲取相應(yīng)的用戶書架的網(wǎng)頁并提取相應(yīng)的書籍信息。接下來,簡(jiǎn)單實(shí)現(xiàn)一個(gè) item pipeline 用于保存書架上的書籍信息,該代碼位于 scrapy/pipelines.py 文件中,默認(rèn)的 pipelines 都寫會(huì)在這里:# 源碼位置:scrapy/pipelines.py# Define your item pipelines here## Don't forget to add your pipeline to the ITEM_PIPELINES setting# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.htmlimport json# useful for handling different item types with a single interfacefrom itemadapter import ItemAdapterclass QidianSpiderPipeline: def open_spider(self, spider): self.file = open("bookcase.json", 'w+', encoding='utf-8') def close_spider(self, spider): self.file.close() def process_item(self, item, spider): data = json.dumps(dict(item), ensure_ascii=False) self.file.write(f"{data}\n") return item最后別忘了在 settings.py 中添加這個(gè) item pipeline:ITEM_PIPELINES = { 'qidian_spider.pipelines.QidianSpiderPipeline': 300,}我們運(yùn)行下這個(gè)爬蟲,看看是否能抓到我們想要的數(shù)據(jù):PS C:\Users\Administrator\Desktop> scrapy crawl bookcase最后的結(jié)果如下:獲取用戶的書架上書籍信息這樣,我們就成功實(shí)現(xiàn)了用戶登錄后的訪問動(dòng)作。接下來我們?cè)谶@個(gè)基礎(chǔ)上進(jìn)一步擴(kuò)展,實(shí)現(xiàn)清除書架上所有的書籍,類似于淘寶的一鍵清除購物車。
在 Nginx 命令行的使用中,有幾個(gè)非常重要的參數(shù),也是在操作 Nginx 時(shí)經(jīng)常用到的:常用參數(shù)作用 -t 測(cè)試 Nginx.conf 文件的語法是否正常 -c 指定 nginx.conf 文件 -s 最重要的操作,不帶 - s 是啟動(dòng),-s reload 是熱加載 -s stop 是停止,-s reopen 是重新打開日志實(shí)例:# 進(jìn)入sbin目錄$ cd /root/nginx/sbin # 啟動(dòng)Nginx$ ./nginx # 檢查nginx.conf$ ./nginx -tc /root/nginx/conf/nginx.conf # 重新加載nginx$ ./nginx -s reload # 停止nginx$ ./nginx -s stop 啟動(dòng) Nginx 后,首先使用 ps -ef | grep nginx 可以查看 Nginx 進(jìn)程是否已經(jīng)啟動(dòng),基于默認(rèn)的配置,我們將看到 2 個(gè) Nginx 的啟動(dòng)進(jìn)程:master 進(jìn)程和 worker 進(jìn)程。這是我們?cè)谇懊嬷v到的 Nginx 的 Master-Worker 機(jī)制,后面會(huì)進(jìn)行詳細(xì)講解。另外,我們可以用命令 netstat -anltp | grep 80,看到 CentOS 上已經(jīng)在監(jiān)聽 80 端口,而這個(gè)監(jiān)聽服務(wù)正是 Nginx。最后可以用瀏覽器或者 curl 命令直接在 CentOS 機(jī)器上檢查 Nginx 服務(wù):$ curl http://localhost當(dāng)出現(xiàn) “Welcome to Nginx!" 這樣的歡迎語句,表明我們的 Nginx 已經(jīng)正常運(yùn)行了。
我們先從工作流程說起,基本的 Git 工作流程如下:a. 修改:修改工作區(qū)(Working Directory)中的文件。b. 暫存:選擇要作為下一次提交部分的那些更改,僅將這些更改添加到暫存區(qū)域(Staging Area)。c. 提交:執(zhí)行一次提交,該提交將獲取本次所在版本的文件并將該快照永久存儲(chǔ)到你的 Git 目錄(.git directory)中。將這三個(gè)流程串起來,可以通俗地理解為:先將本地文件做一修改,修改之后我們要怎么告訴 Git 呢?所以就需要通過命令將本地修改先添加到暫存區(qū),將暫存區(qū)理解為一個(gè)臨界區(qū)。但提交到暫存區(qū)之后,還未正式納入 Git 的管理,等你確認(rèn)這部分內(nèi)容需要正式提交到 Git 倉庫的時(shí)候,再通過提交命令執(zhí)行提交操作。那么,一個(gè)最基本的工作流程就結(jié)束了。對(duì)于一些內(nèi)容大家是不是還有點(diǎn)懵呢?接下來我挑其中涉及到的概念詳細(xì)說明下,或許對(duì)大家理解有幫助。請(qǐng)繼續(xù)往下看。
本小節(jié)實(shí)現(xiàn)列出所有聯(lián)系人的功能,如下所示:elif choice == '2': for person in persons: print('%s,%s,%s' % (person['name'], person['address'], person['phone']))在第 1 行,如果 choice == ‘2’,則執(zhí)行列出所有聯(lián)系人的功能在第 2 行,遍歷列表 persons,循環(huán)變量 person 是一個(gè)字典在第 3 行,打印變量 person 的內(nèi)容對(duì)每個(gè)聯(lián)系人打印輸出一行,假設(shè)通訊錄中已經(jīng)存儲(chǔ)了張三和李四兩個(gè)聯(lián)系人,輸出如下:C:\> python addr-manage.py1. create person2. list all persons3. query person4. delete person5. quitEnter a number(1-5): 2張三,南京,12306李四,北京,10086在第 7 行,用戶選擇執(zhí)行功能 2在第 8 行,打印聯(lián)系人張三的信息在第 9 行,打印聯(lián)系人李四的信息查詢聯(lián)系人本小節(jié)實(shí)現(xiàn)查詢聯(lián)系人的功能,如下所示:elif choice == '3': name = input('name: ') for person in persons: if person['name'] == name: print('%s,%s,%s' % (person['name'], person['address'], person['phone']))在第 1 行,如果 choice == ‘3’,則執(zhí)行查詢聯(lián)系人的功能在第 2 行,獲取用戶輸入的 name在第 3 行,遍歷列表 persons,循環(huán)變量 person 是一個(gè)字典在第 4 行,如果用戶輸入的 name 和循環(huán)訪問 person 的 name 相同,則表示找到指定的 person在第 5 行,打印變量 person 的內(nèi)容
本小節(jié)實(shí)現(xiàn)一個(gè)通訊錄管理程序,通過這個(gè)案例來融會(huì)貫通之前所學(xué)習(xí)的知識(shí),該程序使用到如下知識(shí)點(diǎn):條件選擇循環(huán)列表字典鍵盤輸入屏幕輸出編寫程序 addr-manage.py 實(shí)現(xiàn)通訊錄管理系統(tǒng),通訊錄包含若干聯(lián)系人,每個(gè)聯(lián)系人包括:姓名、地址、電話 3 項(xiàng)內(nèi)容。程序提供 4 項(xiàng)基本功能:增加聯(lián)系人: 用戶輸入姓名、地址、電話等信息,將信息保存在一個(gè)列表中列出聯(lián)系人: 打印輸出所有聯(lián)系人的信息查詢聯(lián)系人: 用戶輸入聯(lián)系人姓名,打印輸出該聯(lián)系人的信息刪除聯(lián)系人: 用戶輸入聯(lián)系人姓名,從通訊錄中刪除該聯(lián)系人通過命令行界面實(shí)現(xiàn)以上功能,程序 addr-manage.py 運(yùn)行時(shí)首先打印一個(gè)菜單,如下所示:C:\> python addr-manage.py1. create person2. list all persons3. query person4. delete person5. quitEnter a number(1-5): 總共有 5 個(gè)選項(xiàng),用戶輸入對(duì)應(yīng)的數(shù)字選擇相應(yīng)的功能,如下表所示:數(shù)字選項(xiàng)功能描述1. create person增加聯(lián)系人2. list all persons列出聯(lián)系人3. query person查詢聯(lián)系人4. delete person刪除聯(lián)系人5. quit退出通訊錄程序
正如我們?cè)谇把灾兴岬降?,?dāng)程序規(guī)模越來越大,類的數(shù)量也會(huì)隨之增多。使用包將這些類分組后,可以讓類更易于查找和使用,也可以避免命名沖突,還可以控制訪問。易于查找和使用很容易理解,試想我們電腦的某一個(gè)文件夾中,存放著成百上千的各類文件,即使使用搜索功能都不能迅速定位到我們想要的文件,而將這些文件按照功能或特點(diǎn)分類存入到不同的文件夾下,就可以大大提高我們查找和使用的效率。對(duì)于 Java 語言的類也是同樣的道理。對(duì)于命名沖突,我們來看實(shí)際開發(fā)中的一個(gè)例子:一個(gè) Java 工程由 3 個(gè)程序員協(xié)同開發(fā),程序員 A 寫了一個(gè)工具類 Util,程序員 B 也寫了一個(gè)工具類 Util,程序員 C 既想使用 A 的 Util,又想使用 B 的 Util,這個(gè)時(shí)候可以分別將 A、B 兩個(gè)人的 Util 類放入兩個(gè)不同的包下,就可以解決命名沖突了。對(duì)于控制訪問,我們將在本小節(jié)的包作用域部分舉例講解。
Python 的第三方模塊提供了自動(dòng)化運(yùn)維所需的功能,如:監(jiān)控系統(tǒng)資源、網(wǎng)絡(luò)配置等,常用的模塊如下:psutilpsutil 是一個(gè)跨平臺(tái)庫能夠?qū)崿F(xiàn)獲取系統(tǒng)運(yùn)行的進(jìn)程和系統(tǒng)利用率(內(nèi)存,CPU,磁盤,網(wǎng)絡(luò)等),主要用于系統(tǒng)監(jiān)控,分析和系統(tǒng)資源及進(jìn)程的管理。dnspythondnspython 是一個(gè) DNS 工具包,可以用于查詢、傳輸并動(dòng)態(tài)更新 DNS 區(qū)域信息,在系統(tǒng)管理方面,可以利用查詢功能來實(shí)現(xiàn) DNS 服務(wù)監(jiān)控以及解析結(jié)果的校驗(yàn)。smtplibsmtplib 是一個(gè)發(fā)送電子郵件的工具包,它對(duì) smtp 協(xié)議進(jìn)行了簡(jiǎn)單的封裝。當(dāng)監(jiān)控系統(tǒng)發(fā)現(xiàn)問題時(shí),通過調(diào)用 smtplib 發(fā)送報(bào)警郵件。IPyIPy 提供了對(duì)地址進(jìn)行處理的功能,主要提供了包括網(wǎng)段、網(wǎng)絡(luò)掩碼、廣播地址、子網(wǎng)數(shù)、IP類型的處理等功能。pycurlcURL 是一個(gè)利用 URL 語法在命令行下工作的文件傳輸工具,cURL支持的通信協(xié)議有FTP、FTPS、HTTP、HTTPS、TFTP、SFTP、Gopher、SCP、Telnet、DICT、FILE、LDAP、LDAPS、IMAP、POP3、SMTP和RTSP。pycurl 是一個(gè)用 C 語言寫的 libcurl Python實(shí)現(xiàn),可以理解為 linux 下 curl 命令功能的 Python 封裝。scapyscapy 是一個(gè)由 Python 編寫的數(shù)據(jù)包處理程序,它能夠?qū)?shù)據(jù)包進(jìn)行偽造或解包,提供發(fā)送數(shù)據(jù)包、包嗅探、應(yīng)答和反饋等功能,目前很多優(yōu)秀的網(wǎng)絡(luò)掃描攻擊工具都使用了這個(gè)模塊。ansibleansible 是一個(gè)綜合的自動(dòng)化運(yùn)維工具,基于 Python 開發(fā),集合了眾多運(yùn)維工具的優(yōu)點(diǎn),實(shí)現(xiàn)了批量系統(tǒng)配置、批量程序部署、批量運(yùn)行命令等功能。ansible 提供了二次開發(fā)的接口,可以編寫 python 腳本請(qǐng)求 ansible 的 接口,開發(fā)自己的運(yùn)維工具。saltstacksaltstack 是一種基于 C/S 架構(gòu)的集中化管理平臺(tái),管理端稱為 Master,客戶端稱為 Minion。saltstack 具備配置管理、遠(yuǎn)程執(zhí)行、監(jiān)控等功能,saltstack 本身是基于 Python 語言開發(fā)實(shí)現(xiàn),結(jié)合了輕量級(jí)的消息隊(duì)列軟件 ZeroMQ 與 Python 第三方模塊構(gòu)建。通過部署 SaltStack 環(huán)境,運(yùn)維人員可以在成千上萬臺(tái)服務(wù)器上做到批量執(zhí)行命令,根據(jù)不同的業(yè)務(wù)特性進(jìn)行配置集中化管理、分發(fā)文件、采集系統(tǒng)數(shù)據(jù)及軟件包的安裝與管理等。saltstack 提供了二次開發(fā)的接口,可以編寫 python 腳本請(qǐng)求 saltstack 的 接口,開發(fā)自己的運(yùn)維工具。
剛開始學(xué)習(xí)的讀者,現(xiàn)在去深究如何命名一個(gè)變量還有些尚早,因?yàn)榻Y(jié)合了具體的需求場(chǎng)景才能體會(huì)到一個(gè)好的變量名的重要性??梢韵仍诖俗鰝€(gè)了解。對(duì)于變量名,除了上面提到的變量命名的規(guī)范,最需要注意的就是給變量起一個(gè)有意義的名字。如求和:var num1 = 1;var num2 = 2;var num3 = 3;var num4 = 4;var count = num1 + num2 + num3 + num4;其中num是number的縮寫,是很常用的一種縮寫。count則是總數(shù),表示求和的結(jié)果。如果將上述例子做如下修改:var a = 1;var b = 2;var c = 3;var d = 4;var e = a + b + c + d;缺少了有意義的變量名就比較難看出代碼具體在做什么。當(dāng)然這段代碼本身意義就不大,場(chǎng)景太過簡(jiǎn)單。剛才提到的縮寫,其實(shí)也是要注意的一點(diǎn),縮寫上一定要使用通用的縮寫,如含有fn表示一個(gè)功能或者函數(shù),avg 表示平均值,pwd 表示密碼,i18n 為國際化。這些縮寫比較通用,大部分開發(fā)者都可以看得懂。隨著編碼經(jīng)驗(yàn)的增加,會(huì)在他人代碼里見到大量的縮寫,從而累積到自己的大腦的縮寫庫中。最后需要注意的一點(diǎn)是業(yè)務(wù)中盡量不要含有中文拼音或中文拼音的縮寫,排開鄙視鏈的原因,最大的問題是會(huì)讓變量名變得冗長難懂。以上內(nèi)容在寫 demo 或者測(cè)試功能的時(shí)候可以不需要考慮,寫 demo 等大部分情況是為了驗(yàn)證自己的猜想。// 不合理的變量名var ln = 'World'; // last namevar zs = 0; // 總數(shù)var jinNianDeNianShouRu = 1999999999; // 今年的年收入以上是針對(duì)變量名的意義展開的討論。還有需要注意的是變量命名的格式,大部分前端程序員會(huì)使用駝峰命名法,也就是第一個(gè)字母小寫,后續(xù)如果有新的單詞來進(jìn)行構(gòu)成,單詞的第一個(gè)字符都大寫。如:var firstName = 'Hello';var lastName = 'world';var createAt = 1577895179196;var userInfo = '用戶信息'; // Info => Informationvar isPaidUser = '是否付費(fèi)用戶';可以見到上面的變量,從構(gòu)成變量名的第二個(gè)單詞開始,首字母都是大寫,這就是駝峰命名的格式,本 Wiki 所有變量名使用的就是這種格式。當(dāng)然還有大駝峰,就是第一個(gè)字母也大寫。除此之外最常用的還有使用下劃線分隔變量,如 user_info,還有按功能來劃分的變量名,如使用匈牙利命名法,這里不再做展開。
label 項(xiàng)用于配置折線圖點(diǎn)上的文本標(biāo)簽,支持顯示點(diǎn)的系列名、數(shù)據(jù)名、數(shù)據(jù)值以及其他自定義信息。與 tooltip 不同,label 顯示的文本信息與折線上的點(diǎn)強(qiáng)相關(guān),通常配置在點(diǎn)的附近顯示,例如:1349示例效果:3.3.1 配置標(biāo)簽位置可通過如下配置項(xiàng)設(shè)定標(biāo)簽的顯示位置:配置名類型默認(rèn)值說明positionstring|arraytop標(biāo)簽位置,支持?jǐn)?shù)組與字符串類型,默認(rèn)為 top 即坐標(biāo)點(diǎn)上方distancenumber5標(biāo)簽與坐標(biāo)點(diǎn)的距離,僅當(dāng) position 為字符串選項(xiàng)值時(shí)有效rotatenumber標(biāo)簽選擇角度offsetarray[0, 0]標(biāo)簽距離默認(rèn)位置的偏移值其中, position 支持?jǐn)?shù)組如 [10, 30] 用于指定像素位置值,或如 [10%, 20%] 用于指定百分比位置值。此外,還支持如下枚舉值:topleftrightbottominsideinsideLeftinsideRightinsideTopinsideBottominsideTopLeftinsideBottomLeftinsideTopRightinsideBottomRight枚舉值對(duì)應(yīng)位置如圖:position 屬性決定了標(biāo)簽的大致位置,之后可以通過配置其他位置屬性做出微調(diào),如下示例:1350示例效果:3.3.2 配置標(biāo)簽內(nèi)容可通過 label.formatter 屬性修改折線圖標(biāo)簽內(nèi)容,支持模板字符串與回調(diào)函數(shù)兩種形式。模板字符串支持如下變量:{a}:系列名;:數(shù)據(jù)名;{c}:數(shù)據(jù)值。{@xxx}:數(shù)據(jù)中名為’xxx’的維度的值,如{@product}表示名為 ‘product’ 的維度的值;{@[n]}:數(shù)據(jù)中維度 n 的值,如 {@[3]} 表示維度 3 的值,從 0 開始計(jì)數(shù)。標(biāo)簽內(nèi)容默認(rèn)只展示數(shù)據(jù)值,相當(dāng)于 {c}。模板字符串方式的配置相對(duì)來說更加清晰簡(jiǎn)單,例如:1351示例效果:回調(diào)函數(shù)方式支持傳入格式化函數(shù),函數(shù)將收到當(dāng)前數(shù)據(jù)項(xiàng)的描述對(duì)象作為參數(shù),形如:{ componentType: 'series', // 系列類型 seriesType: string, // 系列在傳入的 option.series 中的 index seriesIndex: number, // 系列名稱 seriesName: string, // 數(shù)據(jù)名,類目名 name: string, // 數(shù)據(jù)在傳入的 data 數(shù)組中的 index dataIndex: number, // 傳入的原始數(shù)據(jù)項(xiàng) data: Object, // 傳入的數(shù)據(jù)值。在多數(shù)系列下它和 data 相同。在一些系列下是 data 中的分量(如 map、radar 中) value: number|Array|Object, // 坐標(biāo)軸 encode 映射信息, // key 為坐標(biāo)軸(如 'x' 'y' 'radius' 'angle' 等) // value 必然為數(shù)組,不會(huì)為 null/undefied,表示 dimension index 。 // 其內(nèi)容如: // { // x: [2] // dimension index 為 2 的數(shù)據(jù)映射到 x 軸 // y: [0] // dimension index 為 0 的數(shù)據(jù)映射到 y 軸 // } encode: Object, // 維度名列表 dimensionNames: Array<String>, // 數(shù)據(jù)的維度 index,如 0 或 1 或 2 ... // 僅在雷達(dá)圖中使用。 dimensionIndex: number, // 數(shù)據(jù)圖形的顏色 color: string,}函數(shù)方式更靈活,適合用于描繪復(fù)雜標(biāo)簽的場(chǎng)景,例如計(jì)算系列上每個(gè)節(jié)點(diǎn)數(shù)值在整個(gè)系列的百分比,示例:1352示例效果:
IP 地址有 IPv4 和 IPv6 兩個(gè)版本。IPv4 地址長度是 32 bit,4 個(gè)字節(jié),每個(gè)字節(jié)是獨(dú)立取值,通常用點(diǎn)分十進(jìn)制的形式表示。例如,192.168.0.100。IPv4 地址范圍是 0.0.0.0 ~ 255.255.255.255,最多包含 4294967296(2^32) 個(gè) IP 地址。而 IPv6 的地址格式是八元組形式,比如 2001:0DB8::1428:57ab。本節(jié)只討論 IPv4 地址。IPv4 地址通常劃分成網(wǎng)絡(luò) ID和主機(jī) ID兩部分。比如:IP 地址分類劃分如下:分類起始地址結(jié)束地址A0.0.0.0127.255.255.255B128.0.0.0191.255.255.255C192.0.0.0223.225.255.255D224.0.0.0239.255.255.255IP 地址分類劃分缺乏靈活性,對(duì)于 A 類地址來說,網(wǎng)絡(luò) ID 只有 2^7 = 128 個(gè),但是主機(jī) ID 多達(dá) 2^24 = 16777216 個(gè),主機(jī) ID 浪費(fèi)很大。對(duì)于 C 類地址來說,網(wǎng)絡(luò) ID 可以有 2^21 = 2097152 個(gè),但是主機(jī) ID 只有 2^8 = 256 個(gè),對(duì)于有些組織來說主機(jī) ID 不夠劃分。于是 1993 年出現(xiàn)了 CIDR(Classless Inter-Domain Routing)的編址策略,叫做無類別域間路路由選擇。CIDR 編址是一種 IP 地址的壓縮表示方式,將 IP 地址分為網(wǎng)絡(luò)前綴和主機(jī)標(biāo)識(shí)兩部分,形如 A.B.C.D/L 的表示方式,L 是一個(gè)小于 32 的十進(jìn)制數(shù)字,代表網(wǎng)絡(luò)前綴占用 L 個(gè)比特,主機(jī)標(biāo)識(shí)占用 32 - L 個(gè)比特。比如,200.101.80.0/20 表示網(wǎng)絡(luò)前綴占用 20 個(gè)比特,主機(jī)標(biāo)識(shí)占用 12 個(gè)比特。在 CIDR 編址方式下,如何通過 IP 地址快速確定網(wǎng)絡(luò) ID 呢?是通過子網(wǎng)掩碼來確定的。對(duì)于形如 A.B.C.D/L 的子網(wǎng),子網(wǎng)掩碼是由 L 個(gè) bit 1 和 32 - L 個(gè) bit 0 組成的二進(jìn)制數(shù)。只要把 A.B.C.D 和子網(wǎng)掩碼做一個(gè)按位與(&)運(yùn)算,就可以得到網(wǎng)絡(luò) ID??梢哉f,形如 A.B.C.D/L 的表示,可以唯一確定一個(gè)網(wǎng)絡(luò) ID,我們通常把 A.B.C.D/L 表示叫做網(wǎng)段。你可以說 A.B.C.D/L 表示了一個(gè)網(wǎng)段,網(wǎng)段就是形如 A.B.C.D/L 的表示形式。比如,200.101.80.0/20 網(wǎng)段的子網(wǎng)掩碼的二進(jìn)制形式是 11111111 11111111 11110000 00000000,十進(jìn)制形式是 255.255.240.0。將 200.101.80.0 和 255.255.240.0 做按位與(&)運(yùn)算,得到的網(wǎng)絡(luò) ID 是 200.101.80.0。那么 IP 地址 200.101.96.1 是 200.101.80.0/20 網(wǎng)段的 IP 嗎?我們只需要把 200.101.96.1 和 255.255.240.0 做一個(gè)按位與(&)運(yùn)算,查看結(jié)果是否等于 200.101.80.0 即可。采用 CIDR 編碼方式優(yōu)勢(shì)如下:簡(jiǎn)單靈活有效利用 IP 地址空間減小路由表規(guī)模。比如 200.101.80.0/20 網(wǎng)段中的 IP 地址 200.101.80.100,如果按照分類,屬于 C 類地址,網(wǎng)絡(luò) ID 占用 24 個(gè) bit,主機(jī) ID 占用 8 個(gè) bit;如果采用 CIDR 方式,網(wǎng)絡(luò) ID 占用 20 個(gè) bit,主機(jī) ID 占用 12 個(gè) bit。對(duì)于主機(jī)較多的網(wǎng)絡(luò),極大地提高了 IP 地址的利用率。
在計(jì)算機(jī)發(fā)展的早期,編程語言提供了手動(dòng)內(nèi)存管理的機(jī)制,例如 C 語言,提供了用于分配和釋放的函數(shù) malloc 和 free,如下所示:#include <stdlib.h>void *malloc(size_t size);void free(void *p);函數(shù) malloc 分配指定大小 size 的內(nèi)存,返回內(nèi)存的首地址函數(shù) free 釋放之前申請(qǐng)的內(nèi)存程序員負(fù)責(zé)保證內(nèi)存管理的正確性:使用 malloc 申請(qǐng)一塊內(nèi)存后,如果不再使用,需要使用 free 將其釋放,示例如下:#include <stdlib.h>void test(){ void *p = malloc(10); 訪問 p 指向的內(nèi)存區(qū)域; free(p);}int main(){ test();}使用 malloc(10) 分配一塊大小為 10 個(gè)字節(jié)的內(nèi)存區(qū)域使用 free§ 釋放這塊內(nèi)存區(qū)域如果忘記釋放之前使用 malloc 申請(qǐng)的內(nèi)存,則會(huì)導(dǎo)致可用內(nèi)存不斷減少,這種現(xiàn)象被稱為 “內(nèi)存泄漏”,示例如下:#include <stdio.h>#include <stdlib.h>void test(){ void *p = malloc(10); 訪問 p 指向的內(nèi)存區(qū)域;}int main(){ while (1) test();}在函數(shù) test 中,使用 malloc 申請(qǐng)一塊內(nèi)存但是使用完畢后,忘記釋放了這塊內(nèi)存在函數(shù) main 中,循環(huán)調(diào)用函數(shù) test()每次調(diào)用函數(shù) test(),都會(huì)造成內(nèi)存泄漏最終,會(huì)耗盡所有的內(nèi)存
上面我們安裝的 Anaconda 工具中,里面集成了 pip 工具,它是一個(gè)軟件包管理器,可簡(jiǎn)化 Python 軟件包的安裝,升級(jí)和卸載。1. 我們打開python模塊包網(wǎng)站,找到 Pandas 模塊版本列表,上面我們 Anaconda 中帶的 Python 是 3.8 版本,我們下載 Pandas?1.1.3?cp38?cp38?win_amd64.whl,這里的 1.1.3 是 Pandas的版本號(hào),cp 后面的 38 是 Python 的版本號(hào),win_amd64 表示 Windows 64 位操作系統(tǒng):2. 下載完成后,打開 Anaconda 中的 Anaconda Powershell Prompt 工具(類似于 Windows中命令操作窗口):3. 看到當(dāng)前的操作路徑為:C:\Users\13965,把剛才下載的 pandas 的 pandas-1.1.3-cp38-cp38-win_amd64.whl 文件移動(dòng)到該文件夾下:4. 復(fù)制命令:pip install pandas-1.1.3-cp38-cp38-win_amd64.whl 到命令行中,按回車鍵運(yùn)行,就會(huì)開始安裝 pandas1.1.3 :輸出 Successfully installed pandas-1.1.3 說明已經(jīng)成功安裝了 Pandas 1.1.3 版本。5. 接下來我們打開 Anaconda 中的 Jupyter notebook 開發(fā)工具:6. 在該開發(fā)工具中,通過輸入下面代碼:import pandas as pd pd.__version__然后,點(diǎn)擊運(yùn)行,看到輸出結(jié)果,里面包含了 Pandas 的版本信息,到此證明我們的 Pandas 開發(fā)環(huán)境已經(jīng)配置好,Pandas 庫也已經(jīng)安裝成功了。
上一小節(jié)中介紹了連接操作中內(nèi)連接,本小節(jié),我們將學(xué)習(xí)外連接。外連接有些許不同,它并不要求兩張表中的記錄都能夠匹配,即使沒有匹配到也會(huì)保留數(shù)據(jù),被保留全部數(shù)據(jù)的表被稱為保留表。外連接可以根據(jù)保留表來進(jìn)一步分為:左外連接(左邊的表數(shù)據(jù)會(huì)被保留),右外連接(右邊的表數(shù)據(jù)會(huì)被保留)和全連接(兩邊的表均被保留)。外連接沒有隱式的連接方式,必須通過 Join 與 On 顯式的指定連接方式和連接條件。本小節(jié)測(cè)試數(shù)據(jù)如下,請(qǐng)先在數(shù)據(jù)庫中執(zhí)行:DROP TABLE IF EXISTS imooc_class;CREATE TABLE imooc_class( id int PRIMARY KEY, class_name varchar(20));INSERT INTO imooc_class(id,class_name) VALUES(1,'SQL必知必會(huì)'), (2,'C語言入門'),(3,'JAVA高效編程'),(4,'JVM花落知多少');DROP TABLE IF EXISTS imooc_user;CREATE TABLE imooc_user( id int PRIMARY KEY, username varchar(20), class_id int references imooc_class(id));INSERT INTO imooc_user(id,username,class_id) VALUES(1,'pedro', 1), (2,'peter', 1),(3,'faker', 2), (4,'lucy', 4),(5,'jery', NULL);說明: 我們分別新建了 imooc_class 表——課程表,和 imooc_user 表——用戶表;其中 imooc_user 表中的 class_id 作為外鍵指向 imooc_class 的主鍵id;若 class_id 為 NULL 則表示該用戶暫時(shí)還未加入任何課程,否則 class_id 表示用戶參加課程的 id 。