948 ℉

15.01.06

go语言中的map实战

1. 简介

哈希表是计算机科学中最重要的数据结构之一。许多哈希表的实现有着千差万别的特性,但是总体上他们都提供了快速查询,添加和删除功能。go语言提供了内置数据类型map。

2. 声明和初始化

map的声明格式如下:

map[KeyType] ValueType

KeyType类型必须是可以比较的,而ValueType可以是任意类型,甚至是另一个map。

以下这个m是一个键值为string,值为int的哈希表:

var m map[string]int

哈希表类型是引用类型,像指针或者切片m指向的值是nil;它没有指向一个初始化了的哈希表。一个nil哈希表在读的时候,像一个空的哈希表,但是尝试向m中写数据会引发一个运行时panic,所以别那样做。 使用内置函数make初始化一个哈希表:

m = make(map[string]int) 

make函数申请并初始化了一个哈希表的数据结构并且返回一个指向这个初始化好了的哈希表。 哈希表的数据结构是go本身运行时的一个实现细节,并没有被语言本身所规定【翻者补充:类似c++不同编译器如何实现虚函数一样吧】。 文章中只关心哈希表的使用而非实现。

3. 使用哈希表

go中的哈希表的使用方法和其他语言相似,向哈希表中插入一个键为“route”值为66的语句为:

m["route"] = 66

查询键为“route”并且把对应的值赋给新的变量i的语句为:

i := m["route"]

如果查询的键值在哈希表中不存在,将拿到值类型的“0”值。 以m为例,值类型为int,则“0”值为0:

j := m["root"]
// j == 0

len是返回哈希表中数据个数的内置函数:

n := len(m)

delete是删除哈希表中某一键值数据的内置函数:

delete(m, "route")

delete函数返回值为空,如果键值不存在则不做任何操作。

使用两个返回值的方式可以检查键值是否存在:

i, ok := m["route"]

在这个语句中,i被赋值为哈希表中键值为“route”的值。如果那个键值不存在,i被赋值为值类型中的“0”值。第二个返回值是布尔类型,如果是true,表明键值存在,否则不存在。

如果只是检查键值是否存在,则第一个返回值使用下划线“_“:

_, ok := m["route"]

如果要遍历一个哈希表中的内容,使用range关键字:

for key, value := range m {
    fmt.Println("Key:", key, "Value:", value)
}

如果要初始化数据,使用哈希表的字面表示:

commits := map[string]int{
    "rsc": 3711,
    "r":   2138,
    "gri": 1908,
    "adg": 912,
}

同样的语法可以初始化一个空的哈希表,这种用法达到的效果和make一致:

m = map[string]int{}

4. 利用“0”值 

在哈希表中查询数据,如果键值不存在,返回一个值类型的“0”值是很方便的:

例如,一个布尔值的哈希表可以被用来当做一个set使用(布尔类型的“0”值是false)。下边这个例子遍历一个Node的链表并且打印他们的值,它使用了一个Node的指针为key的哈希表去判断链表中是否有环:

type Node struct {
    Next  *Node
    Value interface{}
}
var first *Node

visited := make(map[*Node]bool)
for n := first; n != nil; n = n.Next {
    if visited[n] {
        fmt.Println("cycle detected")
        break
    }
    visited[n] = true
    fmt.Println(n.Value)
}

visited[n]表达式如果是真,表明n已经被访问过了,如果是假,表明还没有。 这样就不需要使用两个返回值的方式去检查n是否在map中真的存在;默认的“0”值帮我们做了。

另一个有用的例子是切片的哈希表。向一个空切片中添加数据会申请一个新的切片所以向一个切片的map中append数据会只占用一行;不需要检查key是否存在。在下边的例子中,切片被用来存放person类型的值,每个Person有一个Name字段和一个切片,这个例子中创建了一个哈希表关联每种物品和一个他所喜欢东西的切片。【做了个倒排?】

type Person struct {
    Name  string
    Likes []string
}
var people []*Person

likes := make(map[string][]*Person)
for _, p := range people {
    for _, l := range p.Likes {
        likes[l] = append(likes[l], p)
    }
}

打印喜欢cheese的People:

for _, p := range likes["cheese"] {
    fmt.Println(p.Name, "likes cheese.")
}

打印出喜欢bacon的人数

 fmt.Println(len(likes["bacon"]), "people like bacon.")

注意,这里range函数和len函数都把nil切片看做一个长度为零的切片,即使没有人喜欢cheese或者bacon,也不会有问题。

5. 键的类型

像刚才提过的,键的类型必须是可比较的。go语言的spec中准确的定义了这个要求,简而言之,可以比较的类型包括:布尔,数字,字符串,指针,消息channel,接口类型和任何包含了以上类型的结构体和数组。不在此范围的类型包括切片,哈希表和函数;这些类型不能使用 “==” 做比较,也不能被用来做哈希表的键值。

很明显字符串,整型和其他基础类型可以作为哈希表的键。 意想不到的是结构体也可以作为键值,例如,这个哈希表的哈希表可以用来存放不同国家的访问数。

hits := make(map[string]map[string]int)

这是一个键为字符串,值为字符串到int的哈希表。最外边表的每一个键值是到达内部哈希表的路径,每个被嵌套的哈希表的键值是一个两个字母的国家码。这个表达式可以得到一个澳大利亚人访问文档页面的次数。

n := hits["/doc/"]["au"]

不幸的是这个方法在添加数据的时候很笨重,对每个键,需要判断嵌套的map是否存在,如果有必要的话需要创建:

func add(m map[string]map[string]int, path, country string) {
    mm, ok := m[path]
    if !ok {
        mm = make(map[string]int)
        m[path] = mm
    }
    mm[country]++
}
add(hits, "/doc/", "au")

