R語言學習筆記——R語言面向對象編程系列2

最近在看任坤大神的新作——《R語言編程指南》,其中對於編程語言中非常流行的面向對象編程範式(OOP)在R語言中的實現進行了非常詳盡的講解,強烈推薦各位有志於進階R語言編程的小夥伴兒進行閱讀。

R語言內目前可以實現OOP範式的一共有四套標準:S3、S4、RC、R6,其中關於S3、S4兩種範式在早期的各種擴展包中使用比較多,是基於泛型函數而實現的,之前在學習Python的面向對象編程系列時曾經做過粗淺的練習:

左手用R右手Python系列——面向對象編程基礎

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()。

S3的範式存在很大的隱患,對於類與對象的定義都不夠嚴謹,S4範式在很大程度上彌補了S3的缺陷,但是在實現方式和方法分派上與主流的面向對象語言仍然存在很大的差距,方法分配、類與方法的定義都是割裂獨立執行的,在封裝上非常不方便,而RC以及在RC基礎上進一步發展的R6標準已經逐步開始接近主流編程語言中面向對象的實現模式。

RC 是一種具有引用語義的類系統,它更像其他面向對象編程語言中的類系統。

它將所有的類屬性及對應方法都封裝在一個實例生成器中,通過生成器可以生成需要的實例,進而執行對應的類方法。在方法中修改欄位的值,需要用<<-

以下是使用RC引用類實現的一個小爬蟲:

#載入擴展包library("RCurl")library("XML")library("magrittr")

首先定義類:

  • 類內包含必要的欄位(其實就是數據抓取需要用到的參數
  • 定義要執行的方法(方法可以有多個)

hellobi <- setRefClass( "hellobi", fields = list( i = "numeric", fullinfo = "data.frame", headers = "character" ), methods = list( GetData = function() { d <- debugGatherer() handle <- getCurlHandle(debugfunction=d$update,followlocation=TRUE,cookiefile="",verbose = TRUE) while (i < 10){ i <<- i + 1 url <- sprintf("https://www.hellobi.com/jobs/search?page=%d",i) tryCatch({ content <- getURL(url,.opts=list(httpheader=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) fullinfo <<- rbind(fullinfo,myreslut) cat(sprintf("第【%d】頁已抓取完畢!",i),sep = "
") },error = function(e){ cat(sprintf("第【%d】頁抓取失敗!",i),sep = "
") }) Sys.sleep(runif(1)) } cat("all page is OK!!!") return (fullinfo) } ) )

創建一個類實例:

mydata <- hellobi( 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" ) )

調用類中對應的方法執行爬蟲程序:

mydatainfo <- mydata$GetData()

預覽數據:

DT::datatable(mydatainfo)

R6是基於RC引用類系統的進一步升級版,它明確的的將類內所有的屬性(欄位)和方法進行了共有和私有的區分,這樣可以控制那些對象對於用戶是可見的,那些是不可見的,增加程序的安全性,並儘可能使得可見部分簡潔明了,易於理解。

library("R6")#R6不是內置包,是一個第三方擴展包,因此在使用R6系統前需要提前載入該包

創建R6對象:

  • 設置公有變數部分(內含可見的參數、初始化函數等)
  • 設置私有變數(內可以包含安全級別高的一些變數、參數、函數)

hellobi <- R6Class( "hellobi", public = list( i = NA, fullinfo = NA, headers = NA, #初始化函數 initialize = function(i,fullinfo,headers) { #以下主要是進行參數檢查並進行分配初始化參數 if (!missing(i)) self$i <- i if (!missing(fullinfo)) self$fullinfo <- fullinfo if (!missing(headers)) self$headers <- headers }, #方法調用(這裡我將爬蟲程序定義在私有域內,然後在公有域內進行引用) GetData = function() { private$Crawler() } ), #定義私有域(這裡私有域主要定義爬蟲程序) private = list( Crawler = function(){ d <- debugGatherer() handle <- getCurlHandle(debugfunction=d$update,followlocation=TRUE,cookiefile="",verbose = TRUE) while (self$i < 10){ self$i <<- self$i + 1 url <- sprintf("https://www.hellobi.com/jobs/search?page=%d",self$i) tryCatch({ content <- getURL(url,.opts=list(httpheader=self$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) self$fullinfo <<- rbind(self$fullinfo,myreslut) cat(sprintf("第【%d】頁已抓取完畢!",self$i),sep = "
") },error = function(e){ cat(sprintf("第【%d】頁抓取失敗!",self$i),sep = "
") }) Sys.sleep(runif(1)) } cat("all page is OK!!!") return (self$fullinfo) } ) )

創建類實例:

mydata <- hellobi$new( 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" ) )

調用類中的方法執行爬蟲程序:

mydatainfo2 <- mydata$GetData()

關於面向對象的一些高級特性——繼承、多態等屬性,有待以後有更深理解之後再做分享,因為自己理解的不夠深刻,今天也是抱著試一試的心態嘗試著熟練二者的區別,如果各位對此有更加精闢的理解,歡迎交流分享。

推薦閱讀:

TAG:R編程語言 | 面向對象編程 | 數據分析 |