R語言基於S3的面向對象編程


R語言基於S3的面向對象編程

歡迎關注天善智能,我們是專注於商業智能BI,人工智能AI,大數據分析與挖掘領域的垂直社區,學習,問答、求職一站式搞定!對商業智能BI、大數據分析挖掘、機器學習,python,R等數據領域感興趣的同學加微信:tstoutiao,邀請你進入數據愛好者交流群,數據愛好者們都在這兒。

作者:張丹,R語言中文社區專欄特邀作者,《R的極客理想》系列圖書作者,民生銀行大數據中心數據分析師,前況客創始人兼CTO。

個人博客 http://fens.me, Alexa全球排名70k。

前言

對於R語言的面向對象編程,不同於其他的編程語言,R語言提供了3種底層對象類型,一種是S3類型,一種是S4類型,還有一種是RC類型。

S3對象簡單、具有動態性、結構化特徵不明顯;S4對象結構化、功能強大;RC對象是2.12版本後使用的新類型,用於解決S3,S4很難實現的對象。

本文將從S3對象開始,介紹R語言面向對象編程的細節。

目錄

  1. S3對象介紹
  2. 創建S3對象
  3. 泛型函數和方法調用
  4. 查看S3對象的函數
  5. S3對象繼承
  6. S3對象的缺點
  7. S3對象的使用


1.S3對象介紹

在R語言中,基於S3對象的面向對象編程,是一種基於泛型函數的實現方式。泛型函數是一種特殊的函數, 根據傳入對象的類型決定調用哪個具體的方法。基於S3對象實現的面向對象編程,不同其他語言的面向對象編程,是一種動態函數調用的模擬實現。S3對象被廣泛應用於R的早期的開發包中。

關於面向對象的介紹,請參考文章:

2.創建S3對象

本文的系統環境

  • Linux: Ubuntu Server 12.04.2 LTS 64bit
  • R: 3.0.1 x86_64-pc-linux-gnu

注:pryr只支持Linux系統環境

為了方便我們檢查對象的類型,引入pryr包作為輔助工具。

1# 加載pryr包
2> library(pryr)

通過變量創建S3對象

 1> x 2> attr(x,'class') 3
4> x
5[1] 1
6attr(,"class")
7[1] "foo"
8
9> class(x)
10[1] "foo"
11
12# 用pryr包的otype函數,檢查x的類型
13> otype(x)
14[1] "S3"

通過structure()函數創建S3對象

 1> y  2
3> y
4[1] 2
5attr(,"class")
6[1] "foo"

7
8> class(y)
9[1] "foo"
10
11> otype(y)
12[1] "S3"

創建一個多類型的S3對象

S3對象沒有明確結構關係,一個S3對象可以有多個類型, S3對象的 class 屬性可以是一個向量,包括多種類型。

1> x2> attr(x,'class')3> class(x)
4[1] "foo" "bar"
5> otype(x)
6[1] "S3"

3.泛型函數和方法調用

對於S3對象的使用,通常用UseMethod()函數來定義一個泛型函數的名稱,通過傳入參數的class屬性,來確定不同的方法調用。

定義一個teacher的泛型函數

+ 用UseMethod()定義teacher泛型函數

+ 用teacher.xxx的語法格式定義teacher對象的行為

+ 其中teacher.default是默認行為

 1# 用UseMethod()定義teacher泛型函數
2> teacher 3
4# 用pryr包中ftype()函數,檢查teacher的類型

5> ftype(teacher)
6[1] "s3" "generic"
7
8# 定義teacher內部函數
9> teacher.lecture 10> teacher.assignment 11> teacher.correcting 12> teacher.default

方法調用時,通過傳入參數的class屬性,來確定不同的方法調用。

  • 定義一個變量a,並設置a的class屬性為lecture
  • 把變量a,傳入到teacher泛型函數中
  • 函數teacher.lecture()函數的行為被調用