另一方面,使用结构体作为键的哈希表可以解除这种复杂性:

type Key struct {
    Path, Country string
}

hits := make(map[Key]int)

当一个越南人访问主页的时候,一行就可以搞定:

hits[Key{"/", "vn"}]++

查看多少个瑞士人查看了spec页面的语句也很简单:

n := hits[Key{"/ref/spec", "ch"}]

6. 并发

哈希表在有并发的场景并不安全:同时读写一个哈希表的后果是不确定的。如果你需要使用goroutines同时对一个哈希表做读写,对哈希表的访问需要通过某种同步机制做协调。一个常用的方法是是使用 sync.RWMutex。

这个语句声明了一个counter变量,这是一个包含了一个map和sync.RWMutex的匿名结构体。

var counter = struct{
    sync.RWMutex
    m map[string]int
}{m: make(map[string]int)}

读counter前,获取读锁:

counter.RLock()
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)

写counter前,获取写锁

counter.Lock()
counter.m["some_key"]++
counter.Unlock()

7. 遍历顺序

当使用range循环遍历一个哈希表的时候,遍历顺序是不保证稳定的。因为Go1版本将map的便利顺序随机化了,如果程序依赖之前实现中的稳定的便利顺序的话。 如果你需要一个稳定的遍历顺序,你必须维护一个独立的数据结构用来保证这个顺序。下面这个例子使用了一个独立的排序切片,按照键值顺序打印一个 map[int] string数据:

import "sort"

var m map[int]string
var keys []int
for k := range m {
    keys = append(keys, k)
}
sort.Ints(keys)
for _, k := range keys {
    fmt.Println("Key:", k, "Value:", m[k])
}

另:@特价萝卜 的 《Go并发实战》有列斯的讲解 详见P83 代码

jack.zh 标签:go 继续阅读

1839 ℉

14.12.25

Golang的HttpClient

Golang的HttpClient


golang要请求远程网页,可以使用net/http包中的client提供的方法实现。查看了官方网站有一些示例,没有太全面的例子,于是自己整理了一下。

Get请求

Get请求可以直接http.Get方法,非常简单。

func httpGet() {
	resp, err := http.Get("http://www.example.com/aa?id=1")
	if err != nil {
		// handle error
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// handle error
	}
	fmt.Println(string(body))
}

Post请求

一种是使用http.Post方式

func httpPost() {
	resp, err := http.Post("http://www.example.com/bb",
		"application/x-www-form-urlencoded",
		strings.NewReader("name=cjb"))
	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// handle error
	}
	fmt.Println(string(body))
}
// Tips:使用这个方法的话,第二个参数要设置成”application/x-www-form-urlencoded”,否则post参数无法传递。

一种是使用http.PostForm方法

func httpPostForm() {
	resp, err := http.PostForm("http://www.example.com/cc",
		url.Values{"key": {"Value"}, "id": {"123"}})
	if err != nil {
		// handle error
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// handle error
	}
	fmt.Println(string(body))
}

复杂的请求

有时需要在请求的时候设置头参数、cookie之类的数据,就可以使用http.Do方法。

func httpDo() {
	client := &http.Client{}
	req, err := http.NewRequest("POST", "http://www.example.com/dd", strings.NewReader("name=cjb"))
	if err != nil {
		// handle error
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
	req.Header.Set("Accept-Charset","GBK,utf-8;q=0.7,*;q=0.3")
	req.Header.Set("Accept-Encoding","gzip,deflate,sdch")
	req.Header.Set("Accept-Language","zh-CN,zh;q=0.8")
	req.Header.Set("Cache-Control","max-age=0")
	req.Header.Set("Connection","keep-alive")
	req.Header.Set("User-Agent","chrome 100")
	req.Header.Set("Cookie", "name=anny")
	resp, err := client.Do(req)
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// handle error
	}
	fmt.Println(string(body))
}
// 同上面的post请求,必须要设定Content-Type为application/x-www-form-urlencoded,post参数才可正常传递。

然后我们其实可以利用上面的知识做一个ab测试工具

当然 已经有人先行了

压力测试工具

golang的类似AB测试工具:gohttpbench

go build -o gb github.com/parkghost/gohttpbench

甜点 Golang的Web Server简单示例

package main
 
import (
    "net/http"
)
 
func SayHello(w http.ResponseWriter, req *http.Request) {
    w.Write([]byte("Hello"))
}
 
func main() {
    http.HandleFunc("/hello", SayHello)
    http.ListenAndServe(":8001", nil)
 
}

完整示例

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
)

func httpGet() {
	resp, err := http.Get("http://www.example.com/aa?id=1")
	if err != nil {
		// handle error
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// handle error
	}

	fmt.Println(string(body))
}

func httpPost() {
	resp, err := http.Post("http://www.example.com/bb",
		"application/x-www-form-urlencoded",
		strings.NewReader("name=cjb"))
	if err != nil {
		fmt.Println(err)
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// handle error
	}

	fmt.Println(string(body))
}

func httpPostForm() {
	resp, err := http.PostForm("http://www.example.com/cc",
		url.Values{"key": {"Value"}, "id": {"123"}})

	if err != nil {
		// handle error
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// handle error
	}

	fmt.Println(string(body))
}

func httpDo() {
	client := &http.Client{}

	req, err := http.NewRequest("POST", "http://www.example.com/dd", strings.NewReader("name=cjb"))
	if err != nil {
		// handle error
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
	req.Header.Set("Accept-Charset","GBK,utf-8;q=0.7,*;q=0.3")
	req.Header.Set("Accept-Encoding","gzip,deflate,sdch")
	req.Header.Set("Accept-Language","zh-CN,zh;q=0.8")
	req.Header.Set("Cache-Control","max-age=0")
	req.Header.Set("Connection","keep-alive")
	req.Header.Set("User-Agent","chrome 100")
	req.Header.Set("Cookie", "name=anny")

	resp, err := client.Do(req)

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// handle error
	}

	fmt.Println(string(body))
}

