Jadual Kandungan
1,函数" >1,函数
2,作用域" >2,作用域
3,变量解析规则" >3,变量解析规则
4,变量生存周期" >4,变量生存周期
5,函数参数" >5,函数参数
6,嵌套函数" >6,嵌套函数
7,函数是Python世界中的一级类对象" >7,函数是Python世界中的一级类对象
8,闭包" >8,闭包
9,装饰器" >9,装饰器
10. 使用 @ 标识符将装饰器应用到函数和利用*args and **kwargs" >10. 使用 @ 标识符将装饰器应用到函数和利用*args and **kwargs
Rumah pembangunan bahagian belakang Tutorial Python Python装饰器的详细介绍

Python装饰器的详细介绍

Jul 20, 2017 pm 09:16 PM
python ringkaskan

装饰器本身是一个Python函数,他可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个额外的对象。

先来了解几个定义:

1,函数

在python中,函数通过def关键字、函数名和可选的参数列表定义。通过return关键字返回值。我们举例来说明如何定义和调用一个简单的函数:

 

#coding:UTF8

def foo():
     return 1
print foo()

1
Salin selepas log masuk

方法体(当然多行也是一样的)是必须的,通过缩进来表示,在方法名的后面加上双括号()就能够调用函数

2,作用域

在Python中,函数会创建一个新的作用域. Python开发者可能会说函数有自己的命名空间.这就意味着在函数内部碰到一个变量的时候函数会优先在自己的命名空间里寻找.来简单举例说明本地作用域与全局作用域

#coding:UTF8

a_string = "This is a global variable"
def foo():
     print locals()
print globals() # doctest: +ELLIPSIS

foo()  #2


