左手用R右手Python系列——面向对象编程基础

时间:2022-05-08
本文章向大家介绍左手用R右手Python系列——面向对象编程基础,主要内容包括定义类、定义类中的可调用方法、创建类实例(同时将实例与方法绑定):、执行程序、注册类:、创建类、定义泛型函数:、将泛型函数作为方法与类绑定、定义类:、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

面向对象编程是程序设计中一种重要且高效的编程规范,它区别于常见的面向过程编程。在R语言以及Python的程序包开发过程中,大量使用了面向对象的编程范式。

百度百科关于面向对象编程的权威解释是:

面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是一种程序设计范型,同时也是一种程序开发的方法。其最重要的三大特征是封装、继承、多态。

对象指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。

R语言中的面向对象编程是通过泛型函数来实现的,R语言中现有的S3类、S4类、以及R6类等都可以实现面向对象的编程规范。

如果你还对R语言的S3、S4类不熟悉,可以参考张丹老师的这几篇博客:

http://blog.fens.me/r-object-oriented-intro/
http://blog.fens.me/r-class-s3/
http://blog.fens.me/r-class-s4/

张丹老师的这几篇文章详细的介绍了R语言中S3类、S4类面向对象的实现。

以下我将之前一篇介绍多进程/多线程的案例改造成基于S3、S4类的面向对象模式。

library("RCurl")
library("XML")
library("magrittr")

定义类

因为我们的任务是抓取天善智能主页上大数据相关的职位信息,所以类定义为GetData,而后仅仅定义了一个可调用的方法——hellobi(类中可以定义的方法调用可以有很多个。)

GetData <- function(object) UseMethod("GetData")

定义类中的可调用方法

GetData.hellobi <- function(object){
    d      <- debugGatherer()
    handle <- getCurlHandle(debugfunction=d$update,followlocation=TRUE,cookiefile="",verbose = TRUE)
    while (object$i < 10){
        object$i = object$i+1
        url <- sprintf("https://www.hellobi.com/jobs/search?page=%d",object$i)
        tryCatch({
        content    <- getURL(url,.opts=list(httpheader=object$headers),.encoding="utf-8",curl=handle) %>% htmlParse() 
        job_item   <- content %>% xpathSApply(.,"//div[@class='job_item_middle pull-left']/h4/a",xmlValue)
        job_links  <- content %>% xpathSApply(.,"//div[@class='job_item_middle pull-left']/h4/a",xmlGetAttr,"href")
        job_info   <- content %>% xpathSApply(.,"//div[@class='job_item_middle pull-left']/h5",xmlValue,trim = TRUE) 
        job_salary <- content %>% xpathSApply(.,"//div[@class='job_item-right pull-right']/h4",xmlValue,trim = TRUE) 
        job_origin <- content %>% xpathSApply(.,"//div[@class='job_item-right pull-right']/h5",xmlValue,trim = TRUE)
        myreslut   <-  data.frame(job_item,job_links,job_info,job_salary,job_origin,stringsAsFactors = FALSE) 
        object$fullinfo  <- rbind(object$fullinfo,myreslut) 
        cat(sprintf("第【%d】页已抓取完毕!",object$i),sep = "n")
        },error = function(e){
        cat(sprintf("第【%d】页抓取失败!",object$i),sep = "n")
        })
        Sys.sleep(runif(1))
    }
        cat("all page is OK!!!")
        return (object$fullinfo)
}

创建类实例(同时将实例与方法绑定):

initialize <- list(
             i = 0,
             fullinfo = data.frame(),
             headers  = c(
                       "Referer"="https://www.hellobi.com/jobs/search",
                       "User-Agent"="Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36"
                     )
              )
mywork <- structure(initialize, class = "hellobi")

执行程序

mydata <- GetData(mywork)

当然你也可以在GetData类下定义多个方法,比如抓取课程信息,抓取博客文章信息等等。仅需将实例绑定到对应的方法上,那么在类中传入实例之后,类便可以自动搜寻到该实例的方法,并自动执行该实例对应方法的函数调用,R语言中的summary、plot、print函数等都是通过这种泛型函数的模式来实现的。

使用基于S4类的方法来实现以上案例的面向对象模式

initialize <- list(
             i = 0,
             fullinfo = data.frame(),
             headers  = c(
                       "Referer"="https://www.hellobi.com/jobs/search",
                       "User-Agent"="Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36"
                      )
             )

注册类:

setClass("GetData",
         slots = c(
                    i="numeric", 
                    fullinfo="data.frame",
                    headers="character"
                    ),
         prototype = initialize
        )

创建类

GetData <- new('GetData')

定义泛型函数:

setGeneric('hellobi',  
    function(object) {  
        standardGeneric('hellobi')  
    }  
  )

将泛型函数作为方法与类绑定