func main() {
	httpGet()
	httpPost()
	httpPostForm()
	httpDo()
}

jack.zh 标签:golang 继续阅读

1207 ℉

14.12.24

Golang json 基本处理Example

``` go package main

import ( “encoding/json” “fmt” “os” )

type ConfigStruct struct { Host string json:"host" Port int json:"port" AnalyticsFile string json:"analytics_file" StaticFileVersion int json:"static_file_version" StaticDir string json:"static_dir" TemplatesDir string json:"templates_dir" SerTcpSocketHost string json:"serTcpSocketHost" SerTcpSocketPort int json:"serTcpSocketPort" Fruits []string json:"fruits" }

type Other struct { SerTcpSocketHost string json:"serTcpSocketHost" SerTcpSocketPort int json:"serTcpSocketPort" Fruits []string json:"fruits" }

func main() { jsonStr := { "host": "http://localhost:10086", "port": 10086, "analytics_file": "abc", "static_file_version": 1, "static_dir": "/Users/jack/workspace/gowork/src/goTest/", "templates_dir": "/Users/jack/workspace/gowork//templates/", "serTcpSocketHost": ":12340", "serTcpSocketPort": 12340, "fruits": ["apple", "peach"] }

//json str 转map
var dat map[string]interface{}
if err := json.Unmarshal([]byte(jsonStr), &dat); err == nil {
    fmt.Println("==============json str 转map=======================")
    fmt.Println(dat)
    fmt.Println(dat["host"])
}

//json str 转struct
var config ConfigStruct
if err := json.Unmarshal([]byte(jsonStr), &config); err == nil {
    fmt.Println("================json str 转struct==================")
    fmt.Println(config)
    fmt.Println(config.Host)
}

//json str 转struct(部份字段)
var part Other
if err := json.Unmarshal([]byte(jsonStr), &part); err == nil {
    fmt.Println("================json str 转struct===================")
    fmt.Println(part)
    fmt.Println(part.SerTcpSocketPort)
}

//struct 到json str
if b, err := json.Marshal(config); err == nil {
    fmt.Println("================struct 到json str===================")
    fmt.Println(string(b))
}

//map 到json str
fmt.Println("================map 到json str============================")
enc := json.NewEncoder(os.Stdout)
enc.Encode(dat)

//array 到 json str
arr := []string{"hello", "apple", "python", "golang", "base", "peach", "pear"}
lang, err := json.Marshal(arr)
if err == nil {
    fmt.Println("================array 到 json str========================")
    fmt.Println(string(lang))
}

//json 到 []string
var wo []string
if err := json.Unmarshal(lang, &wo); err == nil {
    fmt.Println("================json 到 []string===========================")
    fmt.Println(wo)
}

}```

jack.zh 标签:golang 继续阅读

1161 ℉

14.12.18

Linux发行版之我见

写这么一篇小文是因为一个哥们要去做Linux系统了,好高大上的职位呢,但是他是一个Linux小白,所以我就自告奋勇写了这么一篇东西,让我这个Linux大白装一下逼。顺便说一下,我装过的Linux发行版最起码是这个的介绍的3倍。但这有什么用,我又不是电脑城搞装机的。

前提:我是作为开发者来平说这些发行版的

以下言论均个人浅显意见,不代表任何其他个人和组织。

Debian系列

Debian 地址

Debian是Debian系列的鼻祖,其稳定性与强大的包管理系统使其能够胜任作为服务器操作系统的责任,但是我很少见开发者直接使用它作为桌面系统,最起码在我的使用周期里面,我连桌面都没有安装,直接终端操作。

Ubuntu 地址

作为最为人知的操作系统,Ubuntu在Linux的推广和被人们熟知的介绍中功不可没,Ubuntu的推出,标志着Linux桌面系统推广的正式开始。我的工作中大量的使用Ubuntu,原因也在于其友好的桌面和deb包得方便。当然,我只是用他的XFCE版本。

Mint 地址

Mint完全来自于Ubuntu,更多的是预装软件和桌面上下功夫。不过可惜的是, 我对Cinnamon和KDE都不感冒,他的预装软件对我的吸引能力也有限,而且我还得自己去配置中文环境,所以,在我看来没什么特色。

Luna 地址

说句实话,我被他惊艳到了,桌面确实做的不错,很有MAC的感觉,前提是你得准备好升级你老机子的配置,还的有足够的耐心忍受它莫名其妙的卡顿甚至崩溃。奥,对了,多说一句 他也基于Ubuntu。

RedHat 系列

RedHat 地址

老牌服务器厂商,在Linux江湖掘金的开创性公司的发行版本,收费。我第一次安装使用Linux就是他了,虽然后来使用的不多。还记得三张光盘依次放进光驱的场景,what the F**k。不多说(其实我知道的不多,嘘…),没准你们公司的服务器跑的还是他。

Fedora 地址

RedHat公司发行的社区版,用户被称为小白鼠,激进的软件更新,强大的开发者工具。我做过这个小白鼠两年,说句实在话,我喜欢这个发行版。

  1. 简洁而且厚重,桌面简洁,Gnome3的桌面,是你更加想投入工作而不是点击鼠标
  2. 强大的开发者支持,这得益于RPM包和Fedora内置的工具的全面
  3. 社区丰富而且干净,丰富是不缺乏解决问题的答案,干净是很少有如Ubuntu社区版包罗万象,比如桌面怎么设置3D,在Ubuntu下如何看片,在Fedora社区基本匿迹
  4. 没传说的那么不稳定,当然,作为服务器,还是慎重,CentOS或许在服务器上面是更好地选择