{&#39;foo&#39;: <function foo at 0x00000000026ECF98>, ...., &#39;a_string&#39;: &#39;This is a global variable&#39;,...}
{}
Salin selepas log masuk

内置的函数globals返回一个包含所有Python解释器知道的变量名称的字段(省略一部分)在#2调用了函数foo 把函数背部本地作用域里面的内容打印出来.可以看到,函数foo有自己的独立的命名空间,即使暂时命名空间啥也没有.

3,变量解析规则

当然并不是说在函数里就不能访问外面的全局变量.在Python的作用域规则里,创建变量一定会在当前作用域里创建一个变量,但访问或者修改变量是会现在当前作用域查找变量,没有找到匹配变量会依次向上在闭合的作用域里进行查找.so 如修改函数foo的是实现打印全局的作用域的变量也是可以的

#coding:UTF8

a_string = "This is a global variable"
def foo():
     print a_string   #1

foo()

This is a global variable
Salin selepas log masuk

在#1处,Python解释器会尝试查找变量a_string,当然在函数的本地作用域是找不到,so接着会在上层的作用域去查找
但在另外一方面,假如在函数的内部给全局变量赋值,结果会不一样 

#coding:UTF8

a_string = "This is a global variable"
def foo():
    a_string=&#39;Test&#39;  #1
    print locals()
    
foo()

{&#39;a_string&#39;: &#39;Test&#39;}

print a_string  #2

This is a global variable
Salin selepas log masuk

全局变量能够被访问到(如果是可变数据类型(像list,dict这些),甚至能够被更改),但赋值就不行了,在函数内部的#1,实际上新创建了一个局部变量,隐藏全局作用域中的同名变量,可以通过打印出全局命名空间中的内容得出这个结论.也可以在#2处打印出来的a_string没有改变

4,变量生存周期

值得注意的是:变量不仅是生存在一个个的命名空间里,都有自己的生存周期,如下:

#coding:UTF8

def foo():
     x = 1
foo()
print x # 1

NameError: name &#39;x&#39; is not defined
Salin selepas log masuk

#1处发生的错误不仅仅是因为作用域规则导致,而且和Python以及其他很多编程语言中函数调用实现的机制有关.在此地方执行时间点并没有什么有效的语法可以获取变量x的值,因为压根就不存在,函数foo的命名空间随着函数的调用开始而开始,结束而销毁

5,函数参数

Python允许想函数传递参数,参数会变成本地变量存在与函数内部

#coding:UTF8
def foo(x):
     print locals()
foo(1)

{&#39;x&#39;: 1}
Salin selepas log masuk

在Python中有很多的方式来定义和传递参数,简要说明下:函数的参数是必须的位置参数或是可选的命名,默认参数

#coding:UTF8
def foo(x, y=0): # 1
     return x - y
 
print foo(3, 1) # 2
2
print foo(3) # 3
3
print foo() # 4
TypeError: foo() takes at least 1 argument (0 given)
print foo(y=1, x=3) # 5
2
Salin selepas log masuk

  

在#1处定义了函数foo,有一个位置参数x和一个命名参数y 在#2通过常规的方式来调用函数,即使只有一个命名参数,但参数依然可以通过位置参数传递给函数.在调用函数的时候,对于命名参数y也可以完全不管就想#3所示一样.如命名参数没有接收到任何值的话,Python会自动使用声明的默认值.但不能省略第一个位置参数x,否则会像#4发生错误

python支持函数调用时的命名参数。看看#5处的函数调用,传递的是两个命名实参,这个时候因为有名称标识,参数传递的顺序也就不用在意了。

当然相反的情况也是正确的:函数的第二个形参是y,但通过位置的方式传递值给它。在#2处的函数调用foo(3,1),我们把3传递给了第一个参数,把1传递给了第二个参数,尽管第二个参数是一个命名参数。

6,嵌套函数

Python允许创建嵌套函数,就意味着可以在函数里定义函数而且现有的作用域和变量生存周期依旧适用

#coding:UTF8
def outer():
     x = 1
     def inner():
         print x # 1
     inner() # 2
 
outer()


1
Salin selepas log masuk

Python解释器需找一个叫x的本地变量,查找失败之后会继续向上层的作用域里查,这个上层的作用域定义在另外一个函数里,对于函数outer来说,变量x是一个本地变量
函数inner可以访问封闭的作用域.在#2处,可以调用函数inner,inner也仅仅是一个遵循Python变量解析规则的变量名,Python解释器会优先在outer的作用域里面对变量名inner查找匹配的变量

7,函数是Python世界中的一级类对象

在Python里函数和其他东西一样都是对象

#coding:UTF8
print issubclass(int, object) # all objects in Python inherit from a common baseclass
True
def foo():
     pass
print foo.__class__ # 1
<type &#39;function&#39;>
print issubclass(foo.__class__, object)
True
Salin selepas log masuk

  

函数在Python里就是对象,和其他一样,在Python里,函数只是一些普通的值而已,也就是说把函数像参数一样传递给其他的函数或者从函数里返回函数,如:

#coding:UTF8
def add(x, y):
     return x + y
def sub(x, y):
     return x - y
def apply(func, x, y): # 1
     return func(x, y) # 2
print apply(add, 2, 1) # 3
3
print apply(sub, 2, 1)
1
Salin selepas log masuk

  

在#1处看到函数准备接收一个函数的变量,只是一个普通的变量而已,和其他变量一样,在#2处调用传进来的函数:"()代表这调用函数的操作并且调用变量包含额值.在#3处,能看到传递函数并没有特殊的用法".函数的名称只是跟其他变量一样的标识符而已

Python把频繁要用的操作变成函数作为参数进行使用,向通过传递一个函数给内置排序函数的key参数 从而 来自定义排序规则

#coding:UTF8
def outer():
     def inner():
         print "Inside inner"
     return inner # 1
 
foo = outer() #2
print foo 
<function inner at 0x000000000269C048>

foo()
Inside inner
Salin selepas log masuk

在#1处恰好是函数标识符的变量inner作为返回值返回出来 "把函数inner返回出来,否则它根本不可能会被调用到" 每次函数outer呗调用,函数inner都会被重新定义,如果它不被当做变量返回额话,每次执行过后将不复存在

在#2处捕获返回值--函数inner,将它存在一个新的变量foo里.当对foo进行求值,确定包含函数inner,而且能够对它进行调用

8,闭包

#coding:UTF8

def outer():
     x = 1
     def inner():
         print x # 1
     return inner
foo = outer()
print foo.func_closure

(<cell at 0x00000000026861F8: int object at 0x0000000001E279A8>,)
Salin selepas log masuk

x是outer里的一个局部变量,当函数inner在#1处打印x时,Python解释器会在inner内部查找相应的变量,事实也查不到,接着会到封闭作用域里查找,并且找到匹配

从变量的生存周期来看,变量x是函数outer的一个本地变量,意味着只有当函数outer正在运行时才会存在,根据Python运行模式,无法再函数outer返回之后继续调用函数inner,在函数inner调用时,变量x早已不复存在,可能会发生一个运行时的错误
但返回的函数inner可以继续工作,Python支持一个叫做函数闭包的特性,嵌套定义在非全局作用域里的函数能够记住它在被定义的时候它所处的封闭命名空间,这能够通过查看函数的func_closure属性得出结论,这个属性里面包含封闭作用域里面的值(只会包含被捕捉到的值,比如x,如果在outer里面还定义了其他的值,封闭作用域里面是不会有的)

每次函数outer被调用的时候,函数inner都会被重新定义。现在变量x的值不会变化,所以每次返回的函数inner会是同样的逻辑
稍微改动下:

#coding:UTF8

def outer(x):
     def inner():
         print x # 1
     return inner
print1 = outer(1)
print2 = outer(2)

print1()
1
print2()
2
Salin selepas log masuk

从中可以看到闭包--被函数记住的封闭作用域--能够被用来创建自定义的函数,本质上是一个硬编码的参数.事实上并不是传递参数1或者2给函数inner,实际上是创建了能够打印各种数字的各种自定义版本

闭包单独拿出来就是一个非常强大的功能,在某些方面:outer像是给inner服务器的构造器,x像是一个私有变量

9,装饰器

装饰器其实就是一个闭包,把一个函数当做参数然后返回一个替代版参数

#coding:UTF8

def outer(func):
     def inner():
         print "before func"
         ret = func() # 1
         return ret + 1
     return inner
def foo():
     return 1
decorated = outer(foo) # 2
print decorated()

before func
2
Salin selepas log masuk

  

定义了一个函数outer,只有一个func参数,在其定义了嵌套的函数inner,inner会打印一串字符串,然后调用func,在#1得到返回值,在outer每次调用时func值可能会不一样,但不管怎用,都会调用它,最后,inner返回func()+1的值,通过调用在#2处存储decorated里的函数能够看到被打印出来的字符串以及返回值2,而不是期望中调用函数foo得到的返回值1。


可以认为变量decorated是函数foo的一个装饰版本,一个加强版本。事实上如果打算写一个有用的装饰器的话,可能会想愿意用装饰版本完全取代原先的函数foo,这样总是会得到我们的”加强版“foo。想要达到这个效果,完全不需要学习新的语法,简单地赋值给变量foo就行了:

foo = outer(foo)
Salin selepas log masuk

现在,任何怎么调用都不会牵扯到原先的函数foo,都会得到新的装饰版本的foo,现在还是来写一个有用的装饰器

#coding:UTF8

import time
def bar():
    time.sleep(2)
    print(&#39;in the bar&#39;)
def test2(func):
    print(func)
    return func

# print(test2(bar))
bar=test2(bar)
bar()  #run bar

<function bar at 0x00000000026BCF98>
in the bar
Salin selepas log masuk

  

10. 使用 @ 标识符将装饰器应用到函数和利用*args and **kwargs

Python2.4支持使用标识符@将装饰器应用在函数上,只需要在函数的定义前加上@和装饰器的名称。在上一节的例子里我们是将原本的方法用装饰后的方法代替:

bar=test2(bar)
Salin selepas log masuk

这种方式能够在任何时候对任意方法进行包装。但是如果自定义一个方法,可以使用@进行装饰:

 1 #coding:UTF8 2  3 import time 4  5 def test2(func): 6     print(func) 7     return func 8 @test2 9 def bar():10     time.sleep(2)11     print(&#39;in the bar&#39;)12 13 bar()  #run bar
Salin selepas log masuk

 1 #coding:UTF8 2  3 import time 4 def timer(func): #timer(test1)  func=test1 5     def deco(*args,**kwargs): 6         start_time=time.time() 7         func(*args,**kwargs)   #run test1() 8         stop_time = time.time() 9         print("the func run time  is %s" %(stop_time-start_time))10     return deco11 @timer  #test1=timer(test1)12 def test1():13     time.sleep(1)14     print(&#39;in the test1&#39;)15 16 @timer # test2 = timer(test2)  = deco  test2(name) =deco(name)17 def test2(name,age):18     print("test2:",name,age)19 20 test1()21 test2("Tom",22)22 23 24 in the test125 the func run time  is 1.0520000457826 (&#39;test2:&#39;, &#39;Tom&#39;, 22)27 the func run time  is 0.0
Salin selepas log masuk

下面贡献一个高级版的装饰器:

 1 #coding:utf8 2 import time 3 user,passwd = &#39;hbert&#39;,&#39;abc&#39; 4 def auth(auth_type): 5     print("auth func:",auth_type) 6     def outer_wrapper(func): 7         def wrapper(*args, **kwargs): 8             #print("wrapper func args:", *args, **kwargs) 9             if auth_type == "local":10                 username = raw_input("Username:").strip()11                 password = raw_input("Password:").strip()12                 if user == username and passwd == password:13                     print("\033[32;1mUser has passed authentication\033[0m")14                     res = func(*args, **kwargs)  # from home15                     print("---after authenticaion ")16                     return res17                 else:18                     exit("\033[31;1mInvalid username or password\033[0m")19             elif auth_type == "ldap":20                 print("搞毛线ldap,不会。。。。")21 22         return wrapper23     return outer_wrapper24 25 def index():26     print("welcome to index page")27 @auth(auth_type="local") # home = wrapper()28 def home():29     print("welcome to home  page")30     return "from home"31 32 @auth(auth_type="ldap")33 def bbs():34     print("welcome to bbs  page")35 36 index()37 print(home()) #wrapper()38 bbs()
Salin selepas log masuk

 

Atas ialah kandungan terperinci Python装饰器的详细介绍. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

AI Hentai Generator

AI Hentai Generator

Menjana ai hentai secara percuma.

Artikel Panas

R.E.P.O. Kristal tenaga dijelaskan dan apa yang mereka lakukan (kristal kuning)
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Tetapan grafik terbaik
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Cara Memperbaiki Audio Jika anda tidak dapat mendengar sesiapa
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: Cara Membuka Segala -galanya Di Myrise
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Adakah Mysql perlu membayar Adakah Mysql perlu membayar Apr 08, 2025 pm 05:36 PM

MySQL mempunyai versi komuniti percuma dan versi perusahaan berbayar. Versi komuniti boleh digunakan dan diubahsuai secara percuma, tetapi sokongannya terhad dan sesuai untuk aplikasi dengan keperluan kestabilan yang rendah dan keupayaan teknikal yang kuat. Edisi Enterprise menyediakan sokongan komersil yang komprehensif untuk aplikasi yang memerlukan pangkalan data yang stabil, boleh dipercayai, berprestasi tinggi dan bersedia membayar sokongan. Faktor yang dipertimbangkan apabila memilih versi termasuk kritikal aplikasi, belanjawan, dan kemahiran teknikal. Tidak ada pilihan yang sempurna, hanya pilihan yang paling sesuai, dan anda perlu memilih dengan teliti mengikut keadaan tertentu.

Cara Menggunakan MySQL Selepas Pemasangan Cara Menggunakan MySQL Selepas Pemasangan Apr 08, 2025 am 11:48 AM

Artikel ini memperkenalkan operasi pangkalan data MySQL. Pertama, anda perlu memasang klien MySQL, seperti MySqlworkbench atau Command Line Client. 1. Gunakan perintah MySQL-Uroot-P untuk menyambung ke pelayan dan log masuk dengan kata laluan akaun root; 2. Gunakan CreateTatabase untuk membuat pangkalan data, dan gunakan Pilih pangkalan data; 3. Gunakan createtable untuk membuat jadual, menentukan medan dan jenis data; 4. Gunakan InsertInto untuk memasukkan data, data pertanyaan, kemas kini data dengan kemas kini, dan padam data dengan padam. Hanya dengan menguasai langkah -langkah ini, belajar menangani masalah biasa dan mengoptimumkan prestasi pangkalan data anda boleh menggunakan MySQL dengan cekap.

Mysql tidak dapat dipasang setelah memuat turun Mysql tidak dapat dipasang setelah memuat turun Apr 08, 2025 am 11:24 AM

Sebab utama kegagalan pemasangan MySQL adalah: 1. Isu kebenaran, anda perlu menjalankan sebagai pentadbir atau menggunakan perintah sudo; 2. Ketergantungan hilang, dan anda perlu memasang pakej pembangunan yang relevan; 3. Konflik pelabuhan, anda perlu menutup program yang menduduki port 3306 atau mengubah suai fail konfigurasi; 4. Pakej pemasangan adalah korup, anda perlu memuat turun dan mengesahkan integriti; 5. Pembolehubah persekitaran dikonfigurasikan dengan salah, dan pembolehubah persekitaran mesti dikonfigurasi dengan betul mengikut sistem operasi. Selesaikan masalah ini dan periksa dengan teliti setiap langkah untuk berjaya memasang MySQL.

Fail muat turun MySQL rosak dan tidak boleh dipasang. Penyelesaian pembaikan Fail muat turun MySQL rosak dan tidak boleh dipasang. Penyelesaian pembaikan Apr 08, 2025 am 11:21 AM

Fail muat turun mysql adalah korup, apa yang perlu saya lakukan? Malangnya, jika anda memuat turun MySQL, anda boleh menghadapi rasuah fail. Ia benar -benar tidak mudah hari ini! Artikel ini akan bercakap tentang cara menyelesaikan masalah ini supaya semua orang dapat mengelakkan lencongan. Selepas membacanya, anda bukan sahaja boleh membaiki pakej pemasangan MySQL yang rosak, tetapi juga mempunyai pemahaman yang lebih mendalam tentang proses muat turun dan pemasangan untuk mengelakkan terjebak pada masa akan datang. Mari kita bercakap tentang mengapa memuat turun fail rosak. Terdapat banyak sebab untuk ini. Masalah rangkaian adalah pelakunya. Gangguan dalam proses muat turun dan ketidakstabilan dalam rangkaian boleh menyebabkan rasuah fail. Terdapat juga masalah dengan sumber muat turun itu sendiri. Fail pelayan itu sendiri rosak, dan sudah tentu ia juga dipecahkan jika anda memuat turunnya. Di samping itu, pengimbasan "ghairah" yang berlebihan beberapa perisian antivirus juga boleh menyebabkan rasuah fail. Masalah Diagnostik: Tentukan sama ada fail itu benar -benar korup

Penyelesaian kepada perkhidmatan yang tidak dapat dimulakan selepas pemasangan MySQL Penyelesaian kepada perkhidmatan yang tidak dapat dimulakan selepas pemasangan MySQL Apr 08, 2025 am 11:18 AM

MySQL enggan memulakan? Jangan panik, mari kita periksa! Ramai kawan mendapati bahawa perkhidmatan itu tidak dapat dimulakan selepas memasang MySQL, dan mereka sangat cemas! Jangan risau, artikel ini akan membawa anda untuk menangani dengan tenang dan mengetahui dalang di belakangnya! Selepas membacanya, anda bukan sahaja dapat menyelesaikan masalah ini, tetapi juga meningkatkan pemahaman anda tentang perkhidmatan MySQL dan idea anda untuk masalah penyelesaian masalah, dan menjadi pentadbir pangkalan data yang lebih kuat! Perkhidmatan MySQL gagal bermula, dan terdapat banyak sebab, mulai dari kesilapan konfigurasi mudah kepada masalah sistem yang kompleks. Mari kita mulakan dengan aspek yang paling biasa. Pengetahuan asas: Penerangan ringkas mengenai proses permulaan perkhidmatan MySQL Startup. Ringkasnya, sistem operasi memuatkan fail yang berkaitan dengan MySQL dan kemudian memulakan daemon MySQL. Ini melibatkan konfigurasi

Cara mengoptimumkan prestasi pangkalan data selepas pemasangan MySQL Cara mengoptimumkan prestasi pangkalan data selepas pemasangan MySQL Apr 08, 2025 am 11:36 AM

Pengoptimuman prestasi MySQL perlu bermula dari tiga aspek: konfigurasi pemasangan, pengindeksan dan pengoptimuman pertanyaan, pemantauan dan penalaan. 1. Selepas pemasangan, anda perlu menyesuaikan fail my.cnf mengikut konfigurasi pelayan, seperti parameter innodb_buffer_pool_size, dan tutup query_cache_size; 2. Buat indeks yang sesuai untuk mengelakkan indeks yang berlebihan, dan mengoptimumkan pernyataan pertanyaan, seperti menggunakan perintah menjelaskan untuk menganalisis pelan pelaksanaan; 3. Gunakan alat pemantauan MySQL sendiri (ShowProcessList, ShowStatus) untuk memantau kesihatan pangkalan data, dan kerap membuat semula dan mengatur pangkalan data. Hanya dengan terus mengoptimumkan langkah -langkah ini, prestasi pangkalan data MySQL diperbaiki.

Bagaimana untuk mengoptimumkan prestasi MySQL untuk aplikasi beban tinggi? Bagaimana untuk mengoptimumkan prestasi MySQL untuk aplikasi beban tinggi? Apr 08, 2025 pm 06:03 PM

Panduan Pengoptimuman Prestasi Pangkalan Data MySQL Dalam aplikasi yang berintensifkan sumber, pangkalan data MySQL memainkan peranan penting dan bertanggungjawab untuk menguruskan urus niaga besar-besaran. Walau bagaimanapun, apabila skala aplikasi berkembang, kemunculan prestasi pangkalan data sering menjadi kekangan. Artikel ini akan meneroka satu siri strategi pengoptimuman prestasi MySQL yang berkesan untuk memastikan aplikasi anda tetap cekap dan responsif di bawah beban tinggi. Kami akan menggabungkan kes-kes sebenar untuk menerangkan teknologi utama yang mendalam seperti pengindeksan, pengoptimuman pertanyaan, reka bentuk pangkalan data dan caching. 1. Reka bentuk seni bina pangkalan data dan seni bina pangkalan data yang dioptimumkan adalah asas pengoptimuman prestasi MySQL. Berikut adalah beberapa prinsip teras: Memilih jenis data yang betul dan memilih jenis data terkecil yang memenuhi keperluan bukan sahaja dapat menjimatkan ruang penyimpanan, tetapi juga meningkatkan kelajuan pemprosesan data.

Adakah mysql memerlukan internet Adakah mysql memerlukan internet Apr 08, 2025 pm 02:18 PM

MySQL boleh berjalan tanpa sambungan rangkaian untuk penyimpanan dan pengurusan data asas. Walau bagaimanapun, sambungan rangkaian diperlukan untuk interaksi dengan sistem lain, akses jauh, atau menggunakan ciri -ciri canggih seperti replikasi dan clustering. Di samping itu, langkah -langkah keselamatan (seperti firewall), pengoptimuman prestasi (pilih sambungan rangkaian yang betul), dan sandaran data adalah penting untuk menyambung ke Internet.

See all articles