
技术改变世界,代码编辑人生。
iOS10以前本地通知(UILocalNotification) 使用步骤: 创建一个UILocalNotification对象 设置触发时间及标题、内容 注册并安排通知 // 1. 创建一个UILocalNotification对象 let localNotification = UILocalNotification() // 2. 设置触发时间及标题、内容 localNotification.fireDate = Date(timeIntervalSinceNow: 3) localNotification.alertTitle = "Title" localNotification.alertBody = "alertBodyalertBodyalertBodyalertBody" // 0. 注册通知(一般在程序刚启动时注册通知) UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings(types: [.badge, .alert, .sound], categories: nil)) // 3. 安排通知 UIApplication.shared.scheduleLocalNotification(localNotification) UILocalNotification的其他属性 applicationIconBadgeNumber :应用程序图标上的数字标记 repeatInterval :重复间隔(按照年、月、日、时、分重复) soundName :发出通知时的提示音,使用UILocalNotificationDefaultSoundName或者指定的音频文件名 userInfo :与通知相关的额外的字典,用户在通知上看不到此数据 应用程序处理收到的通知 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // ...... // 点击通知启动程序(程序不在前台也不在后台,即程序退出时),在此可获取被点击的通知并处理 if let localNotification = launchOptions?[.localNotification] { print(localNotification) } return true } // 应用程序收到通知时,在此方法中处理收到的通知 func application(_ application: UIApplication, didReceive notification: UILocalNotification) { print(notification) } iOS10+使用通知请求(UNNotificationRequest)创建本地通知 使用步骤 请求授权 创建通知内容 创建通知触发时间 使用唯一标识字符串、内容、触发器创建通知请求 将通知请求加到通知中心 // 1. 创建通知内容 let content = UNMutableNotificationContent() // 标题 content.title = NSString.localizedUserNotificationString(forKey: "Hello!", arguments: nil) // 内容 content.body = NSString.localizedUserNotificationString(forKey: "Hello_message_body", arguments: nil) // 通知提示音 content.sound = .default // 2. 创建通知触发器 // Deliver the notification in five seconds. let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false) // 3. 使用唯一标识字符串、内容、触发器创建通知请求 let uuidString = UUID().uuidString let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger) // 获取当前程序的通知中心 let notificationCenter = UNUserNotificationCenter.current() // 设置代理,用来处理收到的通知 notificationCenter.delegate = self // 0. 请求授权(一般在程序刚启动时请求通知授权) notificationCenter.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in } // 4. 将通知请求加到通知中心 notificationCenter.add(request) { (error) in if error != nil { // Handle any errors. } } UNMutableNotificationContent 的其他常用属性 subtitle :子标题 badge :应用程序图标上的数字标记 userInfo :与通知相关的额外的字典,用户在通知上看不到此数据 UNNotificationTrigger 常见的通知触发器 UNTimeIntervalNotificationTrigger : 几秒后触发,如果要设置可重复触发需要大于60 UNCalendarNotificationTrigger :某年某月某日某天某时某分某秒触发 UNLocationNotificationTrigger :在某个位置触发 处理接收到的通知(使用UNUserNotificationCenterDelegate中的两个方法) // Asks the delegate to process the user's response to a delivered notification. func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { // 处理代码 ...... completionHandler() } // 应用程序运行在前台时,此方法处理收到的通知 func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { // 处理代码 ...... completionHandler(.sound) }
CoreLocation(定位与地理编码等) 使用步骤: 导入CoreLocation库 创建CLLocationManager对象 请求用户授权 --- 需要在Info.plist中加入 NSLocationWhenInUseUsageDescription 这个键 设置代理 --- 并实现相应代理方法 调用开始定位方法 调用结束定位方法 import UIKit import CoreLocation class ViewController: UIViewController { // 1. 创建CLLocationManager对象 lazy var locationManger = CLLocationManager() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. // 2. 请求用户授权 locationManger.requestWhenInUseAuthorization() // 3. 设置代理 locationManger.delegate = self // 4. 调用开始定位方法 locationManger.startUpdatingLocation() } } // MARK: - 实现相应代理方法 extension ViewController: CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { let lastLocation = locations.last! print(lastLocation) // 5. 调用结束定位方法 manager.stopUpdatingLocation() } } 持续定位优化(设置这些属性可降低功耗) 设置距离筛选 // 距离筛选器,当位置发生至少10米的改变时才会调用更新位置的代理方法 locationManger.distanceFilter = 10 设置定位精确度 // 定位的精准度 /* * kCLLocationAccuracyBestForNavigation 精准度尽可能高(导航时使用) * kCLLocationAccuracyBest 精准度尽可能高 * kCLLocationAccuracyNearestTenMeters 10米以内误差 * kCLLocationAccuracyHundredMeters 百米误差 * kCLLocationAccuracyKilometer 千米误差 * kCLLocationAccuracyThreeKilometers 三千米误差 */ locationManger.desiredAccuracy = kCLLocationAccuracyBest CLLocation常用属性与方法 经纬度 let location = lastLocation let coordinate = location.coordinate // 维度 print(coordinate.latitude) // 经度 print(coordinate.longitude) 两个位置之间的距离(直线距离) let distance = location.distance(from: location2) 关于授权 使用期间授权:只有应用在前台(屏幕显示应用程序主界面)时,应用才开使用定位服务 // 对应Info.plist中的 NSLocationWhenInUseUsageDescription,描述为何使用定位服务 locationManger.requestWhenInUseAuthorization() 始终授权:即使应用在后台,应用也可以使用定位服务 // 对应Info.plist中的 NSLocationAlwaysAndWhenInUseUsageDescription locationManger.requestAlwaysAuthorization() CLGeocoder(地理编码) 地理编码(使用地名获取地理信息) CLGeocoder().geocodeAddressString("上海市") { (placemarks, error) in // 使用前需要判断下placemarks, error // 需要注意重名问题 print(placemarks) } 反地理编码(使用经纬度获取位置信息) CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: 31.23727100, longitude: 121.45111100)) { (placemarks, error) in // 使用前需要判断下placemarks, error print(placemarks) } 注意:placemarks是[CLPlacemark]类型,可以通过其属性获取详细地址,街道地址,城市名等位置信息 MapKit MapView 使用: 导入MapKit框架 import MapKit 对于storyboard或xib从控件库中拖拽一个map kit view到一个view上;或者使用代码创建一个MKMapView对象添加到视图当中 在地图上显示当前所在位置 获取授权并设置 userTrackingMode 属性 var locationManager = CLLocationManager() locationManager.requestWhenInUseAuthorization() // 要使用代理方法就设置,否则不用设置 mapView.delegate = self // case none = 0 // the user's location is not followed // case follow = 1 // the map follows the user's location 显示位置 // case followWithHeading = 2 // the map follows the user's location and heading 显示位置和方向 mapView.userTrackingMode = .follow 使用代理可获取最新位置的详细信息 extension MapController: MKMapViewDelegate { func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) { print(userLocation.location) // 可在此设置用户所在位置大头针的信息 //CLGeocoder().reverseGeocodeLocation(userLocation.location!) { (placemarks, error) in // let placemark = placemarks?.last // 被点击后弹出气泡的标题与子标题 // userLocation.title = placemark?.locality // userLocation.subtitle = placemark?.name //} } } 设置地图类型 // A street map that shows the position of all roads and some road names. // case standard = 0 // 标准地图,有街道信息等 // Satellite imagery of the area. // case satellite = 1 // 卫星图 // A satellite image of the area with road and road name information layered on top. // case hybrid = 2 // 卫星图并显示街道信息 /* 这几种暂时在中国区没啥用 // @available(iOS 9.0, *) // case satelliteFlyover = 3 // @available(iOS 9.0, *) // case hybridFlyover = 4 // @available(iOS 11.0, *) // case mutedStandard = 5 */ mapView.mapType = .standard 设置地图中心点坐标或显示区域 // 设置地图所显示的中心点 // mapView.centerCoordinate = userLocation.location!.coordinate // 设置地图所显示的中心点和所显示的区域 // mapView.region = MKCoordinateRegion(center: userLocation.location!.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)) // 设置地图所显示的中心点和所显示的区域(当从另一区域变化到所设置的区域时有动画) // 可调用此属性进行地图的放大与缩小 mapView.setRegion(MKCoordinateRegion(center: userLocation.location!.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)), animated: true) 额外信息或控件的显示与隐藏 指北针 // 显示指北针 默认就是true,当用户旋转地图时显示,点击回到初始方向 mapView.showsCompass = true 交通状况 // 显示交通状况 mapView.showsTraffic = true 比例尺 // 显示比例尺 mapView.showsScale = true 大头针 基本使用 自定义大头针数据模型类(需遵守MKAnnotation协议) 创建大头针对象 将大头针添加到地图上 // 1. 自定义大头针数据模型类 class MyAnnotation: NSObject, MKAnnotation { var coordinate: CLLocationCoordinate2D var title: String? var subtitle: String? init(coordinate: CLLocationCoordinate2D, title: String?, subtitle: String?) { self.coordinate = coordinate self.title = title self.subtitle = subtitle } } // 写在存在MapView的视图控制器的方法中 // 2. 创建大头针对象 let annotiton1 = MyAnnotation(coordinate: CLLocationCoordinate2D(latitude: 39.90680600, longitude: 116.39877350), title: "北京", subtitle: "中国北京") // 2. 创建大头针对象 let annotiton2 = MyAnnotation(coordinate: CLLocationCoordinate2D(latitude: 31.23170600, longitude: 121.47264400), title: "上海", subtitle: "中国上海") // 3. 将大头针添加到地图上 mapView.addAnnotations([annotiton1, annotiton2]) 自定义大头针视图(实现下面的代理方法) func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { // 如果是用户位置的大头针视图直接返回nil,否则会将其也变为自定义大头针视图 guard !(annotation is MKUserLocation) else { return nil } // 1. 从缓存池中获取可重用的大头针视图 需自己实现 MyAnnotationView 的定义 let view = mapView.dequeueReusableAnnotationView( withIdentifier: "MyAnnotationView") as? MyAnnotationView annotationView = view ?? MyAnnotationView(annotation: annotation, reuseIdentifier: "MyAnnotationView") // 2. 设置MyAnnotationView需要设置的属性 …… // 返回自定义大头针视图 return annotationView } 地图上画线 创建起点MKMapItem 创建终点MKMapItem 创建方向请求并设置起点终点 根据请求创建MKDirections对象 计算路线 添加路线到地图视图上 实现代理方法 let direction = "上海" CLGeocoder().geocodeAddressString(direction!) { (placemarks, error) in // 1. 创建起点MKMapItem let sourceItem = MKMapItem.forCurrentLocation() // 2. 创建终点MKMapItem let destinationItem = MKMapItem(placemark: MKPlacemark(placemark: placemarks!.last!)) // 3. 创建方向请求并设置起点终点 let request = MKDirections.Request() request.source = sourceItem request.destination = destinationItem request.transportType = .automobile // 4. 根据请求创建MKDirections对象 let directions = MKDirections(request: request) // 5. 计算路线 directions.calculate { (response, error) in guard let response = response else { if let error = error { print("Error: \(error)") } return } response.routes.forEach({ (route) in // 6. 添加路线到地图视图上 let polyine = route.polyline self.mapView.addOverlay(polyine, level: .aboveRoads) }) } } // 7. 实现代理方法 func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { let renderer = MKPolygonRenderer(overlay: overlay) // 设置线的颜色 renderer.strokeColor = .blue // 设置线宽 renderer.lineWidth = 5 return renderer } 导航(跳转到系统地图) 使用导航目的地创建一个MKMapItem 构建打开系统地图时的选项(如导航模式、地图类型等) 调用openMaps方法打开系统地图 // 0. 导航目的地 let destination = "上海" CLGeocoder().geocodeAddressString(direction!) { (placemarks, error) in // 1. 使用导航目的地创建一个MKMapItem let destinationItem = MKMapItem(placemark: MKPlacemark(placemark: placemarks!.last!)) // 2. 构建打开系统地图时的选项(如导航模式、地图类型等) let options = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving, MKLaunchOptionsMapTypeKey: 0, MKLaunchOptionsShowsTrafficKey: true] as [String : Any] // 3. 调用openMaps方法打开系统地图 MKMapItem.openMaps(with: [destinationItem], launchOptions: options) }
一、SVM的简介 SVM(Support Vector Machine,中文名:支持向量机),是一种非常常用的机器学习分类算法,也是在传统机器学习(在以神经网络为主的深度学习出现以前)中一种非常牛X的分类算法。关于它的发展历史,直接引用Wikipedia中的,毕竟本文主要介绍它的推导过程,而不是历史发展。 The original SVM algorithm was invented by Vladimir N. Vapnik and Alexey Ya. Chervonenkis in 1963. In 1992, Bernhard E. Boser, Isabelle M. Guyon and Vladimir N. Vapnik suggested a way to create nonlinear classifiers by applying the kernel trick to maximum-margin hyperplanes. The current standard[according to whom?] incarnation (soft margin) was proposed by Corinna Cortes and Vapnik in 1993 and published in 1995. 接下来,就让我们回到过去,扮演它的发明者。(不要想太多,这个非常简单,只需基础的线性代数基础) 二、一个最简单的分类问题 有如下几条直线,哪条是黑白两种点的最佳分割线? 如果你看到了上面的那张图,你肯定会毫不犹豫的说是H3,因为H1明显没有满足要求,H2虽然分开了,但是给人的感觉没有那么好!如果现在在图中给你一个未知颜色的点,让你判断它是黑还是白,该如何判断?如果是我,我就会说如果这个未知点在H3左边的它就是黑色,如果他在H3的右边,他就是白色。 如果到这儿你都完全理解,那么距离明白SVM就已经非常接近了。使用计算机程序寻找H3的过程,我们管它叫做训练;使用H3对未知点进行分类的过程,我们管它叫做预测。 接下来,我们就需要知道计算机是如何找到H3这条线,和如何使用H3做出决策?(计算机不是人类,所以它不能靠感觉,而要编写计算机程序,则必须有一个严谨的算法过程。) 三、SVM推导 首先,我们将上面寻找H3的问题转换一下, 如上图,找到最佳的分割线,也就是让两条虚线之间的距离最大。 首先我们假设这条分割线的法向量为,我们知道在直角坐标系中,任意一点都可以表示为一向量,w · u则表示该向量在上投影的长度,对于任意一个正样本(设黑为+,白为-) 有w · u ≥ C,设b = C,则将其整理一下即可写为w · u - b ≥ 0, 如果已知w和b,使用此公式,我们便可对未知点进行预测(或者叫分类)。 由上述,我们知道了决策过程,接下来,我们需要推导出训练过程,即怎样得到w和b? 首先对于训练集,在训练集中对于任意一点xi 我们知道它的标签yi(如果为正例yi = 1,如果为负例yi = -1),然后对于正负例我们假设(假设当点刚好在边缘时等号成立), 不等式两边同乘以yi就可以得到。 两条虚线之间的宽度求法如下: 即我们要做工作的是: 即我们需要在的约束下(只需关注边界上的点),求。(这个问题,相信对于学过大学高等数学的人来说是非常简单的) 使用拉格朗日乘数可以很容易的进行求解, 设则: , 将w回带到L中, 化简得, 注意上式的末尾,要使L取极值(画出决策边界),结果只与训练集中已知点向量的点积有关,与其它量无关。 如果再将 带入到决策函数中,则 if result = + else result = - 综上所述,可以发现,要求得最大间隔与对一个未知点的分类预测只与已知虚线点的点积有关。 四、核函数 在上述中,最后的决策函数为,但这个决策函数对线性不可分的数据便无能为力了,比如: 上图,不能简单的使用一条直线将其分开,但是,如果我们换个角度, 对其多加一个维度Z,很容易便可将其用一条直线将其分开,如果我们再回到最开始的维度下,则其如下图所示, 这也就告诉我们,在我们当前维度下线性不可分的数据,如果换个角度,则其就会线性可分。 又由于决策函数为, 向量和在二维z坐标系中,(这里的指的是向量在第一和第二维度上的值),假设为和在某个维度的点积,则其决策函数就可写为 ,而. (称K为核函数) 通过上述两式就可画出最佳分割超平面,和对未知数据做出决策。 常见的核函数有(摘自Wikipedia): Polynomial (homogeneous): Polynomial (inhomogeneous): Gaussian radial basis function: , for . Sometimes parametrized using Hyperbolic tangent: , for some (not every) and 注:大部分的机器学习任务使用这些核函数都可以得到解决。
一、引言 在开始算法介绍之前,让我们先来思考一个问题,假设今天你准备出去登山,但起床后发现今天早晨的天气是多云,那么你今天是否应该选择出去呢? 你有最近这一个月的天气情况数据如下,请做出判断。 这个月下雨的天数占10% 这个月早晨是多云的天数占40% 在下雨的天数中早晨是多云的占50% 如果有普通本科的概率论知识,这个问题就不难解决,计算一下今天会下雨的概率,然后根据概率决定即可。解决方式如下: 可以发现,今天下雨的概率只有12.5%,还是可以出去玩的(当然如果怕万一,那还是呆在家里)。 二、Bayes’s theorem 没错,上面的计算公式就是贝叶斯定理,这个定理的数学表示如下: 这个定理在日常生活中的应用非常广泛,比如: If dangerous fires are rare (1%) but smoke is fairly common (10%) due to barbecues, and 90% of dangerous fires make smoke. 如果烟雾报警器检测到有烟,请问发生火灾的可能为多少? Suppose that a test for using a particular drug is 99% sensitive and 99% specific. That is, the test will produce 99% true positive results for drug users and 99% true negative results for non-drug users. Suppose that 0.5% of people are users of the drug. What is the probability that a randomly selected individual with a positive test is a drug user? .... 对上述公式做一个变形如下: 在对其进行扩展则如下: 三、Naive Bayes Classifier 上面说了这么多,好像与机器学习分类器没啥关系啊!但是不是,是有关系的, Naive Bayes Classifier就是一种基于概率的分类器。 首先,我们假设一组向量 ,这组向量的各个值表示某个数据的特征值,那么它属于某个类别 的概率就可用这个形式表示: 。 根据上面的 Bayes’s theorem,这个概率的计算方法就如下: (1) 对于条件概率的定义如下: 则, = 可将上式写成如下形式: (2) 然后,我们假设xi与xj(j不等于i)独立(即特征之间没有关系),则: 则,对于(2)式(即(1)式右半部分的分母),就可以写成如下形式: 假设,则(1)式可以写成如下形式: (3) 通过(3)式,我们就可以计算一个数据属于某个类别的概率,分类结果也就是概率最大的那一个类别,所以Naive Bayes Classifier的完整表达形式如下: 注意:由于在最后的结果比较中,每一个结果的计算都含有,即它不影响比较结果,可以直接忽略。 四、说明 关于Naive Bayes Classifier,我们假设了特征之间不存在任何关系,然而现实是特征之间是不可能没有关系的。比如对于水果的类别,它们的颜色,大小,重量之间比如存在某种联系;再比如对于人类的性别,身高与体重等特征也是存在联系的。但是,Naive Bayes Classifier往往会取得比较好的结果,如果对数据和样本能做一些合适的预处理,它取得的结果是非常好的。 Naive Bayes Classifier在现实生活中的应用:垃圾邮件的分类,拼写检查与自动纠正,银行关于信用卡欺诈的检测等等。 五、参考与扩展链接 关于本篇文章的参考链接:https://www.mathsisfun.com/data/bayes-theorem.html https://www.wikiwand.com/en/Bayes%27_theorem# https://www.wikiwand.com/en/Naive_Bayes_classifier 这些链接中的很多知识本篇文章中还没有讲到,推荐大家去阅读学习。
一、Table for Content 在之前的文章中我们介绍了Decision Trees Agorithms,然而这个学习算法有一个很大的弊端,就是很容易出现Overfitting,为了解决此问题人们找到了一种方法,就是对Decision Trees 进行 Pruning(剪枝)操作。 为了提高Decision Tree Agorithm的正确率和避免overfitting,人们又尝试了对它进行集成,即使用多棵树决策,然后对于分类问题投票得出最终结果,而对于回归问题则计算平均结果。下面是几条是本篇要讲的主要内容。 Pruning (decision trees) What is Random forest algorithm? Why Random Forest algorithm? How Random Forest algorithm works? Advantages of Random Forest algorithm. Random Forest algorithm real life example. 本文主要参考一下几篇文章,有能力的读者可自行前往阅读原文: 1. Wikipedia上的Pruning (decision trees) 和 Random Froest algorithm。 2. Dataaspirant上的《HOW THE RANDOM FOREST ALGORITHM WORKS IN MACHINE LEARNING》 3. medium上的《How Random Forest Algorithm Works in Machine Learning》 同时推荐读者去阅读《The Random Forest Algorithm》,因为这篇文章讲解了在scikit-learn中Random Forest Agorithm常用的重要参数。 二、Pruning(decision trees) There are two approaches to avoiding overfitting in building decision trees: Pre-pruning that stop growing the tree earlier, before it perfectly classifies the training set. Post-pruning that allows the tree to perfectly classify the training set, and then post prune the tree. Pre-pruning(预剪枝),该方法是在建立决策树的过程中,判断当决策树的node满足一定条件(比如当树的深度达到事先设定的值,或者当该node下的样例个数小于等于某个数)时,不在继续建立子树,所以也叫Early stopping。 Post-pruning(后剪枝),对于此方法,先建立完整的决策树,然后通过一定的算法,将某个非leaf node设为leaf node(即将该node下的子树丢弃)实现pruning。 由于Pre-pruning较为简单就不做具体介绍,所以介绍一下Cost complexity pruning(通过此方法选择某个node设为leaf node,此方法来自wikipedia),当然还有许多其他的方法就不一一介绍了,读者可自行查阅。 三、What is Random Forest algorithm? 关于Random Froest algorithm(随机森林)算法的介绍,很多文章的介绍用例都大同小异,所以在这里也就不另起炉灶了,参考某篇文章的介绍,并做本土特色化翻译如下: 假设有一名学生叫小明,他今年暑假准备去旅游,但他不知道该去哪儿,于是就去问自己的好朋友小刚的意见,小刚则问他一些问题,比如你以前去过哪儿啊,你对要去地方的天气有什么要求啊等等,然后小刚通过这些问题给小明一个建议。决策树就是这样一种思想,通过对样本数据的各个特征值建立一定的规则,让后使用这些规则对新数据做出决策,跟此例非常相似。 但是小明觉得只是一个人的建议,可能比较片面,于是他就问去问了一下他的其他几个朋友,而这几个朋友也问了他一些问题,这些问题有的跟小刚的问题一样,有的不一样,然后他们各自给出了建议,小明拿到这些建议后,综合了一下,有5个朋友建议他去西安,3个朋友建议他去重庆,2个朋友建议他去成都,他最终就决定这个暑假去西安游玩。Random Froest algorithm(随机森林)算法也是如此,很多颗树使用随机样本的随机特征值建立不同的规则,然后各树对于新数据得出不同的结果,最终结果取综合(分类投票,回归取平均)。 Random Froest algorithm(随机森林)的维基百科定义如下: Random forests or random decision forests are an ensemble learningmethod for classification, regression and other tasks, that operate by constructing a multitude of decision trees at training time and outputting the class that is the mode of the classes (classification) or mean prediction (regression) of the individual trees. 四、Why Random Forest algorithm? 关于这个问题,主要有以下几点理由: The same random forest algorithm or the random forest classifier can use for both classification and the regression task. Random forest classifier will handle the missing values. When we have more trees in the forest, random forest classifier won’t overfit the model. Can model the random forest classifier for categorical values also. 五、How Random Forest algorithm works? 建立随机森林的过程如下图: 对左图中的Dataset创建包含三棵树的随机森林,过程如下: step1:在Dataset的众多特征中,随机选取5个特征,在随机选取j个样本数据。 step2: 然后以这些数据构建一颗decesion tree。 step3:重做step1, step2,直到森林中树的数目满足要求。 所以构建Random Forest的通用算法如下: 1. Randomly select “K” features from total “m” features where k << m, then randomly seletct “J” samples from total “n” samples . 2. Among the “K” features of “J” samples, calculate the node “d” using the best split point. 3. Split the node into daughter nodes using the best split. 4. Repeat the 1 to 3 steps until “l” number of nodes has been reached. 5. Build forest by repeating steps a to d for “q” number times to create “q” number of trees. Random Forest classifier的使用步骤如下: 1. Takes the test features and use the rules of each randomly created decision tree to predict the outcome and stores the predicted outcome(target). 2. Calculate the votes for each predicted target. 3. Consider the high voted predicted target as the final prediction from the random forest algorithm. 六、Advantages of Random Forest algorithm 至于Random Forest algorithm的优点,跟使用它的理由比较相似,主要如下: 1. 对于分类问题,永远不会出现overfitting。 2. 相同的Random Forest algorithm,对于分类问题和回归问题都适用。 3. 它可以识别出数据集中最重要的特征,这也叫feature engineering。 七、Random Forest algorithm real life example 主要应用场景如下: 1. 对于银行业务,它可以被用来分析诚信客户与欺诈客户,对于诚信客户可以给予他们更高的信用额度,而欺诈客户,将面临风险。 2. 对于医药行业,可以使用它来分析制药配方,或者对病人进行病情分析。 3. 对于股市,可以根据以往的数据记录预测将来的趋势,用来做获益或损失的决策。 4. 对于电子商务,那就更不用说了,可以对用户以往的交易记录、浏览记录做定制的广告推送。
一、Decision Trees Agorithms的简介 决策树算法(Decision Trees Agorithms),是如今最流行的机器学习算法之一,它即能做分类又做回归(不像之前介绍的其他学习算法),在本文中,将介绍如何用它来对数据做分类。 本文参照了Madhu Sanjeevi ( Mady )的Decision Trees Algorithms,有能力的读者可去阅读原文。 说明:本文有几处直接引用了原文,并不是不想做翻译,而是感觉翻译过来总感觉不够清晰,而原文却讲的很明白清晰。(个人观点:任何语言的翻译都会损失一定量的信息,所以尽量支持原版) 二、Why Decision trees? 在已经有了很多种学习算法的情况下,为什么还要创造出回归树这种学习算法呢?它相比于其他算法有和优点? 至于为什么,原因有很多,这里主要讲两点,这两点也是在我看来相比于其他算法最大的优点。 其一,决策树的算法思想与人类做决定时的思考方式很相似,它相比于其他算法,无需计算很多很多的各种参数,它能像人类一样综合各种考虑,做出很好的选择(不一定是最好啊ㄟ(▔,▔)ㄏ)。 其二,它能将它做出决策的逻辑过程可视化(不同于SVM, NN, 或是神经网络等,对于用户而言是一个黑盒), 例如下图,就是一个银行是否给客户发放贷款使用决策树决策的一个过程。 三、What is the decision tree?? A decision tree is a tree where each node represents a feature(attribute), each link(branch) represents a decision(rule) and each leaf represents an outcome(categorical or continues value). 类似于下图中左边的数据,对于数据的分类我们使用右边的方式对其分类: step 1:判断Age,Age<27.5,则Class=High;否则,执行step 2。 step 2: 判断CarType,CarType∈Sports,则Class=High;否则Class=Low。 对于一组数据,只需按照决策树的分支一步步的走下去,便可得到最终的结果,有点儿类似于程序设计中的多分支选择结构。 四、How to build this?? 学习新知识,最主要的三个问题就是why,what,how。前两个问题已经在上面的介绍中解决了,接下来就是how,即如何建立一颗决策树? 建立决策树,有很多种算法,本文主要讲解一下两种: ID3 (Iterative Dichotomiser 3) → uses Entropy function and Information gain as metrics. CART (Classification and Regression Trees) → uses Gini Index(Classification) as metric. ————————————————————————————————————————————————————————————————————————————————————————————————————— 首先,我们使用第一种算法来对一个经典的分类问题建立决策树: Let’s just take a famous dataset in the machine learning world which is whether dataset(playing game Y or N based on whether condition). We have four X values (outlook,temp,humidity and windy) being categorical and one y value (play Y or N) also being categorical. So we need to learn the mapping (what machine learning always does) between X and y. This is a binary classification problem, lets build the tree using the ID3 algorithm. 首先,决策树,也是一棵树,在计算机科学中,树是一种数据结构,它有根节点(root node),分枝(branch),和叶子节点(leaf node)。 而对于一颗决策树,each node represents a feature(attribute),so first, we need to choose the root node from (outlook, temp, humidity, windy). 那么改如何选择呢? Answer: Determine the attribute that best classifies the training data; use this attribute at the root of the tree. Repeat this process at for each branch. 这也就意味着,我们要对决策树的空间进行自顶向下的贪婪搜索。 所以问题又来了,how do we choose the best attribute? Answer: use the attribute with the highest information gain in ID3. In order to define information gain precisely, we begin by defining a measure commonly used in information theory, called entropy(熵) that characterizes the impurity of an arbitrary collection of examples.” So what's the entropy? (下图是wikipedia给出的定义) 从上面的公式中我们可以得到,对于一个二分类问题,如果entropy=0,则要么全为正样本,要么全为负样本(即理论上样本应该属于两个,实际上所有的样本全属于一类)。如果entropy=1,则正负样本各占一半。 有了Entropy的概念,便可以定义Information gain: 有了上述两个概念,便可建立决策树了,步骤如下: 1.compute the entropy for data-set 2.for every attribute/feature: 1.calculate entropy for all categorical values 2.take average information entropy for the current attribute 3.calculate gain for the current attribute 3. pick the highest gain attribute. 4. Repeat until we get the tree we desired. 对于这个实例,我们来具体使用一下它: step1(计算数据集整体的entropy): step2(计算每一项feature的entropy and information gain): 这里只计算了两项,其他两项的计算方法类似。 step3 (选择Info gain最高的属性): 上表列出了每一项feature的entropy and information gain,我们可以发现Outlook便是我们要找的那个attribute。 So our root node is Outlook: 接着对于图中左边的未知节点,我们将由sunny得来的数据当做数据集,然后从这些数据中按照上述的步骤选择其他三个属性的一种作为此节点,对于右边的节点做类似操作即可: 最终,建立的决策树如下: ————————————————————————————————————————————————————————————————————————————————————————————————————— 接着,我们使用第二种算法来建立决策树(Classification with using the CART algorithm): CART算法其实与ID3非常相像,只是每次选择时的指标不同,在ID3中我们使用entropy来计算Informaition gain,而在CART中,我们使用Gini index来计算Gini gain。 同样的,对于一个二分类问题而言(Yes or No),有四种组合:1 0 , 0 1 , 1 0 , 0 0,则存在 P(Target=1).P(Target=1) + P(Target=1).P(Target=0) + P(Target=0).P(Target=1) + P(Target=0).P(Target=0) = 1 P(Target=1).P(Target=0) + P(Target=0).P(Target=1) = 1 — P^2(Target=0) — P^2(Target=1) 那么,对于二分类问题的Gini index定义如下: A Gini score gives an idea of how good a split is by how mixed the classes are in the two groups created by the split. A perfect separation results in a Gini score of 0, whereas the worst case split that results in 50/50 classes. 所以,对于一个二分类问题,最大的Gini index: = 1 — (1/2)^2 — (1/2)^2 = 1–2*(1/2)^2 = 1- 2*(1/4) = 1–0.5 = 0.5 和二分类类似,我们可以定义出多分类时Gini index的计算公式: Maximum value of Gini Index could be when all target values are equally distributed. 同样的,当取最大的Gini index时,可以写为(一共有k类且每一类数量相等时): = 1–1/k 当所有样本属于同一类别时,Gini index为0。 此时我们就可以根据Gini gani来选择所需的node,Gini gani的计算公式(类似于information gain的计算)如下: 那么便可以使用类似于ID3的算法的思想建立decision tree,步骤如下: 1.compute the gini index for data-set 2.for every attribute/feature: 1.calculate gini index for all categorical values 2.take average information entropy(这里指GiniGain(A,S)的右半部分,跟ID3中的不同) for the current attribute 3.calculate the gini gain 3. pick the best gini gain attribute. 4. Repeat until we get the tree we desired. 最终,形成的decision tree如下: 其实这两种算法本质没有任何区别,只是选择node时所用的指标(表达式)不同而已。
一、逻辑回归简介 logistic回归又称logistic回归分析,是一种广义的线性回归分析模型,常用于数据挖掘,疾病自动诊断,经济预测等领域。 logistic回归是一种广义线性回归(generalized linear model),因此与多重线性回归分析有很多相同之处。 其公式如下: 其图像如下: 我们通过观察上面的图像可以发现,逻辑回归的值域为(0, 1),当输入为0时,其输出为0.5;当输入小于0,并且越来越小时,其输出越来越接近于0;相反的,当其输入大于0,并且越来越大时,其输出越来越接近于1。 通常我们使用线性回归来预测值,但逻辑回归随有“回归”二字,却通常是用来解决二分类问题的。 当其输出大于0.5时,我们可以认为该样本属于甲类;小于0.5时,认为该样本属于已类。 但是由于一个样本数据通常会有多个特征,我们不能将其直接带入logistic回归公式中,所以,就需要借助之前所介绍的线性回归,使该样本的多个特征值生成一个特定的值,在带入公式中,对其分类,所以z的表达式如下: 即可得到对于一个数据关于逻辑回归的详细表达式: 通过上式,我们就可以对一个任意数据进行逻辑回归分析了,但是这当中存在一个问题,即关于θ的取值,只有公式中的θ已知,我们才能对一个未分类的数据运用此公式,那么该如何求得θ呢? 请看下面的公式推导。 二、Logistic Regression公式推导 在上面,我们得到 后,需要求得θ,关于如何求得θ,将在此进行详细分析。 通常在机器学习中,我们常常有一个过程叫训练,所谓训练,即通过已知分类(或标签)的数据,求得一个模型(或分离器),然后使用这个模型对未知标签的数据打上标签(或者对其进行分类)。 所以,我们使用样本(即已知分类的数据),进行一系列的估算,得到θ。这个过程在概率论中叫做参数估计。 在此,我们将使用极大似然估计的推导过程,求得关于计算θ的公式: (1) 首先我们令: (2) 将上述两式整合: (3) 求其似然函数: (4) 对其似然函数求对数: (5) 当似然函数为最大值时,得到的θ即可认为是模型的参数。求似然函数的最大值,我们可以使用一种方法,梯度上升,但我们可以对似然函数稍作处理,使之变为梯度下降,然后使用梯度下降的思想来求解此问题,变换 的表达式如下: (由于乘了一个负的系数,所以梯度上升变梯度下降。) (6) 因为我们要使用当前的θ值通过更新得到新的θ值,所以我们需要知道θ更新的方向(即当前θ是加上一个数还是减去一个数离最终结果近),所以得到J(θ)后对其求导便可得到更新方向(为什么更新方向这么求?以及得到更新方向后为什么按照下面的式子处理?请看下方的梯度下降公式的演绎推导),求导过程如下: (7) 得到更新方向后便可使用下面的式子不断迭代更新得到最终结果。 三、梯度下降公式的演绎推导 关于求解函数的最优解(极大值和极小值),在数学中我们一般会对函数求导,然后让导数等于0,获得方程,然后通过解方程直接得到结果。但是在机器学习中,我们的函数常常是多维高阶的,得到导数为0的方程后很难直接求解(有些时候甚至不能求解),所以就需要通过其他方法来获得这个结果,而梯度下降就是其中一种。 对于一个最简单的函数:, 我们该如何求出y最小是x的值呢(不通过解2x = 0的方法)? (1) 首先对x任取一个值,比如x = -4,可以得到一个y值。 (2) 求得更新方向(如果不求更新方向对x更新,比如x-0.5,或x+0.5,得到图像如下)。 可以发现,我们如果是向负方向更新x,那么我就偏离了最终的结果,此时我们应该向正方向更新,所以我们在对x更新前需要求得x的更新方向(这个更新方向不是固定的,应该根据当前值确定,比如当x=4时,应向负方向更新) 求其导函数在这一点的值,y' = 2x,x = -4, y' = -8,那么它的更新方向就是y',对x更新我们只需x:=x-α·y'(α(大于0)为更新步长,在机器学习中,我们叫它学习率)。 PS:之前说了是多维高阶方程,无法求解,而不是不能对其求导,所以可以对其求导,然后将当前x带入。 (3) 不断重复之前的(1),(2)步,直到x收敛。 梯度下降方法: 对于这个式子,如果: (1) m是样本总数,即每次迭代更新考虑所有的样本,那么就叫做批量梯度下降(BGD),这种方法的特点是很容易求得全局最优解,但是当样本数目很多时,训练过程会很慢。当样本数量很少的时候使用它。 (2)当m = 1,即每次迭代更新只考虑一个样本,公式为,叫做随机梯度下降(SGD),这种方法的特点是训练速度快,但是准确度下降,并不是全局最优。比如对下列函数(当x=9.5时,最终求得是区部最优解): (3) 所以综上两种方法,当m为所有样本数量的一部分(比如m=10),即我们每次迭代更新考虑一小部分的样本,公式为,叫做小批量梯度下降(MBGD),它克服了上述两种方法的缺点而又兼顾它们的优点,在实际环境中最常被使用。
一、线性回归算法的简介 线性回归是利用数理统计中回归分析,来确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法,运用十分广泛。其表达形式为y = w'x+e,e为误差服从均值为0的正态分布。 回归分析中,只包括一个自变量和一个因变量,且二者的关系可用一条直线近似表示,这种回归分析称为一元线性回归分析。如果回归分析中包括两个或两个以上的自变量,且因变量和自变量之间是线性关系,则称为多元线性回归分析。 本文主要介绍线性回归算法的演绎推导,关于线性回归的详细介绍请参阅线性回归在百度百科中的介绍。 线性回归算法是机器学习中的基础算法,所以对于想要学习机器学习的读者来说,最好完全理解该算法。 二、线性回归算法的演绎推导 假设,在银行中申请行用卡的额度与如下两个参数有关,即年龄和工资,有一申请人的资料如下图,那么知道一个人的年龄和工资该如何预测出他所能申请信用卡的额度呢? 对于一个线性关系,我们使用y=ax+b表示,但在这种关系中y只受一个x的影响,二者的关系可用一条直线近似表示,这种关系也叫一元线性回归。而在本例中,设额度为h,工资和年龄分别为x1和x2,则可以表示成下式,,在这种关系中结果收到多个变量的影响,称为多元线性回归分析。 我们将上式中的θ和x分别表示成两个一维矩阵[θ0 θ1 θ2]和[x0 x1 x2],则可将上式化为(令x0=1)。 而实际结果不可能完全符合我们的计算结果,所以两者之间必定存在误差,假设对于第i个样本,存在如下关系,,其中为真实误差。 误差独立并且具有相同的分布(通常认为是均值为0的高斯分布)。 所以可以得到下式: 那么,如果存在大量的样本,我们就可以通过和做关于θ的参数估计, 求似然函数如下: 对上式求对数: 对上式求导,使其值为0,便可求得θ的最大似然估计。 在上式中,被标记的两部分都是常数,前一部分求导后为零,后一部分为一个因数,不会影响最终结果。所以,对于最终结果,只需让未被标记的部分求导后为0。所以使: , 将上式化简,并对θ求偏导: 将求导的结果设值为0,便可求得θ的最大似然估计(最小二乘法), 得到θ后,我们即通过样本训练出了一个线性回归模型,便可使用对结果未知的数据进行预测。 PS: 读者只需理解改算法的推导过程即可,对于数据的计算,编程解决即可,无需手动计算(对于多维矩阵的计算量相当大,而且很容易算错 ( ̄▽ ̄)")。
一、pandas的简介 Python Data Analysis Library 或 pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。Pandas 纳入了大量库和一些标准的数据模型,提供了高效地操作大型数据集所需的工具。pandas提供了大量能使我们快速便捷地处理数据的函数和方法。 pandas的数据结构: Series:一维数组,与Numpy中的一维array类似。二者与Python基本的数据结构List也很相近,其区别是:List中的元素可以是不同的数据类型,而Array和Series中则只允许存储相同的数据类型,这样可以更有效的使用内存,提高运算效率。 Time- Series:以时间为索引的Series。 DataFrame:二维的表格型数据结构。很多功能与R中的data.frame类似。可以将DataFrame理解为Series的容器。以下的内容主要以DataFrame为主。 Panel :三维的数组,可以理解为DataFrame的容器。 本文主要介绍DateFrame和Series,其中DataFrame充电介绍。 本文中用到的数据文件地址:pandas的基本使用.zip 本文只是结合实例介绍pandas的基本使用,若要详细深入学习,请参阅pandas官方文档。 二、pandas中的DateFrame 使用pandas我们可以很方便的对二维表结构进行一些常规操作。 1. 使用pandas读取csv(或excel等)文件 import pandas food_info = pandas.read_csv("food_info.csv") # 读取csv文件 # 读取Excel文件使用pandas.read_excel()即可 print(type(food_info)) # food_info为一个DataFrame对象 print(food_info.dtypes) # 各项数据的类型 <class 'pandas.core.frame.DataFrame'> NDB_No int64 Shrt_Desc object Water_(g) float64 Energ_Kcal int64 Protein_(g) float64 Lipid_Tot_(g) float64 Ash_(g) float64 Carbohydrt_(g) float64 Fiber_TD_(g) float64 Sugar_Tot_(g) float64 Calcium_(mg) float64 Iron_(mg) float64 Magnesium_(mg) float64 Phosphorus_(mg) float64 Potassium_(mg) float64 Sodium_(mg) float64 Zinc_(mg) float64 Copper_(mg) float64 Manganese_(mg) float64 Selenium_(mcg) float64 Vit_C_(mg) float64 Thiamin_(mg) float64 Riboflavin_(mg) float64 Niacin_(mg) float64 Vit_B6_(mg) float64 Vit_B12_(mcg) float64 Vit_A_IU float64 Vit_A_RAE float64 Vit_E_(mg) float64 Vit_D_mcg float64 Vit_D_IU float64 Vit_K_(mcg) float64 FA_Sat_(g) float64 FA_Mono_(g) float64 FA_Poly_(g) float64 Cholestrl_(mg) float64 dtype: object 输出 2. 获取数据 food_info.head(10) # 获取前10行数据,默认获取5行 # first_rows = food_info.head() # first_rows # food_info.tail(8) # 获取尾8行数据,默认获取5行 # print(food_info.tail()) print(food_info.columns) # 获取foodinfo的各字段名(即表头)# print(food_info.shape) # 获取结构 比如此文件时8618行×36列 Index(['NDB_No', 'Shrt_Desc', 'Water_(g)', 'Energ_Kcal', 'Protein_(g)', 'Lipid_Tot_(g)', 'Ash_(g)', 'Carbohydrt_(g)', 'Fiber_TD_(g)', 'Sugar_Tot_(g)', 'Calcium_(mg)', 'Iron_(mg)', 'Magnesium_(mg)', 'Phosphorus_(mg)', 'Potassium_(mg)', 'Sodium_(mg)', 'Zinc_(mg)', 'Copper_(mg)', 'Manganese_(mg)', 'Selenium_(mcg)', 'Vit_C_(mg)', 'Thiamin_(mg)', 'Riboflavin_(mg)', 'Niacin_(mg)', 'Vit_B6_(mg)', 'Vit_B12_(mcg)', 'Vit_A_IU', 'Vit_A_RAE', 'Vit_E_(mg)', 'Vit_D_mcg', 'Vit_D_IU', 'Vit_K_(mcg)', 'FA_Sat_(g)', 'FA_Mono_(g)', 'FA_Poly_(g)', 'Cholestrl_(mg)'], dtype='object') 输出1: # print(food_info.loc[0]) # 获取第0行数据 print(food_info.loc[6000]) # 获取第6000行数据 # food_info.loc[10000] # 获取第10000行数据,超过数据文件本身长度,报错KeyError: 'the label [10000] is not in the [index]' NDB_No 18995 Shrt_Desc KELLOGG'S EGGO BISCUIT SCRAMBLERS BACON EGG & CHS Water_(g) 42.9 Energ_Kcal 258 Protein_(g) 8.8 Lipid_Tot_(g) 7.9 Ash_(g) NaN Carbohydrt_(g) 38.3 Fiber_TD_(g) 2.1 Sugar_Tot_(g) 4.7 Calcium_(mg) 124 Iron_(mg) 2.7 Magnesium_(mg) 14 Phosphorus_(mg) 215 Potassium_(mg) 225 Sodium_(mg) 610 Zinc_(mg) 0.5 Copper_(mg) NaN Manganese_(mg) NaN Selenium_(mcg) NaN Vit_C_(mg) NaN Thiamin_(mg) 0.3 Riboflavin_(mg) 0.26 Niacin_(mg) 2.4 Vit_B6_(mg) 0.02 Vit_B12_(mcg) 0.1 Vit_A_IU NaN Vit_A_RAE NaN Vit_E_(mg) 0 Vit_D_mcg 0 Vit_D_IU 0 Vit_K_(mcg) NaN FA_Sat_(g) 4.1 FA_Mono_(g) 1.5 FA_Poly_(g) 1.1 Cholestrl_(mg) 27 Name: 6000, dtype: object 输出2 # food_info.loc[3:6] # 获取第3到6行数据 two_five_ten = [2,5,10] print(food_info.loc[two_five_ten]) # 获取第2,5,10数据 NDB_No Shrt_Desc Water_(g) Energ_Kcal Protein_(g) \ 2 1003 BUTTER OIL ANHYDROUS 0.24 876 0.28 5 1006 CHEESE BRIE 48.42 334 20.75 10 1011 CHEESE COLBY 38.20 394 23.76 Lipid_Tot_(g) Ash_(g) Carbohydrt_(g) Fiber_TD_(g) Sugar_Tot_(g) \ 2 99.48 0.00 0.00 0.0 0.00 5 27.68 2.70 0.45 0.0 0.45 10 32.11 3.36 2.57 0.0 0.52 ... Vit_A_IU Vit_A_RAE Vit_E_(mg) Vit_D_mcg Vit_D_IU \ 2 ... 3069.0 840.0 2.80 1.8 73.0 5 ... 592.0 174.0 0.24 0.5 20.0 10 ... 994.0 264.0 0.28 0.6 24.0 Vit_K_(mcg) FA_Sat_(g) FA_Mono_(g) FA_Poly_(g) Cholestrl_(mg) 2 8.6 61.924 28.732 3.694 256.0 5 2.3 17.410 8.013 0.826 100.0 10 2.7 20.218 9.280 0.953 95.0 输出3 # food_info['Shrt_Desc'] # 获取字段名为'Shrt_Desc'的这一列 ndb_col = food_info['NDB_No'] # 获取字段名为'NDB_No'的这一列 # print(ndb_col) col_name = 'Shrt_Desc' print(food_info[col_name]) 0 BUTTER WITH SALT 1 BUTTER WHIPPED WITH SALT 2 BUTTER OIL ANHYDROUS 3 CHEESE BLUE 4 CHEESE BRICK 5 CHEESE BRIE 6 CHEESE CAMEMBERT 7 CHEESE CARAWAY 8 CHEESE CHEDDAR 9 CHEESE CHESHIRE 10 CHEESE COLBY 11 CHEESE COTTAGE CRMD LRG OR SML CURD 12 CHEESE COTTAGE CRMD W/FRUIT 13 CHEESE COTTAGE NONFAT UNCRMD DRY LRG OR SML CURD 14 CHEESE COTTAGE LOWFAT 2% MILKFAT 15 CHEESE COTTAGE LOWFAT 1% MILKFAT 16 CHEESE CREAM 17 CHEESE EDAM 18 CHEESE FETA 19 CHEESE FONTINA 20 CHEESE GJETOST 21 CHEESE GOUDA 22 CHEESE GRUYERE 23 CHEESE LIMBURGER 24 CHEESE MONTEREY 25 CHEESE MOZZARELLA WHL MILK 26 CHEESE MOZZARELLA WHL MILK LO MOIST 27 CHEESE MOZZARELLA PART SKIM MILK 28 CHEESE MOZZARELLA LO MOIST PART-SKIM 29 CHEESE MUENSTER ... 8588 BABYFOOD CRL RICE W/ PEARS & APPL DRY INST 8589 BABYFOOD BANANA NO TAPIOCA STR 8590 BABYFOOD BANANA APPL DSSRT STR 8591 SNACKS TORTILLA CHIPS LT (BAKED W/ LESS OIL) 8592 CEREALS RTE POST HONEY BUNCHES OF OATS HONEY RSTD 8593 POPCORN MICROWAVE LOFAT&NA 8594 BABYFOOD FRUIT SUPREME DSSRT 8595 CHEESE SWISS LOW FAT 8596 BREAKFAST BAR CORN FLAKE CRUST W/FRUIT 8597 CHEESE MOZZARELLA LO NA 8598 MAYONNAISE DRSNG NO CHOL 8599 OIL CORN PEANUT AND OLIVE 8600 SWEETENERS TABLETOP FRUCTOSE LIQ 8601 CHEESE FOOD IMITATION 8602 CELERY FLAKES DRIED 8603 PUDDINGS CHOC FLAVOR LO CAL INST DRY MIX 8604 BABYFOOD GRAPE JUC NO SUGAR CND 8605 JELLIES RED SUGAR HOME PRESERVED 8606 PIE FILLINGS BLUEBERRY CND 8607 COCKTAIL MIX NON-ALCOHOLIC CONCD FRZ 8608 PUDDINGS CHOC FLAVOR LO CAL REG DRY MIX 8609 PUDDINGS ALL FLAVORS XCPT CHOC LO CAL REG DRY MIX 8610 PUDDINGS ALL FLAVORS XCPT CHOC LO CAL INST DRY... 8611 VITAL WHEAT GLUTEN 8612 FROG LEGS RAW 8613 MACKEREL SALTED 8614 SCALLOP (BAY&SEA) CKD STMD 8615 SYRUP CANE 8616 SNAIL RAW 8617 TURTLE GREEN RAW Name: Shrt_Desc, Length: 8618, dtype: object 输出4 columns = ['Water_(g)', 'Shrt_Desc'] zinc_copper = food_info[columns] # 获取字段名为'Water_(g)', 'Shrt_Desc'的这两列 print(zinc_copper) # 获取以"(mg)"结尾的各列数据 col_names = food_info.columns.tolist() # print(col_names) milligram_columns = [] for items in col_names: if items.endswith("(mg)"): milligram_columns.append(items) milligram_df = food_info[milligram_columns] print(milligram_df) 3. 对数据的简单处理: import pandas food_info = pandas.read_csv('food_info.csv') # food_info.head(3) # print(food_info.shape) # print(food_info['Iron_(mg)']) # Iron_(mg)这一列的单位是mg,将其转为mg,对其值除以1000 div_1000 = food_info['Iron_(mg)'] / 1000 # print(div_1000) # 对每行数据中的其中两列进行计算 water_energy = food_info['Water_(g)'] * food_info['Energ_Kcal'] # print(food_info.shape) # DateFrame结构插入一列,字段名为'water_energy',值为water_energy的数据 food_info['water_energy'] = water_energy # print(food_info[['Water_(g)', 'Energ_Kcal', 'water_energy']]) # print(food_info.shape) # 求某列的最大值 max_calories = food_info['Energ_Kcal'].max() # print(max_calories) # 对指定字段排序,inplace=False将排序后的结果生成一个新的DataFrame,inplace=True则在原来的基础上进行排序,默认升序排序 # food_info.sort_values('Sodium_(mg)', inplace=True) # print(food_info['Sodium_(mg)']) a = food_info.sort_values('Sodium_(mg)', inplace=False, ascending=False) # ascending=False 使用降序排序 # print(food_info['Sodium_(mg)']) # print(a['Sodium_(mg)']) 4. 对数据的常规操作 import pandas as pd import numpy as np titanic_survival = pd.read_csv('titanic_train.csv') # titanic_survival.head() age = titanic_survival['Age'] # print(age.loc[0:10]) age_is_null = pd.isnull(age) # 迭代判断值是否为空,结果可以作为一个索引 # print(age_is_null) age_null_true = age[age_is_null] # 获取值为空的数据集 # print(age_null_true) print(len(age_null_true)) # 判断一共有多少个空数据 # 求平均值,应用不为空的数据集求 good_ages = age[age_is_null == False] # 获取值不为空的数据集 # print(good_ages) correct_mean_age = sum(good_ages) / len(good_ages) # 求平均 print(correct_mean_age) # 或者使用pandas内置的求均值函数,自动去除空数据 correct_mean_age = age.mean() # 求平均,将空值舍弃 print(correct_mean_age) # pivot_table方法默认求平均值,如果需求是求平均aggfunc参数可以不写 # index tells the method which column to group by # values is the column that we want to apply the calculation to # aggfunc specifies the calculation we want to perform passenger_surival = titanic_survival.pivot_table(index='Pclass', values='Survived', aggfunc=np.mean) # 对index相同的分别求平均值 print(passenger_surival) # 分组对多列求和 # port_stats = titanic_survival.pivot_table(index="Embarked", values=['Fare', "Survived"], aggfunc=np.sum) # ,分别对价格和存活人数求和 # print(port_stats) # 丢弃空值数据 drop_na_columns = titanic_survival.dropna(axis=1, inplace=False) # axis=1,以行为判断依据,数据为空,则从Dataframe中丢弃,inplace=False返回一个新的Dataframe对象,否则对当前对象做操作 # print(drop_na_columns) new_titanic_survival = titanic_survival.dropna(axis=0, subset=['Age', 'Sex'], inplace=False) # axis=0,以列为判断依据,需要指定判断列的字段,数据为空,则从Dataframe中丢弃 # print(new_titanic_survival) # 具体定位到某行某列 row_index_83_age = titanic_survival.loc[83, 'Age'] row_index_766_pclass = titanic_survival.loc[766, 'Pclass'] print(row_index_83_age) print(row_index_766_pclass) new_titanic_survival = titanic_survival.sort_values("Age", ascending=False) # 每行的年龄按降序排序 print(new_titanic_survival[0:10]) print('------------------------>') titanic_reindexed = new_titanic_survival.reset_index(drop=True) # 重置每行的索引值 print(titanic_reindexed[0:20]) # 自定义函数,对每行或每列逐个使用 def null_count(column): column_null = pd.isnull(column) null = column[column_null] return len(null) column_null_count = titanic_survival.apply(null_count, axis=0) # 通过自定义函数,统计每列为空的个数 print(column_null_count) def which_class(row): pclass = row['Pclass'] if pclass == 1: return 'First Class' elif pclass == 2: return 'Second Class' elif pclass == 3: return 'Third Class' else: return 'Unknow' classes = titanic_survival.apply(which_class, axis=1) # 通过自定义函数,替换每行的Pclass值, 注意axis=1 print(classes) 5. 配合numpy将数据载入后进行预处理 import pandas as pd import numpy as np fandango = pd.read_csv('fandango_score_comparison.csv') # print(type(fandango)) # 返回一个新的dataframe,返回的新数据以设定的值为index,并将丢弃index值为空的数据,drop=True,丢弃为索引的列,否则不丢弃 fandango_films = fandango.set_index('FILM', drop=False) # fandango_films # print(fandango_films.index) # 按索引获取数据 fandango_films["Avengers: Age of Ultron (2015)" : "Hot Tub Time Machine 2 (2015)"] fandango_films.loc["Avengers: Age of Ultron (2015)" : "Hot Tub Time Machine 2 (2015)"] fandango_films.loc['Southpaw (2015)'] movies = ['Kumiko, The Treasure Hunter (2015)', 'Do You Believe? (2015)', 'Ant-Man (2015)'] fandango_films.loc[movies] # def func(coloumn): # return np.std(coloumn) types = fandango_films.dtypes # print(types) float_columns = types[types.values == 'float64'].index # 获取特定类型的数据的索引 # print(float_columns) float_df = fandango_films[float_columns] # 获取特定类型的数据 # print(float_df.dtypes) # float_df # print(float_df) deviations = float_df.apply(lambda x: np.std(x)) # 计算每列标准差 print(deviations) # print('----------------------->') # print(float_df.apply(func)) # help(np.std) rt_mt_user = float_df[['RT_user_norm', 'Metacritic_user_nom']] print(rt_mt_user.apply(np.std, axis=1)) # 计算每行数据标准差 # rt_mt_user.apply(np.std, axis=0) 三、DataFrame中的Series Series为DateFrame中一行或一列的数据结构 1. 获取一个Series对象 import pandas as pd from pandas import Series fandango = pd.read_csv('fandango_score_comparison.csv') series_film = fandango['FILM'] # 获取fandango中FILM这一列 # print(type(series_film)) print(series_film[0:5]) series_rt = fandango['RottenTomatoes'] # 获取fandango中RottenTomatoes这一列 print(series_rt[0:5]) 2. 对Series对象的一些常规操作 file_names = series_film.values # 获取series_film的所有值,返回值为一个<class 'numpy.ndarray'> # print(type(file_names)) # print(file_names) rt_scores = series_rt.values # print(rt_scores) series_custom = Series(rt_scores, index=file_names) # 构建一个新的Series, index为file_names, value为rt_scores # help(Series) print(series_custom[['Top Five (2014)', 'Night at the Museum: Secret of the Tomb (2014)']]) # 以index获取数据 # print(type(series_custom)) print('--------------------------------->') print(series_custom[5:10]) # 切片操作 # print(series_custom[["'71 (2015)"]]) original_index = series_custom.index.tolist() # 获取所有的index值并将其转为list # print(original_index) sorted_index = sorted(original_index) # 对list排序 # print(sort_index) sorted_by_index = series_custom.reindex(sorted_index) # 以排过序的list重新为series_custom设置索引 print(sorted_by_index) sc2 = series_custom.sort_index() # 以index按升序排序整个series_custom # print(sc2) sc3 = series_custom.sort_values(ascending=False) # 以values按降序排序整个series_custom print(sc3) import numpy as np # print(np.add(series_custom, series_custom)) #将series_custom当成一个矩阵,使用numpy进行计算 print(np.sin(series_custom)) print(np.max(series_custom)) # series_custom > 50 series_greater_than_50 = series_custom[series_custom > 50] # 获取series_custom的值大于50的数据 # series_greater_than_50 criteria_one = series_custom > 50 criteria_two = series_custom < 75 both_criteria = series_custom[criteria_one & criteria_two] # 获取series_custom的值大于50且小于75的数据 print(both_criteria) rt_critics = Series(fandango['RottenTomatoes'].values, index=fandango['FILM']) rt_users = Series(fandango['RottenTomatoes_User'].values, index=fandango['FILM']) rt_mean = (rt_critics + rt_users) / 2 # 将rt_critics 和 rt_users的值相加除以2 print(rt_mean)
一、numpy的简介 numpy是Python的一种开源的数值计算扩展库。这种工具可用来存储和处理大型矩阵,比Python自身的嵌套列表(nested list structure)结构要高效的多(该结构也可以用来表示矩阵(matrix))。 NumPy(Numeric Python)提供了许多高级的数值编程工具,如:矩阵数据类型、矢量处理,以及精密的运算库。专为进行严格的数字处理而产生。 Numpy中包含了大量的矩阵运算,所以读者最好具有一点儿线性代数的基础。 二、numpy基本使用 1. 导入numpy库并使用numpy的array方法配合Python中的list生成矩阵 import numpy as np vetor = np.array([1,2,3,4]) # 一维 matrix = np.array([ [1,1,1], [2,2,2], [3,3,3] ]) # 二维 print(vetor) print(matrix) 输出: [1 2 3 4] [[1 1 1] [2 2 2] [3 3 3]] 2. 获取矩阵的组成 print(vetor.shape) print(matrix.shape) 输出: (4,) (3, 3) 3. 数据类型 numbers = np.array([1,3,5,7]) # 全为int型 print(numbers) numbers.dtype 输出: [1 3 5 7] dtype('int64') numbers = np.array([1,3,5,7.0]) # 有一个为float型,全为float型 print(numbers) numbers.dtype 示例2 [1. 3. 5. 7.] dtype('float64') 输出2 numbers = np.array([1,3,5,'7']) # 有一个为字符串,全为字符串 print(numbers) numbers.dtype 示例3 ['1' '3' '5' '7'] dtype('<U21') 输出3 4. 操作矩阵的某个值,某些值 vetor = np.array([1,2,3,4]) matrix = np.array([[1,2,3],[4,5,6],[7,8,9]]) print(vetor[2]) # 打印vetor的第2个元素 print(vetor[:3]) # 打印vetor的第0到第二个(不包括第三个)元素 print(matrix[2,2]) # 打印matrix第2行,第2列的元素 print(matrix[:, 1]) # 打印每一行的第1列元素 输出: 3 [1 2 3] 9 [2 5 8] 5. 迭代判断矩阵中的所有值是否等于某个值 import numpy as np vector = np.array([1,3,5,7]) vector == 5 输出: array([False, False, True, False]) matrix = np.array([ [1, 4, 7], [2, 5, 8], [3, 6, 9] ]) matrix == 5 示例2 array([[False, False, False], [False, True, False], [False, False, False]]) 输出2 vector = np.array([1,3,4,5,7]) equal_three_and_five = (vector == 3) & (vector == 5) # 迭代判断vector中的所有值是否即等于3又等于5 print(equal_three_and_five) 示例3 [False False False False False] 输出3 vector = np.array([1,3,4,5,7]) equal_three_or_five = (vector == 3) | (vector == 5) # 迭代判断vector中的所有值是否即等于3或者等于5 print(equal_three_or_five) 示例4 [False True False True False] 输出4 matrix = np.array([ [1, 4, 7], [2, 5, 8], [3, 6, 9] ]) equal_ten = (matrix == 5) # 比较的结果可视为一个索引 print(equal_ten) print(matrix[equal_ten]) 进阶使用1 [[False False False] [False True False] [False False False]] [5] 进阶输出1 matrix = np.array([ [1, 4, 7], [2, 5, 8], [3, 6, 9] ]) equal_ten = (matrix[:, 1] == 5) # 迭代判断matrix的第1列的值是否等于5 print(equal_ten) print(matrix[equal_ten, :]) 进阶使用2 [False True False] [[2 5 8]] 进阶输出2 vector = np.array([1,3,4,5,7]) equal_three_or_five = (vector == 3) | (vector == 5) # 迭代判断vector中的所有值是否即等于3或者等于5 vector[equal_three_or_five] = 10 # 将等于3或者等于5的值替换成10 print(vector) 进阶使用3 [ 1 10 4 10 7] 进阶输出3 6.类型转换 vector = np.array(['1', '3', '5', '7']) print(vector.dtype) print(vector) vector = vector.astype(int) #使用astype方法做类型转换 print(vector.dtype) print(vector) 输出: <U1 ['1' '3' '5' '7'] int64 [1 3 5 7] 7.常规计算 vector = np.array([1,3,4,5,7]) print(vector.min()) #求最大值 print(vector.max()) #求最小组 输出: 1 7 # 示例2matrix = np.array([ [1, 4, 7], [2, 5, 8], [3, 6, 9] ]) print(matrix.min(axis=1)) # 按行求最小值,axis=1表示按行 print(matrix.max(axis=0)) # 按列求最大值,axis=0表示按列 print(matrix.max()) # 所有值求最大值 # 输出2 [1 2 3] [3 6 9] 9 # 示例3 matrix = np.array([ [1, 4, 7], [2, 5, 8], [3, 6, 9] ]) print(matrix.sum(axis=1)) # 按行求和 print(matrix.sum(axis=0)) # 按列求和 print(matrix.sum()) # 所有值求和 # 输出3 [12 15 18] [ 6 15 24] 45 8.生成初始矩阵 # 示例1 print(np.arange(15)) # 取0到14组成一个一维矩阵 a = np.arange(15).reshape(3,5) # 取0到14组成一个3行5列的2维矩阵 print(a) # 输出1 [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14] [[ 0 1 2 3 4] [ 5 6 7 8 9] [10 11 12 13 14]] # 示例2 a.ndim # 获取a的维度 a.dtype.name #获取a中数据的类型 a.size # a中数据的数量 # 示例3 b = np.zeros((3, 4)) # 一个3行4列的矩阵, 由0构成 print(b) # 输出3 [[0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.]] # 示例4 c = np.ones((2,3,4), dtype=np.int32) # 3维矩阵,2组3行4列的2维矩阵,由1构成 print(c) # 输出4 [[[1 1 1 1] [1 1 1 1] [1 1 1 1]] [[1 1 1 1] [1 1 1 1] [1 1 1 1]]] # 示例5 d = np.arange(10, 30, 5) #初值为10,增量为5,最大不超过30,构成一维矩阵,numpy中arange的用法几乎和Python中的range一样 print(d) # 输出5 [10 15 20 25] # 示例6 e = np.random.random((2, 3)) #由0到1之间的随机数构成一个2行3列的矩阵,np.random中的方法几乎和Python的random模块中的方法相同 print(e) # 输出6 [[0.26358359 0.86922218 0.12168824] [0.58244693 0.30264221 0.795065 ]] # 示例7 from numpy import pi f = np.linspace(0, 2*pi, 100) #由0到2pi中间的100个数构成一个一维矩阵,这些书之间的增量相同 print(f) # 输出7 [0. 0.06346652 0.12693304 0.19039955 0.25386607 0.31733259 0.38079911 0.44426563 0.50773215 0.57119866 0.63466518 0.6981317 0.76159822 0.82506474 0.88853126 0.95199777 1.01546429 1.07893081 1.14239733 1.20586385 1.26933037 1.33279688 1.3962634 1.45972992 1.52319644 1.58666296 1.65012947 1.71359599 1.77706251 1.84052903 1.90399555 1.96746207 2.03092858 2.0943951 2.15786162 2.22132814 2.28479466 2.34826118 2.41172769 2.47519421 2.53866073 2.60212725 2.66559377 2.72906028 2.7925268 2.85599332 2.91945984 2.98292636 3.04639288 3.10985939 3.17332591 3.23679243 3.30025895 3.36372547 3.42719199 3.4906585 3.55412502 3.61759154 3.68105806 3.74452458 3.8079911 3.87145761 3.93492413 3.99839065 4.06185717 4.12532369 4.1887902 4.25225672 4.31572324 4.37918976 4.44265628 4.5061228 4.56958931 4.63305583 4.69652235 4.75998887 4.82345539 4.88692191 4.95038842 5.01385494 5.07732146 5.14078798 5.2042545 5.26772102 5.33118753 5.39465405 5.45812057 5.52158709 5.58505361 5.64852012 5.71198664 5.77545316 5.83891968 5.9023862 5.96585272 6.02931923 6.09278575 6.15625227 6.21971879 6.28318531] 9.矩阵的基础运算 # 示例2 # 矩阵的加减运算 a = np.array([20, 30, 40, 50]) b = np.arange(4) print(a) print(b) c = a - b # 将a与b对应的位置相减 d = a + b # 将a与b对应的位置相加 print('----------->') print(c) print(d) c = c -2 print('----------->') print(c) print(b ** 2) # 对b中的每个元素平方 print(a < 40) # 迭代判断a中的元素是否小于40 #输出1 [20 30 40 50] [0 1 2 3] -----------> [20 29 38 47] [20 31 42 53] -----------> [18 27 36 45] [0 1 4 9] [ True True False False] # 示例2 # 矩阵的乘法 A = np.array([ [1, 1], [0, 1] ]) B = np.array([ [2, 0], [3, 4] ]) print(A) print("=============") print(B) print("=============") print(A*B) # 矩阵对应位置的元素相乘 print("=============") print(A.dot(B)) # 矩阵乘积 print("=============") print(np.dot(A, B)) # 矩阵乘积 # 输出2 [[1 1] [0 1]] ============= [[2 0] [3 4]] ============= [[2 0] [0 4]] ============= [[5 4] [3 4]] ============= [[5 4] [3 4]] 10.矩阵的其他操作 # 示例1 import numpy as np B = np.arange(3) print(B) print(np.exp(B)) # 分别计算e的0,1,2(即B中的每个元素)次幂 print(np.sqrt(B)) # 对B求算数平方根 # 输出1: [0 1 2] [1. 2.71828183 7.3890561 ] [0. 1. 1.41421356] # 示例2 a = np.random.random((3, 4)) print(a) b = np.floor(10 * a) #取整,截除小数点后面的部分 print(b) print("------------------------------>") c = b.ravel() # 二维转一维,即矩阵转向量,返回一个新的矩阵,自身结构不改变 print(c) c.shape = (6, 2) # 一维转二维,向量转为6×2的矩阵,对自己自身结构 print(c) print("------------------------------>") d = c.T print(c.T) # 对矩阵进行转置 # 输出2: [[0.07997894 0.66199346 0.66872968 0.09003685] [0.80189354 0.02278636 0.82955998 0.3037011 ] [0.31794432 0.67269324 0.12022113 0.12148777]] [[0. 6. 6. 0.] [8. 0. 8. 3.] [3. 6. 1. 1.]] ------------------------------> [0. 6. 6. 0. 8. 0. 8. 3. 3. 6. 1. 1.] [[0. 6.] [6. 0.] [8. 0.] [8. 3.] [3. 6.] [1. 1.]] ------------------------------> [[0. 6. 8. 8. 3. 1.] [6. 0. 0. 3. 6. 1.]] # 示例3 import numpy as np a = np.floor(10 * np.random.random((2, 2))) b = np.floor(10 * np.random.random((2, 2))) print(a) print(b) print("------------------------------>") c = np.vstack((a, b)) # 将两个矩阵按行合并,两个矩阵的列必须相同 print(c) print("------------------------------>") d = np.hstack((a, b)) # 两个矩阵按列合并,两个矩阵的行必须相同 print(d) # 输出3: [[8. 7.] [0. 0.]] [[6. 9.] [0. 1.]] ------------------------------> [[8. 7.] [0. 0.] [6. 9.] [0. 1.]] ------------------------------> [[8. 7. 6. 9.] [0. 0. 0. 1.]] # 示例4 import numpy as np a = np.floor(10 * np.random.random((2, 12))) print(a) print("------------------------------>") b,c,d = np.hsplit(a, 3) # 按列切平均分为3份 print(b) print(c) print(d) print("------------------------------>") e,f,g = np.vsplit(a.T, 3) # 按行切平均分为3份 print(e) print(f) print(g) print("------------------------------>") h, i, j, k = np.hsplit(a, (3, 6, 8)) # 在第3列前面,第6列前面,第8列后面进行分割,分割为4个矩阵,np.vsplit()用法相同 print(h) print(i) print(j) print(k) # 输出4: [[3. 1. 7. 4. 1. 1. 5. 8. 3. 9. 5. 6.] [2. 7. 6. 3. 7. 1. 6. 3. 5. 7. 9. 0.]] ------------------------------> [[3. 1. 7. 4.] [2. 7. 6. 3.]] [[1. 1. 5. 8.] [7. 1. 6. 3.]] [[3. 9. 5. 6.] [5. 7. 9. 0.]] ------------------------------> [[3. 2.] [1. 7.] [7. 6.] [4. 3.]] [[1. 7.] [1. 1.] [5. 6.] [8. 3.]] [[3. 5.] [9. 7.] [5. 9.] [6. 0.]] ------------------------------> [[3. 1. 7.] [2. 7. 6.]] [[4. 1. 1.] [3. 7. 1.]] [[5. 8.] [6. 3.]] [[3. 9. 5. 6.] [5. 7. 9. 0.]] 11.矩阵的复制 # 示例1 a = np.arange(12) b = a # 这种方式对b赋值,b和a是同一个对象,改变一个,另一个也改变,相当于别名 print(b) print(a) print(b is a) print("------------------------------>") a.shape = (3, 4) print(a) print(b) print(b.shape) print(id(a)) print(id(b)) 输出1: [ 0 1 2 3 4 5 6 7 8 9 10 11] [ 0 1 2 3 4 5 6 7 8 9 10 11] True ------------------------------> [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] (3, 4) 4426322848 4426322848 # 示例2 # 浅拷贝(不建议使用) a = np.arange(12) a.shape = (2, 6) c = a.view() # 浅拷贝,c和a是两个对象,但是是同一份数据 print(a) print(c) print(c is a) print("------------------------------>") a.shape = (3, 4) # 改变结构,两个不相互影响 print(a) print(c) print("------------------------------>") c[0,4] = 10000 # 改变数据,会影响另外一个 print(a) print(c) # 输出2: [[ 0 1 2 3 4 5] [ 6 7 8 9 10 11]] [[ 0 1 2 3 4 5] [ 6 7 8 9 10 11]] False ------------------------------> [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] [[ 0 1 2 3 4 5] [ 6 7 8 9 10 11]] ------------------------------> [[ 0 1 2 3] [10000 5 6 7] [ 8 9 10 11]] [[ 0 1 2 3 10000 5] [ 6 7 8 9 10 11]] # 示例3 # 深拷贝 a = np.arange(12) a.shape = (2, 6) d = a.copy() # 深拷贝,d和a是两个对象,两份数据 print(a) print(d) print(d is a) print("------------------------------>") a.shape = (3, 4) # 改变结构,两个不相互影响 print(a) print(d) print("------------------------------>") d[0,4] = 10000 # 改变数据,两个不相互影响 print(a) print(d) # 输出3: [[ 0 1 2 3 4 5] [ 6 7 8 9 10 11]] [[ 0 1 2 3 4 5] [ 6 7 8 9 10 11]] False ------------------------------> [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] [[ 0 1 2 3 4 5] [ 6 7 8 9 10 11]] ------------------------------> [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] [[ 0 1 2 3 10000 5] [ 6 7 8 9 10 11]] 12.numpy的一些常用方法: # 示例1 import numpy as np data = np.sin(np.arange(20)).reshape(5, 4) print(data) ind = data.argmax(axis = 0) # 按列求最大值,得到行的索引,比如第1列的最大值在第2行,则返回2,依次求出所有列最大值的索引组成一个向量 print(ind) print(range(data.shape[1])) data_max = data[ind, range(data.shape[1])] # 相当于由data[2,0],data[0,1],data[3,2],data[1,3]构成的一个矩阵 print(data_max) # 输出1: [[ 0. 0.84147098 0.90929743 0.14112001] [-0.7568025 -0.95892427 -0.2794155 0.6569866 ] [ 0.98935825 0.41211849 -0.54402111 -0.99999021] [-0.53657292 0.42016704 0.99060736 0.65028784] [-0.28790332 -0.96139749 -0.75098725 0.14987721]] [2 0 3 1] range(0, 4) [0.98935825 0.84147098 0.99060736 0.6569866 ] # 示例2 a = np.arange(0, 10, 2) print(a) b = np.tile(a, (4, 3)) # 将a当成一个整体,将其复制为4行3列构成一个矩阵 print(b) print('----------------------------------->') c = np.arange(0, 11, 2) c.shape = (2, 3) # 对于二维同样如此 print(c) d = np.tile(c, (4, 3)) print(d) # 输出2: [0 2 4 6 8] [[0 2 4 6 8 0 2 4 6 8 0 2 4 6 8] [0 2 4 6 8 0 2 4 6 8 0 2 4 6 8] [0 2 4 6 8 0 2 4 6 8 0 2 4 6 8] [0 2 4 6 8 0 2 4 6 8 0 2 4 6 8]] -----------------------------------> [[ 0 2 4] [ 6 8 10]] [[ 0 2 4 0 2 4 0 2 4] [ 6 8 10 6 8 10 6 8 10] [ 0 2 4 0 2 4 0 2 4] [ 6 8 10 6 8 10 6 8 10] [ 0 2 4 0 2 4 0 2 4] [ 6 8 10 6 8 10 6 8 10] [ 0 2 4 0 2 4 0 2 4] [ 6 8 10 6 8 10 6 8 10]] # 示例3 a = np.floor(10 * np.random.random((15))) c = a.copy() a.shape = (5, 3) print(a) print('----------------------------------->') b = np.sort(a, axis=0) # axis=0按列进行升序排序, axis=1按行升序排序, 默认按行排序 print(b) print('----------------------------------->') a.sort(axis=1) print(a) print('----------------------------------->') j = np.argsort(c) # 排序,取原来的索引,比如[5 3 6]执行,得到[1 0 2], print(c) print(j) print(c[j]) # 输出3: [[2. 5. 5.] [6. 4. 9.] [8. 3. 0.] [2. 3. 0.] [5. 5. 4.]] -----------------------------------> [[2. 3. 0.] [2. 3. 0.] [5. 4. 4.] [6. 5. 5.] [8. 5. 9.]] -----------------------------------> [[2. 5. 5.] [4. 6. 9.] [0. 3. 8.] [0. 2. 3.] [4. 5. 5.]] -----------------------------------> [2. 5. 5. 6. 4. 9. 8. 3. 0. 2. 3. 0. 5. 5. 4.] [ 8 11 0 9 7 10 4 14 1 2 12 13 3 6 5] [0. 0. 2. 2. 3. 3. 4. 4. 5. 5. 5. 5. 6. 8. 9.]
本篇博客主要介绍一下HTML/CSS的基本使用,关于它们的介绍便不在赘述,读者可自行google或百度。 一、HTML 先来简单介绍一下HTML标签: HTML 标签是由尖括号包围的关键词,比如 <html> HTML 标签通常是成对出现的,比如 <b> 和 </b> 标签对中的第一个标签是开始标签,第二个标签是结束标签 开始和结束标签也被称为开放标签和闭合标签 首先在写每个HTML的文档之前需要一个基本模板,一般的HTML编辑器都会自动生成。 <!DOCTYPE html><!--在html文档中注释写成这种格式--><!--一般的html标签都是成对出现--><html lang="en"><head> <meta charset="UTF-8" /> <title>Title</title> </head> <body> </body> </html> 注1:如<html lang="en">,html是标签名称,lang是html标签的属性。 注2:在HTML文档中,在<head>标签内设置一个文件的编码,标题等,在<body>标签内写文档的内容等。 注3:在HTML中,存在自闭和标签,例如上面的<meta />标签,自闭和标签不需要结束标签,通常自闭和标签在结束时写一个'/'与普通标签区别。 一、<head>标签内的常用标签: <!DOCTYPE html> <html lang="en"> <head> <!--设置编码--> <meta charset="UTF-8" /> <!--刷新和跳转只能使用一个--> <!--设置每隔3秒自动刷新--> <!--<meta http-equiv="Refresh" content="3" />--> <!--设置5秒后自动跳转--> <!--<meta http-equiv="Refresh" content="5; url=https://www.baidu.com" />--> <!--设置标题--> <title>Hello</title> <!--设置图标--> <link rel="shortcut icon" href="image/abc.jpg"> <!--设置关键字,供搜索引擎使用,当用户搜索这些关键字时,搜索引擎可将你的网页链接提供给用户--> <meta name="keywords" content="博客园,html,w3c" /> <!--简要描述网站的信息,写一下网站是干什么的--> <meta name="description" content="此网页用与学习html/css的基本使用" /> </head> <body> <h1>hello world</h1> </body> </html> 注1:在html中可以告诉浏览器当前文档的语言,如上述代码中<html lang="en">,就是告诉浏览器这个文档时英文的,比如当使用Chrome浏览器打开一个英文网页时浏览器会弹出是否翻译的询问窗口,浏览器辨别网页内容是什么语言时就会用到这个属性,不做强制要求。 注2:上述代码中所说的标题和图标如下图,Hello是标题,它前面的那个是图标。 二、<body>标签内的常用标签: 1 <!--块级标签:--> 2 <!--block元素(块级标签)的特点是:--> 3 <!-- 总是在新行上开始;--> 4 <!-- 高度,行高以及顶和底边距都可控制;--> 5 <!-- 宽度缺省是它的容器的100%,除非设定一个宽度--> 6 <!-- <div>, <p>, <h1>, <form>, <ul>和<li>是块元素的例子。--> 7 <h1></h1> 8 <h2></h2> 9 …… 10 <h6></h6> --- 标题标签,默认效果,加大加粗,从h1到h6加大字号不同。 11 <p></p> --- 段落标签,被p标签所包裹的内容独占一段,段落与段落之间有间距。 12 <div></div> --- 默认无效果,是最常见的块级标签。 块级标签 1 <!--内联标签--> 2 <!--inline元素(内联标签)的特点是:--> 3 <!-- 和其他元素都在一行上;--> 4 <!-- 高,行高及顶和底边距不可改变;--> 5 <!-- 宽度就是它的文字或图片的宽度,不可改变。--> 6 <!-- <span>, <a>, <label>, <input>, <img>, <strong>和<em>是inline元素的例子。--> 7 <span></span> --- 默认没有任何效果。 内联标签 1 <!--最常用的特殊符号:--> 2 3 <br /> --- 换行标签,在一般的文本文档中使用\r或\r\n做换行符,在HTML文档中使用此标签。 4 5 < ------ &lt; 6 > ------ &gt; 7 空格 ------ &nbsp; 特殊符号 注1:在html中,一些特殊符号是无法直接显示的,比如要把<p>显示在网页上,可以这样写--------- &lt;p&gt; 像这些特殊符号需要使用特殊的代码来表示,我们可以查阅HTML特殊字符编码对照表。 练习使用一下上面的标签(编辑成一个HTML文档,用浏览器打开): <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>标题一</h1> <h2>标题二</h2> <h3>标题三</h3> <h4>标题四</h4> <h5>标题五</h5> <h6>标题六</h6> <p>123</p><p>456</p><p>678</p> <p>hello<br />world</p> <span>abc</span><span>def</span> <div>hij</div><div>klm</div> <div>asdfsfafsaf</div> </body> </html> 三、表单标签: 在html中常常使用表单标签来向服务器端提交数据。html中的表单标签是<form></form>,其中间包裹的内容就是向服务端提交的数据(比如用户的账号密码,在某网站要发表的文章等)。 from标签内标签---input标签系列 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <!--在form标签中,属性action写将表单数据提交到哪个URL,method写以哪种方法提交(get/post)--> <form action="http://localhost:8888/index" method="get"> <div> <!--input标签中,type="text"时,输入框内可输入任意文本内容--> <!--当input做输入框时,name属性必须设置,后台程序使用name的值提取表单数据--> <input type="text" name="user" /> </div> <div> <!--input标签中,type="password"时,输入框内输入密码--> <input type="password" name="password" /> </div> <div> <!--当input做输入框时,可使用value属性为输入框提供默认值--> <input type="text" name="email" value="abc@example.com" /> </div> <p> <span>请选择性别:</span> <!--使用input标签做单选按钮,属性设置type="radio" name="gender"几个选项的name必须一样--> <!--当checked="checked"时,该项默认选中--> 男:<input type="radio" name="gender" value="F" checked="checked" /> 女:<input type="radio" name="gender" value="M"/> <br /> <span>爱好:</span> <!--使用input标签做复选按钮,属性设置type="checkbox" name="hobby"几个选项的name必须一样--> <!--当checked="checked"时,该项默认选中--> 篮球:<input type="checkbox" name="hobby" value="1" checked="checked" /> 游泳:<input type="checkbox" name="hobby" value="2" /> 阅读:<input type="checkbox" name="hobby" value="3" checked="checked" /> 唱歌:<input type="checkbox" name="hobby" value="4" /> </p> <div> <!--input标签中,type="button"时,为普通按钮,默认无任何功能,可用CSS为其添加功能--> <!--当input做按钮时,value的值显示在按钮上--> <input type="button" value="按钮" /> </div> <div> <!--input标签中,type="reset"时,为重置按钮,重置当前表单的所有内容--> <input type="reset" value="重置"> <!--input标签中,type="submit"时,为提交按钮,在浏览器中点击后向服务器端提交表单数据--> <input type="submit" value="提交" /> </div> </form> <p> <!--当要上传文件时,form标签的enctype="multipart/form-data"属性需设置--> <form enctype="multipart/form-data"> <!--input标签做文件上传按钮,type="file"--> 上传文件:<input type="file" name="filename" /><br /> <input type="submit"> </form> </p> </div> </body> </html> from标签内标签---其他标签 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <!--在form标签中,属性action写将表单数据提交到哪个URL,method写以哪种方法提交(get/post)--> <form action="http://xxx.xxx.xxx/xxx" method="get"> <div> <span>城市:</span> <!--下拉选择框使用select标签,单选--> <select name="city"> <!--选项使用option标签,提交选项的value的值--> <option value="BeiJing">北京</option> <!--默认选中,使用selected="selected"属性--> <option value="ShangHai" selected="selected">上海</option> <option value="GuangZhou">广州</option> <option value="ShenZhen">深圳</option> </select> <br /> <span>城市(多选):</span> <!--下拉选择框使用select标签,多选使用multiple="multiple"属性, size为默认显示几条选项--> <select name="city" multiple="multiple" size="4"> <!--选项使用option标签,提交选项的value的值--> <option value="BeiJing">北京</option> <!--默认选中,使用selected="selected"属性--> <option value="ShangHai" selected="selected">上海</option> <option value="GuangZhou">广州</option> <option value="ShenZhen">深圳</option> </select> <br /> <span>城市(分组选):</span> <!--下拉选择框使用select标签,单选--> <select name="city"> <!--使用optgroup标签对选项进行分组--> <optgroup label="中国"> <!--选项使用option标签,提交选项的value的值--> <option value="BeiJing">北京</option> <!--默认选中,使用selected="selected"属性--> <option value="ShangHai" selected="selected">上海</option> <option value="GuangZhou">广州</option> <option value="ShenZhen">深圳</option> </optgroup> <optgroup label="美国"> <!--选项使用option标签,提交选项value属性的值--> <option value="1">华盛顿</option> <!--默认选中,使用selected="selected"属性--> <option value="2" selected="selected">纽约</option> <option value="3">洛杉矶</option> </optgroup> </select> </div> <p> <!--多行文本输入,使用textarea标签--> <textarea name="docs">默认值</textarea> <input type="submit" /> </p> </form> </div> </body> </html> 四、a标签的用途 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--a标签做超链接,target="_blank",用一个新标签页打开超链接内容--> <a href="https://www.baidu.com" target="_blank">百度</a> <br /> <div> <!--a标签做锚,href="#目标标签的ID值"--> <a href="#1">第一节</a> <a href="#2">第二节</a> <a href="#3">第三节</a> <a href="#4">第四节</a> </div> <div> <!--在一个HTML文档中,标签的id值不能相同--> <div id="1" style="height: 800px;">第一节的内容</div> <div id="2" style="height: 800px;">第二节的内容</div> <div id="3" style="height: 800px;">第三节的内容</div> <div id="4" style="height: 800px;">第四节的内容</div> </div> </body> </html> body标签内的图片标签和列表标签 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--将图标标签包裹在a标签之内,当用户点击图片时跳转到指定链接--> <a href="https://www.baidu.com"> <!--img是图片标签,属性src是图片的位置,属性title的值在鼠标指针悬停在图片上时显示,当图片未加载成功显示alt的值--> <img src="image/1.png" title="山" alt="风景" style="height: 200px"/> </a> <!--列表标签由三种,ul,ol,dl--> <ul> <li>ul列表标签的样式</li> <li>ul列表标签的样式</li> <li>ul列表标签的样式</li> </ul> <ol> <li>ol列表标签的样式</li> <li>ol列表标签的样式</li> <li>ol列表标签的样式</li> </ol> <dl> <dt>dl列表标签的样式</dt> <dd>hello world</dd> <dd>hello world</dd> <dd>hello world</dd> <dt>ol列表标签的样式</dt> <dd>hello world</dd> <dd>hello world</dd> <dd>hello world</dd> </dl> </body> </html> 上面三种列表的样式如下图: body标签内的表格标签: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--table标签是表格标签,border="1"是显示表格边框,不写则不显示--> <table border="1"> <!--thead标签,表示表头--> <thead> <!--在table标签内,tr标签表示一行--> <tr> <!--在thead标签内,th标签表示一列--> <th>表头1</th> <th>表头2</th> <th>表头3</th> <th>表头4</th> </tr> </thead> <!--tbody标签表示表格的数据部分--> <tbody> <tr> <!--在tbody标签内,td表示一列--> <td >1行,1列</td> <!--属性rowspan="2",表示此单元格竖向占两个单元格,即将1行2列和2行2列合并,此属性常用来单元格合并--> <td rowspan="2">1行,2列</td> <!--属性colspan="2",表示此单元格横向占两个单元格,即将1行3列和1行4列合并,此属性常用来单元格合并--> <td colspan="2">1行,3列</td> <!--由于1行,3列单元格合并需要,所以需删掉此单元格--> <!--<td>1行,4列</td>--> </tr> <tr> <td>2行,1列</td> <!--由于1行,2列单元格合并需要,所以需删掉此单元格--> <!--<td>2行,2列</td>--> <td>2行,3列</td> <td>2行,4列</td> </tr> <tr> <td>3行,1列</td> <td>3行,2列</td> <td colspan="2" rowspan="2">3行,3列</td> <!--<td>3行,4列</td>--> </tr> <tr> <td>4行,1列</td> <td>4行,2列</td> <!--<td>4行,3列</td>--> <!--<td>4行,4列</td>--> </tr> </tbody> </table> </body> </html> 上面的表格标签形成如下图所示的表格: label标签与fieldset标签: label标签常用来与input标签做关联,当用户点击label标签包裹的内容时,光标将出现在与其关联的输入框内。fieldset标签常与legend标签搭配使用形成如下图所示的边框。 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 9 <fieldset> 10 <legend>登录界面</legend> 11 <div> 12 <label for="i1">*账号:</label> 13 <input id="i1" type="text" name="username"/> 14 </div> 15 <div> 16 <label for="i2">*密码:</label> 17 <input id="i2" type="password" name="username"/> 18 </div> 19 <input type="submit" value="登录"> 20 </fieldset> 21 </body> 22 </html> label标签与fieldset标签的使用 二、CSS的基本使用 CSS是用来装饰HTML标签的,比如设置标签的宽度,高度,背景色,字体的颜色,大小等。只有熟练的使用HTML和CSS才能做出漂亮的网页。 例一、给标签设置背景色,高度和宽度 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div style="background-color: red; width: 60%; height: 45px">hello world!</div> </body> </html> 在标签的style属性中进行样式设置: background-color: red; --- 设置标签背景色为红色 width: 60%; --- 设置标签宽度为浏览器显示宽度的60% height: 45px --- 设置标签高度为45像素 注1:高度,宽度的设置单位可以是px, %, cm等。 注2:关于颜色得设置,可以直接写先颜色的名称,也可以写每种颜色所对应的编码(可通过RGB颜色查询对照表查询)。 例二、设置标签中文字的颜色,大小,宽度,边框 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div style="color: blue; font-size: 20px; font-weight: bolder; border: 1px solid red">hello world!</div> </body> </html> color: blue; --- 设置字体颜色 font-size: 20px; --- 设置字体大小 font-weight: bolder --- 加宽字体 border: 1px solid red; --- 设置边框, 宽度为1px,虚线,红色 注:所有颜色的设置都可以使用RGB颜色查询对照表中的颜色和编码。 例三、设置文字在标签中的位置(比如,让文字在标签所占区域中居中) <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div style="background-color: red; height: 35px; text-align: center; line-height: 35px;">hello world!</div> </body> </html> text-align: center; --- 设置水平居中 line-height: 35px; --- 设置垂直居中, 这里的数值等于标签高度的数值 在标签的style属性中固然可以设置样式,但是如果多个标签要用同一个样式,那就得多次重复相同的代码,所以可以使用选择器来设置样式。 例四、选择器 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 /* 以.开头为类选择器 */ 8 .c1{ 9 background-color: red; 10 width: 60%; 11 height: 45px; 12 color: blue; 13 font-size: 20px; 14 font-weight: bolder; 15 text-align: center; 16 line-height: 45px; 17 } 18 </style> 19 </head> 20 <body> 21 <!--使用类选择器,只需使标签的class属性的值为类名即可,下面的p标签就使用了c1中的样式--> 22 <p class="c1">hello world!</p> 23 </body> 24 </html> 类选择器 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 /* 标签选择器,为此HTML中的每一个div标签设置此样式 */ 8 div{ 9 background-color: red; 10 width: 60%; 11 height: 45px; 12 color: blue; 13 font-size: 20px; 14 font-weight: bolder; 15 text-align: center; 16 line-height: 45px; 17 } 18 </style> 19 </head> 20 <body> 21 <div>hello world</div> 22 <span>1234</span> 23 <div>asdf</div> 24 </body> 25 </html> 标签选择器 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 /* 属性选择器,对具有特定属性(可以是自定义的属性)的特定标签设置样式,下面的样式只给具有type="text"属性的input标签设置 */ 8 input[type="text"]{ 9 background-color: aliceblue; 10 width: 180px; 11 height: 45px; 12 } 13 </style> 14 </head> 15 <body> 16 <div> 17 <input type="text" name="user" /> 18 <input type="text" name="email" /> 19 <input type="password" name="pwd" /> 20 </div> 21 </body> 22 </html> 属性选择器 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 /* 关联选择器,为此HTML中的每一个被span标签包裹的div标签设置此样式 */ 8 span div{ 9 background-color: red; 10 width: 60%; 11 height: 45px; 12 color: blue; 13 font-size: 20px; 14 font-weight: bolder; 15 text-align: center; 16 line-height: 45px; 17 } 18 </style> 19 </head> 20 <body> 21 <div>asdfghjkl</div> 22 <span>12345678</span> 23 <span> 24 <div>hello world</div> 25 </span> 26 27 </body> 28 </html> 关联选择器 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 /* id选择器 */ 8 /* 当使用关联选择器是,标签路径太过复杂,可以使用id选择器,为某一标签设置样式*/ 9 /*给包裹hello world的div标签设置样式*/ 10 #i1{ 11 background-color: red; 12 width: 60%; 13 height: 45px; 14 color: blue; 15 font-size: 20px; 16 font-weight: bolder; 17 text-align: center; 18 line-height: 45px; 19 } 20 </style> 21 </head> 22 <body> 23 <div> 24 <span> 25 <p> 26 <div> 27 <div id="i1">hello world</div> 28 </div> 29 </p> 30 </span> 31 </div> 32 </body> 33 </html> id选择器 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 /*组合选择器,为多个标签设置同一个样式*/ 8 div,p{ 9 background-color: red; 10 width: 60%; 11 height: 45px; 12 color: blue; 13 font-size: 20px; 14 font-weight: bolder; 15 text-align: center; 16 line-height: 45px; 17 } 18 </style> 19 </head> 20 <body> 21 <div>1. hello world</div> 22 <p> 2. hello world</p> 23 </body> 24 </html> 组合选择器 注:对于关联选择器和组合选择器而言,可以使用不同类型的其他选择器构成这两种选择器。你可以使用id选择器和标签选择器构成一个关联选择器,你也可以使用类型选择器和属性选择器构成一个组合选择器,对于使用哪种选择器视情况而定即可。 例五、内联标签与块级标签的相互转换 HTML标签的默认属性是固定的,但我们可以使用CSS修改默写默认属性,比如p标签、div标签默认是块级标签,可以将其转为内联标签,span标签默认是内联标签,可以将其转为块级标签。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--使用style中的display字段即可转换块级标签和内联标签--> <p style="display: inline; background-color: green">123</p> <span style="display: block; background-color: red">abc</span> <div style="display: inline; background-color: green">456</div> </body> </html> display: none 让标签消失 display: inline 块级标签转内联 display: block 内联标签转块级 display: inline-block 同时具有内联标签和块级标签的部分特性 注:内联标签默认无法设置宽度和高度,要对其做此设置须转为块级标签。 例六、是标签浮动起来 要实现下面的效果,需要将两个块级标签放到一行,但块级标签默认是占一行的,该如何实现呢?这就需要使用CSS的float字段了,使用float可以让标签浮动起来,从而实现多个块级标签出现在同一水平线上,实现标签堆叠。 hello world <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div style="background-color: red; width: 40%; float: left">hello</div> <div style="background-color: green; width: 60%; float: left">world</div> </body> </html> float: - left标签从左开始浮动堆叠 - right标签从右开始浮动堆叠 例七、边距 外边距:标签外部的边距。如下图,绿色的标签据顶部红色边框25px。外边距由属性margin设置。 hello world 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <div style="height: 75px; border: 1px solid red;"> 9 <div style="height: 80px; background-color: green; margin-bottom: 10px">hello world</div> 10 </div> 11 </body> 12 </html> 13 14 margin --- 设置上下左右四个方向的边距 15 margin-top --- 设置上方边距 16 margin-bottom --- 设置下方边距 17 margin-left --- 设置左边边距 18 margin-right --- 设置右边边距 外边距 内边距:标签内部的边距。如下图,标签内的字距上方35px。 hello world 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <div style="background-color: green; height: 45px;">hello world</div> 9 <br /> 10 <div style="background-color: green; height: 45px; padding-top: 35px">hello world</div> 11 </body> 12 </html> 内边距 例八、标签在页面布局中的位置 有一些标签在页面中的位置是固定不变的,比如返回顶部的按钮,就一直在页面的右下角,有些网站的菜单栏,就一直在浏览器界面的正上方,无论将页面拉倒什么位置它们的位置都不会变。 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 7 <style> 8 #i1{ 9 background-color: #000000; 10 color: #ffffff; 11 display: inline-block; 12 position: fixed; /* postion: fixed 设置标签位置固定 */ 13 bottom: 1cm; /* 距浏览器显示界面底部1cm */ 14 right: 1cm; /* 距浏览器显示界面右边界1cm */ 15 } 16 #i2{ 17 height: 5000px; 18 background-color: #dddddd; 19 } 20 </style> 21 </head> 22 <body style="margin: 0 auto"> 23 <div id="i1">返回顶部</div> 24 <!--检验下拉是否会改变标签位置--> 25 <div id="i2">asdf</div> 26 </body> 27 </html> 返回顶部按钮 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 7 <style> 8 #i1{ 9 background-color: black; 10 height: 60px; 11 color: white; 12 position: fixed; 13 top:0; 14 left: 0; 15 right: 0; 16 } 17 #i2{ 18 background-color: #dddddd; 19 height: 5000px; 20 margin-top: 75px; 21 } 22 </style> 23 </head> 24 <body style="margin: 0 auto"> 25 <div id="i1">菜单栏</div> 26 <div id="i2">内容</div> 27 </body> 28 </html> 菜单栏 还有一些标签的位置是相对于其他标签而言的,比如下图,黑色背景标签相对于红色边框标签在它的左下角,这就是标签的相对位置。 hello 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <div style="width: 15cm; height: 8cm; border: 1px solid red; margin: 0 auto; position: relative"> 9 <div style="width: 4cm; height: 2cm; background-color: black; position: absolute; left: 0; bottom: 0">hello</div> 10 </div> 11 <div style="width: 500px; height: 280px; border: 1px solid red; margin: 0 auto; position: relative"> 12 <div style="width: 4cm; height: 2cm; background-color: black; position: absolute; right: 0; bottom: 0"></div> 13 </div> 14 <div style="width: 500px; height: 280px; border: 1px solid red; margin: 0 auto; position: relative"> 15 <div style="width: 4cm; height: 2cm; background-color: black; position: absolute; left: 0; top: 0"></div> 16 </div> 17 </body> 18 </html> 标签的相对位置 注1:postion设置为fixed时,设置标签的固定位置。 注1:只有当外层标签的postion设置为relative,内层标签的postion设置为absolute才能设置标签的相对位置。 注2:使用top bottom right left这四个参数设置标签的位置。 例九、页面内容分层 我们常常在访问一个网页时会出现这样一种情景,点击某个按钮,标签弹出一个输入框,这时在这个输入框下的内容边不能再被选中,这其实是将整个页面分为三层,最底层为刚开始的内容,当点击按钮是显示第二层和第三层。当使用position设置标签位置时,标签其实已经不在同一层,在实际运用中我们还要设置层级顺序,透明度(常用在覆盖内容层)等。 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 7 <style> 8 #i1{ 9 height: 5000px; 10 background-color: green; 11 } 12 13 #i2{ 14 background-color: black; 15 z-index: 9; /*设置层级顺序,数字越大,越在上层*/ 16 opacity: 0.7; /*设置透明度,从0~1,0为完全透明,1为完全不透明*/ 17 position: fixed; 18 /*覆盖整个页面*/ 19 top: 0; 20 bottom: 0; 21 left: 0; 22 right: 0; 23 } 24 25 #i3{ 26 width: 150mm; 27 height: 80mm; 28 background-color: white; 29 z-index: 10; 30 position: fixed; 31 top: 15%; 32 left: 50%; 33 margin-left: -75mm; 34 } 35 </style> 36 </head> 37 <body> 38 <!--最上层,可输入--> 39 <div id="i3"> 40 <input type="text" name="user" /> 41 <br /> 42 <input type="password" name="pwd" /> 43 <br /> 44 <input type="submit" value="登录" /> 45 </div> 46 <!--第二层,用以覆盖最底层--> 47 <div id="i2"></div> 48 <!--最底层--> 49 <div id="i1">hello world</div> 50 </body> 51 </html> 一个三层内容的页面 例十、img在标签内的限制 在上面的的HTML标签使用介绍中我们了解了img标签(图片标签的使用),它常常被其他标签所包裹,但是在CSS中我们学会了设置标签的大小,如果图片的大小超过了它外层标签的大小(比如外层标签的大小是800×400而它包裹的图片是)会怎样呢? 此时图片便会超出外层标签的限制,换而言之图片会完全覆盖掉外层标签,此时就需要在外层标签对内层做出限制,使用CSS的overflow字段。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div style="width: 15cm; height: 8cm; overflow: auto"> <img src="image/1.png"> </div> <div style="width: 15cm; height: 8cm; overflow: hidden"> <img src="image/1.png"> </div> </body> </html> overflow: auto ----- 从图片的左上角开始显示符合范围的大小,可拉动滚动条查看其余部分 overflow: hidden ----- 从图片的左上角开始显示符合范围的大小,其余部分将被隐藏 例十一、鼠标指针移到某个标签改变其样式 在我们访问网站时,当鼠标移到菜单栏上的某个选项时,其样式会改变,如下图,这是某购物网站的菜单栏,当指针移到腕表这一选项时,此选项的背景色,字体色都发生了改变,这种效果是如何实现的呢? 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 .c1{ 8 height: 48px; 9 background-color: #2459a2; 10 line-height: 48px; 11 position: fixed; 12 right: 0; 13 left: 0; 14 top: 0; 15 } 16 .c2{ 17 margin-top: 50px; 18 } 19 .w{ 20 width: 980px; 21 margin: auto; 22 } 23 .c1 .menu{ 24 display: inline-block; 25 margin: 0 100px; 26 color: white; 27 } 28 .c1 .menu:hover{ 29 background-color: blue; 30 } 31 </style> 32 </head> 33 <body> 34 <div class="c1"> 35 <div class="w"> 36 <a class="logo">LOGO</a> 37 <a class="menu">选项一</a> 38 <a class="menu">选项二</a> 39 <a class="menu">选项三</a> 40 </div> 41 </div> 42 <div class="c2"> 43 <div class="w">a</div> 44 </div> 45 </body> 46 </html> 47 48 49 /* 使用类似下面这种格式的选择器,为标签设置的样式,当指针移到这个标签时此样式才会生效*/ 50 .c1 .menu:hover{ 51 background-color: blue; 52 } 被鼠标指针指到样式生效 例十二、背景图片 在之前的样式里,介绍了标签背景色的使用,在此例中,将结束标签背景图片的设置,如下两图就是背景图片的使用: 标签的背景图片是标签占多大背景图片占多大,当图片大小超过标签大小时会按标签大小从左上角开始切割图片,当图片大小小于标签大小时,图片重复显示。 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 .c1{ 8 width: 100px; 9 height: 100px; 10 background-image: url(image/1.png); 11 background-repeat: no-repeat; 12 } 13 </style> 14 </head> 15 <body> 16 <div class="c1" style="background-position: 0 0;">hello</div> 17 <br /> 18 <div class="c1" style="background-position: 0 -140px;">world</div> 19 </body> 20 </html> 21 22 23 背景图片: 24 background-image:url() --- 默认div大,图片重复放,url括号内写图片地址 25 当标签大小大于图片大小时:重复, 水平方向重复,垂直方向重复,不重复 26 background-repeat: repeat,repeat-x, repeat-y, no-repeat 27 28 以左上角顶点做参考 29 background-position-x 向水平方向移动图片 负数向左移 整数向右移 30 background-position-y 向垂直方向移动图片 负数向上移 正数向下移 31 32 可简写为 background: url(image/1.png) no-repeat 0 -140px; 背景图片
一、JavaScript的简单介绍 JavaScript是一种属于网络的脚本语言(简称JS),已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。通常JavaScript脚本是通过嵌入在HTML中来实现自身的功能的。它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在HTML(标准通用标记语言下的一个应用)网页上使用,用来给HTML网页增加动态功能。 JavaScript代码常存在于HTML文档中,被script标签所包裹。为了方便多个HTML使用同一份JavaScript代码,也可以将JavaScript代码写在js文件中,需要在HTML文档中使用,导入即可。 1 <script> 2 //JavaScript代码 3 alert(123); 4 </scpript> 5 6 <script type="text/javascript"> 7 //JavaScript代码 8 alert(123); 9 </scpript> 代码存在于HTML文档中 使用HTML文档导入JavaScript代码 注:script标签可放在HTML文档的head标签内,但建议将其放置在body标签内部的最下方(这样写浏览器会先加载网页的内容,再加载其动态效果)。 二、JavaScript的基本数据类型 JavaScript的基本数据类型有数字(在JS中,数字不区分整型和浮点类型)、数组,字符串,布尔(只用两个值,true和false)。 在JavaScript中申明一个变量: PI = 3.14 // 声明一个全局变量,赋值3.14, 数字类型 var a = 6; // 声明一个局部变量a,为其赋值6,是数字类型 var b = [1,2,3,4] // 数组 var c = "hell world" // 字符串 var d = true // 布尔类型 var e = {'k1':v1, 'k2':v2, 'k3':v3} //字典 c = null // 清空变量c 字符串类型的常用方法: "string".length 返回字符串长度 " string ".trim() 移除字符串的空白 " string ".trimLeft() 移除字符串左侧的空白 " string ".trimRight) 移除字符串右侧的空白 "string".charAt(n) 返回字符串中的第n个字符 "string".concat(string2, string3, ...) 拼接 "string".indexOf(substring,start) 子序列起始位置 "string".lastIndexOf(substring,start) 子序列终止位置 "string".substring(from, to) 根据索引获取子序列 "string".slice(start, end) 切片 "string".toLowerCase() 将字符串转为大写 "string".toUpperCase() 将字符串抓为小写 "string".split(delimiter, limit) 以特定字符分割字符串 // 与正则表达式搭配使用 "string".search(regexp) 从头开始匹配,返回匹配成功的第一个位置(g无效) "string".match(regexp) 全局搜索,如果正则中有g表示找到"string".replace(regexp, replacement) 替换,正则中有g则替换所有,否则只替换第一个匹配项, $数字:匹配的第n个组内容; $&:当前匹配的内容; $`:位于匹配子串左侧的文本; $':位于匹配子串右侧的文本 $$:直接量$符号 数组类型的常用方法: obj.length 数组的大小 obj.push(ele) 尾部追加元素 obj.pop() 从尾部弹出元素 obj.unshift(ele) 头部插入元素 obj.shift() 头部移除元素 // 可用此句完成对数组的插入,删除,替换操作 obj.splice(start, deleteCount, value, ...) 从start位置开始,删除deleteCount个元素,插入value,... obj.slice( ) 切片 obj.reverse( ) 反转 obj.join(sep) 将数组元素连接起来以构建一个字符串 obj.concat(val,..) 连接数组 obj.sort( ) 对数组元素进行排序 四、JavaScript的选择和循环语句 与其他编程语言类型,JS也有三种程序执行流程,即顺序,选择,和循环。 1 if (判断条件){ 2 // js代码 3 }else if(判断条件){ 4 // js代码 5 }else{ 6 // js代码 7 } 8 9 // 常用判断符号与逻辑符号 10 && 逻辑与 11 || 逻辑或 12 > < 大于 小于 13 == != 判断值 14 === !== 判断值和类型 选择语句_1 1 // x代指变量,case为x不动的取值情况,defult当上面的case都不满足执行defult,相当于else 2 switch(x){ 3 case '1': 4 var i = 1; 5 break; 6 case '2': 7 var i = 2; 8 break; 9 case '3': 10 var i = 3; 11 break; 12 defult: 13 i = 0; 14 } 选择语句_2 1 1. 循环时循环的是索引 2 a = [11,22,33]; 3 for (var item in a){ 4 console.log(a[item]); // 在浏览器的console中打印a[item]的值,item是索引 5 } 6 7 8 a = {'k1':'v1', 'k2':'v2', 'k3':'v3'}; 9 for (var item in a){ 10 console.log(a[item]); 11 } 12 13 2. 常规for循环 14 for (var i=0; i<10; i++){ 15 //js代码 16 } for循环 1 var len = 10; 2 var i = 0; 3 while(i < len){ 4 console.log(i); // 代指js代码 5 i++; 6 } while循环 五、JavaScript的函数申明 与其他编程语言类型,JS也有函数,在JS中申明一个函数特别简单。 // 常规函数 function funcname(a, b, c) { var a = 0; // JS代码块 } // 匿名函数,匿名函数只有某一块代码使用,在声明时已经确定由谁来执行它 setIntervar(function(){ console.log(123); // js代码块 }, 5000) // 自执行函数,函数声明完后立即执行并且不会被其他代码调用 (function(arg){ console.log(arg); //js代码块 })(1) 六、JS面向对象 在JavaScript中,面向对象其实是函数的一种变种,与函数的定义非常类似。 方式一: // 定义一个类 function Foo(n){ this.name = n; // 属性 // 方法 this.sayHello = function(){ console.log("hello,", name); } } // 实例化一个对象 var obj = new Foo('abc'); obj.name; // 调用属性 obj.sayHello(); // 调用方法 方式二: function Foo(n){ this.name = n; } Foo.prototype = { 'sayHello': function(){ console.log("hello,", this.name); } } var obj = new Foo('abc'); obj.name; obj.sayHello(); 注:通常使用方式二来定义和使用一个对象。 七、JavaScript中常用的系统方法 1. 定时器,每隔特定的时间执行一次特定的任务,用途十分广泛。 var a = setInterval(function(){console.log('hello');}, 间隔时间);clearInterval(a); // 清除定时器 2. 超时器,设置一段时间,比如5秒钟,当时间超过5秒钟后执行某一任务。 var b = setTimeout(funciton(){console.log('hello')}, 5000); // 设置超时器 clearTimeout(b); // 清除超时器,比如当用户执行某一操作后,取消超时器使用它 3. 弹出提示框 alert('message') // message代指要提示的信息 4. 弹出确认框 // 向用户弹出一个确认框,如果用户选择确认,v=true;否则,v=false。 var v = confirm('message') // message为确认提示信息,比如真的要删除吗? 5. 刷新页面 location.reload(); // 执行此方法浏览器自动刷新当前页面,可以和定时器配合使用用以定时刷新页面 6. 页面跳转 location.href // 获取当前页面的url location.href = url; // 从当前页面跳转到指定url,可与超时器配合使用,做网站的页面跳转 location.href = location.href; // 与location.reload()效果相同,刷新当前页面 7. eval(使用eval,可将字符串转换成代码执行) var s = '1+2'; var a = eval(s); console.log(a); 8. 序列化 序列化试将一个对象转化成字符串,方便存储,打印等。在任何一门编程语言中都存在序列化。 而反序列化是将读取到的字符串转化为对象,方便程序使用。 在js中,序列化与反序列画的操作如下: ls = [1,2,3,4]; s = JSON.stringify(ls); //object ---> string console.log(s); new_ls = JSON.parse(s) //string ---> object console.log(new_ls); 9. 字符串的编码与解码 为了能够在所有计算机上识别出字符串,在传输过程中要对字符串编码,传输完成后要对字符串解码。 escape(str) // 对字符串编码 unescape(str) // 对字符串解码 encodeURI() // 对URI编码 decodeURI() // 对URI解码与encodeURI() 对应 encodeURIComponent() // 对URI编码 decodeURIComponent() // 对URI解码与encodeURIComponent() 对应 八、触发JavaScript的事件 当我们用鼠标点击浏览器界面中的一个按钮时就触发了一个事件(即点击事件),类似的还有很多其他事件,比如当鼠标移动到某个标签上,光标选中某个输入框等,这些都是事件,那么就,接下来就介绍一下一些常用的事件。 标签绑定事件的方式: 对div标签绑定点击事件 方式一: <div id="i1" onclick="funcname">点我</div> 方式二:<div id="i1">点我</div> tag = document.getElementById("i1"); tag.onclick = funciton () {console.log("div i1");}; 方式三:<div id="i2">点我</div> tag = document.getElementById("i2"); tag.addEventListener("click", function () { console.log("div i2"); }, false); 注:使用方式二和方式三可做到事件与标签相分离,在测试代码时,可以使用方式一绑定标签与事件,在日常编程中建议使用方式二和方式三。 常用的事件有: onclick --- 鼠标点击执行js函数 onfocus --- 光标选中执行js函数 onblur --- 光标移走执行js函数 onmouseover --- 鼠标移到某个标签执行js函数 onmouseout --- 鼠标从某个标签移走执行js函数 八、使用JavaScript完成一些常用功能 1. HTML中的模态对话框 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 7 <style> 8 .c2{ 9 background-color: black; 10 opacity: 0.4; 11 z-index: 8; 12 position: fixed; 13 top: 0; 14 right: 0; 15 bottom: 0; 16 left: 0; 17 18 } 19 .c3{ 20 background-color: white; 21 z-index: 9; 22 height: 200px; 23 width: 500px; 24 position: fixed; 25 top: 25%; 26 left: 50%; 27 margin-left: -250px; 28 } 29 .hid{ 30 display: none; 31 } 32 </style> 33 </head> 34 <body style="margin: 0 auto;"> 35 <div> 36 <table style="border: 1px solid red" border="1"> 37 <thead> 38 <tr> 39 <th>host</th> 40 <th>port</th> 41 </tr> 42 </thead> 43 <tbody> 44 <tr> 45 <td>1.1.1.1</td> 46 <td>1111</td> 47 </tr> 48 <tr> 49 <td>1.1.1.2</td> 50 <td>1112</td> 51 </tr> 52 <tr> 53 <td>1.1.1.3</td> 54 <td>1113</td> 55 </tr> 56 </tbody> 57 </table> 58 </div> 59 <div> 60 <input type="button" value="添加" onclick="ShowModel();"/> 61 </div> 62 63 <!--遮罩层--> 64 <div id="i2"; class="c2 hid"></div> 65 <!--对话框--> 66 <div id="i3" class="c3 hid"> 67 <fieldset style="border: 1px solid red;"> 68 <legend>添加</legend> 69 <div> 70 <label>host:</label> 71 <input type="text"/> 72 </div> 73 <div> 74 <label>port:</label> 75 <input type="text"/> 76 </div> 77 <p> 78 <input type="button" value="取消" onclick="HiddenModel();"/> 79 <input type="button" value="确定"/> 80 </p> 81 </fieldset> 82 </div> 83 84 <script> 85 function ShowModel() { 86 tag = document.getElementById('i2').classList.remove('hid'); 87 tag = document.getElementById('i3').classList.remove('hid'); 88 } 89 function HiddenModel() { 90 tag = document.getElementById('i2').classList.add('hid'); 91 tag = document.getElementById('i3').classList.add('hid'); 92 } 93 </script> 94 </body> 95 </html> View Code 2. HTML中的全选,反选,取消按钮 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <div> 9 <div> 10 <input type="button" value="全选" onclick="checkedAll();"/> 11 <input type="button" value="反选" onclick="reverseAll();"/> 12 <input type="button" value="取消" onclick="cancelAll();"/> 13 </div> 14 15 <table style="border: 1px solid red;" border="1"> 16 <thead> 17 <tr> 18 <th>opotion</th> 19 <th>host</th> 20 <th>port</th> 21 </tr> 22 </thead> 23 <tbody id="i3"> 24 <tr> 25 <td><input type="checkbox"></td> 26 <td>1.1.1.1</td> 27 <td>1111</td> 28 </tr> 29 <tr> 30 <td><input type="checkbox"></td> 31 <td>1.1.1.2</td> 32 <td>1112</td> 33 </tr> 34 <tr> 35 <td><input type="checkbox"></td> 36 <td>1.1.1.3</td> 37 <td>1113</td> 38 </tr> 39 <tr> 40 <td><input type="checkbox"></td> 41 <td>1.1.1.4</td> 42 <td>1114</td> 43 </tr> 44 <tr> 45 <td><input type="checkbox"></td> 46 <td>1.1.1.4</td> 47 <td>1114</td> 48 </tr> 49 <tr> 50 <td><input type="checkbox"></td> 51 <td>1.1.1.5</td> 52 <td>1115</td> 53 </tr> 54 </tbody> 55 </table> 56 </div> 57 <script> 58 function checkedAll() { 59 var tags = document.getElementById("i3").children; 60 for (var i in tags) { 61 var checkbox = tags[i].firstElementChild.firstElementChild; 62 checkbox.checked = true; 63 } 64 } 65 66 function cancelAll() { 67 var tags = document.getElementById("i3").children; 68 for (var i in tags) { 69 var checkbox = tags[i].firstElementChild.firstElementChild; 70 checkbox.checked = false; 71 } 72 } 73 74 function reverseAll() { 75 var tags = document.getElementById("i3").children; 76 for (var i in tags) { 77 var checkbox = tags[i].firstElementChild.firstElementChild; 78 if (checkbox.checked) { 79 checkbox.checked = false; 80 } 81 else { 82 checkbox.checked = true; 83 } 84 85 } 86 } 87 </script> 88 </body> 89 </html> View Code 3. 类似于购物商城的左侧菜单栏 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 7 <style> 8 .item { 9 margin-bottom: 15px; 10 } 11 12 .menu { 13 background-color: blueviolet; 14 height: 30px; 15 line-height: 30px; 16 font-weight: bolder; 17 } 18 19 .hide { 20 display: none; 21 } 22 23 .content{ 24 background-color: lightseagreen; 25 } 26 </style> 27 </head> 28 <body> 29 30 <div style="height: 150px;"></div> 31 <div style="width: 350px; border: 1px solid red;"> 32 <div class="item"> 33 <div id="i1" class="menu" onclick="ChangeMenu('i1');">菜单1</div> 34 <div class="content"> 35 <div>内容1</div> 36 <div>内容1</div> 37 <div>内容1</div> 38 <div>内容1</div> 39 </div> 40 </div> 41 <div class='item'> 42 <div id="i2" class="menu" onclick="ChangeMenu('i2');">菜单2</div> 43 <div class="content hide"> 44 <div>内容2</div> 45 <div>内容2</div> 46 <div>内容2</div> 47 <div>内容2</div> 48 </div> 49 </div> 50 <div class='item'> 51 <div id="i3" class="menu" onclick="ChangeMenu('i3');">菜单3</div> 52 <div class="content hide"> 53 <div>内容3</div> 54 <div>内容3</div> 55 <div>内容3</div> 56 <div>内容3</div> 57 </div> 58 </div> 59 <div class='item'> 60 <div id="i4" class="menu" onclick="ChangeMenu('i4');">菜单4</div> 61 <div class="content hide"> 62 <div>内容4</div> 63 <div>内容4</div> 64 <div>内容4</div> 65 <div>内容4</div> 66 </div> 67 </div> 68 </div> 69 70 <script> 71 function ChangeMenu(id) { 72 menu = document.getElementById(id); 73 items = menu.parentElement.parentElement.children; 74 75 for (var i=0; i<items.length; i++) { 76 var current_item = items[i].children; 77 current_item[1].classList.add('hide'); 78 } 79 menu.nextElementSibling.classList.remove('hide'); 80 } 81 </script> 82 </body> 83 </html> View Code 4. 鼠标移到标签某行改变其样式,移走恢复 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <table border="1" width="300px"> 9 <tr><td>1</td><td>2</td><td>3</td></tr> 10 <tr><td>1</td><td>2</td><td>3</td></tr> 11 <tr><td>1</td><td>2</td><td>3</td></tr> 12 </table> 13 14 <script> 15 var myTag = document.getElementsByTagName('tr'); // 找到标签 16 17 for (var i=0, len=myTag.length; i<len; i++) { 18 myTag[i].onmouseover = function () { 19 this.style.backgroundColor = "red"; // 改变样式 20 } 21 22 myTag[i].onmouseout = function () { 23 this.style.backgroundColor = ""; //恢复样式 24 } 25 </script> 26 </body> 27 </html> View Code 5. 搜索框提示信息 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 .search{ 8 margin: 0 auto; 9 border: 1px solid red; 10 } 11 </style> 12 </head> 13 <body> 14 <div style="width: 100%;"> 15 <input id="i1" class="search" type="text" name="keywords" onfocus="Focus();" onblur="Blur();" value="请输入关键字"/> 16 <input type="button" value="搜索"/> 17 </div> 18 19 <script> 20 function Focus() { 21 tag = document.getElementById('i1'); 22 val = tag.value; 23 console.log(val); 24 if (val == "请输入关键字") { 25 tag.value = ""; 26 } 27 } 28 function Blur() { 29 console.log(2); 30 tag = document.getElementById('i1'); 31 val = tag.value; 32 if (val == "") { 33 tag.value = "请输入关键字"; 34 } 35 } 36 37 </script> 38 </body> 39 </html> View Code
一、DOM简介 文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展置标语言的标准编程接口。它是一种与平台和语言无关的应用程序接口(API),它可以动态地访问程序和脚本,更新其内容、结构和www文档的风格(目前,HTML和XML文档是通过说明部分定义的)。 DOM 可被 JavaScript 用来读取、改变 HTML、XHTML 以及 XML 文档,它本身属于浏览器。 二、使用DOM查找HTML元素 HTML 文档中的所有节点组成了一个文档树(或节点树)。HTML 文档中的每个元素、属性、文本等都代表着树中的一个节点。树起始于文档节点,并由此继续伸出枝条,直到处于这棵树最低级别的所有文本节点为止。所以查找HTML中的某个标签即是查抄树上的某个节点。 1. 直接查找(通过属性、标签名等) 1 document.getElementById("i1"); //根据id获取单个元素 2 document.getElementsByName("i1"); //根据name属性获取多个元素 3 document.getElementsTagName('a'); //根据标签名称获取多个元素 4 document.getElementsByClassName('c1'); //根据class属性获取多个元素 2. 间接查找(通过已找到的其他标签) 1 tag = document.getElementById("i1"); 2 tag.parentElement // 父节点标签元素 3 tag.children // 所有子标签 4 tag.firstElementChild // 第一个子标签元素 5 tag.lastElementChild // 最后一个子标签元素 6 tag.nextElementSibling // 下一个兄弟标签元素 7 tag.previousElementSibling // 上一个兄弟标签元素 三、使用DOM操作标签 1. 操作标签内容 tag = document.getElementById("i1"); content = tag.innerText; //获取标签中的文本内容 tag.innerText = "new_content"; //将标签内部文本赋值为new_content //使用innerText只能操作标签内部的文本内容,即使标签内部包裹了其他标签也会被其忽略 // 常用的操作标签内容的方法,使用方法同innerText innerText --- 仅文本 innerHTML --- 所有内容(包括标签) value: ---- 标签的值(下面分别是对有value属性的标签,使用value获取/操作值的详细介绍) input --- value获取当前输入的值 select --- 获取选中value的值(selectedIndex) textarea --- 获取当前输入的 2. 操作标签一般属性 obj = document.getElementById("i1"); obj.attributes //---- 获取标签的所有属性 obj.getAttributes(key) //---- 获取标签某个属性的值 obj.setAttributes(key, value) //---- 为标签设置属性 obj.removeAttributes(key) //---- 为标签移除属性 3. 操作标签class属性 tag = document.getElementById('i1') tag.className // 获取tag的使用的class选择器名称 tag.className = 'c1' // tag使用名为c1的class选择器 tag.classList.add('c2') // 给tag增添c2中的样式 tag.classList.remove('c3') // 移除tag使用的c3中的样式 4. 操作标签样式 // 在上面的操作class属性中,是对一个标签添加或删除一个css代码样式块, 对一个标签具体样式的操作如下 //比如,设置一个标签字体的大小 tag = document.getElementById("i1"); tag.style.fontSize = '16px'; // 其他的各种样式也是采用这种方式设置,设置时样式字段去掉'-',将每个单词的首字母大写(整体首字母小写,tag.style.backgroundColor) 5. 创建标签和操作标签 // 创建标签方式一: var tag = "<a class='c1' href='http//:www.cnblogs.com'>博客园</a>" // 创建标签方式二: var tag = document.createElement('a') tag.innerText = "博客园" tag.className = "c1" tag.href = "http://www.cnblogs.com" // 操作标签,即将创建的插入整个HTML文档的某个位置 // 方式一 var mytag = document.getElementById('i1') var obj = "<input type='text' />"; mytag.insertAdjacentHTML("beforeEnd",obj); //将标签插入到mytab所代表的标签结束之前的位置,即最后一个子标签 mytag.insertAdjacentElement('afterBegin',document.createElement('p')); //将标签插入到mytab所代表的标签开始之后的位置,即第一个子标签 //注意:第一个参数只能是'beforeBegin'、 'afterBegin'、 'beforeEnd'、 'afterEnd' // 方式二 var tag = document.createElement('a') xxx.appendChild(tag) // 将新标签作为xxx的最后一个标签 xxx.insertBefore(tag,xxx[1]) // 将新标签插入xxx的第一个子标签之前
随着需要存储数据的结构不断复杂化,使用数据库来存储数据是一个必须面临的问题。那么应该如何在python中使用数据库?下面就在本篇博客中介绍一下在python中使用mysql。 首先,本博客已经假定阅读者已经安装了python和mysql,所以不会讲解关于它们的安装(如果未安装,请查阅官方文档进行下载安装)。 在python中使用pymysql操作mysql python的标准库中,是没有可以直接连接操作mysql的模块,首先我们应安装python的第三方模块pymysql。 使用pymysql操作mysql的步骤: 1)使用pymysql.connect连接并登录mysql 2) 使用connection.cursor建立游标 3) 使用cursor.execute()或cursor.executemany()执行sql语句 例一(使用pymysql执行简单的mysql操作): (1) 首先在mysql中建立一张用户表 CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `email` varchar(255) COLLATE utf8_bin NOT NULL, `password` varchar(255) COLLATE utf8_bin NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ; (2) 使用pymysql连接数据库并操作这张表 1 import pymysql 2 3 # Connect to the database 4 # 连接mysql,host指定主机;port指定端口,如果mysql为默认端口3306可以不写; 5 # user,password分别指定登录mysql的用户名和密码; 6 # db指定数据库;charset指定字符集; 7 connection = pymysql.connect(host='localhost', 8 user='root', 9 password='', 10 db='test', 11 charset='utf8mb4', 12 cursorclass=pymysql.cursors.DictCursor) 13 14 try: 15 with connection.cursor() as cursor: 16 # Create a new record 17 # 构建sql语句 18 sql = "INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)" 19 # 相当于在mysql终端执行 20 # "INSERT INTO `users` (`email`, `password`) VALUES ('webmaster@python.org', 'very-secret')" 21 cursor.execute(sql, ('webmaster@python.org', 'very-secret')) 22 23 # connection is not autocommit by default. So you must commit to save 24 # your changes. 25 # 向mysql提交更改,如果是查询语句,无需执行connection.commit() 26 # 可以通过设置connection.autocommit()来自动提交,传入True即可 27 connection.commit() 28 29 with connection.cursor() as cursor: 30 # Read a single record 31 # sql = "SELECT `id`, `password` FROM `users` WHERE `email`=%s" 32 # cursor.execute(sql, ('webmaster@python.org',)) 33 sql = "SELECT * FROM `users`" 34 # 执行cursor.execute(sql),等于在mysql终端执行sql语句。 35 cursor.execute(sql) 36 # 获取sql语句执行结果并打印 37 result = cursor.fetchall() 38 print(result) 39 finally: 40 # 关闭连接 41 connection.close() pymysql_example.py 例二(向mysql中的表插入多条信息): 1 import pymysql 2 3 connection = pymysql.Connect(host="localhost", 4 user="root", 5 password="", 6 db="test", 7 charset="utf8mb4", 8 cursorclass=pymysql.cursors.DictCursor) 9 10 try: 11 # # 执行多次INSERT操作 12 # with connection.cursor() as cursor: 13 # users_info = [('xiaoming@123.com','simple'), ('xiaoqiang@123.com','simple'), 14 # ('xiaozhang@123.com','very-secret'), ('xiaoli@123.com', 'simple'), 15 # ('xiangwang@123.com','simple'), ('xiaohong@123.com','very-secret')] 16 # sql = "INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)" 17 # # 执行多次相同操作使用cursor.executemany() 18 # cursor.executemany(sql, users_info) 19 # connection.commit() 20 21 # 查询所有用户信息 22 with connection.cursor() as cursor: 23 sql = "SELECT * FROM `users`" 24 cursor.execute(sql) 25 result = cursor.fetchall() 26 print("-----all users-----") 27 for user_info in result: 28 print(user_info) 29 30 with connection.cursor() as cursor: 31 sql = "SELECT * FROM `users` WHERE `password`=%s" 32 cursor.execute(sql, ('very-secret',)) 33 result = cursor.fetchall() 34 print("-----password is very-secret-----") 35 for user_info in result: 36 print(user_info) 37 finally: 38 connection.close() test_pymysql.py 注:在python程序中使用pymysql,最好只执行对表的增删该查即可(使用pymysql虽然能执行原生SQL语句,但不建议使用它进行建数据库,表,修改数据库,表属性等操作(如果要进行这些操作不妨直接登录mysql,直接在mysql终端执行这些操作)。 下面将介绍一些pymysql的一些常用API(在pymysq中只有两个常用object): (1)Connection Object: 常用属性: host – mysql主机地址 user – 登录用户名 password – 登录用户密码 port – mysql端口,默认3306 charset – 字符集 connect_timeout – 连接最大时间,超时自动断开。(default: 10, min: 1, max: 31536000) autocommit – 是否自动提交更改。(default: False) db – 使用指定的数据库 cursorclass – 指定cursor类 注:以上参数应在连接数据库时指定,只是常用参数(详细请参见:http://pymysql.readthedocs.io/en/latest/modules/connections.html)。 常用方法: begin() - 开启一个事件 与 在mysql终端执行BEGIN效果相同 close() - 关闭与mysql的连接 commit() - 提交对mysql中存储数据的更改 cursor(cursor=None) - 创建一个cursor对象,cursor类在连接时未指明,可以在此指明,使用默认cursor忽略参数即可 ping(reconnect=True) - 检测连接是否存活,如果连接超过设置的connet_timeout会自动断开,所以在进行对mysql操作前应使用此方法检测 rollback() - 使用了begin()后,对mysql的操作未提交前,可以只用此方法恢复到未操作之前 select_db(db) - 选择数据库,如果要操作的表不在连接时指定的数据库,使用此方法切换。 show_warnings() - 显示警告信息 (2)Cursor Objects: 常用方法: execute(query, args=None) - 执行一条sql语句 Parameters: query (str) – 要被执行的sql语句 args (tuple, list or dict) – sql语句中用到的参数 Returns: 多少行信息收到影响 Return type: int 如果args是以tuple的形式指定,则按位置依次传入sql语句中;如果是以dict传入,则以关键字传入sql语句中。 executemany(query, args) - 多次执行这条sql语句 参数与上相同,不过要使用[]将多个args括起来。 此方法可提高多行INSERT和REPLACE的性能。 否则,它等价于使用execute() 循环args。 fetchone() - 取结果中的一行 fetchall() - 取所有的结果 fetchmany(size=None) - 取结果中的size行 close() - 关闭当前cursor max_stmt_length = 1024000 - 指定 executemany() 执行最多max_stmt_length次sql语句 注:只写了常用方法,详细请参见:http://pymysql.readthedocs.io/en/latest/modules/cursors.html 使用sqlalchemy操作数据库(重点) 例三(使用sqlalchemy创建一张数据表并插入数据): 使用pymysql固然可以与mysql进行交互,但还是在源代码中使用了原生SQL语句,使代码的重用行和扩展性大大降低,这不符合面向对象的编程的特性。那么该如何像操作对象一样操作数据库呢? 我们使用一种叫做ORM(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping)的技术,是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。在python中我们使用一个名为SQLAlchemy(基于ORM的开发组件)来进行对数据库的操作,这样就不必在源代码中使用SQL语句,大大降低了程序员学习SQL的成本,由于不必再拼接复杂的SQL语句,大大提高开发效率,并且使程序有更高的扩展性。 1 import sqlalchemy 2 from sqlalchemy import create_engine 3 from sqlalchemy.ext.declarative import declarative_base 4 from sqlalchemy import Column, Integer, String 5 from sqlalchemy.orm import sessionmaker 6 7 # 检查sqlalchemy的版本 8 # print(sqlalchemy.__version__) 9 10 # 创建一个engine 11 # 传入一个URL作为第一个位置参数(格式为:dialect[+driver]://user:password@host/dbname[?key=value..]) 12 # dialect is a database name such as mysql, oracle, postgresql, , 13 # and driver the name of a DBAPI, such as psycopg2, pyodbc, cx_oracle, pymysql. 14 # 打印操作数据库的过程,则设置echo=True,否则默认即可 15 engine = create_engine('mysql+pymysql://root:123456@localhost/test') 16 17 Base = declarative_base() 18 19 # 将要创建的表结构 20 class User(Base): 21 # 表名 22 __tablename__ = 'users' 23 24 # 字段名,字段属性 25 id = Column(Integer, primary_key=True) 26 name = Column(String(32)) 27 fullname = Column(String(64)) 28 password = Column(String(64)) 29 30 def __repr__(self): 31 return "<User(name='%s', fullname='%s', password='%s')>" % ( 32 self.name, self.fullname, self.password) 33 34 # 可以同时创建多个表,在前面以上面的形式写好所有表结构,最后统一创建 35 Base.metadata.create_all(engine) 36 37 # 创建一个Session类 38 # Session = sessionmaker() 39 # Session.configure(bind=engine) 40 # 等同于上面两行 41 Session = sessionmaker(bind=engine) 42 # 生成一个session实例 43 session = Session() 44 45 # 构造要插入表中的数据 46 ed_user = User(name='ed', fullname='Ed Jones', password='edspassword') 47 # 将数据放入session中,如果有多条数据使用session.add_all([data1,data2,...]) 48 session.add(ed_user) 49 # session.add_all([User(name='wendy', fullname='Wendy Williams', password='foobar'), 50 # User(name='mary', fullname='Mary Contrary', password='xxg527'), 51 # User(name='fred', fullname='Fred Flinstone', password='blah')]) 52 # 向数据库提交 53 # session.commit() 54 55 data = session.query(User).filter(User.id>2).all() 56 print(data) sqlalchemy_test.py # 使用上面的代码生成的数据表结构 mysql> desc users; +----------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------+-------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(32) | YES | | NULL | | | fullname | varchar(64) | YES | | NULL | | | password | varchar(64) | YES | | NULL | | +----------+-------------+------+-----+---------+----------------+ 4 rows in set (0.00 sec) # 使用上面代码插入表中的数据 mysql> select * from users; +----+------+----------+-------------+ | id | name | fullname | password | +----+------+----------+-------------+ | 1 | ed | Ed Jones | edspassword | +----+------+----------+-------------+ 1 row in set (0.00 sec) 例四(使用sqlalchemy进行对数据的查,改,删) 1 # 查询时在filter_by(或filter)中写上条件即可,查询到的结果可能是多条,first()代表取第一条,all()代表取所有 2 our_user = session.query(User).filter_by(name='ed').first() 3 # 如果有多个查询条件,data = session.query(User).filter(User.id>2).filter(User.id<4).all(),这样使用即可 4 data = session.query(User).filter(User.id>2).all() 5 print("-------这是查询数据的结果-------") 6 print(our_user) 7 print(data) 8 print('\n') 9 10 # 直接修改查询的结果,然后提交即可 11 our_user.password = 'f8s7ccs' 12 session.commit() 13 new_user = session.query(User).filter_by(name='ed').first() 14 print("-------这是修改数据的结果-------") 15 print(new_user) 16 print('\n') 17 18 # 先查询出要删除的数据,然后使用session.delete()和session.delete()即可 19 data = session.query(User).filter(User.id==5).first() 20 # print(data) 21 session.delete(data) 22 session.commit() 使用sqlalchemy操作数据库中的数据 例五(使用sqlalchemy实现数据表的外键关联): 作为关系型数据库,表与表之间的外键关联是比不可少的,也是至关重要的,那么改如何使用sqlalchemy在python对象中通过类的形式映射这种关系呢? 请看下面的代码。 1 import sqlalchemy 2 from sqlalchemy import create_engine 3 from sqlalchemy.ext.declarative import declarative_base 4 from sqlalchemy import Column, Integer, String, Enum, ForeignKey 5 from sqlalchemy.orm import sessionmaker, relationship 6 7 engine = create_engine('mysql+pymysql://root:123456@localhost/student') 8 9 Base = declarative_base() 10 11 class Student(Base): 12 __tablename__ = 'student_info' 13 14 # 设置id, 类型为int, 不能为空, id是这张表的主键 15 id = Column(Integer, nullable=False, primary_key=True) 16 # 设置stu_id, 类型为int, 不能为空, id在这张表中的值唯一 17 stu_id = Column(Integer, nullable=False, unique=True) 18 name = Column(String(32), nullable=False, ) 19 age = Column(Integer, nullable=False, ) 20 gender = Column(Enum('F', 'M'), nullable=False) 21 22 # 查询结果的显示是此函数返回的格式 23 def __repr__(self): 24 return "<Student(stu_id='%s', name='%s', age='%s', gender='%s')>" % ( 25 self.stu_id, self.name, self.age, self.gender) 26 27 class Study(Base): 28 __tablename__ = 'study_level' 29 30 id = Column(Integer, nullable=False, primary_key=True) 31 # 设置stu_id为study_level表的外键,与student_info表中的stu_id关联 32 stu_id = Column(Integer, ForeignKey('student_info.stu_id')) 33 mathematics = Column(Integer) 34 physics = Column(Integer) 35 chemistry = Column(Integer) 36 37 # 定义关系,可以在本类中使用属性student_info查询表student_info中的数据(以同样的条件) 38 # 也可以在Student类中使用属性study_level查询表study_level中的数据 39 student_info = relationship('Student', backref='study_level') 40 41 def __repr__(self): 42 return "<Study(name=%s, mathematics=%s, physics=%s, chemistry=%s)>" % ( 43 self.student_info.name, self.mathematics, self.physics, self.chemistry) 44 45 # Base.metadata.create_all(engine) 46 47 Session = sessionmaker(engine) 48 session = Session() 49 50 # 插入4个学生信息 51 # session.add_all([Student(stu_id=10001, name='zhangsan', age=16, gender='F'), 52 # Student(stu_id=10002, name='lisi', age=17, gender='M'), 53 # Student(stu_id=10003, name='wangwu', age=16, gender='M'), 54 # Student(stu_id=10004, name='zhouliu', age=15, gender='F')]) 55 # 56 # 插入考试成绩,成绩不到60分的科目需补考,再插入补考成绩 57 # session.add_all([Study(stu_id=10001, mathematics=78, physics=70, chemistry=83), 58 # Study(stu_id=10002, mathematics=87, physics=85, chemistry=92), 59 # Study(stu_id=10003, mathematics=60, physics=54, chemistry=76), 60 # Study(stu_id=10004, mathematics=52, physics=46, chemistry=44), 61 # Study(stu_id=10003, physics=68), 62 # Study(stu_id=10004, mathematics=63, physics=61, chemistry=65)]) 63 # session.commit() 64 65 # 使用这种方法查询多张表,表之间可以没有任何关系 66 data = session.query(Student, Study).filter(Student.stu_id==Study.stu_id).all() 67 print(data) 68 print('\n') 69 70 71 # 使用下面的方法通过一张表查询其他表,表之间必须有外键关联 72 # 因为每个学生的信息唯一,所以使用first() 73 student = session.query(Student).filter(Student.stu_id==10003).first() 74 print(student) 75 # print(student.study_level)相当于Student.stu_id==10003时,下面的两行代码 76 # data = session.query(Study).filter(session.query(Study).filter(Student.stu_id==Study.stu_id).all()).all() 77 # print(data) 78 print(student.study_level) 79 print('\n') 80 81 # 因为一个学生可能会有多次考试记录,所以使用all() 82 score = session.query(Study).filter(Study.stu_id==10003).all() 83 print(score) 84 # print(score[0].student_info)相当于Study.stu_id==10003时 85 # 因为在student_info表中stu_id的值唯一,所以只有一条数据 86 # data = session.query(Student).filter(Study[0].stu_id==Student.stu_id).first() 87 # print(data) 88 print(score[0].student_info) fk_sqlalchemy.py mysql> select * from student_info; +----+--------+----------+-----+--------+ | id | stu_id | name | age | gender | +----+--------+----------+-----+--------+ | 1 | 10001 | zhangsan | 16 | F | | 2 | 10002 | lisi | 17 | M | | 3 | 10003 | wangwu | 16 | M | | 4 | 10004 | zhouliu | 15 | F | +----+--------+----------+-----+--------+ 4 rows in set (0.00 sec) mysql> select * from study_level; +----+--------+-------------+---------+-----------+ | id | stu_id | mathematics | physics | chemistry | +----+--------+-------------+---------+-----------+ | 1 | 10001 | 78 | 70 | 83 | | 2 | 10002 | 87 | 85 | 92 | | 3 | 10003 | 60 | 54 | 76 | | 4 | 10004 | 52 | 46 | 44 | | 5 | 10003 | NULL | 68 | NULL | #学号为10003的学生,只有一科成绩小于60,只补考一科 | 6 | 10004 | 63 | 61 | 65 | #学号为10004的学生,三科成绩都小于60,需补考三科 +----+--------+-------------+---------+-----------+ 6 rows in set (0.00 sec) 注:对有外键关联的数据表,进行数据的增删该查,与上例中使用的方式一样,不过受外键约束,约束条件同mysql中外键的约束相同。(详细请参见:http://www.cnblogs.com/God-Li/p/8157312.html) 例六(使用sqlalchemy实现mysql中多对多的关系): 多对多的数据关系是最常见的实际生产的数据关系,比如超市的商品与顾客之间的关系(一个顾客可以买多种商品,一种商品可以被多个顾客购买),比如电影与演员的关系(一名演员可以参演多部电影,一部电影会有多个演员),这些数据是我们经常使用的,比如我们在视频网站查找电影时,会有按演员查找,对于一部电影我们也经常关注是哪些演员参演的。那么改如何使用sqlalchemy在mysql中存储这些关系呢?我们就以超市商品与顾客之间的关系来做一个示例,请看下面的代码。 为了便于理解,我们先来看一下表结构(一共三张表) # 商品表,存储商品的名称,价格,和生产日期(为了简单只存这几样信息) mysql> select * from products; +----+-------------+-------+------------+ | id | name | price | pro_date | +----+-------------+-------+------------+ | 1 | iPhone8 | 6988 | 2017-09-18 | | 2 | Apple Watch | 2588 | 2017-06-20 | | 3 | Airpods | 1288 | 2017-01-11 | | 4 | MacBook | 10288 | 2017-05-13 | +----+-------------+-------+------------+ 4 rows in set (0.00 sec) # 顾客表,存储顾客的姓名(这里为了简单只存了姓名,其实还应该用性别、年龄等具体信息) mysql> select * from customers; +----+-----------+ | id | name | +----+-----------+ | 1 | ZhangSang | | 2 | WangWu | | 3 | XiaoMing | | 4 | LiSi | | 5 | ZhaoLiu | +----+-----------+ 5 rows in set (0.00 sec) # 商品顾客关系表,存储商品与用户的关系,可通过用户查购买了哪些商品,也可通过商品查有哪些用户购买 mysql> select * from product_to_customer; +------------+-------------+ | product_id | customer_id | +------------+-------------+ | 4 | 4 | | 4 | 3 | | 3 | 2 | | 2 | 1 | | 2 | 4 | | 2 | 2 | | 2 | 5 | | 2 | 3 | | 1 | 1 | | 1 | 4 | | 1 | 5 | +------------+-------------+ 11 rows in set (0.00 sec) 接着我们来看一下如何使用python来创建这些表,插入并查询这些信息。 1 import sqlalchemy 2 from sqlalchemy import Table, Column, Integer, String, DATE, ForeignKey 3 from sqlalchemy.orm import relationship 4 from sqlalchemy.ext.declarative import declarative_base 5 from sqlalchemy import create_engine 6 7 Base = declarative_base() 8 9 # 商品与顾客关系表结构 10 product_to_customer = Table('product_to_customer', Base.metadata, 11 Column('product_id', Integer, ForeignKey('products.id')), 12 Column('customer_id', Integer, ForeignKey('customers.id'))) 13 14 # 用户表结构 15 class Customer(Base): 16 __tablename__ = 'customers' 17 18 id = Column(Integer, primary_key=True) 19 name = Column(String(32)) 20 21 def __repr__(self): 22 return self.name 23 24 # 商品表结构 25 class Product(Base): 26 __tablename__ = 'products' 27 28 id = Column(Integer, primary_key=True) 29 name = Column(String(32)) 30 price = Column(Integer) 31 pro_date = Column(DATE) 32 customers = relationship(Customer, backref='products', secondary='product_to_customer') 33 34 def __repr__(self): 35 return self.name 36 37 38 engine = create_engine('mysql+pymysql://root:123456@localhost/supermarket') 39 Base.metadata.create_all(engine) table_struct.py 1 import table_struct 2 from sqlalchemy.orm import sessionmaker 3 4 Session = sessionmaker(table_struct.engine) 5 session = Session() 6 7 # 构建商品信息 8 # p1 = table_struct.Product(name='iPhone8', price='6988', pro_date='2017-9-18') 9 # p2 = table_struct.Product(name='MacBook', price='10288', pro_date='2017-5-13') 10 # p3 = table_struct.Product(name='Airpods', price='1288', pro_date='2017-1-11') 11 # p4 = table_struct.Product(name='Apple Watch', price='2588', pro_date='2017-6-20') 12 # 13 # 构建顾客信息 14 # c1 = table_struct.Customer(name="ZhangSang") 15 # c2 = table_struct.Customer(name="LiSi") 16 # c3 = table_struct.Customer(name="WangWu") 17 # c4 = table_struct.Customer(name="ZhaoLiu") 18 # c5 = table_struct.Customer(name="XiaoMing") 19 # 20 # 构建商品与顾客的关系 21 # p1.customers = [c1, c2, c4] 22 # p2.customers = [c2, c5] 23 # p3.customers = [c3] 24 # p4.customers = [c1, c2, c3, c4, c5] 25 # 26 # session.add_all([p1, p2, p3, p4, c1, c2, c3, c4, c5]) 27 # session.commit() 28 29 # 通过顾客查询他购买了哪些商品 30 customer_obj = session.query(table_struct.Customer).filter(table_struct.Customer.name=='XiaoMing').first() 31 print(customer_obj.products) 32 33 # 通过商品查询有哪些顾客购买 34 product_obj = session.query(table_struct.Product).filter(table_struct.Product.name=="iPhone8").first() 35 print(product_obj.customers) database_api.py
1. 什么是数据库? 数据库(Database)是按照数据结构来组织、存储和管理数据的建立在计算机存储设备上的仓库。 简单来说是本身可视为电子化的文件柜——存储电子文件的处所,用户可以对文件中的数据进行新增、截取、更新、删除等操作。 严格来说,数据库是长期储存在计算机内、有组织的、可共享的数据集合。数据库中的数据指的是以一定的数据模型组织、描述和储存在一起、具有尽可能小的冗余度、较高的数据独立性和易扩展性的特点并可在一定范围内为多个用户共享。 2. 为什么需要使用数据库? 假设这样一个场景,需要存储一所学校所有学生的姓名,性别,出生日期和各科成绩,是用Word文档来存,还是使用Excel表格?相信一个正常人都会选择Excel表格;当然,使用Word文档也能实现这一需求;使用Word文档实现存储这一需求当然没有任何问题,但是如果某个学生来要查询自己的学生,或者需要修改某个学生的成绩,给这些学生按成绩排名等这些需求,如果需要再扩展一下存储内容,比如再存一下每个学生的联系方式,那使用Word就几乎不可能实现了。同样的,在程序中所需要存储的各种信息会比上面的场景更加复杂,使用一种有结构,有规律的数据存储方式就非常的有必要了。 3. 如何管理操作数据库? 我们可以使用一种叫做数据库管理系统(DBMS)的软件来操作数据库。 数据库管理系统(Database Management System)是一种操纵和管理数据库的大型软件,用于建立、使用和维护数据库,简称DBMS。它对数据库进行统一的管理和控制,以保证数据库的安全性和完整性。用户通过DBMS访问数据库中的数据,数据库管理员也通过dbms进行数据库的维护工作。它可使多个应用程序和用户用不同的方法在同时或不同时刻去建立,修改和询问数据库。大部分DBMS提供数据定义语言DDL(Data Definition Language)和数据操作语言DML(Data Manipulation Language),供用户定义数据库的模式结构与权限约束,实现对数据的追加、删除等操作。 常见的数据库管理系统有:Oracle数据库、DB2、MySQL、Informix、SQL Server等。 4. 如何使用数据库管理系统(DBMS)? 一般我们使用SQL语言与DBMS交互来操作关系型数据库(一般日常使用的MySQL、SQL Server、Oracle数据库都是关系型数据库)。 SQL语言在这些数据库管理系统中基本通用。不能说完全通用是因为不同的数据库系统在其实践过程中都对SQL规范作了某些编改和扩充。(基本上能在一种数据库管理系统中熟练的使用SQL,迁移到其他系统其实很简单) 在本篇博客中,就详细介绍一下如何在mysql中使用SQL语言实现常规操作。 首先介绍一下mysql中的数据类型(可以先略过,用到时再来查看): 参考自(http://tool.oschina.net/apidocs/apidoc?api=mysql-5.1-zh) MySQL有多种类型,大致可以分为三类:数值、日期/时间和字符串(字符)类型。 (1) 数值类型: MySQL支持所有标准SQL数值数据类型。这些类型包括严格数值数据类型(INTEGER、SMALLINT、DECIMAL和NUMERIC),以及近似数值数据类型(FLOAT、REAL和DOUBLE PRECISION)。关键字INT是INTEGER的同义词,关键字DEC是DECIMAL的同义词。 BIT数据类型保存位字段值,并且支持MyISAM、MEMORY、InnoDB和BDB表。 作为SQL标准的扩展,MySQL也支持整数类型TINYINT、MEDIUMINT和BIGINT。下面的表显示了需要的每个整数类型的存储和范围。 类型 大小 范围(有符号) 范围(无符号) 用途 TINYINT 1 字节 (-128,127) (0,255) 小整数值 SMALLINT 2 字节 (-32 768,32 767) (0,65 535) 大整数值 MEDIUMINT 3 字节 (-8 388 608,8 388 607) (0,16 777 215) 大整数值 INT或INTEGER 4 字节 (-2 147 483 648,2 147 483 647) (0,4 294 967 295) 大整数值 BIGINT 8 字节 (-9 233 372 036 854 775 808,9 223 372 036 854 775 807) (0,18 446 744 073 709 551 615) 极大整数值 FLOAT 4 字节 (-3.402 823 466 E+38,1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) 0,(1.175 494 351 E-38,3.402 823 466 E+38) 单精度浮点数值 DOUBLE 8 字节 (1.797 693 134 862 315 7 E+308,2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) 双精度浮点数值 DECIMAL 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 依赖于M和D的值 依赖于M和D的值 小数值 注:MySQL还支持选择在该类型关键字后面的括号内指定整数值的显示宽度(例如,INT(4))。该可选显示宽度规定用于显示宽度小于指定的列宽度的值时从左侧填满宽度。 (2)日期/时间类型: 表示时间值的DATE和时间类型为DATETIME、DATE、TIMESTAMP、TIME和YEAR。每个时间类型有一个有效值范围和一个“零”值,当指定不合法的MySQL不能表示的值时使用“零”值。 类型 大小(字节) 范围 格式 用途 DATE 3 1000-01-01/9999-12-31 YYYY-MM-DD 日期值 TIME 3 '-838:59:59'/'838:59:59' HH:MM:SS 时间值或持续时间 YEAR 1 1901/2155 YYYY 年份值 DATETIME 8 1000-01-01 00:00:00/9999-12-31 23:59:59 YYYY-MM-DD HH:MM:SS 混合日期和时间值 TIMESTAMP 4 1970-01-01 00:00:00/2037 年某时 YYYYMMDD HHMMSS 混合日期和时间值,时间戳 (3)字符串类型: 字符串类型指CHAR、VARCHAR、BINARY、VARBINARY、BLOB、TEXT、ENUM和SET。 CHAR和VARCHAR类型类似,但它们保存和检索的方式不同。它们的最大长度和是否尾部空格被保留等方面也不同。在存储或检索过程中不进行大小写转换。 CHAR和VARCHAR类型声明的长度表示你想要保存的最大字符数。例如,CHAR(30)可以占用30个字符。 CHAR列的长度固定为创建表时声明的长度。长度可以为从0到255的任何值。当保存CHAR值时,在它们的右边填充空格以达到指定的长度。当检索到CHAR值时,尾部的空格被删除掉。在存储或检索过程中不进行大小写转换。 VARCHAR列中的值为可变长字符串。长度可以指定为0到65,535之间的值。(VARCHAR的最大有效长度由最大行大小和使用的字符集确定。整体最大长度是65,532字节)。 同CHAR对比,VARCHAR值保存时只保存需要的字符数,另加一个字节来记录长度(如果列声明的长度超过255,则使用两个字节)。 类型 大小 用途 CHAR 0-255字节 定长字符串 VARCHAR 0-65535 字节 变长字符串 TINYBLOB 0-255字节 不超过 255 个字符的二进制字符串 TINYTEXT 0-255字节 短文本字符串 BLOB 0-65 535字节 二进制形式的长文本数据 TEXT 0-65 535字节 长文本数据 MEDIUMBLOB 0-16 777 215字节 二进制形式的中等长度文本数据 MEDIUMTEXT 0-16 777 215字节 中等长度文本数据 LONGBLOB 0-4 294 967 295字节 二进制形式的极大文本数据 LONGTEXT 0-4 294 967 295字节 极大文本数据 注:BINARY和VARBINARY类类似于CHAR和VARCHAR,不同的是它们包含二进制字符串而不要非二进制字符串。也就是说,它们包含字节字符串而不是字符字符串。这说明它们没有字符集,并且排序和比较基于列值字节的数值值。 BLOB是一个二进制大对象,可以容纳可变数量的数据。有4种BLOB类型:TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB。它们只是可容纳值的最大长度不同。 有4种TEXT类型:TINYTEXT、TEXT、MEDIUMTEXT和LONGTEXT。这些对应4种BLOB类型,有相同的最大长度和存储需求。 例一(显示一下当前系统中有哪些数据库): mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | test | +--------------------+ 5 rows in set (0.00 sec) 注1:使用"show databases;"命令可以显示当前系统中有哪些数据库。 注2: 在mysql中,sql语句的结束必须使用";"。 注3: SQL语言不区分大小写。 例二(进入某个数据库并显示这个数据库中都有哪些表): mysql> use test; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> show tables; +----------------+ | Tables_in_test | +----------------+ | A | | B | | cats | | users | +----------------+ 4 rows in set (0.00 sec) 注:"use test;"命令,进入了test数据库;"show tables;"命令,显示了test数据库中的表。 例三(显示表的结构): mysql> desc users; +----------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | email | varchar(255) | NO | | NULL | | | password | varchar(255) | NO | | NULL | | +----------+--------------+------+-----+---------+----------------+ 3 rows in set (0.00 sec) 注1:使用"desc users;"命令显示了users这张表的结构; 注2: 上面这张user表的结构如下表所示: 有id(存储11为的int类型数据,而且id是自增的),emali(存储255位的varchar类型数据),password(同前)三个字段,相等于Excel表格中的三列(mysql中的数据类型在上面有介绍),id,emial,password即为这张表的字段。 例四(查询某张表中数据): mysql> select * from users; +----+----------------------+-------------+ | id | email | password | +----+----------------------+-------------+ | 1 | webmaster@python.org | very-secret | | 2 | webmaster@python.org | very-secret | | 3 | webmaster@python.org | very-secret | | 4 | xiaoming@123.com | simple | | 5 | xiaoqiang@123.com | simple | | 6 | xiaozhang@123.com | very-secret | | 7 | xiaoli@123.com | simple | | 8 | xiangwang@123.com | simple | | 9 | xiaohong@123.com | very-secret | +----+----------------------+-------------+ 9 rows in set (0.00 sec) 注1:使用"select * form users"查询user表中的所有数据,*代表user表中的所有字段。 mysql> select * from users where users.password='simple'; +----+-------------------+----------+ | id | email | password | +----+-------------------+----------+ | 4 | xiaoming@123.com | simple | | 5 | xiaoqiang@123.com | simple | | 7 | xiaoli@123.com | simple | | 8 | xiangwang@123.com | simple | +----+-------------------+----------+ 4 rows in set (0.01 sec) 注2:使用" select * form users where users.password='simple' "命令就可以查询users表中所有password为simple的数据(在where后面加条件即可)。 mysql> select users.email from users; +----------------------+ | email | +----------------------+ | webmaster@python.org | | webmaster@python.org | | webmaster@python.org | | xiaoming@123.com | | xiaoqiang@123.com | | xiaozhang@123.com | | xiaoli@123.com | | xiangwang@123.com | | xiaohong@123.com | +----------------------+ 9 rows in set (0.00 sec) 注3: 如果只想查看某一字段的数据,只需使用"select tablename.x from tablename"即可。 # 查询条件为password='simple', 只显示users.email字段mysql> select users.email from users where password='simple'; +-------------------+ | email | +-------------------+ | xiaoming@123.com | | xiaoqiang@123.com | | xiaoli@123.com | | xiangwang@123.com | +-------------------+ 4 rows in set (0.00 sec) 注4: 查询字段与查询条件没有关系。 例五(将下面数据插入表中): mysql> insert into users (email,password) values ('abc@xyz.com','very-simple'); Query OK, 1 row affected (0.01 sec) mysql> select * from users where password='very-simple'; +----+-------------+-------------+ | id | email | password | +----+-------------+-------------+ | 10 | abc@xyz.com | very-simple | +----+-------------+-------------+ 1 row in set (0.00 sec) 注1:在例三中,我们知道id是自增的,所以不用人为指定,只需指定其他两个字段的值。 注2: 向表中插入数据的语法如下: INSERT INTO table_name ( field1, field2,...fieldN ) VALUES ( value1, value2,...valueN ); 例六(修改表中的数据): 修改表中数据的语法如下。 UPDATE table_name SET field1=new-value1, field2=new-value2 [WHERE Clause] 注意例四,我们将id为2的password改为"hello word"。 mysql> update users set password='hello word' where id=2; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from users where id=2; +----+----------------------+------------+ | id | email | password | +----+----------------------+------------+ | 2 | webmaster@python.org | hello word | +----+----------------------+------------+ 1 row in set (0.00 sec) 例七(删除表中的数据): 删除表中数据的语法。 DELETE FROM table_name [WHERE Clause] 删除users表中id>6的数据。 mysql> select * from users; +----+----------------------+-------------+ | id | email | password | +----+----------------------+-------------+ | 1 | webmaster@python.org | very-secret | | 2 | webmaster@python.org | hello word | | 3 | webmaster@python.org | very-secret | | 4 | xiaoming@123.com | simple | | 5 | xiaoqiang@123.com | simple | | 6 | xiaozhang@123.com | very-secret | | 7 | xiaoli@123.com | simple | | 8 | xiangwang@123.com | simple | | 9 | xiaohong@123.com | very-secret | | 10 | abc@xyz.com | very-simple | +----+----------------------+-------------+ 10 rows in set (0.00 sec) mysql> delete from users where id>6; Query OK, 4 rows affected (0.01 sec) mysql> select * from users; +----+----------------------+-------------+ | id | email | password | +----+----------------------+-------------+ | 1 | webmaster@python.org | very-secret | | 2 | webmaster@python.org | hello word | | 3 | webmaster@python.org | very-secret | | 4 | xiaoming@123.com | simple | | 5 | xiaoqiang@123.com | simple | | 6 | xiaozhang@123.com | very-secret | +----+----------------------+-------------+ 6 rows in set (0.00 sec) 上面的例子都是介绍对数据库表中数据的操作,接下来就来了解一下对表及数据库本身的操作。 例八(创建数据库): #创建数据库语法 CREATE DATABASE DABASENAME; # 创建数据库 mysql> create database mysql_test; Query OK, 1 row affected (0.01 sec) mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | mysql_test | | performance_schema | | sys | | test | +--------------------+ 6 rows in set (0.00 sec) 例九(在数据库中创建表): 创建一个如下结构的student表: 字段stu_id可存储的类型为11位int(不能为空),字段name可存储的类型为32位char(不能为空),字段age可存储的类型为11位int(不能为空),字段register_data可存储的类型为date。其中stu_id为主键,并且是自增的。 #创建数据表语法 CREATE TABLE table_name (column_name column_type); #进入刚才建立的数据库,建立student表 mysql> use mysql_test; Database changed mysql> show tables; Empty set (0.00 sec) mysql> create table student( -> stu_id int not null auto_increment, #字段名,类型,属性 -> name char(32) not null, -> age int not null, -> register_date date, -> primary key(stu_id) # 设置某个字段为主键 -> ); Query OK, 0 rows affected (0.03 sec) mysql> show tables; +----------------------+ | Tables_in_mysql_test | +----------------------+ | student | +----------------------+ 1 row in set (0.00 sec) mysql> desc student; +---------------+----------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +---------------+----------+------+-----+---------+----------------+ | stu_id | int(11) | NO | PRI | NULL | auto_increment | | name | char(32) | NO | | NULL | | | age | int(11) | NO | | NULL | | | register_date | date | YES | | NULL | | +---------------+----------+------+-----+---------+----------------+ 4 rows in set (0.01 sec) 使用alter修改表结构 # 修改表名称 ALTER TABLE tablename RENAME TO new_tablename; # 删除表的某个字段 alter table tablename drop fieldname; # 向表中添加字段 alter table tablename add fieldname int(11) not null; # 修改字段类型,把字段 c 的类型从 CHAR(4) 改为 CHAR(8) ALTER TABLE tablename MODIFY c CHAR(8); # change语句,CHANGE 关键字之后,紧跟着的是要修改的字段名,然后指定新字段名及类型。 # 修改字段名及类型 ALTER TABLE tablename CHANGE x y BIGINT; # 修改字段类型 ALTER TABLE testalter_tbl CHANGE x x INT; 例十(删除数据库即库中所有的表): # 取消数据库及数据库中的所有表语法 DROP DATABASE DATABASENAME; # 取消数据库中某张表语法 DROP TABLE TABLENAME; 注: DROP DATABASE用于取消数据库中的所用表格和取消数据库。使用此语句时要非常小心!如果要使用DROP DATABASE,您需要获得数据库DROP权限。 # 删除刚创建的mysql_test数据库及库中的student表mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | mysql_test | | performance_schema | | sys | | test | +--------------------+ 6 rows in set (0.01 sec) mysql> drop database mysql_test; Query OK, 1 row affected (0.02 sec) mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | test | +--------------------+ 5 rows in set (0.00 sec) 例十一(在MySql中的外键关联): 上面的例子所介绍的对数据和表的操作都是单张数据表内的,而MySql是关系型数据库,这代表它管理的是多张相互之间有关联的数据表。这里就需要介绍两个概念,主键(primary key)和外键(foreing key)。 主键(primary key):是每条记录的ID,在整张表中的值唯一(例如如上图两张表中每行最外面的行号)。 外键(foreing key):用于同其他表中数据做关联,在整张表中的值可以不唯一(如上图,如果某个学生参加了补考,可以再向表中加入一次他的成绩)。例如上图学生成绩表就以stu_id与学生信息表做关联,它们对于同一个人有相同的取值,使用外键便可将这两张表中的数据关联起来。 注意:一个表的外键在另一个表中的字段值必须唯一(可以是主键)。 mysql> create table student_info(id int not null auto_increment, stu_id int not null unique, name varchar(32) not null, age int not null, gender enum('F', 'M'), primary key(id)); Query OK, 0 rows affected (0.03 sec) mysql> desc student_info; +--------+---------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------+---------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | stu_id | int(11) | NO | UNI | NULL | | | name | varchar(32) | NO | | NULL | | | age | int(11) | NO | | NULL | | | gender | enum('F','M') | YES | | NULL | | +--------+---------------+------+-----+---------+----------------+ 5 rows in set (0.00 sec) mysql> create table study_level(id int not null auto_increment, stu_id int not null, mathematics int not null, physics int not null, chemistry int not null, primary key(id), key fk_study (stu_id), constraint fk_study foreign key (stu_id) references student_info (stu_id)); Query OK, 0 rows affected (0.02 sec) mysql> desc study_level; +-------------+---------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+---------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | stu_id | int(11) | NO | MUL | NULL | | | mathematics | int(11) | NO | | NULL | | | physics | int(11) | NO | | NULL | | | chemistry | int(11) | NO | | NULL | | +-------------+---------+------+-----+---------+----------------+ 5 rows in set (0.00 sec) 注:在本例中,使用stu_id作为study_level表的外键,而stu_id在student_info中的值是唯一的。 # 向表中插入图中数据,插入数据的sql语法前面有介绍 mysql> select * from student_info; +----+--------+----------+-----+--------+ | id | stu_id | name | age | gender | +----+--------+----------+-----+--------+ | 1 | 10001 | zhangsan | 16 | F | | 2 | 10002 | lisi | 17 | M | | 3 | 10003 | wangwu | 16 | M | | 4 | 10004 | zhouliu | 15 | F | +----+--------+----------+-----+--------+ 4 rows in set (0.00 sec) mysql> select * from study_level; +----+--------+-------------+---------+-----------+ | id | stu_id | mathematics | physics | chemistry | +----+--------+-------------+---------+-----------+ | 1 | 10001 | 78 | 70 | 83 | | 2 | 10002 | 87 | 85 | 92 | | 3 | 10003 | 60 | 54 | 76 | | 4 | 10004 | 52 | 46 | 44 | +----+--------+-------------+---------+-----------+ 4 rows in set (0.00 sec) 上面的示例还看不出来使用外键的好处,那就在下面来演示一下为什么需要使用外键。 # 外键对插入数据的约束# 向study_level表再插入一条stu_id=10004的记录,没有任何问题 mysql> insert into study_level (stu_id, mathematics, physics, chemistry) values(10004, 65, 70, 63); Query OK, 1 row affected (0.01 sec) mysql> select * from study_level; +----+--------+-------------+---------+-----------+ | id | stu_id | mathematics | physics | chemistry | +----+--------+-------------+---------+-----------+ | 1 | 10001 | 78 | 70 | 83 | | 2 | 10002 | 87 | 85 | 92 | | 3 | 10003 | 60 | 54 | 76 | | 4 | 10004 | 52 | 46 | 44 | | 5 | 10004 | 65 | 70 | 63 | +----+--------+-------------+---------+-----------+ 5 rows in set (0.00 sec) # 向study_level表插入一条stu_id=10010的记录,由于在student_info表中没有关于这个值的记录,所以会出错,这就是外键约束 mysql> insert into study_level (stu_id, mathematics, physics, chemistry) values(10010, 65, 70, 63); ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`study_level`, CONSTRAINT `fk_study` FOREIGN KEY (`stu_id`) REFERENCES `student_info` (`stu_id`)) # 外键对于删除数据的约束 # 删除study_level表中stu_id=10004的数据,没有任何问题 mysql> delete from study_level where stu_id=10004; Query OK, 2 rows affected (0.00 sec) mysql> select * from study_level; +----+--------+-------------+---------+-----------+ | id | stu_id | mathematics | physics | chemistry | +----+--------+-------------+---------+-----------+ | 1 | 10001 | 78 | 70 | 83 | | 2 | 10002 | 87 | 85 | 92 | | 3 | 10003 | 60 | 54 | 76 | +----+--------+-------------+---------+-----------+ 3 rows in set (0.01 sec) # 删除student_info表中stu_id=10002的数据,由于study_level表中有数据关联了这条数据,所以无法删除 mysql> delete from student_info where stu_id=10002; ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`test`.`study_level`, CONSTRAINT `fk_study` FOREIGN KEY (`stu_id`) REFERENCES `student_info` (`stu_id`)) 例十二(mysql中多表的连接查询): 上面示例介绍的在表中查询数据都是在一张表中,但在实际生产过程中多表的跨表查询才是最常见的。那么在本例中将介绍一下在mysql中使用join来进行连接查询。 常用的连接有一下几种: INNER JOIN(内连接,或等值连接)、LEFT JOIN(左外连接)、RIGHT JOIN(右外连接)、FULL JOIN(全外连接)。 我们先来看一下两表的结构,为了便于理解都是非常简单的表。 mysql> select * from A; +---+ | a | +---+ | 1 | | 2 | | 3 | | 4 | +---+ 4 rows in set (0.00 sec) mysql> select * from B; +---+ | b | +---+ | 3 | | 4 | | 5 | | 6 | | 7 | +---+ 5 rows in set (0.00 sec) INNER JOIN(内连接,或等值连接):指使用等号"="比较两个表的连接列的值,相当于两表执行笛卡尔后,取两表连结列值相等的记录。 说明:通俗来讲,相当于取两表连接列内容的交集。 mysql> select * from A inner join B on A.a = B.b; +---+---+ | a | b | +---+---+ | 3 | 3 | | 4 | 4 | +---+---+ 2 rows in set (0.00 sec) LEFT JOIN(左外连接):指将左表的所有记录与右表符合条件的记录,返回的结果除内连接的结果,还有左表不符合条件的记录,并在右表相应列中填NULL。 说明:比如对于表A和B,结果显示A,A∩B。 mysql> select * from A left join B on A.a = B.b; +---+------+ | a | b | +---+------+ | 3 | 3 | | 4 | 4 | | 1 | NULL | | 2 | NULL | +---+------+ RIGHT JOIN(右外连接):与左外连接相反,指将右表的所有记录与左表符合条件的记录,返回的结果除内连接的结果,还有右表不符合条件的记录,并在左表相应列中填NULL。 说明:比如对于表A和B,结果显示A∩B,B。 mysql> select * from A right join B on A.a = B.b; +------+---+ | a | b | +------+---+ | 3 | 3 | | 4 | 4 | | NULL | 5 | | NULL | 6 | | NULL | 7 | +------+---+ 5 rows in set (0.00 sec) FULL JOIN(全外连接):指将左表所有记录与右表所有记录进行连接,返回的结果除内连接的结果,还有左表与右表不符合条件的记录,并在左表与右表相应列中填NULL。 说明:通俗来讲,相当于取两表连接列内容的并集。 mysql> select * from A left join B on A.a = B.b UNION select * from A right join B on A.a = B.b; +------+------+ | a | b | +------+------+ | 3 | 3 | | 4 | 4 | | 1 | NULL | | 2 | NULL | | NULL | 5 | | NULL | 6 | | NULL | 7 | +------+------+ 7 rows in set (0.00 sec) 注:由于mysql并不直接支持full join,所以不能使用select * from A full join B on A.a = B.b来进行full join查询。 例十二(mysql中的事务): 在数据库管理系统中,事务是一组不可被分割执行的SQL语句集合,如果有必要,可以撤销。一个最简单的例子,银行账号之间的转账,A--->B转账10000元,要在数据库中完成这个操作,必须得实现两个操作,A的账户余额减少10000元,B的账户余额增加10000元(这两个操作缺一不可),但如果完成了第一个操作(即A的账户减少10000元,而B的账户还未增加)之后数据库管理系统出现异常,比如出现服务器宕机,程序异常终止等这些无法预估的意外,这时重启了系统我们就希望能恢复到最开始的状态(即A的账户未减少,B的账户未增加)。这时使用事务就非常的有必要。 mysql的事务支持与服务器本身无关,只与使用的存储引擎有关,在mysql中只有使用了Innodb数据库引擎的数据库或表才支持事务。 事务处理用来维护数据库的完整性,保证一组SQL语句要么全部执行,要么全部不执行。 事务的特点ACID: 事务的原子性(Atomicity):一组事务,要么成功;要么撤回。 稳定性(Consistency) : 有非法数据(外键约束之类),事务撤回。 隔离性(Isolation):事务独立运行。一个事务处理后的结果,影响了其他事务,那么其他事务会撤回。事务的100%隔离,需要牺牲速度。 可靠性(Durability):软、硬件崩溃后,InnoDB数据表驱动会利用日志文件重构修改。可靠性和高速度不可兼得, innodb_flush_log_at_trx_commit选项 决定什么时候吧事务保存到日志里。 使用事务的步骤: 1. 开启事务start transaction,可写为 begin。 2. 然后记录需要执行的一组sql。 3. 提交commit。 4. 如果所有的sql都执行成功,则提交(commit),将sql的执行结果持久化到数据表内,否则回滚(rollback)。 注意: a. 无论回滚还是提交,都会关闭事务!需要再次开启,才能使用。 提交(commit)后,便不能再回滚(rollback)。 b. 还有一点需要注意,就是事务只针对当前连接。 说明:提交与回滚相当与word中的保存与撤销。 # 查寻A表所有数据 mysql> select * from a; +---+ | a | +---+ | 1 | | 2 | | 3 | | 4 | +---+ 4 rows in set (0.01 sec) # 开启一个事务 mysql> begin; Query OK, 0 rows affected (0.00 sec) # 插入数据 mysql> insert into A (a) values(666); Query OK, 1 row affected (0.01 sec) # 查询,数据已加入A表,但还未持久化 mysql> select * from a; +-----+ | a | +-----+ | 1 | | 2 | | 3 | | 4 | | 666 | +-----+ 5 rows in set (0.00 sec) # 回滚 mysql> rollback; Query OK, 0 rows affected (0.01 sec) # 再次查询,因为回滚,刚才的操作被撤销 mysql> select * from a; +---+ | a | +---+ | 1 | | 2 | | 3 | | 4 | +---+ 4 rows in set (0.00 sec) # 开启事务 mysql> begin; Query OK, 0 rows affected (0.00 sec) # 插入数据 mysql> insert into A (a) values(777); Query OK, 1 row affected (0.00 sec) # 提交,将刚刚的操作持久化 mysql> commit; Query OK, 0 rows affected (0.00 sec) # 查询 mysql> select * from a; +-----+ | a | +-----+ | 1 | | 2 | | 3 | | 4 | | 777 | +-----+ 5 rows in set (0.01 sec) 例十三(索引): 为了在大量数据中快速实现查找功能,使用索引是非常有必要的。那么, 在mysql中索引是什么? 在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。 使用索引的利与弊: 使用索引能极大提高数据检索的效率。但是,索引也是一张表(该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占空间),更新数据表时,MySQL不仅要保存数据,还要保存索引文件,索引在大大提高查询速度的同时会降低更新表的速度(对表进行增,改,删操作的速率会降低很多,尤其是在数据量很大的时)。 索引的类型: 单值索引(单列索引):即一个索引只包含单个列,一个表可以有多个单列索引。 唯一索引:索引列的值必须唯一,但允许有空值。 复合索引:即一个索引包含多个列。 # 创建索引sql语句 # 创建索引 CREATE INDEX indexName ON tablename(fieldname(length)); # 创建唯一索引 CREATE UNIQUE INDEX indexName ON tablename(fieldname(length)); # 使用修改表结构的方法添加索引 ALTER tablename ADD INDEX [indexName] ON (fieldname(length)) # 使用修改表结构的方法添加唯一索引 ALTER tablename ADD UNIQUE [indexName] ON (fieldname(length)) 注1:本文对mysql索引的只是一个简单的介绍,若要详细了解索引请参阅mysql官方文档。 注2:一个表的主键(primary key)默认就是索引。
在之前的有关线程,进程的博客中,我们介绍了它们各自在同一个程序中的通信方法。但是不同程序,甚至不同编程语言所写的应用软件之间的通信,以前所介绍的线程、进程队列便不再适用了;此种情况便只能使用socket编程了,然而不同程序之间的通信便不再像线程进程之间的那么简单了,要考虑多种情况(比如其中一方断线另一方如何处理;消息群发,多个程序之间的通信等等),如果每遇到一次程序间的通信,便要根据不同情况编写不同的socket,还要维护、完善这个socket这会使得编程人员的工作量大大增加,也使得程序更易崩溃。所以,一般遇到这种情况,便使用消息队列MQ(Message Queue),那么问题来了。 1. 什么是消息队列MQ? MQ是一种应用程序对应用程序的通信方法。应用程序通过读出(写入)队列的消息(针对应用程序的数据)来通信,而无需使用专用连接来链接它们。消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,排队指的是应用程序通过 队列来通信。队列的使用排除了接收和发送应用程序同时执行的要求。 2. 什么是rabbitmq?如何使用它? RabbitMQ是流行的开源消息队列系统,用erlang语言开发。RabbitMQ是AMQP(高级消息队列协议)的标准实现。 RabbitMQ也是前面所提到的生产者消费者模型,一端发送消息(生产任务),一端接收消息(处理任务)。 rabbitmq的详细使用(包括各种系统的安装配置)可参见其官方文档:http://www.rabbitmq.com/documentation.html 由于应用程序之间的通信情况异常复杂,rabbitmq支持的编程语言有10多种,所以在此博客中不可能完全演示rabbitmq的所有使用。本片博客将会介绍rabbitmq在python中的基本使用,如果你只想使用rabbitmq完成一些简单的任务,则本篇博客足以满足你的需求;如果你想深入学习了解rabbitmq的工作原理,那么读完本篇博客,你可以更容易的读懂rabbitmq的官方文档;当然这些只限于你在使用python编程。 在python中我们使用pika(第三方模块,使用pip安装即可使用)模块进行rabbitmq的操作,接下来,使用python实现一个rabbitmq最简单的通信。 In the diagram below, "P" is our producer and "C" is our consumer. The box in the middle is a queue - a message buffer that RabbitMQ keeps on behalf of the consumer. Our overall design will look like: Producer sends messages to the "hello" queue. The consumer receives messages from that queue. 例一(简单的消息收发): Sending Our first program send.py will send a single message to the queue. The first thing we need to do is to establish a connection with RabbitMQ server. 1 import pika 2 3 connection = pika.BlockingConnection(pika.ConnectionParameters("localhost")) # 建立程序与rabbitmq的连接 4 channel = connection.channel() 5 6 channel.queue_declare(queue='hello') # 定义hello队列 7 channel.basic_publish(exchange='', 8 routing_key='hello', # 告诉rabbitmq将消息发送到hello队列中 9 body='Hello world!') # 发送消息的内容 10 print(" [x] Sent 'Hello World!'") 11 connection.close() # 关闭与rabbitmq的连接 send.py Receiving Our second program receive.py will receive messages from the queue and print them on the screen. 1 import pika 2 import time 3 4 connection = pika.BlockingConnection(pika.ConnectionParameters("localhost")) # 建立程序与rabbitmq的连接 5 channel = connection.channel() 6 7 # 在接收端定义队列,参数与发送端的相同 8 channel.queue_declare(queue='hello') 9 10 def callback(ch, method, properties, body): 11 """ 12 收到消息调用callback处理消息 13 :param ch: 14 :param method: 15 :param properties: 16 :param body: 17 :return: 18 """ 19 print(" [x] received %r" % body) 20 # time.sleep(30) 21 print("Done....") 22 23 channel.basic_consume(callback, 24 queue='hello', # 告诉rabbitmq此程序从hello队列中接收消息 25 no_ack=True) 26 27 # channel.basic_consume(callback, 28 # queue='hello') 29 30 print(' [*] Waiting for messages. To exit press CTRL+C') 31 channel.start_consuming() # 开始接收,未收到消息阻塞 receive.py 注1:我们可以打开time.sleep()的注释(模仿任务处理所需的时间),将no_ack设为默认值(不传参数),同时运行多个receive.py, 运行send.py发一次消息,第一个开始运行的receive.py接收到消息,开始处理任务,如果中途宕机(任务未处理完);那么第二个开始运行的receive.py就会接收到消息,开始处理任务;如果第二个也宕机了,则第三个继续;如果依次所有运行的receive都宕机(任务未处理完)了,则下次开始运行的第一个receive.py将继续接收消息处理任务,这个机制防止了一些必须完成的任务由于处理任务的程序异常终止导致任务不能完成。如果将no_ack设为True,中途宕机,则后面的接收端不会再接收消息处理任务。 注2:如果发送端不停的发消息,则接收端分别是第一个开始运行的接收,第二个开始运行的接收,第三个开始运行接收,依次接收,这是rabbitmq的消息轮循机制(相当于负载均衡,防止一个接收端接收过多任务卡死,当然这种机制存在弊端,就是如果就收端机器有的配置高有的配置低,就会使配置高的机器得不到充分利用而配置低的机器一直在工作)。这一点可以启动多个receive.py,多次运行send.py验证。 上面的例子我们介绍了消息的接收端(即任务的处理端)宕机,我们该如何处理。接下来,我们将重点放在消息的发送端(即服务端),与接收端不同,如果发送端宕机,则会丢失存储消息的队列,存储的消息(要发送给接收端处理的任务),这些信息一旦丢失会造成巨大的损失,所以下面的重点就是消息的持久化,即发送端异常终止,重启服务后,队列,消息都将自动加载进服务里。其实只要将上面的代码稍微修改就可实现。 例二(消息的持久化): Sending: 1 import pika 2 3 connection = pika.BlockingConnection(pika.ConnectionParameters("localhost")) 4 channel = connection.channel() 5 6 channel.queue_declare(queue='task_queue', durable=True) #使队列持久化 7 8 message = "Hello World" 9 channel.basic_publish(exchange='', 10 routing_key='task_queue', 11 body=message, 12 properties=pika.BasicProperties( 13 delivery_mode=2, #使消息持久化 14 )) 15 16 print(" [x] Sent %r" % message) 17 connection.close() new_task.py Receiving: 1 import pika 2 import time 3 4 connection = pika.BlockingConnection(pika.ConnectionParameters("localhost")) 5 channel = connection.channel() 6 7 channel.queue_declare(queue='task_queue', durable=True) #再次申明队列,和发送端参数应一样 8 print(' [*] Waiting for messages. To exit press CTRL+C') 9 10 def callback(ch, method, properties, body): 11 print(" [x] received %r" % body) 12 time.sleep(2) 13 print(" [x] Done") 14 # 因为没有设置no_ask=True, 所以需要告诉rabbitmq消息已经处理完毕,rabbitmq将消息移出队列。 15 ch.basic_ack(delivery_tag=method.delivery_tag) 16 17 #同一时间worker只接收一条消息,等这条消息处理完在接收下一条 18 channel.basic_qos(prefetch_count=1) 19 channel.basic_consume(callback, 20 queue='task_queue') 21 22 23 channel.start_consuming() worker.py 注1:worker.py中的代码如果不设置,则new_task.py意外终止在重启后,worker会同时接收终止前没有处理的所有消息。两个程序中的queue设置的参数要相同,否则程序出错。no_ask=True如果没设置,则worker.py中的ch.basic_ack(delivery_tag=method.delivery_tag)这行代码至关重要,如果不写,则不管接收的消息有没有处理完,此消息将一直存在与队列中。 注2:这句代码---channel.basic_qos(prefetch_count=1),解决了上例中消息轮循机制的代码,即接收端(任务的处理端)每次只接收一个任务(参数为几接收几个任务),处理完成后通过向发送端的汇报(即注1中的代码)来接收下一个任务,如果有任务正在处理中它不再接收新的任务。 前面所介绍的例一,例二都是一条消息,只能被一个接收端收到。那么该如何实现一条消息多个接收端同时收到(即消息群发或着叫广播模式)呢? 其实,在rabbitmq中只有consumer(消费者,即接收端)与queue绑定,对于producer(生产者,即发送端)只是将消息发送到特定的队列。consumer从与自己相关的queue中读取消息而已。所以要实现消息群发,只需要将同一条放到多个消费者队列即可。在rabbitmq中这个工作由exchange来做,它可以设定三种类型,它们分别实现了不同的需求,我们分别来介绍。 例三(exchange的类型为fanout): 当exchange的类型为fanout时,所有绑定这个exchange的队列都会收到发来的消息。 1 import pika 2 import sys 3 4 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 5 channel = connection.channel() 6 7 # 申明一个exchange,两个参数分别为exchange的名字和类型;当exchang='fanout'时,所有绑定到此exchange的消费者队列都将收到消息 8 channel.exchange_declare(exchange='logs', 9 exchange_type='fanout') 10 11 # 消息可以在命令行启动脚本时以参数的形式传入 12 # message = ' '.join(sys.argv[1:]) or "info: Hello World!" 13 message = 'Hello World!' 14 15 channel.basic_publish(exchange='logs', 16 routing_key='', 17 body=message) 18 print(" [x] Sent %r" % message) 19 connection.close() emit_log.py 1 import pika 2 3 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 4 channel = connection.channel() 5 6 channel.exchange_declare(exchange='logs', 7 exchange_type='fanout') 8 9 # 随机生成一个queue,此queue唯一,且在连接端开后自动销毁 10 result = channel.queue_declare(exclusive=True) 11 # 得到随机生成消费者队列的名字 12 queue_name = result.method.queue 13 14 # 将消费者队列与exchange绑定 15 channel.queue_bind(exchange='logs', 16 queue=queue_name) 17 18 print(' [*] Waiting for logs. To exit press CTRL+C') 19 20 def callback(ch, method, properties, body): 21 print(" [x] received %r" % body) 22 23 channel.basic_consume(callback, 24 queue=queue_name, 25 no_ack=True) 26 27 channel.start_consuming() receive_logs.py 注1:emit_log.py为消息的发送端,receive_logs.py为消息的接收端。可以同时运行多个receive_logs.py,当emit_log.py发送消息时,可以发现所有正在运行的receive_logs.py都会收到来自发送端的消息。 注2:类似与广播,如果消息发送时,接收端没有运行,那么它将不会收到此条消息,即消息的广播是即时的。 例四(exchange的类型为direct): 当exchange的类型为direct时,发送端和接收端都要指明消息的级别,接收端只能接收到被指明级别的消息。 1 import pika 2 import sys 3 4 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 5 channel = connection.channel() 6 7 channel.exchange_declare(exchange='direct_logs', 8 exchange_type='direct') 9 10 # 命令行启动时,以参数的的形式传入发送消息的级别,未传怎默认设置未info 11 # severity = sys.argv[1] if len(sys.argv) > 2 else 'info' 12 # 命令行启动时,以参数的的形式传入发送消息的内容,未传怎默认设置Hello World! 13 # message = ' '.join(sys.argv[2:]) or 'Hello World!' 14 15 # 演示使用,实际运用应用上面的方式设置消息级别 16 severity = 'info' #作为例子直接将消息的级别设置为info 17 # severity = 'warning' 18 message = 'Hello World' 19 20 #使用exchang的direct模式时,routing_key的值为消息的级别 21 channel.basic_publish(exchange='direct_logs', 22 routing_key=severity, 23 body=message) 24 print(" [x] Sent %r:%r" % (severity, message)) 25 connection.close() emit_log_direct.py 1 #!/usr/bin/env python 2 import pika 3 import sys 4 5 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 6 channel = connection.channel() 7 8 channel.exchange_declare(exchange='direct_logs', 9 exchange_type='direct') 10 11 result = channel.queue_declare(exclusive=True) 12 queue_name = result.method.queue 13 14 # 命令行启动时以参数的形式传入要接收哪个级别的消息,可以传入多个级别 15 # severities = sys.argv[1:] 16 17 # 演示使用,实际运用应该用上面的方式指明消息级别 18 # 作为演示,直接设置两个接收级别,info 和 warning 19 severities = ['info', 'warning'] 20 21 if not severities: 22 """如果要接收消息的级别不存在则提示用户输入级别并退出程序""" 23 sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0]) 24 sys.exit(1) 25 26 for severity in severities: 27 """依次为每个消息级别绑定queue""" 28 channel.queue_bind(exchange='direct_logs', 29 queue=queue_name, 30 routing_key=severity) 31 32 print(' [*] Waiting for logs. To exit press CTRL+C') 33 34 def callback(ch, method, properties, body): 35 print(" [x] %r:%r" % (method.routing_key, body)) 36 37 channel.basic_consume(callback, 38 queue=queue_name, 39 no_ack=True) 40 41 channel.start_consuming() receive_logs_direct 注1:exchange_type=direct时,rabbitmq按消息级别发送和接收消息,接收端只能接收被指明级别的消息,其他消息,即时是由同一个发送端发送的也无法接收。当在接收端传入多个消息级别时,应逐个绑定消息队列。 注2:exchange_type=direct时,同样是广播模式,也就是如果给多个接收端指定相同的消息级别,它们都可以同时收到这一级别的消息。 例三(exchange的类型为topic): 当exchange的类型为topic时,在发送消息时,应指明消息消息的类型(比如mysql.log、qq.info等),我们可以在接收端指定接收消息类型的关键字(即按关键字接收,在类型为topic时,这个关键字可以是一个表达式)。 1 import pika 2 import sys 3 4 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 5 channel = connection.channel() 6 7 channel.exchange_declare(exchange='topic_logs', 8 exchange_type='topic') 9 10 # 以命令行的方式启动发送端,以参数的形式传入发送消息类型的关键字 11 routing_key = sys.argv[1] if len(sys.argv[1]) > 2 else 'anonymous.info' 12 # routing_key = 'anonymous.info' 13 # routing_key = 'abc.orange.abc' 14 # routing_key = 'abc.abc.rabbit' 15 # routing_key = 'lazy.info' 16 17 message = ' '.join(sys.argv[2:]) or 'Hello World!' 18 channel.basic_publish(exchange='topic_logs', 19 routing_key=routing_key, 20 body=message) 21 print(" [x] Sent %r:%r" % (routing_key, message)) 22 connection.close() emit_log_topic.py 1 #!/usr/bin/env python 2 import pika 3 import sys 4 5 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 6 channel = connection.channel() 7 8 channel.exchange_declare(exchange='topic_logs', 9 exchange_type='topic') 10 11 result = channel.queue_declare(exclusive=True) 12 queue_name = result.method.queue 13 14 binding_keys = sys.argv[1:] 15 # binding_keys = '#' #接收所有的消息 16 # binding_keys = ['*.info'] #接收所有以".info"结尾的消息 17 # binding_keys = ['*.orange.*'] #接收所有含有".orange."的消息 18 # binding_keys = ['*.*.rabbit', 'lazy.*'] #接收所有含有两个扩展名且结尾是".rabbit"和所有以"lazy."开头的消息 19 20 if not binding_keys: 21 sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0]) 22 sys.exit(1) 23 24 for binding_key in binding_keys: 25 channel.queue_bind(exchange='topic_logs', 26 queue=queue_name, 27 routing_key=binding_key) 28 29 print(' [*] Waiting for logs. To exit press CTRL+C') 30 31 def callback(ch, method, properties, body): 32 print(" [x] %r:%r" % (method.routing_key, body)) 33 34 channel.basic_consume(callback, 35 queue=queue_name, 36 no_ack=True) 37 38 channel.start_consuming() receive_logs_topic.py 注:当exchange的类型为topic时,发送端与接收端的代码都跟类型为direct时很像(基本只是变一个类型,如果接收消息类型的指定不用表达式,它们几乎一样),但是topic的应用场景更广。 注:rabbitmq指定消息的类型的表达式其实很简单: '#':代表接收所有的消息(一般单独使用),使用它相当于exchang的类型为fanout。 '*':代表任意一个字符(一般与其他单词配合使用)。 不使用'#'或'*',使用它相当于exchang的类型为direct。 前面介绍的都是一端发送,一端接收的消息传递模式,那么rabbitmq该如何实现客户端和服务端都要发送和接收(即RPC)呢? 我们先来简单了解以下RPC,RPC(Remote Procedure Call)采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。 例五(通过rabbitmq实现rpc): 先来看以下在rabbitmq中rpc的消息传递模式: 我们以客户端发送一个数字n,服务端计算出斐波那契数列的第n个数的值返回给客户端为例。 1 import pika 2 3 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 4 channel = connection.channel() 5 channel.queue_declare(queue='rpc_queue') 6 7 def fib(n): 8 """ 9 计算斐波那契数列中第n个数的值 10 :param n: 11 :return: 12 """ 13 if n == 0: 14 return 0 15 elif n == 1: 16 return 1 17 else: 18 return fib(n-1) + fib(n-2) 19 20 def on_request(ch, method, props, body): 21 n = int(body) 22 23 print(" [.] fib(%s)" % n) 24 response = fib(n) 25 26 ch.basic_publish(exchange='', 27 routing_key=props.reply_to, # 使用客户端传来的队列向客户端发送消息的处理结果 28 properties=pika.BasicProperties( 29 correlation_id = props.correlation_id), # 指明处理消息的id 用于客户端确认 30 body=str(response)) 31 ch.basic_ack(delivery_tag = method.delivery_tag) # 未申明no_ack = True, 消息处理完毕需向rabbitmq确认 32 33 34 35 channel.basic_qos(prefetch_count=1) # 每次只处理一条消息 36 channel.basic_consume(on_request, queue='rpc_queue') 37 38 print(" [x] Awaiting RPC requests") 39 channel.start_consuming() # 开始接收消息,未收到消息处于阻塞状态 rpc_server.py 1 import pika 2 import uuid 3 import time 4 5 class FibonacciRpcClient(object): 6 def __init__(self): 7 self.connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 8 9 self.channel = self.connection.channel() 10 11 result = self.channel.queue_declare(exclusive=True) 12 self.callback_queue = result.method.queue # 此队列用于接收从服务端返回的结果 13 14 self.channel.basic_consume(self.on_response, # 写明接收消息后的回调函数 15 no_ack=True, 16 queue=self.callback_queue) # 写明接收消息所使用的队列 17 18 def on_response(self, ch, method, props, body): 19 """ 20 接收消息完毕后执行的回调函数 21 :param ch: 22 :param method: 23 :param props: 24 :param body: 25 :return: 26 """ 27 if self.corr_id == props.correlation_id: # 确认返回的结果是本条消息(任务)的结果 28 self.response = body # 保存服务端的执行结果 29 30 def call(self, n): 31 """ 32 对外接口,传入要服务端接收的消息(任务) 33 :param n: 34 :return: 35 """ 36 self.response = None 37 self.corr_id = str(uuid.uuid4()) # 用与标识每次发送出去的消息与接收到的结果对应 这个id唯一 38 self.channel.basic_publish(exchange='', 39 routing_key='rpc_queue', 40 properties=pika.BasicProperties( 41 reply_to = self.callback_queue, # 发送客户端用于接收消息的队列名 42 correlation_id = self.corr_id, # 发送标识消息的uuid 43 ), 44 body=str(n)) # 发送消息的内容 45 while self.response is None: # 一旦接收到此条消息的结果,便不再循环 46 self.connection.process_data_events() # 相当于channel.start_consuming(),但无阻塞 47 print("no message...") 48 time.sleep(1) # 每隔1秒检测一次结果,在实际应用中这段时间可用于执行其他任务 49 50 return int(self.response) # 返回消息(任务)的结果 51 52 fibonacci_rpc = FibonacciRpcClient() 53 54 print(" [x] Requesting fib(10)") 55 response = fibonacci_rpc.call(10) 56 print(" [.] Got %r" % response) rpc_client.py 注1:测试时,先运行rpc_server.py,再运行rpc_client.py。 注2:客户端之所以每隔一秒检测一次服务端有没有返回结果,是因为客户端接收时时无阻塞的,在这一端时间内(不一定是1秒,但执行的任务消耗的时间不要太长)客户端可以执行其他任务提高效率。 注3:为什么客户端和服务端不使用一个队列来传递消息? 答:如果使用一个队列,以客户端为例,它一边在检测这个队列中有没有它要接收的消息,一边又往这个队列里发送消息,会形成死循环。 (PS:本文例中出现的所有代码是做了一些简单修改(方便读者理解)后的rabbitmq官方文档中的代码。)
1. 什么是协程? 协程(coroutine),又称微线程。协程不是线程也不是进程,它的上下文关系切换不是由CPU控制,一个协程由当前任务切换到其他任务由当前任务来控制。一个线程可以包含多个协程,对于CPU而言,不存在协程这个概念,它是一种轻量级用户态线程(即只针对用户而言)。协程拥有自己的寄存器上下文和栈,协程调度切换到其他协程时,将寄存器上下文和栈保存,在切回到当前协程的时候,恢复先前保存的寄存器上下文和栈。 2. 在编程中为什么要使用协程? 使用协程的好处:(1)CPU无需负担上下文的开销;(2)不需加锁(多个线程操作数据时得加锁);(3)由程序员切换控制流,方便编程;(4)高并发、高扩展、低成本(一个CPU支持上万的协程都不是问题)。 当然,任何事物有优点必有缺点。协程得缺点:(1)协程自己无法利用CPU多核资源(除非与多进程或者多线程配合);(2)遇到阻塞操作会使整个程序阻塞。 例一(使用yield实现在任务间的切换): 1 import time 2 3 def func1(name): 4 print("----func1 start...----") 5 for i in range(6): 6 temp = yield #每次遇到yield,func1在此处阻塞,直到temp接收到func2中con.send()传来的值 7 print("%s in the func1" % (str(temp))) 8 time.sleep(1) 9 10 11 def func2(): 12 print("----func2 start...----") 13 con.__next__() #此处开始真正的func1的调用 14 for i in range(5): 15 con.send(i+1) 16 print("%s in the func2" % i) 17 18 19 if __name__ == '__main__': 20 con = func1(1) #在有yield的函数中此处不是真正的函数调用,打印con便可知道 21 # print(con) 22 p = func2() 使用yield进行任务切换 注:例一严格来说不能算是协程,只是实现了两个任务之间的切换。 3. 既然例一不能算多协程,难么在python中应该如何使用协程? greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator(例一中的con=func1(1)就是做这个操作)。 例二: 1 import greenlet 2 3 def func1(): 4 for i in range(1,6): 5 print(i) 6 g2.switch() #切换到g2 7 8 def func2(): 9 words = ['a', 'b', 'c', 'd', 'e'] 10 for w in words: 11 print(w) 12 g1.switch() #切换到g1 13 14 g1 = greenlet.greenlet(func1) 15 g2 = greenlet.greenlet(func2) 16 g1.switch() #切换到g1 使用greenlent模块实现任务切换 注:使用greenlent可以很简单的进行多任务之间的切换,但是程序运行最耗时的便是I/O操作,要使用协程实现高并发,应当是一旦遇到I/O操作就切换到其他任务,等I/O操作完成后在切回到当前任务(这个过程应当是自动的)。 4. 那么在python中,如何让任务遇到I/O操作就切换? 我们使用第三方库gevent来实现。 gevent的官方定义:gevent is a coroutine -based Python networking library that uses greenlet to provide a high-level synchronous API on top of the libev event loop. 例三(gevent的简单使用): 1 import gevent 2 import time 3 4 def func1(): 5 print("func1: start.....") 6 # Put the current greenlet to sleep for at least *seconds*.(模拟I/O操作,任务在此处自动切换) 7 gevent.sleep(3) 8 print("func1: end") 9 10 def func2(): 11 print("func2: start.....") 12 gevent.sleep(0.5) 13 print("func2: end") 14 15 start_time = time.time() 16 # joinall(greenlets, timeout=None, raise_error=False, count=None) 17 # Wait for the ``greenlets`` to finish. 18 # :return: A sequence of the greenlets that finished before the timeout (if any)expired. 19 gevent.joinall([gevent.spawn(func1), 20 gevent.spawn(func2)]) 21 # spawn(cls, *args, **kwargs) 22 # Create a new :class:`Greenlet` object and schedule it to run ``function(*args, **kwargs)``. 23 # This can be used as ``gevent.spawn`` or ``Greenlet.spawn``. 24 25 print("cost:", time.time()-start_time) 26 # 通过计算程序运行的时间可以发现程序确实是以单线程达模拟出了多任务并行的操作。 gevent的简单使用 例四(gevent和urllib配合同时下载多个网页): 1 import urllib.request 2 import gevent,time 3 import gevent.monkey 4 5 def func(url="", filename=""): 6 print("Download:%s" % url) 7 result = urllib.request.urlopen(url) #请求打开一个网页 8 data = result.read() #读取内容 9 with open(filename, 'wb') as fp: #写入文档 10 fp.write(data) 11 print("Finish:%s" % url) 12 13 if __name__ == "__main__": 14 # Do all of the default monkey patching (calls every other applicablefunction in this module). 15 # 相当与做一个标记,做完此操作gevent就可以检测到此程序中所有的I/O操作 16 gevent.monkey.patch_all() 17 18 async_time = time.time() 19 gevent.joinall([ 20 gevent.spawn(func, "http://www.cnblogs.com/God-Li/p/7774497.html", "7774497.html"), 21 gevent.spawn(func, "http://www.gevent.org/", "gevent.html"), 22 gevent.spawn(func, "https://www.python.org/", "python.html"), 23 ]) 24 print("async download cost:", time.time()-async_time) 25 26 start_time = time.time() 27 func("http://www.cnblogs.com/God-Li/p/7774497.html", "7774497.html") 28 func("http://www.gevent.org/", "gevent.html") 29 func("https://www.python.org/", "python.html") 30 print("download cost:", time.time()-start_time) gevent和urllib配合同时下载多个网页 注:对上例代码稍加改造,加上对html源码的解析功能,就可以实现一个简单的多并发爬虫。 对python --- 网络编程Socket中例二的socket_server2使用gevent改造就可以使其成为一个大并发的socket server。 例五(使用gevent实现并发的socket server): 1 #服务端 2 import socket 3 import gevent 4 import gevent.monkey 5 6 gevent.monkey.patch_all() 7 8 def request_handler(conn): 9 10 ''' 11 Wait for an incoming connection. Return a new socket 12 representing the connection, and the address of the client. 13 ''' 14 while True: 15 # print("ok") 16 data = conn.recv(1024) #接收信息,写明要接收信息的最大容量,单位为字节 17 print("server recv:", data) 18 conn.send(data.upper()) #对收到的信息处理,返回到客户端 19 20 21 22 if __name__ == "__main__": 23 address = ("localhost", 6666) # 写明服务端要监听的地址,和端口号 24 server = socket.socket() # 生成一个socket对象 25 server.bind(address) # 用socket对象绑定要监听的地址和端口 26 server.listen() # 开始监听 27 28 while True: 29 conn, addr = server.accept() # 等带新连接接入服务端,返回一个新的socket对象和地址,地址格式同前面格式 30 gevent.spawn(request_handler, conn) 31 32 server.close() # 关闭服务端 socket_server2的并发实现 注:可使用python --- 网络编程Socket中例二的socket_client2进行测试。
1. 什么是进程? 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。 一个进程至少包含一个线程。 2. 在python中有了多线程编程为何还需要多进程编程? 在python中由于有GIL(全局解释器锁)的存在,在任一时刻只有一个线程在运行(无论你的CPU是多少核),无法实现真正的多线程。那么该如何让python程序真正的并行运行呢?答案就是不要使用多线程,使用多进程。python标准库提供了multiprocessing模块(multiprocessing是一个和threading模块类似,提供API,生成进程的模块。multiprocessing包提供本地和远程并发,通过使用子进程而不是线程有效地转移全局解释器锁。),它的API几乎复制了threading模块的API,当然它还有一行threading模块没有的API。 例一(multiprocessing模块的简单使用): 1 import multiprocessing,time 2 3 class Task(multiprocessing.Process): 4 def __init__(self): 5 super(Task, self).__init__() 6 7 def run(self): 8 print("Process---%s" % self.name) 9 time.sleep(2) 10 11 12 if __name__ == "__main__": 13 for i in range(1, 8+1): 14 t = Task() 15 t.start() View Code 注:由于multiprocessing模块基本的API同threading模块,就不挨个演示了,本文主要讲解multiprocessing模块不同于threading模块的API的使用。要了解其他同threading模块相同的API的使用,可参见:http://www.cnblogs.com/God-Li/p/7732407.html multiprocessing.Process源码: class Process(object): def __init__(self, group=None, target=None, name=None, args=(), kwargs={}): self.name = '' self.daemon = False #守护进程标志,必须在start()之前设置 self.authkey = None #The process’s authentication key (a byte string). self.exitcode = None #The child’s exit code. This will be None if the process has not yet terminated. A negative value -N indicates that the child was terminated by signal N. self.ident = 0 self.pid = 0 #进程ID。在生成进程之前,这将是Non。 self.sentinel = None #A numeric handle of a system object which will become “ready” when the process ends. def run(self): pass def start(self): pass def terminate(self): """ Terminate the process. On Unix this is done using the SIGTERM signal; on Windows TerminateProcess() is used. Note that exit handlers and finally clauses, etc., will not be executed. Note that descendant processes of the process will not be terminated – they will simply become orphaned. :return: """ pass def join(self, timeout=None): pass def is_alive(self): return False multiprocessing模块中的队列: class multiprocessing.Queue([maxsize])实现除task_done()和join()之外的queue.Queue的所有方法,下面列出queue.Queue中没有的方法: class multiprocessing.Queue([maxsize]) close() """ 指示当前进程不会在此队列上放置更多数据。 The background thread will quit once it has flushed all buffered data to the pipe. 当队列被垃圾回收时,这被自动调用。 """ join_thread() """ 加入后台线程。这只能在调用close()之后使用。它阻塞直到后台线程退出,确保缓冲区中的所有数据都已刷新到pipe。 默认情况下,如果进程不是队列的创建者,那么在退出时它将尝试加入队列的后台线程。 该进程可以调用cancel_join_thread()使join_thread()不执行任何操作 """ cancel_join_thread() """ 使join_thread()不执行任何操作 """ class multiprocessing.SimpleQueue是class multiprocessing.Queue([maxsize])的简化,只有三个方法------empty(), get(), put() class multiprocessing.JoinableQueue([maxsize])是class multiprocessing.Queue([maxsize])的子类,增加了take_done()和join()方法 注:由于进程之间内存空间不共享,所以必须将实例化后的queue对象当作参数传入其他进程,其他进程才能使用。而且,每传入一次相当于克隆一份,与原来的queue独立,只是python会同步queue中的数据,而不是像在多线程的queue数据只有一份。 进程之间的通信: multiprocessing.Pipe([duplex]) --------------- 返回表示管道末端的Connection对象(类似与socket中的连接可用于发送和接收数据)的(conn1, conn2)。 如果duplex是True(默认值),则管道是双向的。如果duplex是False,则管道是单向的:conn1只能用于接收消息,conn2用于发送消息。 例二(multiprocessing.Pipe使用演示): 1 import multiprocessing,time 2 3 class Processing_1(multiprocessing.Process): 4 def __init__(self, conn): 5 super(Processing_1, self).__init__() 6 self.conn = conn 7 def run(self): 8 send_data = "this message is from p1" 9 self.conn.send(send_data) #使用conn发送数据 10 time.sleep(0.8) 11 recv_data = self.conn.recv() #使用conn接收数据 12 print("p1 recv: " + recv_data) 13 self.conn.close() 14 15 16 class Processing_2(multiprocessing.Process): 17 def __init__(self, conn): 18 super(Processing_2, self).__init__() 19 self.conn = conn 20 21 def run(self): 22 send_data = "this message is from p2" 23 self.conn.send(send_data) 24 time.sleep(0.8) 25 recv_data = self.conn.recv() 26 print("p2 recv: " + recv_data) 27 self.conn.close() 28 29 if __name__ == "__main__": 30 conn1, conn2 = multiprocessing.Pipe() #实例化Pipe对象,conn1, conn2分别代表连接两端 31 32 p1 = Processing_1(conn1) #将连接对象当作参数传递给子进程 33 p2 = Processing_2(conn2) 34 35 p1.start() 36 p2.start() 37 38 p1.join() 39 p2.join() multiprocessing.Pipe使用演示 进程之间的数据共享: multiprocessing.Manager() ----------- 返回开始的SyncManager对象,可用于在进程之间共享对象。返回的管理器对象对应于生成的子进程,并且具有将创建共享对象并返回相应代理的方法。管理器进程将在垃圾收集或其父进程退出时立即关闭。 例三(Manager的简单使用): 1 import multiprocessing,time 2 import os 3 4 class Processing(multiprocessing.Process): 5 def __init__(self, d, l): 6 super(Processing, self).__init__() 7 self.d = d 8 self.l = l 9 10 def run(self): 11 self.d[os.getpid()] = os.getpid() #当作正常dict使用即可 12 self.l.append(1) 13 print(self.l) 14 15 if __name__ == "__main__": 16 17 manager = multiprocessing.Manager() #生成Manager 对象 18 d = manager.dict() #生成共享dict 19 l = manager.list() #生成共享list 20 21 p_s = [] 22 for i in range(10): 23 p = Processing(d, l) 24 p.start() 25 p_s.append(p) 26 27 for p in p_s: 28 p.join() 29 30 print(d) 31 print(l) Manager简单使用 manager可以生成以下共享数据对象(常用): Event() Create a shared threading.Event object and return a proxy for it. Lock() Create a shared threading.Lock object and return a proxy for it. Namespace() Create a shared Namespace object and return a proxy for it. Queue([maxsize]) Create a shared queue.Queue object and return a proxy for it. RLock() Create a shared threading.RLock object and return a proxy for it. Semaphore([value]) Create a shared threading.Semaphore object and return a proxy for it. Array(typecode, sequence) Create an array and return a proxy for it. Value(typecode, value)¶ Create an object with a writable value attribute and return a proxy for it. dict() dict(mapping) dict(sequence) Create a shared dict object and return a proxy for it. list() list(sequence) Create a shared list object and return a proxy for it. 进程锁: 进程锁有两种multiprocessing.Lock(非递归锁)和multiprocessing.RLock(递归锁)。 multiprocessing.Lock(非递归锁):一旦进程或线程获得了锁,随后从任何进程或线程获取它的尝试将阻塞,直到它被释放;任何进程或线程都可以释放它。 multiprocessing.RLock(递归锁): A recursive lock must be released by the process or thread that acquired it. Once a process or thread has acquired a recursive lock, the same process or thread may acquire it again without blocking; that process or thread must release it once for each time it has been acquired. 这两种锁都只用两种方法:acquire(block=True, timeout=None)和release(),它们的使用基本和线程锁类似(只不是要把锁的示例对象当作参数传入其他的进程):http://www.cnblogs.com/God-Li/p/7732407.html 进程池: 为了便于对多进程的管理,通常使用进程池来进行多进程编程(而不是使用multiprocessing.Process)。 例: 1 import multiprocessing,os 2 import time 3 4 5 def run(): 6 print(str(os.getpid()) + "-----running") 7 time.sleep(2) 8 print(str(os.getpid()) + "-----done") 9 10 def done(): 11 print("done") 12 13 def error(): 14 print("error") 15 16 if __name__ == "__main__": 17 pool = multiprocessing.Pool(processes=4) #实力化进程池对象 18 19 for i in range(8): 20 # pool.apply(func=run) #进程池中的进程串行运行 21 pool.apply_async(func=run) 22 23 pool.close() 24 pool.join() 25 print("finish....") View Code Pool对象常用方法: apply(func[, args[, kwds]]) Call func with arguments args and keyword arguments kwds. It blocks until the result is ready. Given this blocks, apply_async() is better suited for performing work in parallel. Additionally, func is only executed in one of the workers of the pool. 将任务提交到进程池,只有一个进程在工作,其他进程处于阻塞状态(相当于串行运行)。 apply_async(func[, args[, kwds[, callback[, error_callback]]]]) A variant of the apply() method which returns a result object. If callback is specified then it should be a callable which accepts a single argument. When the result becomes ready callback is applied to it, that is unless the call failed, in which case the error_callback is applied instead. If error_callback is specified then it should be a callable which accepts a single argument. If the target function fails, then the error_callback is called with the exception instance. Callbacks should complete immediately since otherwise the thread which handles the results will get blocked. 将任务提交到进程池,多个进程(进程数量由之前实例化时的processes参数设置)同时运行,callback工作进程完成时(由当前进程的父进程)调用由此传入的任务,error_callback工作进程出错时(由当前进程的父进程)调用由此传入的任务。 close() Prevents any more tasks from being submitted to the pool. Once all the tasks have been completed the worker processes will exit. 调用此方法后进程池不能在提交新的任务 terminate() Stops the worker processes immediately without completing outstanding work. When the pool object is garbage collected terminate() will be called immediately. 立即停止工作进程,而不需要等待未完成的工作进程。 join() Wait for the worker processes to exit. One must call close() or terminate() before using join(). 等待进程池中的工作进程结束(在此之前必须调用close()或者terminate())。 注:Pool对象在生成时进程内的进程(阻塞)就已经启动,使用apply(或者apply_async)方法只是将任务提交给线程池,不会再建立新进程。
"Paramiko" is a combination of the Esperanto words for "paranoid" and "friend". It's a module for Python 2.7/3.4+ that implements the SSH2 protocol for secure (encrypted and authenticated) connections to remote machines. Unlike SSL (aka TLS), SSH2 protocol does not require hierarchical certificates signed by a powerful central authority. You may know SSH2 as the protocol that replaced Telnet and rsh for secure access to remote shells, but the protocol also includes the ability to open arbitrary channels to remote services across the encrypted tunnel (this is how SFTP works, for example). paramiko是一个远程控制模块,使用它可以很容易的再python中使用SSH执行命令和使用SFTP上传或下载文件;而且paramiko直接与远程系统交互,无需编写服务端。 例一(实现一个简单的SSH客户端): 1 import paramiko 2 3 #实例化一个ssh客户端实例 4 ssh = paramiko.SSHClient() 5 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 6 #连接远程 --- 填入要连接的地址,端口,用户,密码 7 ssh.connect(hostname="192.168.56.50", port=22, username='libin', password='123456') 8 9 while True: 10 command = input(">>>:") 11 12 #exec_command()返回三个值:the stdin, stdout, and stderr of the executing command, as a 3-tuple 13 stdin, stdout, stderr = ssh.exec_command(command) 14 15 result = stdout.read() 16 error = stderr.read() 17 18 if result: 19 print(result.decode()) 20 else: 21 print(error.decode()) 22 #关闭连接 23 ssh.close() ssh_client 例二(实现文件的上传和下载操作): 1 import paramiko 2 3 transport = paramiko.Transport('192.168.56.50', 22) 4 transport.connect(username='libin', password='123456') 5 #实例化一个SFTP客户端实例 6 sftp = paramiko.SFTPClient.from_transport(transport) 7 8 #put('localpath', 'remotepath')上传本地文件至服务器 9 #sftp.put(r'E:\tempdownload\Sau_Authentication_Client_For_Windows_V6.82.exe', '/tmp/abc.exe') 10 #get('remotepath', 'localpath')将远程文件下载至本地 11 sftp.get('/tmp/abc.exe', r'E:\tempdownload\abc.exe') 12 13 transport.close() sftp_client 为了连接安全,我们可以对上述两例稍加改变,使其使用密钥认证建立连接。 例四(以密钥认证实现SSH): 1 import paramiko 2 3 #实例化一个ssh客户端实例 4 ssh = paramiko.SSHClient() 5 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 6 #连接远程 --- 填入要连接的地址,端口,用户,密码 7 8 #导入私钥文件 9 private_key = paramiko.RSAKey.from_private_key_file('id_rsa_2048') 10 #连接远程 11 ssh.connect(hostname="192.168.56.50", port=22, username='libin', pkey=private_key) #无需再填入用户密码 12 13 while True: 14 command = input(">>>:") 15 16 #exec_command()返回三个值:the stdin, stdout, and stderr of the executing command, as a 3-tuple 17 stdin, stdout, stderr = ssh.exec_command(command) 18 19 result = stdout.read() 20 error = stderr.read() 21 22 if result: 23 print(result.decode()) 24 else: 25 print(error.decode()) 26 #关闭连接 27 ssh.close() key_ssh_client 例五(以密钥认证实现SFTP): 1 import paramiko 2 3 transport = paramiko.Transport('192.168.56.50', 22) 4 #导入私钥文件 5 privat_key = paramiko.RSAKey.from_private_key_file('id_rsa_2048') 6 transport.connect(username='libin', pkey=privat_key) 7 #实例化一个SFTP客户端实例 8 sftp = paramiko.SFTPClient.from_transport(transport) 9 10 #put('localpath', 'remotepath')上传本地文件至服务器 11 sftp.put(r'E:\tempdownload\test.exe', '/tmp/123.exe') 12 13 # # get('remotepath', 'localpath')将远程文件下载至本地 14 # sftp.get('/tmp/abc.exe', r'E:\tempdownload\abc.exe') 15 transport.close() key_sftp_client
1. 什么是队列? 学过数据结构的人都知道,如果不知道队列,请Google(或百度)。 2. 在python中什么是多生产者,多消费模型? 简单来说,就是一边生产(多个生产者),一边消费(多个消费者)。比如,一边有m个线程生产数据,另一边有n个线程消费(使用)数据,这就是多生产者,多消费者模型。 注:消费依赖生产(没有厂家生产产品,就不会有关于这种产品的消费),在python中,如果生产者线程没有产生数据,那么消费者线程有关于消费行为的操作就应当处于阻塞状态。 3. 在python中为什么有queue这个标准模块?它有什么用? 用threading.Lock Objects或其他的锁来完成上一问的需求会很复杂,所以queue这个模块简化了这些操作。 queue模块实现多生产者,多消费者队列。它特别适用于信息必须在多个线程间安全地交换的多线程程序中。该模块中的Queue类实现了所有需要的锁定语义。 queue模块实现了三类队列,主要差别在于取得数据的顺序上。在FIFO(First In First Out,先进先出)队列中,最早加入的任务会被最先得到。在LIFO(Last In First Out,后进先出)队列中,最后加入的任务会被最先得到(就像栈一样)。在优先队列中,任务被保持有序(使用heapq模块),拥有最小值的任务(优先级最高)被最先得到。 queue模块提供的方法: 1 Queue.empty() 2 """ 3 如果队列为空,返回True,否则返回False。如果empty()返回True,它不保证后续调用put()不会阻塞。类似的,如果empty()返回False也不能保证接下来的get()调用不会被阻塞。 4 """ 5 6 Queue.full() 7 """ 8 如果队列已满,则返回True,否则返回False。如果full()返回True,它不保证后续调用get()不会阻塞。类似的,如果full()返回False并不能保证接下来的put()调用不会被阻塞。 9 """ 10 11 Queue.qsize() 12 """ 13 返回队列的近似大小。注意,qsize()> 0不保证随后的get()不会阻塞,qsize() < maxsize也不会保证put()不会被阻塞。 14 """ 15 16 Queue.put(item, block=True, timeout=None) 17 """ 18 将item放入队列中。如果可选的参数block为True且timeout为None(默认的情况,阻塞调用,无超时),如有必要(比如队列满),阻塞调用线程,直到有空闲槽可用。如果超时是正数,则它最多阻塞超时秒,如果在该时间内没有空闲插槽,则引发Full异常。如果block为False,如果有空闲槽可用将数据放入队列,否则立即抛出Full异常(非阻塞调用,timeout被忽略)。 19 """ 20 21 Queue.get(block=True, timeout=None) 22 """ 23 从队列中移除并返回一个数据。如果可选的参数block为True且timeout为None(默认的情况,阻塞调用,无超时),阻塞调用进程直到有数据可用。如果超时是正数,则它最多阻塞超时秒,如果在该时间内没有可用的项,则引发Empty异常。如果block为False,如果有数据可用返回数据,否则立即抛出Empty异常(非阻塞调用,timeout被忽略)。 24 """ 25 26 Queue.put_nowait(item) 27 """ 28 等同于put(item, block=False)(非阻塞调用)。 29 """ 30 31 Queue.get_nowait() 32 """ 33 等同于get(block=False)(非阻塞调用)。 34 """ 35 36 Queue.task_done() 37 """ 38 意味着之前入队的一个任务已经完成。由队列的消费者线程调用。每一个get()调用得到一个任务,接下来的task_done()调用告诉队列该任务已经处理完毕。 39 40 If a join() is currently blocking, it will resume when all items have been processed (meaning that a task_done() call was received for every item that had been put() into the queue). 41 42 如果该方法被调用的次数多于被放入队列中的任务的个数,ValueError异常会被抛出。 43 """ 44 45 Queue.join() 46 """ 47 阻塞调用线程,直到队列中的所有任务被处理掉。 48 49 只要有数据被加入队列,未完成的任务数就会增加。当消费者线程调用task_done()以指示该项目已检索并且其上的所有工作都已完成时,计数将减少。当未完成的任务数降到0,join()解除阻塞。 50 """ Queue对象(Queue、LifoQueue和PriorityQueue)提供了下述的公共方法 注:由于同一进程的线程之间内存空间是共享的,故在同一进程的任一线程中定义的Queue对象,在该进程的任一线程都可以使用。 例(演示queue模块的使用): 1 import queue,threading 2 import random,time 3 4 class Producter(threading.Thread): 5 def __init__(self, name): 6 super(Producter, self).__init__() 7 self.name = "Producter:" + str(name) 8 9 def run(self): 10 things = ['A', 'B', 'C'] 11 for i in range(3): 12 production = random.choice(things) 13 print(self.name + " producted--->" + production) 14 production = production + " from " + self.name 15 all_productions.put(production) #将生产的数据放入队列 16 time.sleep(1) 17 time.sleep(5) 18 19 class Consumer(threading.Thread): 20 def __init__(self, name): 21 super(Consumer, self).__init__() 22 self.name = "Consumer:" + str(name) 23 24 def run(self): 25 for i in range(2): 26 thing = all_productions.get() #拿出已经生产好的数据 27 print(self.name + " is using--->" + thing) 28 time.sleep(2) 29 all_productions.task_done() #告诉队列有关这个数据的任务已经完成 30 time.sleep(5) 31 32 if __name__ == "__main__": 33 all_productions = queue.Queue() 34 35 #启动两个生产者线程生 36 p_s = [] 37 for i in range(2): 38 p = Producter(i) 39 p.start() 40 p_s.append(p) 41 42 #启动三个消费者线程消费 43 c_s = [] 44 for i in range(3): 45 c = Consumer(i) 46 c.start() 47 c_s.append(c) 48 49 #阻塞,直到生产者生产的数据被消耗完 50 all_productions.join() 51 #等待生产者线程结束 52 for p in p_s: 53 p.join() 54 #等待消费者线程结束 55 for c in c_s: 56 c.join() 57 print("finish....") queue模块简单使用演示
在python中进行多线程编程之前必须了解的问题: 1. 什么是线程? 答:线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。 2. 什么是多线程? 答:在单个程序中同时运行多个线程完成不同的工作,称为多线程。 3. 多线程编程的目的? 答:多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。 4. 如何再python中执行多线程编程? 答:在python2.x的版本中提供了thread(这个模块为多线程提供了一个底层 、原始的操作[也可以成为light-weight processes 或者 tasks) — 多个控制线程共享全局数据空间。为了多线程同步,提供了简单的锁(也称呼为 mutexes 后者 binary semaphores) 。]和threading(本模块的高层线程接口构建在低层的thread模块上)两个模块用于线程操作;而在python3.x中,官方只给出了threading模块的文档,对于底层线程造作放在了_thread模块中(即不建议使用)。是故在python中使用threading模块编程即可。 例一(一个简单的双线程程序): 1 import threading 2 import time 3 4 def run(n): 5 print("task-%s" % n) 6 time.sleep(5) 7 8 #实例化一个线程对象,target传入任务名,args以元组的形式传入任务函数的参数 9 task1 = threading.Thread(target=run, args=(1,)) 10 task2 = threading.Thread(target=run, args=(2,)) 11 12 task1.start() #线程启动 13 task2.start() test_threads_1 注:执行上面这个程序一共花费5秒左右的时间,而如果分别调用两次run怎需要10秒(证明两个任务同时运行)。 例二(用面向对象编程实现例二,并讲解threading模块中的一些常用方法): 1 import threading 2 import time 3 4 class Task(threading.Thread): 5 def __init__(self, n): 6 super(Task, self).__init__() #重构了__init__(), 所以先执行父类的构造函数 7 self.n = n #构造函数中传入任务参数参数 8 9 #任务函数 10 def run(self): 11 """ 12 不同于例一,在类中,任务函数只能以run命名,参数从构造函数中传入 13 :return: 14 """ 15 print("task-%s" % self.n) 16 # print(threading.current_thread()) #threading.current_thread()返回当前的Thread对象,对应于调用者控制的线程。 17 time.sleep(2) 18 19 #实例化并启动 20 t1 = Task(1) 21 t2 = Task(2) 22 # t1.setDaemon(True) #将t1设置成为守护线程 23 # t2.setDaemon(True) 24 25 t1.start() 26 #t1.join(timeout=5) #等待t1结束再运行t2,timeout代表等待时间,超过5秒便不再等待。 27 t2.start() 28 29 # print(threading.current_thread()) 30 # print(threading.active_count()) #threading.active_count()返回当前处于alive状态的Thread对象的个数。 test_threads_2 注:建议使用例二这种编程方式,在类中,任务函数只能以run命名,参数从构造函数中传入。 注:有时候难免会遇到一个问题,主程序的执行需要某个子线程(即使用start启动的线程,默认子线程一旦启动主程序将继续运行,两者不再有关联)的执行结果,这时使用join即可,例如例二中的t1.join()。 守护线程:一种特殊的子线程,与主线程(即本文中的主程序,由程序使用者启动)存在一种“主死仆死”的关系,即主线程意外停止或运行结束,不管守护线程是否运行完毕都得终止(非守护线程的子线程启动后与主线程没有联系,不论主线程是否还在运行,子线程不受影响),一个线程是否为守护线程继承于创建它的线程(将一个子线程设置成为守护线程可参考例二,去掉22,23行注释,则主程序结束t1,t2也结束,不会再sleep)。 在进行多线程编程时,经常会有多个线程同时对同一份数据修改,这便会导致最终得得到的数据不是预期结果。所以需要在一个线程修改数据时将数据锁定,只允许当前线程修改,当修改完成后,解锁让其他线程修改。在threading模块中使用Lock Objects解决此问题。 Lock Objects: A primitive lock is a synchronization primitive that is not owned by a particular thread when locked. In Python, it is currently the lowest level synchronization primitive available, implemented directly by the _thread extension module. A primitive lock is in one of two states, “locked” or “unlocked”. It is created in the unlocked state. It has two basic methods, acquire() and release(). When the state is unlocked, acquire() changes the state to locked and returns immediately. When the state is locked, acquire() blocks until a call to release() in another thread changes it to unlocked, then the acquire() call resets it to locked and returns. The release() method should only be called in the locked state; it changes the state to unlocked and returns immediately. If an attempt is made to release an unlocked lock, a RuntimeError will be raised. Locks also support the context management protocol. When more than one thread is blocked in acquire() waiting for the state to turn to unlocked, only one thread proceeds when a release() call resets the state to unlocked; which one of the waiting threads proceeds is not defined, and may vary across implementations. All methods are executed atomically. 例三(多个线程对一个全局变量进行加1操作): 1 import threading,time 2 3 4 n = 5 5 6 class Task(threading.Thread): 7 def __init__(self, n): 8 super(Task, self).__init__() 9 self.name = "Thread-" + str(n) 10 11 def run(self): 12 """ 13 不同于例一,在类中,任务函数只能以run命名,参数从构造函数中传入 14 :return: 15 """ 16 global n 17 time.sleep(0.5) 18 #锁定资源, 此时关于n的操作只能此线程执行 19 if lock.acquire(): 20 n += 1 21 print("%s, n = %s" % (self.name, n)) 22 time.sleep(0.2) 23 lock.release() #解锁 24 25 if __name__ == "__main__": 26 27 lock = threading.Lock() #实例化一个锁,此时锁处于打开状态 28 29 ts = [] #用于存储线程对象 30 #循环启动50个线程 31 for i in range(1, 50+1): 32 t = Task(i) 33 ts.append(t) 34 t.start() 35 #等待所有线程结束 36 start_time = time.time() 37 for t in ts: 38 t.join() 39 40 print("run time:",time.time() - start_time) test_threads_3 注:运行此程序,可发现一共用时10.5(0.2*50 + 0.5)秒左右,可以发现在锁定代码段程序没有并行执行。 注:有时候需要多层加锁,这时Lock Objects已经满足不了这个需求(当Lock处于locked时,遇到下一个acquire()时会阻塞)。这时我们使用RLock Objects,它的调用同Lock Objects。 信号量:信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。 举例说明:以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。 在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。 信号量的特性:信号量是一个非负整数(车位数),所有通过它的线程/进程(车辆)都会将该整数减一(通过它当然是为了使用资源),当该整数值为零时,所有试图通过它的线程都将处于等待状态。 简单来说,当使用Lock或RLock的locked期间,没有并发执行(只unlocked后才并行);而使用Semaphore可以让有限个线程共同访问资源,它的使用也是只有acquire()和release()。 例四(信号量的使用): 1 import threading 2 import time 3 4 class Task(threading.Thread): 5 #任务函数 6 def __init__(self, n): 7 super(Task, self).__init__() 8 self.name = "Thread-" + str(n) 9 10 def run(self): 11 semaphore.acquire() 12 print("%s" % self.name) 13 time.sleep(2) 14 semaphore.release() 15 16 17 if __name__ == "__main__": 18 19 semaphore = threading.BoundedSemaphore(5) #只允许5个线程同时访问资源 20 21 ts = [] #用于存储线程对象 22 #循环启动50个线程 23 24 for i in range(1, 23+1): 25 t = Task(i) 26 ts.append(t) 27 t.start() test_threads_4 注:通过运行上面的代码,可以发现在关键代码段最多只有5个线程在同时运行。 每调用一次acquire(),Semaphore的计数器减1;每调用一次release(),Semaphore的计数器加1,最大不超过设置的计数器初始值。 既然有了多线程编程,那么自然也就有了线程之间的交互。在python中threading模块提供了Event Objocts实现这个功能。 Events Objects: This is one of the simplest mechanisms for communication between threads: one thread signals an event and other threads wait for it(事件对象是线程间最简单的通信机制之一:线程可以激活在一个事件对象上等待的其他线程). An event object manages an internal flag that can be set to true with the set() method and reset to false with the clear() method. The wait() method blocks until the flag is true(每个事件对象管理一个内部标志,可以在事件对象上调用set() 方法将内部标志设为true,调用 clear() 方法将内部标志重置为false。wait()方法将阻塞直至该标志为真。). 例五(简单的线程交互程序,演示Event Objects的使用): 1 import threading,time 2 3 class Task1(threading.Thread): 4 def run(self): 5 while True: 6 for i in range(1, 20+1): 7 print("i = ", i) 8 if i % 5 == 0: 9 eve.set() #到5的倍数,将Event内部标志设置为True 10 time.sleep(0.5) 11 12 class Task2(threading.Thread): 13 def run(self): 14 while True: 15 #检测内部标志是否为True 16 if eve.is_set(): 17 print("hello world") 18 time.sleep(2) 19 eve.clear() #重置Event内部标志 20 else: 21 eve.wait() #内部标志不为True,此线程处于阻塞状态 22 23 if __name__ == "__main__": 24 t1 = Task1() 25 t2 = Task2() 26 eve = threading.Event() #实例化一个Event Objects 27 28 t1.start() 29 t2.start() test_threads_5
这个模块实现了一个通用的接口来实现多个不同的安全哈希和消息摘要算法。包括FIPS安全散列算法SHA1,SHA224,SHA256,SHA384和SHA512(在FIPS 180-2中定义)以及RSA的MD5算法(在因特网 RFC 1321术语“安全散列”和“消息摘要”是可互换的。较旧的算法被称为消息摘要。现代术语是安全哈希。 每种类型的哈希都具有一个命名构造函数(此模块中始终存在的散列算法的构造函数为md5(),sha1(),sha224(),sha256(),sha384()和sha512())。可以使用update()方法以类字节对象填充这个对象(通常为字节)。在连接数据的任何时候,都可以使digest()或hexdigest()方法来向它请求摘要。 例一(计算一串数据的MD5): 1 import hashlib 2 3 str1 = b"hello " 4 str2 = b"world" 5 str3 = b"hello world" 6 7 m1 = hashlib.md5() #生成一个md5对象 8 m1.update(str1) #以bytes类型填充md5对象 9 m1.update(str2) 10 m2 = hashlib.md5() 11 m2.update(str3) 12 13 #digest()返回传递给update()方法的数据的摘要。它是一个大小为digest_size的字节对象,包含的字节可以在0到255整个范围。 14 print("m1 hexdigest: ", m1.digest()) 15 print("m2 hexdigest: ", m2.digest()) 16 #hexdigest()类似digest(),但是摘要以2倍长度的字符串对象返回,只包含十六进制数字。这可用于在电子邮件或其它非二进制环境中安全交换数据。 17 print("m1 hexdigest: ", m1.hexdigest()) 18 print("m2 hexdigest: ", m2.hexdigest()) 19 20 21 22 23 24 >>>: 25 m1 hexdigest: b'^\xb6;\xbb\xe0\x1e\xee\xd0\x93\xcb"\xbb\x8fZ\xcd\xc3' 26 m2 hexdigest: b'^\xb6;\xbb\xe0\x1e\xee\xd0\x93\xcb"\xbb\x8fZ\xcd\xc3' 27 m1 hexdigest: 5eb63bbbe01eeed093cb22bb8f5acdc3 28 m2 hexdigest: 5eb63bbbe01eeed093cb22bb8f5acdc3 View Code 注:可以发现,只要数据一样(格式不同),则MD5码相同(即update()是以原来的基础继续生成MD5码,而不是新内容覆盖旧内容)。 只需更改构造函数即可使用不同的安全散列算法,其调用方式与上面的完全一样。 对于一次性数据,有中更精炼的写法,示例如下(其余算法一样): 1 md5 = hashlib.md5(b'hello world') 2 print(md5.hexdigest()) hashlib.new(name[, data]) 是一个通用构造函数,它接受所需算法的字符串名称作为其第一个参数。它也存在允许访问上面列出的哈希以及您的OpenSSL库可能提供的任何其他算法。命名的构造函数比new()快得多,应该是首选。 使用OpenSSL提供的算法使用new(): 1 h = hashlib.new('ripemd160') 2 h.update(b"hello world") 3 print(h.hexdigest()) View Code
网络编程 定义:所为网络编程即是对信息的发送和接收。 主要工作: (1)发送端:将信息以规定的协议组装成数据包。 (2)接收端:对收到的数据包解析,以提取所需要的信息。 Socket:两个在网络上的程序通过一个双向的通信连接,实现数据的交换,此连接的一端称为一个socket。 Socket的本质:Socket是一个编程接口(API),TCP/IP协议需要向开发者提供做网络开发用的接口,这就是Socket接口,它是对TCP/IP协议网络通信的封装。 python中用有标准库socket,要进行socket编程,只需导入这个模块即可。 例一(实现一个单对单,只能发送一次消息的一次性服务端和客户端): 1 #服务端 2 import socket 3 4 address = ("localhost", 6666) #写明服务端要监听的地址,和端口号 5 server = socket.socket() #生成一个socket对象 6 server.bind(address) #用socket对象绑定要监听的地址和端口 7 server.listen() #开始监听 8 9 conn,addr = server.accept() #等带新连接接入服务端,返回一个新的socket对象和地址,地址格式同前面格式 10 ''' 11 Wait for an incoming connection. Return a new socket 12 representing the connection, and the address of the client. 13 ''' 14 data = conn.recv(1024) #接收信息,写明要接收信息的最大容量,单位为字节 15 print("server recv:", data) 16 conn.send(data.upper()) #对收到的信息处理,返回到客户端 17 18 server.close() #关闭服务端 socket_server 1 #客户端 2 import socket 3 4 address = ('localhost', 6666) #写明要发送消息的服务端的地址和端口号 5 client = socket.socket() 6 client.connect(address) #连接服务端 7 8 client.send(b"hell world") #发送信息,注意在python3中socket的发送只支持bytes类型 9 data = client.recv(1024) #等待接收服务端返回的信息 10 print("client recv:", data) 11 12 client.close() #关闭客户端 socket_client 例二(对上面的代码进行改进,可以挂起多个连接,使每个连接可以进行多次对话且上一个连接断开后下一个连接马上接入): 1 #服务端 2 import socket 3 4 address = ("localhost", 6666) #写明服务端要监听的地址,和端口号 5 server = socket.socket() #生成一个socket对象 6 server.bind(address) #用socket对象绑定要监听的地址和端口 7 server.listen(5) #开始监听 8 9 while True: 10 #一条连接关闭后,接入下一条连接 11 conn,addr = server.accept() #等带新连接接入服务端,返回一个新的socket对象和地址,地址格式同前面格式 12 ''' 13 Wait for an incoming connection. Return a new socket 14 representing the connection, and the address of the client. 15 ''' 16 while True: 17 #使其可以接收多次消息 18 data = conn.recv(1024) #接收信息,写明要接收信息的最大容量,单位为字节 19 # 没收到消息,断开本次连接 20 if not data: 21 break 22 print("server recv:", data) 23 conn.send(data.upper()) #对收到的信息处理,返回到客户端 24 25 server.close() #关闭服务端 socket_server2 1 #客户端 2 import socket 3 4 address = ('localhost', 6666) #写明要发送消息的服务端的地址和端口号 5 client = socket.socket() 6 client.connect(address) #连接服务端 7 8 while True: 9 #使其可以向服务端多次发送消息 10 msg = input(">>>:").strip() 11 #如果发送的消息为空,则不再发送 12 if len(msg) == 0: 13 break 14 msg = msg.encode('utf-8') #将要发送的消息转为bytes类型 15 client.send(msg) #发送信息,注意在python3中socket的发送只支持bytes类型 16 data = client.recv(1024) #等待接收服务端返回的信息 17 print("client recv:", data.decode()) 18 19 client.close() #关闭客户端 socket_client2 例三(对例二稍加改造,就可实现一个简单的ssh的服务端和客户端): 1 #服务端 2 import socket 3 import os 4 5 address = ("localhost", 8888) #写明服务端要监听的地址,和端口号 6 server = socket.socket() #生成一个socket对象 7 server.bind(address) #用socket对象绑定要监听的地址和端口 8 server.listen() #开始监听 9 10 while True: 11 #一条连接关闭后,接入下一条连接 12 conn,addr = server.accept() #等带新连接接入服务端,返回一个新的socket对象和地址,地址格式同前面格式 13 ''' 14 Wait for an incoming connection. Return a new socket 15 representing the connection, and the address of the client. 16 ''' 17 while True: 18 data = conn.recv(1024) #接收信息,写明要接收信息的最大容量,单位为字节 19 # 没收到消息,断开本次连接 20 if not data: 21 break 22 cmd_result = os.popen(data.decode(), 'r').read() #执行命令,将命令执行结果保存到cmd_result 23 if len(cmd_result) == 0: 24 '''命令执行结果为空,认为接收到错误命令''' 25 cmd_result = "It's a wrong command..." 26 27 while True: 28 conn.send(str(len(cmd_result)).encode('utf-8')) #发送命令执行结果的长度 29 confirm = conn.recv(1024).decode() 30 '''客户端确认收到数据长度,发送数据,否则重传;且解决粘包问题''' 31 if confirm == "OK": 32 conn.send(cmd_result.encode('utf-8')) #对收到的信息处理,返回到客户端 33 break 34 else : 35 continue 36 37 38 server.close() #关闭服务端 ssh_socket_server 1 import socket 2 3 address = ("localhost", 8888) 4 client = socket.socket() 5 client.connect(address) 6 7 while True: 8 cmd = input("(command)>>>:").strip() 9 if len(cmd) == 0: 10 '''发送空命令时,结束本次循环''' 11 continue 12 if cmd == "#exit": 13 '''当检测到#exit,客户端与服务端断开连接''' 14 break 15 16 client.send(cmd.encode()) #向服务端发送命令 17 18 19 cmd_result = '' #目前已接收的数据 20 size_data = 0 #目前已接收数据的长度 21 size_cmd_result = int(client.recv(1024).decode()) #接收命令执行结果的长度 22 client.send("OK".encode("utf-8")) #向服务端确认收到数据长度 23 while size_data < size_cmd_result: 24 '''命令的执行结果可能大于设置的接收buffersize,多次接收''' 25 data = client.recv(1024).decode() #每次接收的数据 26 size_data += len(data) 27 cmd_result += data 28 29 print(cmd_result) 30 31 client.close() ssh_socket_client 注:提供另一种接收思路,服务端可以在每次返送完命令执行结果后,再发送一个结束标志,当客户端检测到结束标志时停止循环接收。 例四(改造例三,就可以实现一个简单的ftp的服务端和客户端) 1 #/usr/bin/python3 2 #服务端 3 import socket 4 import os 5 import hashlib 6 7 address = ("0.0.0.0", 8888) #写明服务端要监听的地址,和端口号 8 server = socket.socket() #生成一个socket对象 9 server.bind(address) #用socket对象绑定要监听的地址和端口 10 server.listen() #开始监听 11 12 while True: 13 #一条连接关闭后,接入下一条连接 14 conn,addr = server.accept() #等带新连接接入服务端,返回一个新的socket对象和地址,地址格式同前面格式 15 ''' 16 Wait for an incoming connection. Return a new socket 17 representing the connection, and the address of the client. 18 ''' 19 while True: 20 content = os.popen('ls', 'r').read() 21 conn.send(content.encode('utf-8')) #与客户端建立连接后,将服务端有哪些文件发给客户端,供客户端选择 22 filename = conn.recv(1024).decode() #接收客户端发来的文件名 23 24 if os.path.isfile(filename): 25 '''文件存在,开始发送文件''' 26 file_md5 = hashlib.md5() #初始化MD5对象,用于传输完成后的校验 27 file_size = os.stat(filename)[6] #读取文件大小 28 conn.send(str(file_size).encode('utf-8')) #将文件size发给客户端 29 confirm = conn.recv(1024).decode() #等待客户端确认接收 30 if confirm == "OK": 31 '''发送文件数据''' 32 with open(filename, 'rb') as fp: 33 for line in fp: 34 file_md5.update(line) 35 conn.send(line) 36 37 client_md5 = conn.recv(1024).decode() #传输完成后接收客户端发来的MD5 38 if file_md5.hexdigest() == client_md5: 39 '''确认文件传输未出错''' 40 conn.send("Success...".encode('utf-8')) 41 else : 42 '''文件传输出错,提示客户端删除重传''' 43 conn.send("This file is changed, please delete it and try again...".encode('utf-8')) 44 45 #客户端未确认接收,重试 46 else : 47 conn.send("Error, try again...".encode('utf-8')) 48 continue 49 50 else : 51 '''文件不存在,让客户端重新发送文件名''' 52 conn.send("The file name is wrong and try again".encode('utf-8')) 53 54 server.close() #关闭服务端 ftp_socket_server 1 #/usr/bin/python3 2 import socket 3 import hashlib 4 5 address = ("192.168.56.50", 8888) 6 client = socket.socket() 7 client.connect(address) 8 9 while True: 10 content = client.recv(4096) #接收并打印服务端有哪些文件 11 print("Files List".center(75, '-')) 12 print(content.decode()) 13 14 #向服务端发送想要接收的文件名 15 filename = input("(You want to get)>>>:").strip() 16 if filename == '#exit': 17 break 18 client.send(filename.encode('utf-8')) 19 20 file_size = client.recv(1024).decode() 21 if file_size.isdigit(): 22 '''文件大小是不小于0的数字,则文件存在,准备接收''' 23 file_size = int(file_size) 24 if file_size >= 0: 25 data_size = 0 26 data_md5 = hashlib.md5() #初始化MD5对象,用以向服务端校验 27 client.send("OK".encode('utf-8')) #向服务端确认接收数据 28 with open(filename, 'wb') as fp: 29 while data_size < file_size: 30 data = client.recv(1024) 31 data_md5.update(data) 32 data_size += len(data) 33 fp.write(data) 34 client.send(data_md5.hexdigest().encode('utf-8')) #发送服务端发送数据的MD5码 35 message = client.recv(1024).decode() #接收并打印服务端的校验信息 36 print(message) 37 print('\n\n\n') 38 else : 39 '''文件大小不是数字,则出错,打印服务端的提示信息''' 40 print(file_size) 41 continue 42 43 client.close() ftp_socket_client 注意:如果代码中有两个(或两个以上)socket.send()连在一起(中间无阻塞(比如time.slee(), socket.recv()等)),有粘包风险。 通过上面的代码编写,可以发现尽管socket已经简化了网络编程的过程,但还是给人一种面向过程的感觉。记下来的socketserver便解决了网络服务端编程过于繁琐的问题。 socketserver: socketserver模块简化了编写网络服务器的任务。 有四个基本的具体服务器类: class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True) 这使用Internet TCP协议,它在客户端和服务器之间提供连续的数据流。如果bind_and_activate为true,构造函数将自动尝试调用server_bind()和server_activate()。其他参数传递到BaseServer基类。 class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True) 这使用数据报,其是可能在运输中不按顺序到达或丢失的信息的离散分组。参数与TCPServer相同。 class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True) class socketserver.UnixDatagramServer(server_address, RequestHandlerClass, bind_and_activate=True) 这些更常用的类与TCP和UDP类类似,但使用Unix域套接字;它们在非Unix平台上不可用。参数与TCPServer相同。 这四个类同时处理请求;每个请求必须在下一个请求开始之前完成。如果每个请求需要很长时间来完成,这是不合适的,因为它需要大量的计算,或者因为它返回了很多客户端处理速度慢的数据。解决方案是创建一个单独的进程或线程来处理每个请求; ForkingMixIn和ThreadingMixIn混合类可以用于支持异步行为。 创建服务器需要几个步骤。首先,您必须通过对BaseRequestHandler类进行子类化并覆盖其handle()方法来创建请求处理程序类;此方法将处理传入请求。其次,您必须实例化一个服务器类,将它传递给服务器的地址和请求处理程序类。然后调用服务器对象的handle_request()或serve_forever()方法来处理一个或多个请求。最后,调用server_close()关闭套接字。 当从ThreadingMixIn继承线程连接行为时,应该明确声明您希望线程在突然关闭时的行为。ThreadingMixIn类定义了一个属性daemon_threads,它指示服务器是否应该等待线程终止。如果您希望线程自主行为,您应该明确地设置标志;默认值为False,这意味着Python不会退出,直到ThreadingMixIn创建的所有线程都退出。 #四个基本类的继承关系+------------+ | BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+ 例五(我们使用socketserver实现例二): 1 import socketserver 2 3 class MyTCPRequestHandler(socketserver.BaseRequestHandler): 4 """ 5 The request handler class for our server. 6 7 It is instantiated once per connection to the server, and must 8 override the handle() method to implement communication to the 9 client. 10 """ 11 def handle(self): 12 """ 13 服务端和客户端连接后,数据的交互由这个方法实现,这个方法必须重写 14 """ 15 while True: 16 try: 17 self.data = self.request.recv(1024).strip() 18 print("server recv:", self.data.decode()) 19 self.request.send(self.data.upper()) 20 except ConnectionResetError: 21 break 22 23 if __name__ == "__main__": 24 HOST, PORT = "localhost", 6666 25 server = socketserver.TCPServer((HOST, PORT), MyTCPRequestHandler) 26 server.serve_forever() #处理请求 27 server.server_close() #关闭套接字 TCPSever 注:服务器类具有相同的外部方法和属性,无论它们使用什么网络协议。 例六(使用ThreadingMixIn可以轻松实现一对多同时服务(多线程)): 1 import socketserver 2 3 class MyTCPRequestHandler(socketserver.BaseRequestHandler): 4 """ 5 The request handler class for our server. 6 7 It is instantiated once per connection to the server, and must 8 override the handle() method to implement communication to the 9 client. 10 """ 11 def handle(self): 12 """ 13 服务端和客户端连接后,数据的交互由这个方法实现,这个方法必须重写 14 """ 15 while True: 16 try: 17 self.data = self.request.recv(1024).strip() 18 print("server recv:", self.data.decode()) 19 self.request.send(self.data.upper()) 20 except ConnectionResetError: 21 break 22 23 if __name__ == "__main__": 24 HOST, PORT = "localhost", 6666 25 server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPRequestHandler) #同时处理多个连接 26 server.serve_forever() #处理请求 27 server.server_close() #关闭套接字 28 29 30 31 32 #ThreadingTCPServer的源码 33 ''' 34 class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass 35 ''' ThreadingTCPServer 注:其他协议的多线程实现同上。 PS(本例中所涉及到的模块使用参考): socket模块:http://www.cnblogs.com/God-Li/p/7625825.html hashlib模块:http://www.cnblogs.com/God-Li/p/7631604.html os模块:http://www.cnblogs.com/God-Li/p/7384227.html socketserver模块:http://python.usyiyi.cn/translate/python_352/library/socketserver.html#module-socketserver
socket常用功能函数: socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None) #创建socket对象 socket families(地址簇): AF_UNIX —————————— unix本机之间进行通信 AF_INET —————————— 使用IPv4 AF_INET6 —————————— 使用IPv6 注:socket.socket()中第一个能使用上述值。 socket types: SOCK_STREAM # TCP套接字类型 SOCK_DGRAM # UDP套接字类型 SOCK_RAW #原始套接字类型,这个套接字比较强大,创建这种套接字可以监听网卡上的所有数据帧 SOCK_RDM #是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。 注:这些常量都是套接字类型,应用于socket()函数中的第二个参数中.根据系统的不同,可能有更多的常数。(只有SOCK_STREAM和SOCK_DGRAM似乎通常很有用。) 协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应为CAN_RAW或CAN_BCM 。如果指定fileno,则忽略其他参数,从而导致具有指定文件描述器的套接字返回。与socket.fromfd()不同,fileno将返回相同的套接字,而不是重复。这可能有助于使用socket.close()关闭分离的套接字,一般情况下后两个参数忽略即可。 socket.bind(address) #将socket绑定到地址(常用于服务端) address地址的格式取决于地址族, 在AF_INET下,以元组(host,port)的形式表示地址。 socket.listen([backlog]) #启用服务器以接受连接(常用于服务端)。 backlog >=0,指定系统在拒绝新连接之前将允许的未接受连接的数量。如果未指定,则选择默认的合理值。 socket.accept() #接收一个连接.该socket 必须要绑定一个地址和监听连接.返回值是一对(conn,address)。(常用于服务端) conn是socket对象,可以在该连接上发送和接收数据,address是连接另一端的地址。 socket.recv(bufsize[, flags]) #从socket接收数据,返回值是一个代表所收到的数据的字节对象。 一次性接收的最大数据量由bufsize指定, 参数flags通常忽略。 socket.send(data[, flags]) #将数据发送到socket。 python3中只能发送bytes类型的数据。 socket.connect(address) #连接到远程socket(常用于客户端) 如果连接被信号中断,则该方法等待直到连接完成,或者如果信号处理程序没有引发异常并且套接字正在阻塞或者已经阻塞,则在超时时引入socket.timeout超时。对于非阻塞套接字,如果连接被信号中断(或由信号处理程序引发的异常),则该方法引发InterruptedError异常。 socket.close() #关闭socket 注:被调用后,连接断开,socket不能在发送数据,连接另一端也将不在接收数据。
程序的使用者往往会因为错误的输入导致程序崩溃,比如: 程序需要打开一个文件,使用者输入一个不存在的文件名;使用列表时,输入的索引超出列表的范围;使用字典时,输入了一个错误的key... python中我们使用异常处理来解决这一问题。 比如: 1 filename = 'abcd.txt' #此文件不存在 2 list_1 = ['a', 'b', 'c', 'd'] 3 dict_1 = { 4 'name' : 'John', 5 'age' : 22, 6 'gender' : 'M' 7 } 8 9 try : 10 '''这里写可能会出错的代码''' 11 open(filename, 'r') #如果这里出错,try中的代码不再执行 12 print(list_1[5]) 13 print(dict_1['haha']) 14 print(x) 15 16 # FileNotFoundError程序书写者提前预估到可能会出现这个错误,后面的下同理 17 except FileNotFoundError as e: 18 '''这里写应对FileNotFoundError的代码''' 19 # 捕捉到FileNotFoundError,执行这里的代码 20 print("打开文件错误, 这个文件不存在") 21 print(e) 22 except IndexError as e: 23 print("列表操作错误,请检查index值") 24 print(e) 25 except KeyError as e: 26 print("字典操作错误,请检查key值") 27 print(e) 28 # 可能会出现未预估到的错误,写在最后 29 except: 30 print("出现未知错误...") 31 else: 32 print("之前try下的那段代码没有出错,执行这里的代码...") 33 finally: 34 print("无论之前的代码是否出错,都执行这里的代码") 35 36 37 >>> 38 打开文件错误, 这个文件不存在 39 [Errno 2] No such file or directory: 'abcd.txt' 40 无论之前的代码是否出错,都执行这里的代码 View Code 我们常常使用这种格式来进行异常处理: try: #可能出错的代码 except Error as e: #应对代码 .... except: #出现不可预知错误,提示代码 else: #无异常执行完后,应对代码 finally: #最终代码 注意:else和finally为可选,如果用不到可以不写。 小技巧:异常类型可以通过故意写错某些代码来从pyhon解释器中获得,不必去记忆那些常见异常的总结。 自定义异常: 如果你是一个第三方库的编写者,那么难免需要自定义一些异常,以供库的使用者出现错误操作时获得提示信息,知道是由于那个地方错误导致程序崩溃。 当然我们自定义的异常也可以被try....except处理。 比如(自定义一个简单的异常并触发它): 1 #自定义了一个名为TempError的异常 2 class TempError(Exception): 3 def __init__(self, msg): 4 self.message = msg 5 6 7 raise TempError("Passed a wrong parameter") 8 # try: 9 # #主动触发这个异常 10 # raise TempError("Passed a wrong parameter") 11 # #对这个异常进行处理 12 # except TempError as e: 13 # print(e) 14 15 16 >>> 17 Traceback (most recent call last): 18 File "E:/文件/python学习/python学习代码/第六周/test_5.py", line 7, in <module> 19 raise TempError("Passed a wrong parameter") 20 __main__.TempError: Passed a wrong parameter View Code 在本例中,我们未做判断便主动触发了这个异常。当然在实际应用中,得首先做逻辑判断(比如传进一个错误的地址,导致数据库连不上),然后在触发这个异常,提示用户。 注意:在我们编写的库中,不应该做对自己定义的异常的处理,只需要根据情况触发它,提示给库的使用者即可。本例中的只是做一个示范而已。 我们使用这种格式来定义一个异常: class TempError(Exception): def __init__(self, msg): self.message = msg raise TempError(这个异常的提示信息) #触发这个异常 用一个继承Exception的类来定义它,用raise触发。
json:JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式(用于数据序列化和反序列化)。(适用于多种编程语言,可以与其他编程语言做数据交换) pickle:用于对Python对象结构进行序列化和反序列化。(只适用于python) 对于人类而言,json是人类可读的,而pickle不是。 json常用方法(支持列表,字典,元组等基本数据类型): dumps() --- 将传入的对象序列化。 调用:json.dumps(object) 例如(将列表,字典等不能直接写入文件的数据序列化成字符串): >>> json.dumps([1,2,3,4]) '[1, 2, 3, 4]' >>> json.dumps({'a':1, 'b':2, 'c':3, 'd':4}) '{"a": 1, "c": 3, "b": 2, "d": 4}' dump() --- 将传入的对象序列化并写入文件。 调用:json.dump(object, fp) 例如: 1 import json 2 3 list_1 = [1, 2, 3, 4] 4 dict_1 = {"a": 1, "c": 3, "b": 2, "d": 4} 5 6 filename = "test.json" 7 8 ''' 9 with open(filename, 'w') as fp: 10 fp.write(json.dumps(list_1)) 11 fp.write(json.dumps(dict_1)) 12 ''' 13 14 #等同与上面注释中的代码 15 with open(filename, 'w') as fp: 16 json.dump(list_1, fp) 17 json.dump(dict_1, fp) View Code test.json文件中的数据: {"c": 3, "d": 4, "b": 2, "a": 1} load() --- 加载一杯存储在文件中的json数据。 调用:json.load(fp) 例如(是json.loads()的封装,可以发现数据加载回来后,数据类型没有发生变化): import json filename = "test.json" with open(filename, 'r') as fp: dict_1 = json.load(fp) print(type(dict_1)) print(dict_1) >>> <class 'dict'> >>> {'b': 2, 'a': 1, 'd': 4, 'c': 3} loads() --- 加载python数据。 调用:json.loads(s) (s指从文件中读出来的数据) 例如(相当于先读取再加载): import json filename = "test.json" with open(filename, 'r') as fp: s = fp.read(fp) dict_1 = json.loads(s) print(type(dict_1)) print(dict_1) pickle常用方法(使用等同于json中方法的使用,不过pickle支持更为复杂的数据类型,如果你的数据只用于python程序,推荐使用pickle): dumps() --- dump() --- load() --- loads() ---
相关方法或属性: getcwd() --- 获取当前的操作目录,等同于linux中的pwd命令。 调用:os.getcwd() chdir() --- 改变python脚本的工作目录。 调用:os.chdir(path) (path以字符串形式传入) 例如: >>> os.getcwd() 'C:\\Users\\BLUE' >>> os.chdir('D:\\Program Files') >>> os.getcwd() 'D:\\Program Files' >>> os.chdir(r'C:\Users\BLUE') >>> os.getcwd() 'C:\\Users\\BLUE' curdir --- 当前目录 使用:os.curdir pardir --- 当前目录的父目录 使用: os.pardir 例如: >>> os.curdir '.' >>> os.pardir '..' >>> os.getcwd() 'C:\\Users\\BLUE' >>> os.chdir(os.pardir) >>> os.getcwd() 'C:\\Users' makedirs() --- 递归的创建目录。 调用:os.makedirs('dir_1/dir_2/dir_3/.../dir_n') 例如:os.makedirs(r'C:\a\b\c\d') #该操作会依次在C盘下创建a, b, c, d四个文件夹(若a目录存在则只创建b,c,d三个目录)。 removedirs() --- 若当前目录为空则删除,并切换到父级目录,若为空继续删除,依次递归。 调用:os.removedirs('dir_1/dir_2/dir_3/.../dir_n') 例如:os.removedirs(r'C:\a\b\c\d') #该操作会依次在C盘下依次删除d, c, b, a四个文件夹,如果中间某一级目录不为空,则在该级停止删除。 mkdir() --- 创建单级目录。 调用:os.mkdir('dir_1/dir_2/dir_3/.../dir_n') 例如:os.mkdir(r'C:\a\b\c\d') #该操作会在C盘下创建d文件夹(若a, b, c目录有一个不存在,则无法创建并报错)。 rmdir() --- 删除单级空目录。 调用:os.rmdir('dir_1/dir_2/dir_3/.../dir_n') 例如:os.rmdir(r'C:\a\b\c\d') #若d目录为空,该操作只删除d目录, 否则无法删除并报错。 listdir() --- 以列表的形式列出制定目录下的所有文件(包括隐藏文件),子目录。 调用:os.listdir(path) 例如:(列出D盘下的所有文件) >>> os.listdir(r'D:') ['Anaconda3', 'BaiduNetdisk', 'BHO', 'Data', 'guiminer', 'Intel', 'JetBrains', 'Profiles', 'Program', 'Tencent', 'Thunder', 'Thunder BHO Platform', 'UninstallXLWFP.exe', 'WinRAR', '腾讯游戏'] remove() --- 删除一个文件。 调用:os.remove(path) rename() --- 对一个文件重命名。 调用:os.rename(old_filename, new_filename) #注意不能覆盖已存在文件 stat() --- 获取文件或目录的属性信息。 调用:os.stat(path) 例如: >>> os.stat(r'C:\Windows\regedit.exe') os.stat_result(st_mode=33279, st_ino=281474976742063, st_dev=1893840342, st_nlink=2, st_uid=0, st_gid=0, st_size=321024, st_atime=1489870628, st_mtime=1489870628, st_ctime=1489870628) >>> os.stat(r'C:\Windows') os.stat_result(st_mode=16895, st_ino=281474976712108, st_dev=1893840342, st_nlink=1, st_uid=0, st_gid=0, st_size=32768, st_atime=1502900732, st_mtime=1502900732, st_ctime=1489837220) sep --- 使用os.sep获取当前平台的路径的分隔符(目录与子目录之间)(例如windows下是r‘\’,Linux下时‘/’)。 linesep --- 使用os.linesep获取当前平台的换行符(例如windows下是‘\r\n’,Linux下时‘\n’)。 pathsep --- 使用os.pathsep获取当前平台文件路径的分隔符(文件之间)(例如windows下是‘;’,Linux下时‘:’)。 name --- 使用os.name获取当前平台名称。 例如: >>> os.sep '\\' >>> os.linesep '\r\n' >>> os.pathsep ';' >>> os.name 'nt' system() --- 执行系统命令。 调用:os.system(command) 例如: >>> os.system('ping www.baidu.com') 正在 Ping www.A.sHiFeN.com [220.181.112.244] 具有 32 字节的数据: 来自 220.181.112.244 的回复: 字节=32 时间=38ms TTL=55 来自 220.181.112.244 的回复: 字节=32 时间=38ms TTL=55 来自 220.181.112.244 的回复: 字节=32 时间=38ms TTL=55 来自 220.181.112.244 的回复: 字节=32 时间=37ms TTL=55 220.181.112.244 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失), 往返行程的估计时间(以毫秒为单位): 最短 = 37ms,最长 = 38ms,平均 = 37ms environ --- 使用os.environ获取系统环境变量。 例如: >>> os.environ environ({'COMPUTERNAME': 'DESKTOP-KTUG9G5', 'APPDATA': 'C:\\Users\\BLUE\\AppData\\Roaming', 'USERDOMAIN_ROAMINGPROFILE': 'DESKTOP-KTUG9G5', 'HOMEPATH': '\\Users\\BLUE', 'NUMBER_OF_PROCESSORS': '8', 'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC', 'ONEDRIVE': 'C:\\Users\\BLUE\\OneDrive', 'LOGONSERVER': '\\\\DESKTOP-KTUG9G5', 'OS': 'Windows_NT', 'TEMP': 'C:\\Users\\BLUE\\AppData\\Local\\Temp', 'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files', 'PROGRAMDATA': 'C:\\ProgramData', 'PROMPT': '$P$G', 'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 60 Stepping 3, GenuineIntel', 'LOCALAPPDATA': 'C:\\Users\\BLUE\\AppData\\Local', 'USERNAME': 'BLUE', 'PROCESSOR_REVISION': '3c03', 'PROGRAMFILES': 'C:\\Program Files', 'PROGRAMW6432': 'C:\\Program Files', 'WINDIR': 'C:\\Windows', 'PUBLIC': 'C:\\Users\\Public', 'ASL.LOG': 'Destination=file', 'PSMODULEPATH': 'C:\\Program Files\\WindowsPowerShell\\Modules;C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules', 'PROCESSOR_LEVEL': '6', 'SYSTEMROOT': 'C:\\Windows', 'SESSIONNAME': 'Console', 'ALLUSERSPROFILE': 'C:\\ProgramData', 'SYSTEMDRIVE': 'C:', 'COMSPEC': 'C:\\Windows\\system32\\cmd.exe', 'PROGRAMFILES(X86)': 'C:\\Program Files (x86)', 'PROCESSOR_ARCHITECTURE': 'AMD64', 'HOMEDRIVE': 'C:', 'TMP': 'C:\\Users\\BLUE\\AppData\\Local\\Temp', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', 'PATH': 'D:\\Program Files\\Anaconda3\\Library\\bin;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;D:\\Program Files\\Anaconda3;D:\\Program Files\\Anaconda3\\Scripts;D:\\Program Files\\Anaconda3\\Library\\bin;C:\\Users\\BLUE\\AppData\\Local\\Microsoft\\WindowsApps;C:\\Users\\BLUE\\AppData\\Local\\GitHubDesktop\\bin', 'USERDOMAIN': 'DESKTOP-KTUG9G5', 'USERPROFILE': 'C:\\Users\\BLUE'}) path.abspath() --- 获取文件的绝对路径 调用:os.path.abspath(filename) 例如: >>> os.chdir(r'C:\windows') >>> os.path.abspath('regedit.exe') 'C:\\windows\\regedit.exe' path.split() --- 传入一个文件路径,返回一个tuple(由两部分构成(path, filename))。 调用:os.path.split(path) 例如: path.dirname() --- 获取路径中的目录。 调用:os.path.dirname(path) path.basename() --- 获取路径中的文件名。 调用:os.path.basename(path) >>> os.path.dirname(r'C:\Windows\System32\drivers\etc\hosts') 'C:\\Windows\\System32\\drivers\\etc' >>> os.path.basename(r'C:\Windows\System32\drivers\etc\hosts') 'hosts' >>> os.path.split(r'C:\Windows\System32\drivers\etc\hosts') ('C:\\Windows\\System32\\drivers\\etc', 'hosts') path.exists() --- 判断路径是否存在。 调用:os.path.exists(path) path.isabs() --- 判断路径是否是绝对路径。 调用:os.path.isabs(path) path.isfile() --- 判断是否是文件。 调用:os.path.isfile(path) path.isdir() --- 判断是否是目录。 调用:os.path.isdir(path) 例如: >>> os.path.exists(r'C:\Windows\System32\drivers\etc\hosts') True >>> os.path.exists(r'C:\Windows\System32\drivers\etc\abcd') False >>> os.path.isabs(r'C:\Windows\System32\drivers\etc\hosts') True >>> os.path.isabs(r'../') False >>> os.path.isfile('C:\Windows\System32\drivers\etc\hosts') True >>> os.path.isfile('C:\Windows\System32\drivers\etc') False >>> os.path.isdir('C:\Windows\System32\drivers\etc') True >>> os.path.isdir('C:\Windows\System32\drivers\etc\hosts') False path.join() --- 将一个或多个路径正确地连接起来。 调用:os.path.join(path, *paths) path.getatime() --- 获取文件最后的访问时间(以时间戳的形式返回)。 调用:os.path.getatime(path) path.getmtime() --- 获取文件最后的修改时间(以时间戳的形式返回)。 调用:os.path.getmtime(path) 例如: >>> os.path.join('C:', r'\windows\System32', r'\System32\drivers') 'C:\\System32\\drivers' >>> os.path.join('C:', r'\windows\System32') 'C:\\windows\\System32' >>> os.path.getatime('C:\Windows\System32\drivers\etc\hosts') 1501070798.585747 >>> os.path.getmtime('C:\Windows\System32\drivers\etc\hosts') 1502505489.0068946 popen() --- 打开到命令cmd的管道。返回值是连接到管道的打开文件对象,根据mode是'r'(默认)还是'w'。 调用:os.popen(cmd, mode="r", buffering=-1) #一般后两个参数默认即可 >>> import os >>> f = os.popen("dir") >>> content = f.read() >>> print(content) 驱动器 C 中的卷没有标签。 卷的序列号是 70E1-B5D6 C:\Users\BLUE 的目录 2017/10/06 17:11 <DIR> . 2017/10/06 17:11 <DIR> .. 2017/07/28 15:55 <DIR> .android 2017/07/30 12:44 <DIR> .astropy 2017/08/06 13:58 <DIR> .conda 2017/08/07 22:05 80 .gitconfig 2017/07/30 13:07 <DIR> .ipython 2017/07/30 12:52 <DIR> .matplotlib 2017/07/30 12:34 <DIR> .PyCharm2017.1 2017/10/07 14:08 <DIR> .VirtualBox 2017/07/26 20:51 <DIR> AppData 2017/09/17 10:10 <DIR> Contacts 2017/09/28 20:35 <DIR> Desktop 2017/10/06 18:25 <DIR> Documents 2017/10/06 18:41 <DIR> Downloads 2017/09/17 10:10 <DIR> Favorites 2017/09/27 12:59 <DIR> Links 2017/09/17 10:10 <DIR> Music 2017/09/27 12:59 <DIR> OneDrive 2017/09/17 10:10 <DIR> Pictures 2017/09/17 10:10 <DIR> Saved Games 2017/09/17 10:10 <DIR> Searches 2017/10/07 15:46 <DIR> Videos 1 个文件 80 字节 22 个目录 61,312,913,408 可用字节
random与随机操作有关的模块 常用方法: random() --- 返回0-1之见得一个随机浮点数。 调用:random.random() 例如: >>> random.random() 0.027383887147843344 >>> random.random() 0.5061348573341105 >>> random.random() 0.015448646863463922 randint() --- 返回一个随机整数。 调用:random.randint(a, b) (a<=b)返回随机数n, a <= n<= b 例如: >>> random.randint(1, 10) 6 >>> random.randint(1, 10) 3 >>> random.randint(1, 10) 7 >>> random.randint(1, 10) 8 >>> random.randint(1, 10) 9 >>> random.randint(1, 10) 9 randrange() --- 返回一个随机整数。 调用:random.randrange([start], stop[, step]) 从制定范围内,按制定递增计数取随机值(该随机值最大为stop-1), 例如:random.randrange(1, 10, 2) 即从[1, 3, 5, 7, 9]中取得一个随机数 >>> random.randrange(1,10, 2) 3 >>> random.randrange(1,10, 2) 5 >>> random.randrange(1,10, 2) 7 >>> random.randrange(1,10, 2) 5 >>> random.randrange(1,10, 2) 3 >>> random.randrange(1,10, 2) 7 choice() --- 从一个序列中随机取得一个元素。 调用:random.choice(sequence) (sequence指有序序列) 例如: >>> random.choice([1,2,3,4,5,6]) 2 >>> random.choice([1,2,3,4,5,6]) 3 >>> random.choice([1,2,3,4,5,6]) 4 >>> random.choice([1,2,3,4,5,6]) 4 >>> random.choice([1,2,3,4,5,6]) 6 >>> random.choice('hello word!') 'd' >>> random.choice('hello word!') 'w' >>> random.choice('hello word!') 'r' >>> random.choice('hello word!') 'o' >>> random.choice('hello word!') 'o' >>> random.choice('hello word!') 'l' >>> random.choice('hello word!') '!' sample() --- 从一个序列中取的制定个数的随机值。 调用:random.sample(sequence, n) 例如: >>> random.sample([1,2,3,4,5,6], 3) [4, 3, 2] >>> random.sample([1,2,3,4,5,6], 3) [3, 2, 6] >>> random.sample([1,2,3,4,5,6], 3) [3, 4, 5] >>> random.sample("hello world!", 3) ['l', 'o', 'r'] >>> random.sample("hello world!", 3) ['!', 'd', 'e'] >>> random.sample(("hello world!"), 3) ['l', 'e', 'l'] >>> random.sample(("hello world!"), 3) ['l', 'o', 'w'] >>> random.sample(("hello world!"), 3) ['l', 'd', 'o']
python中的time模块提供一些方法用来进行关于时间的操作,time模块中有以下方法可供使用: time() --- 返回当前时间的时间戳。 调用:time.time(), 可用于计算程序运行的时间,测试算法的优劣性。 sleep() --- 使程序暂停数秒。 调用:time.sleep(seconds), 传入秒数, 程序运行到这个函数时暂停一段时间。 gmtime() --- 以time.struct_time的格式返回当前UTC时间。 调用:time.gmtime([seconds]),传入时间戳,得到UTC tuple,如果未传入参数,以当前时间代替。 例如: >>> time.gmtime(time.time()) time.struct_time(tm_year=2017, tm_mon=8, tm_mday=16, tm_hour=6, tm_min=39, tm_sec=46, tm_wday=2, tm_yday=228, tm_isdst=0) localtime() --- 以time.struct_time的格式返回当前时间(本地时区)。 用法等同于gmtime(). asctime() --- 返回关于时间的格式化字符串, 例如'Sat Jun 06 16:26:11 1998' 调用:time.asctime([tuple]),tuple形式即time.struct_time。 例如: >>> time.asctime(time.localtime()) 'Wed Aug 16 14:59:26 2017' ctime() --- 返回同asctime()一样的字符串。 调用:time.ctime(seconds),传入时间戳。 例如: >>> time.ctime(time.time()) 'Wed Aug 16 15:02:03 2017' mktime() --- 返回时间戳。 调用:time.mktime(tuple),tuple形式即time.struct_time。 例如: >>> time.mktime(time.localtime()) 1502867286.0 strftime() --- 将time.struct_time转成可格式化字符串。 调用:time.strftime(format[, tuple]) 例如: >>> time.strftime("%Y/%m/%d %H:%M:%S", time.localtime()) '2017/08/16 15:15:56' 常用格式代码: %Y 以十进制数字表示年 %m 以十进制数字表示月 %d 以十进制数字表示天 %H 以十进制数字表示时 %M 以十进制数字表示分 %S 以十进制数字表示秒 %z 表示时区偏离UTC的时间差 例如(东八区比UTC快0天8时0分0秒): >>> time.strftime("%z", time.localtime()) '+0800' %a 星期的缩写 %A 星期的全写 %b 月份的缩写 %B 月份的大写 %c 等同于ctime()返回的格式 %I 以12小时制表示时 %p 表示上午还是下午 例如: >>> time.strftime("%a %b %I:%M:%S%p", time.localtime()) 'Wed Aug 03:38:33PM' >>> time.strftime("%A %B %I:%M:%S%p", time.localtime()) 'Wednesday August 03:39:07PM' >>> time.strftime("%c", time.localtime()) 'Wed Aug 16 15:39:19 2017' strptime() --- 将格式化字符串转成tuple(time.struct_time) 调用:time.strptime(string, format) 例如(格式代码同上): >>> time.strptime("2017/08/16 15:15:56", "%Y/%m/%d %H:%M:%S") time.struct_time(tm_year=2017, tm_mon=8, tm_mday=16, tm_hour=15, tm_min=15, tm_sec=56, tm_wday=2, tm_yday=228, tm_isdst=-1)