CentOS 地址

依旧是RedHat系的,其稳定性使其主要的特点,当然,默认的CentOS的软件还是有点旧,鱼和熊掌不可兼得嘛。奥还有,其默认的桌面系统还不错,其实完全可以作为主力开发PC系统的。

BSD系列

BSD当然不属于Linux发行版,这里列出来just多一种选择了

FeeeBSD 地址

这个貌似是最知名的BSD了,稳定性和包管理工具都很不错,而且大部分软件与Linux兼容,毕竟有ISO POSIX嘛。不过我还真见过用FreeBSD做开发的主,常年的FreeBSD,系统IMG定期备份,不装桌面,一个终端屏幕,鼠标上落一层灰,键盘锃亮。对于我这种偶尔用一下也就是看一下FreeBSD真神样子的人来说,真想跪地膜拜,又怕有辱斯文。

NetBSD 地址

FreeBSD的兄弟,NetBSD与OpenBSD更像一些, 没有FreeBSD的/stand/sysinstall,用起来没有FreeBSD那么舒服(个人感觉。 FreeBSD用ports,NetBSD用pkgsrc。Sorry,我知道的不多,有需要我可以介绍你认识那位大神,已移民。

Plan9 地址

你是说这货?好,那我就不说这货了。不说的原因不是它不是Linix发行版,是因为我真的不了解他,只是听说过他的威名,始于Golang。

其他

OpenSUSE 地址

貌似国外很多人用这货,我用过一点时间,不是很习惯,特别是包管理,桌面真心还不错,没有粗劣的感觉。

逼格

下面是Linux鄙视链的顶级货,我或多或少都接触过,虽然这样有一部分原因是可以去鄙视别人,但如果你比我见过的更多,比如可以用一根针和一个空光盘手动刻录一个Linux发行版,或者只是对下面真的都有所了解,求求你,教我,别鄙视我。

自己制作

材料:Kernel + BusyBox

逼格的最高境界

在我眼里,这个不是技术活,更像是手艺活,要有足够的耐心和英语阅读和他妈的推断能力,要不我怎么能知道你勒出的十个选型我选哪个是正确的。深究这个技术,你可以去研读内核源码,或者找一个嵌入式的大牛问一下,我这个嵌入式行内的门外汉就是在一个大牛的指导下掌握这门手艺的。这门手艺外传,从我开始,传女不传男。

LFS 地址

真想有自己的发行版?我确信,LFS会是你的选择,而且,他的社区文档丰富,一步一步教你,还有中文奥…

Gentoo 地址

你要按照他的规则全部手动编译,从内核到rootfs到各种配置,你会学到很多,当然,他也会折腾你,比我,我就在我的IBM T42装了一周才成功。不过,真的在定制后跑的飞快哎,妈妈再也不用担心我的04年的小本跑不动了。

ArchLinux 地址

一个神奇的发行版,也需要你手动安装,只是不需要你编译了,pkg的形式发布的安装就好了。滚动升级,什么都是最新的,你用它会感觉自己很年轻,当然,一般用它的人是真年轻。

ArchBang 地址

连Arch都搞不定?你有福了。

基于Arch的发行版,减少了安装的难度,默认增加了openbox的管理工具。

你装完之后可以自豪的说,看,我的Arch!

国产操作系统

我没骗你,国产操作系统。真的有呢。

还有,我不提中科红旗,有人有意见吗?

起点OS StartOS

不是我的菜,居然不暴漏GRUB和终端,你在搞笑吗?

中标麒麟 Kylin

我确定他在骗我,因为他就是Ubuntu,唯一的Kylin特色就是安装了一些国产软件,而且kylin的那个什么破端口的源超级不稳定。顺便吐槽一下,你他妈的就不能改一下那个该死的农历日历啊,风格不统一,操作方式不统一,你们是在认真的说:“我们在做最好的国产Linux!”

Deepin 地址

我认为最靠谱的国产Linux发行版,最起码我看到每一个发行版都在改进一些东西,在12年和13年,我不喜欢它,虽然每个版本新出来我都会装一下玩一下,开始都有点感觉惊艳,但是几天之后就开始不爽了,主要表现在性能上面,虚拟机里面各种卡顿死机,我的T42真机表现也不好,而且我确信兼容性有问题,没准还内存泄露。据说14版本有了很大的改进,还用到了QML和Golang,都是我喜欢的东西,也许,该是继续关注它的时候了,没准哪天,我会把助主力换成他。最起码他有不输于Terminator和mac上的iTerm的深度终端,BTW,我看过深度终端的代码,草,为什么我就没这个情怀,在他之前写一个这东西造福人类呢,好处是我现在已经有这个觉悟了。

还有,能不能告诉我你们的发行版时间表,预改进项目?我脾气很急很爆得!

最后

我推荐的几个: + Arch 或是 gentoo 如果你喜欢折腾 + 自己编译或者LFS,如果你是被孽狂 + Fedora 已经推荐过 + Ubuntu 开箱即用 + Deepin 最近的了解,感觉这货应该靠谱,不是忽悠钱的, 毕竟,他想摆脱Linux桌面的粗鄙

我上面说的也许太轻浮和“拿来”了,为了挽救这个印象,我会继续写一些Linux Tip之类的小文,并计划写一个好用的终端todo

jack.zh 标签:linux 继续阅读

932 ℉

14.12.12

Python单例-我的单例

#-*- encoding=utf-8 -*-
print '----------------------方法1--------------------------'
#方法1,实现__new__方法
#并在将一个类的实例绑定到类变量_instance上,
#如果cls._instance为None说明该类还没有实例化过,实例化该类,并返回
#如果cls._instance不为None,直接返回cls._instance
class Singleton(object):
    def __new__(cls, *args, **kw):
        if not hasattr(cls, '_instance'):
            orig = super(Singleton, cls)
            cls._instance = orig.__new__(cls, *args, **kw)
        return cls._instance

class MyClass(Singleton):
    a = 1

one = MyClass()
two = MyClass()

two.a = 3
print one.a
#3
#one和two完全相同,可以用id(), ==, is检测
print id(one)
#29097904
print id(two)
#29097904
print one == two
#True
print one is two
#True

print '----------------------方法2--------------------------'
#方法2,共享属性;所谓单例就是所有引用(实例、对象)拥有相同的状态(属性)和行为(方法)
#同一个类的所有实例天然拥有相同的行为(方法),
#只需要保证同一个类的所有实例具有相同的状态(属性)即可
#所有实例共享属性的最简单最直接的方法就是__dict__属性指向(引用)同一个字典(dict)
#可参看:http://code.activestate.com/recipes/66531/
class Borg(object):
    _state = {}
    def __new__(cls, *args, **kw):
        ob = super(Borg, cls).__new__(cls, *args, **kw)
        ob.__dict__ = cls._state
        return ob

class MyClass2(Borg):
    a = 1

one = MyClass2()
two = MyClass2()

#one和two是两个不同的对象,id, ==, is对比结果可看出
two.a = 3
print one.a
#3
print id(one)
#28873680
print id(two)
#28873712
print one == two
#False
print one is two
#False
#但是one和two具有相同的(同一个__dict__属性),见:
print id(one.__dict__)
#30104000
print id(two.__dict__)
#30104000

print '----------------------方法3--------------------------'
#方法3:本质上是方法1的升级(或者说高级)版
#使用__metaclass__(元类)的高级python用法
class Singleton2(type):
    def __init__(cls, name, bases, dict):
        super(Singleton2, cls).__init__(name, bases, dict)
        cls._instance = None
    def __call__(cls, *args, **kw):
        if cls._instance is None:
            cls._instance = super(Singleton2, cls).__call__(*args, **kw)
        return cls._instance

class MyClass3(object):
    __metaclass__ = Singleton2

one = MyClass3()
two = MyClass3()

two.a = 3
print one.a
#3
print id(one)
#31495472
print id(two)
#31495472
print one == two
#True
print one is two
#True

print '----------------------方法4--------------------------'
#方法4:也是方法1的升级(高级)版本,
#使用装饰器(decorator),
#这是一种更pythonic,更elegant的方法,
#单例类本身根本不知道自己是单例的,因为他本身(自己的代码)并不是单例的
def singleton(cls, *args, **kw):
    instances = {}
    def _singleton():
        if cls not in instances:
            instances[cls] = cls(*args, **kw)
        return instances[cls]
    return _singleton

@singleton
class MyClass4(object):
    a = 1
    def __init__(self, x=0):
        self.x = x

one = MyClass4()
two = MyClass4()

two.a = 3
print one.a
#3
print id(one)
#29660784
print id(two)
#29660784
print one == two
#True
print one is two
#True
one.x = 1
print one.x
#1
print two.x
#1

print "高潮来了  我的办法"
#Python真的需要单例模式吗?我指像其他编程语言中的单例模式。

# 答案是:不需要!

# 因为,Python有模块(module),最pythonic的单例典范。

# 模块在在一个应用程序中只有一份,它本身就是单例的,将你所需要的属性和方法,直接暴露在模块中变成模块的全局变量和方法即可!

class MySingleton(object):
    def __init__(self, a):
        self.a = a

    def printa(self):
        print(self.a)

    def seta(self, a):
        self.a = a



mySingleton = MySingleton(1) # 全局变量

jack.zh 标签:python 继续阅读

846 ℉

14.12.11

virtualenv的简单使用手册

virtualenv的简单使用手册

virtualenv

virtualenv用于创建独立的Python环境,多个Python相互独立,互不影响,它能够: + 在没有权限的情况下安装新套件 + 不同应用可以使用不同的套件版本 + 套件升级不影响其他应用

安装

# ubuntu
sudo apt-get install python-virtualenv
# all
pip install virtualenv 

使用方法

virtualenv [虚拟环境名称] 

如,创建ENV的虚拟环境

virtualenv ENV

默认情况下,虚拟环境会依赖系统环境中的site packages,就是说系统中已经安装好的第三方package也会安装在虚拟环境中,如果不想依赖这些package,那么可以加上参数 --no-site-packages建立虚拟环境

virtualenv --no-site-packages [虚拟环境名称]

启动虚拟环境

cd ENV
source ./bin/activate

注意此时命令行会多一个(ENV),ENV为虚拟环境名称,接下来所有模块都只会安装到该目录中去。

退出虚拟环境

deactivate 

在虚拟环境安装Python套件 Virtualenv 附带有pip安装工具,因此需要安装的套件可以直接运行:

pip install [套件名称]

如果没有启动虚拟环境,系统也安装了pip工具,那么套件将被安装在系统环境中,为了避免发生此事,可以在~/.zshrc 或者 ~/.bashrc 文件中加上:

export PIP_REQUIRE_VIRTUALENV=true

或者让在执行pip的时候让系统自动开启虚拟环境:

export PIP_RESPECT_VIRTUALENV=true

Virtualenvwrapper

Virtaulenvwrapper是virtualenv的扩展包,用于更方便管理虚拟环境,它可以做: + 将所有虚拟环境整合在一个目录下 + 管理(新增,删除,复制)虚拟环境 + 切换虚拟环境 + …

安装

sudo easy_install virtualenvwrapper  

此时还不能使用virtualenvwrapper,默认virtualenvwrapper安装在/usr/local/bin下面,实际上你需要运行virtualenvwrapper.sh。那我们就运行它。

source /usr/local/bin/virtualenvwrapper.sh

把这句话加到.bashrc 或者.zshrc中

用法

  • 列出虚拟环境列表

    workon

  • 也可以使用

    lsvirtualenv

  • 新建虚拟环境

    mkvirtualenv [虚拟环境名称]

  • 启动/切换虚拟环境

    workon [虚拟环境名称]

  • 删除虚拟环境

    rmvirtualenv [虚拟环境名称]

  • 离开虚拟环境

    deactivate

jack.zh 标签:python 继续阅读

814 ℉

14.12.09

IP地址概念

IP地址概念

by 邹业盛

目前只考虑 IPv4 的情况.


1. 基本概念

IP 地址是一个 4 字节(32 位)的数据, 通常按每字节分成 4 段, 表示成类似于 192.168.0.1 的格式, 写成 16 进制是 C0.A8.00.01 , 二进制是: 11000000.10101000.00000000.00000001 .

|-| 第一节 |第二节|第三节|第四节|写出来| |-|-|-|-|-|-| |10 进制 |192| 168 |0 |1 |192.168.0.1| |16 进制 |C0| A8| 00 |1| C0.A8.00.01| |2 进制| 11000000| 10101000| 00000000| 00000001| -|

在这些地址当中, 全为 1 的为 广播地址 , 用于向所在网络的所有主机发送报文.

|-| 第一节| 第二节| 第三节| 第四节| 写出来| |-|-|-|-|-|-| |10 进制 |255| 255 |255| 255 |255.255.255.255| |16 进制| FF |FF |FF| FF| FF.FF.FF.FF| |2 进制| 11111111| 11111111| 11111111| 11111111| -|

全为 0 的为 本网络地址 .

|- |第一节| 第二节 |第三节 |第四节| 写出来| |-|-|-|-|-|-| |10 进制 |0 |0| 0| 0 |0.0.0.0| |16 进制 |00 |00 |00 |00| 00.00.00.00| |2 进制| 00000000| 00000000| 00000000| 00000000| -|

第一节为 127 的为 回送地址 , 其中的主机地址, 表示主机自己(无网络传输).

|- |第一节| 第二节 |第三节| 第四节| 写出来| |-|-|-|-|-|-| |10 进制 |127 |0 |0 |1| 127.0.0.1| |16 进制 |7F |00 |00| 01| 7F.00.00.01| |2 进制| 01111111 |00000000 |00000000 |00000001 |-|

127.0.0.0 是表示的网地址, 不是主机地址.

2. 网地址和主机地址

IP 地址当中, 有一层逻辑的划分, 就是 网地址 和 主机地址 . 前面一部分 N 位表示一个网, 后面剩下的表示网中的一台主机.

比如: 192.168.0.1 , 如果你把 192 看成是网地址, 那么剩下的 168.0.1 就是主机地址. 你把 192.168 规定是网地址, 那么剩下的 0.1 就是主机地址. 这个划分是人为定的规则, 表示这个规则的, 是 子网掩码 , 用以标示出 网地址 . 比如如果你是前两节为子网, 对应的子网掩码就是 255.255.0.0 .

|- |第一节| 第二节 |第三节 |第四节| 写出来| |-|-|-|-|-|-| |IP 10 进制 |192| 168 |0 |1| 192.168.0.1| |IP 2 进制| 11000000 |10101000 |00000000 |00000001 |-| |掩码 10 进制| 255 |255| 0 |0| 255.255.0.0| |掩码 2 进制| 11111111| 11111111| 00000000 |00000000| -| |IP & 掩码| 11000000 |10101000| 00000000| 00000000| 192.168.0.0|

IP & 掩码 得到的就是网地址. 网段 IP 的第一个地址是网地址 , 网段 IP 的最后一个地址是广播地址 , 这两个地址是特殊的, 不分配给主机.

对于 IP , 192.168.0.1 , 如果掩码是 255.255.0.0 , 则它对应的网地址是 192.168.0.0 , 此网的广播地址是 192.168.255.255 .

对于 192.168.0.1 , 如果掩码是 255.255.255.0 , 则它对应的网地址是 192.168.0.0 , 此网的广播地址是 192.168.0.255 .

把一个 IP 中的哪部分定义成网地址, 哪部分定义成主机地址, 有一个影响, 就是在 IP 地址中所能表示的 网数量 和 主机数量 是不同的. 直观地不考虑一些特殊地址的情况下, IP 一共只有 32 位, 你用 8 位表示网, 那剩下的 24 位表示主机. 这种情况下, 网数量最大是 511 ( 2 ^ 9-1 ) , 主机数量最大有 33554431 ( 2 ^ 25 - 1 ) .

3. IP地址的分类

前面讲了 网地址 和 主机地址 的概念, 按 网地址 所占位数的不同, IP 地址整个被分成了 A, B, C, D, E 五类. D, E 类特殊, 我们主机会被分配到的地址是 A, B, C 类中的一种.

每类地址在其 网地址 部分都有额外格式规定, 以显示地表明这是哪一类的地址. 否则就会出现前面我们对于 192.168.0.1 这个地址的看法问题, 网地址 占几位是我们自己定的.

一共有 5 类地址, 直观地, 2 ^ 2 = 4, 2 ^ 3 = 8, 我们使用三位地址就可以区分它们了, 000, 001, 010, 011, 100. 但是这种平均分的方式, 对于 A 类地址这种, 消耗太大, 它一共网地址就只有 8 位可用, 标示就占去了 3 位, 肯定尴尬啊. 于是就用上了 进位两分 的方式.

|种类 |第一位| 第二位 |第三位| 第四位| |-|-|-|-|-| |A| 0| - |-| -| |B| 1| 0| -| -| |C| 1| 1| 0| -| |D| 1| 1 |1 |0| |E| 1| 1| 1 |1|

这种分法不是最有效率的, E 类要 4 位才能区分. 但是却是对短的网地址分类更有利, A 类只占用了一位网地址空间.

每类 IP 对网地址的表示定义.

|分类| 网地址位数| 主机地址位数 |十进制| 二进制| |-|-|-|-|-| |A| 8 |24| 10.10.0.1| 00001010.00001010.00000000.00000001| |B| 16 |16 |172.16.0.1 |10101100.00010000.00000000.00000001| |C |24| 8| 192.168.0.1| 11000000.10101000.00000000.00000001|

3.1. A类地址

A 类地址使用 8 位空间表示网地址, 24 位表示主机地址. 网地址以 0 起始作为标示. 剩下能用来网地址有 7 位空间. 所以 A 类地址的网数总数量上能有 2 ^ 7 = 128 个.

A 类地址的 128 个网中, 有两个是特殊的, 0.0.0.0 的 0 网, 127.0.0.1 的 127 网. 所以实际可用的 A 类地址中, 有 126 个网.

A 类地址有 24 位的主机地址空间, 单网下的主机数量有 2 ^ 24 = 16777216 个, 除去第一个表示网本身的地址, 和最后一个用于广播的地址, 可用的主机地址有 16777214 个.

3.2. B类地址

B 类地址使用 16 位的网地址, 16 位的主机地址. 网地址以 10 起始作为标示. 剩下 14 位的网地址空间. B 类地址的网的总数量有 2 ^ 14 = 16384 个. 主机空间 16 位, 则单网的主机数量最多有 2 ^ 16 - 2 = 65534 个.

3.3. C类地址

C 类地址使用 24 位网地址, 8 位的主机地址. 网地址以 110 起始作为标示. 剩下 21 位网地址空间. C 类地址的网的总数量有 2 ^ 21 = 2097152 个. 主机空间 8 位, 单网主机数量 2 ^ 8 - 2 = 254 个.

3.4. D类和E类

D 类地址用于多点广播, 用于一次寻址一组主机.

E 类地址目前保留.

4. 网段表示方法

有时我们需要表示一段的 IP , 直观地, 可能会想到的形式是, 192.168.0.* , 表示的网段是 192.168.0.0 ~ 192.168.0.255 . 但是这种在十进制形式上表示的做法, 和 IP 的数据本质是有一些不匹配的. IP 的数据是 32 位二进制位, 我们只是为了方便可以写成 4 节十进制的形式. 但是在实际使用中, 我们自己规划的网络环境下, 网地址的位数不一定就是 8 的整数倍. 如果我们希望表示的 IP 段是, 前 6 位是 011111 , 后 26 位随意, 那按前面的 * 的形式就无能为力了.

于是, 我们使用 124.0.0.0/6 的形式来表示 “固定前 6 位”. 这种情况通常是前 6 位作为网地址(和 IP 是哪类没关系).

|- |第一节|第二节 |第三节| 第四节 |写出来| |-|-|-|-|-|-| |第一个地址| 01111100| 00000000| 00000000| 00000000 |124.0.0.0| |最后一个地址 01111111 |11111111| 11111111 |11111111 |127.255.255.255|

5. 私有地址

IP 地址是有限的, 而且每个设备很多时候都是需要一个 IP 地址的. 但是, 并不是所有设备都需要接入互联网. 为了规范 IP 地址的使用, 使那些私网自己分配的 IP 地址不和那些互联网公网 IP 地址冲突, 在 IP 地址的协议上, 每类 IP 中都预留了一段地址供私网自己使用, 这些地址不进行公开的分配.

这三段地址是:

|类型| 网段| 范围| |-|-|-| |A| 10.0.0.0/8| 10.0.0.0 ~ 10.255.255.255| |B| 172.16.255.25512 |172.16.0.0 ~ 172.31.255.255| |C| 192.168.0.0/16 |192.168.0.0 ~ 192.168.255.255|

jack.zh 标签:网络 继续阅读

815 ℉

14.12.08

Python yield 使用浅析

Python yield 使用浅析

初学 Python 的开发者经常会发现很多 Python 函数中用到了 yield 关键字,然而,带有 yield 的函数执行流程却和普通函数不一样,yield 到底用来做什么,为什么要设计 yield ?本文将由浅入深地讲解 yield 的概念和用法,帮助读者体会 Python 里 yield 简单而强大的功能.

您可能听说过,带有 yield 的函数在 Python 中被称之为 generator(生成器),何谓 generator ?

我们先抛开 generator,以一个常见的编程题目来展示 yield 的概念。

如何生成斐波那契數列

斐波那契(Fibonacci)數列是一个非常简单的递归数列,除第一个和第二个数外,任意一个数都可由前两个数相加得到。用计算机程序输出斐波那契數列的前 N 个数是一个非常简单的问题,许多初学者都可以轻易写出如下函数:

清单 1. 简单输出斐波那契數列前 N 个数

def fab(max): 
  n, a, b = 0, 0, 1 
  while n < max: 
      print b 
      a, b = b, a + b 
      n = n + 1

执行 fab(5),我们可以得到如下输出:

 >>> fab(5) 
 1 
 1 
 2 
 3 
 5

结果没有问题,但有经验的开发者会指出,直接在 fab 函数中用 print 打印数字会导致该函数可复用性较差,因为 fab 函数返回 None,其他函数无法获得该函数生成的数列。

要提高 fab 函数的可复用性,最好不要直接打印出数列,而是返回一个 List。以下是 fab 函数改写后的第二个版本:

清单 2. 输出斐波那契數列前 N 个数第二版

 def fab(max): 
    n, a, b = 0, 0, 1 
    L = [] 
    while n < max: 
        L.append(b) 
        a, b = b, a + b 
        n = n + 1 
    return

可以使用如下方式打印出 fab 函数返回的 List:

 >>> for n in fab(5): 
 ...     print n 
 ... 
 1 
 1 
 2 
 3 
 5

改写后的 fab 函数通过返回 List 能满足复用性的要求,但是更有经验的开发者会指出,该函数在运行中占用的内存会随着参数 max 的增大而增大,如果要控制内存占用,最好不要用 List来保存中间结果,而是通过 iterable 对象来迭代。

例如,在 Python2.x 中,代码:

清单 3. 通过 iterable 对象来迭代

 for i in range(1000): pass

会导致生成一个 1000 个元素的 List,而代码:

 for i in xrange(1000): pass

则不会生成一个 1000 个元素的 List,而是在每次迭代中返回下一个数值,内存空间占用很小。因为 xrange 不返回 List,而是返回一个 iterable 对象。

利用 iterable 我们可以把 fab 函数改写为一个支持 iterable 的 class,以下是第三个版本的 Fab:

清单 4. 第三个版本

 class Fab(object): 

    def __init__(self, max): 
        self.max = max 
        self.n, self.a, self.b = 0, 0, 1 

    def __iter__(self): 
        return self 

    def next(self): 
        if self.n < self.max: 
            r = self.b 
            self.a, self.b = self.b, self.a + self.b 
            self.n = self.n + 1 
            return r 
        raise StopIteration()

Fab 类通过 next() 不断返回数列的下一个数,内存占用始终为常数:

 >>> for n in Fab(5): 
 ...     print n 
 ... 
 1 
 1 
 2 
 3 
 5

然而,使用 class 改写的这个版本,代码远远没有第一版的 fab 函数来得简洁。如果我们想要保持第一版 fab 函数的简洁性,同时又要获得terable 的效果,yield 就派上用场了:

清单 5. 使用 yield 的第四版

 def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        yield b 
        # print b 
        a, b = b, a + b 
        n = n + 1 

第四个版本的 fab 和第一版相比,仅仅把 print b 改为了 yield b,就在保持简洁性的同时获得了 iterable 的效果。

调用第四版的 fab 和第二版的 fab 完全一致:

 >>> for n in fab(5): 
 ...     print n 
 ... 
 1 
 1 
 2 
 3 
 5

简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。

也可以手动调用 fab(5) 的 next() 方法(因为 fab(5) 是一个 generator 对象,该对象具有 next() 方法),这样我们就可以更清楚地看到 fab 的执行流程:

清单 6. 执行流程

 >>> f = fab(5) 
 >>> f.next() 
 1 
 >>> f.next() 
 1 
 >>> f.next() 
 2 
 >>> f.next() 
 3 
 >>> f.next() 
 5 
 >>> f.next() 
 Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
 StopIteration

当函数执行结束时,generator 自动抛出 StopIteration 异常,表示迭代完成。在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。

我们可以得出以下结论:

一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。

yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。

如何判断一个函数是否是一个特殊的 generator 函数?可以利用 isgeneratorfunction 判断:

清单 7. 使用 isgeneratorfunction 判断

 >>> from inspect import isgeneratorfunction 
 >>> isgeneratorfunction(fab) 
 True

要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator,好比类的定义和类的实例的区别:

清单 8. 类的定义和类的实例

 >>> import types 
 >>> isinstance(fab, types.GeneratorType) 
 False 
 >>> isinstance(fab(5), types.GeneratorType) 
 True

fab 是无法迭代的,而 fab(5) 是可迭代的:

 >>> from collections import Iterable 
 >>> isinstance(fab, Iterable) 
 False 
 >>> isinstance(fab(5), Iterable) 
 True

每次调用 fab 函数都会生成一个新的 generator 实例,各实例互不影响:

 >>> f1 = fab(3) 
 >>> f2 = fab(5) 
 >>> print 'f1:', f1.next() 
 f1: 1 
 >>> print 'f2:', f2.next() 
 f2: 1 
 >>> print 'f1:', f1.next() 
 f1: 1 
 >>> print 'f2:', f2.next() 
 f2: 1 
 >>> print 'f1:', f1.next() 
 f1: 2 
 >>> print 'f2:', f2.next() 
 f2: 2 
 >>> print 'f2:', f2.next() 
 f2: 3 
 >>> print 'f2:', f2.next() 
 f2: 5

return 的作用

在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。

另一个例子

另一个 yield 的例子来源于文件读取。如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:

清单 9. 另一个 yield 的例子

 def read_file(fpath): 
    BLOCK_SIZE = 1024 
    with open(fpath, 'rb') as f: 
        while True: 
            block = f.read(BLOCK_SIZE) 
            if block: 
                yield block 
            else: 
                return

清单10:yield的验证

def fab():
    """this is doc string """
    a,b = 0,1
    while 1:
        a,b=b,a+b
        yield b

gf=fab()
g=fab()
print g,gf
for i in xrange(10):
    print '%4d' % gf.next(),

print gf.__doc__,gf.__name__
print gf.__class__,gf.__format__,

运行打印:

<generator object fab at 0x10669e230> <generator object fab at 0x10669e1e0>
   1    2    3    5    8   13   21   34   55   89 None fab
<type 'generator'> <built-in method __format__ of generator object at 0x10669e1e0>

以上仅仅简单介绍了 yield 的基本概念和用法

注:本文的代码均在 Python 2.7 中调试通过

jack.zh 标签:python 继续阅读

1 2 3 4 5 6 7 8 9 10
Fork me on GitHub