1> a2
3# 給老師變量設置行為
4> attr(a,'class') 5
6# 執行老師的行為
7> teacher(a)
8[1] "講課"

當然,我們也可以直接調用teacher中定義的行為,如果這樣做了就失敗了面向對象封裝的意義。

1> teacher.lecture()
2[1] "講課"
3
4> teacher.lecture(a)
5[1] "講課"
6
7> teacher()
8[1] "你不是teacher"

4.查看S3對象的函數

當我們使用S3對象進行面向對象封裝後,可以用methods()函數來查看S3對象中的定義的內部行為函數。

1# 查看teacher對象
2> teacher
3function(x, ...) UseMethod("teacher")
4
5# 查看teacher對象的內部函數
6> methods(teacher)
7[1] teacher.assignment teacher.correcting teacher.default teacher.lecture

通過methods()的generic.function參數,來匹配泛型函數名字。

1> methods(generic.function=predict)
2 [1] predict.ar* predict.Arima* predict.arima0*
3 [4] predict.glm predict.HoltWinters* predict.lm
4 [7] predict.loess* predict.mlm predict.nls*
5[10] predict.poly predict.ppr* predict.prcomp*
6[13] predict.princomp* predict.smooth.spline* predict.smooth.spline.fit*
7[16] predict.StructTS*
8
9 Non-visible functions are asterisked

通過methods()的class參數,來匹配類的名字。

 1> methods(class=lm)
2 [1] add1.lm* alias.lm* anova.lm case.names.lm*
3 [5] confint.lm* cooks.distance.lm* deviance.lm* dfbeta.lm*
4 [9] dfbetas.lm* drop1.lm* dummy.coef.lm* effects.lm*
5[13] extractAIC.lm* family.lm* formula.lm* hatvalues.lm
6[17] influence.lm* kappa.lm labels.lm* logLik.lm*
7[21] model.frame.lm model.matrix.lm nobs.lm* plot.lm
8[25] predict.lm print.lm proj.lm* qr.lm*
9[29] residuals.lm rstandard.lm rstudent.lm simulate.lm*
10[33] summary.lm variable.names.lm* vcov.lm*
11
12 Non-visible functions are asterisked

用getAnywhere()函數,查看所有的函數。

 1# 查看teacher.lecture函數
2> getAnywhere(teacher.lecture)
3A single object matching ‘teacher.lecture’ was found
4It was found in the following places
5 .GlobalEnv
6 registered S3 method for teacher
7with value
8
9function(x) print("講課")
10
11# 查看不可見的函數predict.ppr
12> predict.ppr
13Error: object 'predict.ppr' not found
14> exists("predict.ppr")
15[1] FALSE
16
17# getAnywhere()函數查找predict.ppr
18> getAnywhere("predict.ppr")
19A single object matching ‘predict.ppr’ was found
20It was found in the following places
21 registered S3 method for predict from namespace stats
22 namespace:stats
23with value
24
25function (object, newdata, ...)
26{
27 if (missing(newdata))
28 return(fitted(object))
29 if (!is.null(object$terms)) {
30 newdata 31 rn 32 Terms 33 m 34 xlev = object$xlevels)
35 if (!is.null(cl 36 .checkMFClasses(cl, m)
37 keep 38 x 39 }
40 else {
41 x 42 keep 43 rn 44 }
45 if (ncol(x) != object$p)
46 stop("wrong number of columns in 'x'")
47 res 48 object$ynames))
49 res[keep, ] 50 as.double(x), as.double(object$smod), y = double(nrow(x) *
51 object$q), double(2 * object$smod[4L]))$y, ncol = object$q)
52 drop(res)
53}
54<bytecode>
55<environment>
/<environment>/<bytecode>

使用getS3method()函數,也同樣可以查看不可見的函數

 1# getS3method()函數查找predict.ppr