setMethod('hellobi', 'GetData',   
    function(object) {  
    d      <- debugGatherer()
    handle <- getCurlHandle(debugfunction=d$update,followlocation=TRUE,cookiefile="",verbose = TRUE)
    while (object@i < 10){
        object@i = object@i+1
        url <- sprintf("https://www.hellobi.com/jobs/search?page=%d",object@i)
        tryCatch({
        content    <- getURL(url,.opts=list(httpheader=object@headers),.encoding="utf-8",curl=handle) %>% htmlParse() 
        job_item   <- content %>% xpathSApply(.,"//div[@class='job_item_middle pull-left']/h4/a",xmlValue)
        job_links  <- content %>% xpathSApply(.,"//div[@class='job_item_middle pull-left']/h4/a",xmlGetAttr,"href")
        job_info   <- content %>% xpathSApply(.,"//div[@class='job_item_middle pull-left']/h5",xmlValue,trim = TRUE) 
        job_salary <- content %>% xpathSApply(.,"//div[@class='job_item-right pull-right']/h4",xmlValue,trim = TRUE) 
        job_origin <- content %>% xpathSApply(.,"//div[@class='job_item-right pull-right']/h5",xmlValue,trim = TRUE)
        myreslut   <-  data.frame(job_item,job_links,job_info,job_salary,job_origin,stringsAsFactors = FALSE) 
        object@fullinfo  <- rbind(object@fullinfo,myreslut) 
        cat(sprintf("第【%d】页已抓取完毕!",object@i),sep = "n")
        },error = function(e){
        cat(sprintf("第【%d】页抓取失败!",object@i),sep = "n")
        })
        Sys.sleep(runif(1))
    }
    cat("all page is OK!!!")
    return (object@fullinfo)
    }  
) 
###执行类中的方法:
mydata1 <- hellobi(GetData)  

DT::datatable(mydata1)

关于S3方法与S4方法之间的区别:

  1. 在定义S3类的时候,没有显式的定义过程,而定义S4类的时候需要调用函数setClass;
  2. 在初始化S3对象的时候,只是建立了一个list,然后设置其class属性,而初始化S4对象时需要使用函数new;
  3. 提取变量的符号不同,S3为$,而S4为@;
  4. 在应用泛型函数时,S3需要定义f.classname,而S4需要使用setMethod函数;
  5. 在声明泛型函数时,S3使用UseMethod(), 而S4使用setGeneric()。

Python:

from urllib.request import urlopen,Request
import pandas as pd
import time
from lxml import etree

定义类:

class GetData:
    #初始化参数
    def __init__(self):
        self.start = 0
        self.headers = {
             'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36',
             'Referer':'https://www.hellobi.com/jobs/search'
              }
        self.myresult =  {
              "job_item":[],
              "job_links":[],
              "job_info":[],
              "job_salary":[],
              "job_origin":[]
              }
              
    #定义可用方法:
    def getjobs(self):
        while self.start < 10:
            self.start +=1 
            url = "https://www.hellobi.com/jobs/search?page={}".format(self.start)
            try:
                pagecontent=urlopen(Request(url,headers=self.headers)).read().decode('utf-8')
                result = etree.HTML(pagecontent)
                self.myresult["job_item"].extend(result.xpath('//div[@class="job_item_middle pull-left"]/h4/a/text()'))
                self.myresult["job_links"].extend(result.xpath('//div[@class="job_item_middle pull-left"]/h4/a/@href'))
                self.myresult["job_info"].extend([ text.xpath('string(.)').strip() for text in  result.xpath('//div[@class="job_item_middle pull-left"]/h5')])
                self.myresult["job_salary"].extend(result.xpath('//div[@class="job_item-right pull-right"]/h4/span/text()'))
                self.myresult["job_origin"].extend(result.xpath('//div[@class="job_item-right pull-right"]/h5/span/text()'))
                print("正在抓取第【{}】页".format(self.start))
            except:
                print("第【{}】页抓取失败".format(self.start))
            time.sleep(1)
        print("everything is OK")
        return pd.DataFrame(self.myresult)
        
if __name__ == "__main__":
    t0 = time.time()
    mydata = GetData()
    myresult = mydata.getjobs()
    t1 = time.time()
    total = t1 - t0
    print("消耗时间:{}".format(total))

以上便是在R语言和Python中使用面向对象编程的模式所做的爬虫写程序,仅作为学习面向对象编程思维的实战案例,至于更为详尽的关于R语言和Python中面向对象的思维及其高阶应用,还需要各位小伙伴儿参考各大主流加载包的源码,比如R语言的ggplot2包、rvest包等内部大量使用基于S3类的编程模式,Python中的主流加载库也都是如此。 往期案例数据请移步本人GitHub: https://github.com/ljtyduyu/DataWarehouse/tree/master/File