2> getS3method("predict", "ppr")
3function (object, newdata, ...)
4{
5 if (missing(newdata))
6 return(fitted(object))
7 if (!is.null(object$terms)) {
8 newdata 9 rn 10 Terms 11 m 12 xlev = object$xlevels)
13 if (!is.null(cl 14 .checkMFClasses(cl, m)
15 keep 16 x 17 }
18 else {
19 x 20 keep 21 rn 22 }
23 if (ncol(x) != object$p)
24 stop("wrong number of columns in 'x'")
25 res 26 object$ynames))
27 res[keep, ] 28 as.double(x), as.double(object$smod), y = double(nrow(x) *
29 object$q), double(2 * object$smod[4L]))$y, ncol = object$q)
30 drop(res)
31}
32<bytecode>
33<environment>
/<environment>/<bytecode>

5.S3對象的繼承關係

S3對象有一種非常簡單的繼承方式,用NextMethod()函數來實現。

定義一個 node泛型函數

 1> node  2> node.default  3
4# father函數
5> node.father 6
7# son函數,通過NextMethod()函數指向father函數
8> node.son 9
10# 定義n1
11> n1 12# 在node函數中傳入n1,執行node.father()函數
13> node(n1)

14[1] "father"
15
16# 定義n2,設置class屬性為兩個
17> n2 18# 在node函數中傳入n2,執行node.son()函數和node.father()函數
19> node(n2)
20[1] "son" "father"

通過對node()函數傳入n2的參數,node.son()先被執行,然後通過NextMethod()函數繼續執行了node.father()函數。這樣其實就模擬了,子函數調用父函數的過程,實現了面向對象編程中的繼承。

6.S3對象的缺點

從上面對S3對象的介紹來看,S3對象並不是完全的面向對象實現,而是一種通過泛型函數模擬的面向對象的實現。

  • S3使用起來簡單,但在實際的面向對象編程過程中,當對象關係有一定的複雜度,S3對象所表達的意義就會變得不太清楚。
  • S3封裝的內部函數,可繞過泛型函數的檢查,以直接被調用。
  • S3參數的class屬性,可以被任意設置,沒有預處理的檢查。
  • S3參數,只能通過調用class屬性進行函數調用,其他屬性則不會被class()函數執行。
  • S3參數的class屬性有多個值時,調用時會按照程序賦值順序來調用第一個合法的函數。

所以,S3只能R語言面向對象的一種簡單的實現。


7.S3對象的使用

S3對象系統,被廣泛地應用於R語言的早期開發中。在base包中,就有很多的S3對象。

base包的S3對象

 1# mean函數
2> mean
3function (x, ...)
4UseMethod("mean")
5
6
7> ftype(mean)
8[1] "s3" "generic"
9
10# t函數
11> ftype(t)
12[1] "s3" "generic"
13
14# plot函數
15> ftype(plot)
16[1] "s3" "generic"

自定義的S3對象

 1# 定義數字型變量a
2> a 3# 變量a的class為numeric

4> class(a)
5[1] "numeric"
6
7# 定義泛型函數f1
8> f1 9+ a 10+ UseMethod("f1")
11+ }
12
13# 定義f1的內部函數
14> f1.numeric 15
16# 給f1()傳入變量a
17> f1(a)
18[1] 2
19
20# 給f1()傳入數字99
21> f1(99)
22[1] 2
23
24# 定義f1的內部函數
25> f1.character 26
27# 給f1()傳入字符a
28> f1("a")
29[1] "char a"

這樣,我們就對S3對象系統有了一個全面認識,開始R語言的面向對象編程之路。

往期精彩:



R語言基於S3的面向對象編程


回覆 爬蟲 爬蟲三大案例實戰回覆 Python 1小時破冰入門回覆 數據挖掘 R語言入門及數據挖掘回覆 人工智能 三個月入門人工智能回覆 數據分析師 數據分析師成長之路 回覆 機器學習 機器學習的商業應用回覆 數據科學 數據科學實戰回覆 常用算法 常用數據挖掘算法


分享到:


相關文章: