Tue 26 April 2016 by

Python单例模式的实现

由于实现的方式很多,先来3种。

1. 类实例实现的单例装饰器

import functools

class Singleton(object):

    def __init__(self):
        self.instances = {}

    def __call__(self, cls):
        @functools.wraps(cls)
        def wrapper(*args, **kwargs)
            if not self.instances.get(cls):
                self.instances[cls] = cls(*args, **kwargs)
            return self.instances[cls]
        return wrapper

singleton = Singleton()

# 用法
@singleton
class Test(object):
    pass

if __name__ == '__main__':
    t1 = Test()
    t2 = Test()
    print(t1, t2)
    print(type(t1), type(t2))
    assert t1 == t2

# 这种方式有一个缺点,就是Test这个类会变成一个function, 
# 这样的话就无法使用instance等方式了。

2. 类实现的单例装饰器

class Singleton(object):

    def __init__(self, cls):
        self.cls = cls
        self.cls_instance = None

    def __call__(self, *args, **kwargs):
        if not self.cls_instance:
            self.cls_instance = self.cls(*args, **kwargs)
        return self.cls_instance

@Singleton
class Test(object):
    pass


if __name__ == '__main__':
    print(Test)
    t1 = Test()
    t2 = Test()
    print(t1, t2)
    assert t1 == t2

# 这种方式Test其实已经是single的一个实例了
# 缺点和第一种方式一样,Test已经不是原来的Test了
# 而t1, t2却还是原来的Test的实例
# 对于这种方式我还存有疑虑,但是又说不出来
# 总感觉怪怪的

3. metaclass元类

class Singleton(type):
    _instances = {}
    def __cal__(self, *args, **kwargs):
        if self not in self._instances:
            self._instances[self] = super(Singleton, self).__call__(*args, *kwargs)
        return self._instances[self]

# py2写法
class Test(object):
    __metaclass__ = Singleton

    pass

# py3写法
class Test(object, metaclass=Singleton):
    pass

if __name__ == '__main__':
    print(Test)
    t1 = Test()
    t2 = Test()
    print(t1, t2)
    assert t1 == t2
    print(isinstance(t1, Test)

# 使用元类是最好的,因为Test的行为都没有被改变
# 所以isinstance, type等函数的调用都没有影响
# 不过理解起来可能会有一些困难了
# 因为我觉得自己理解的还有一些不够通透

This entry was tagged on #singleton and #装饰器

Thu 21 April 2016 by

HHKB Type-S开箱体验

先上一张图

my HHKB Type-S

在上一张图,是今天用这块HHKB Type-s来完成的一个简易私人图床,我起名为Tubed,使用了七牛提供的存储服务。配合Unclutter可以轻松完成图片的上传和浏览。 下图为将qq中的图片上传到我的图床。

tubed usage

至于HHKB Type-s的使用感受,下次有空在写吧!(主要是刚才写了一大段,被我不小心关掉了:<

This entry was tagged on #hhkb

Mon 11 April 2016 by

好用的工具和软件记录

Mac OS X

  1. licecap: 免费好用的gif录屏软件。

Windows

1.unxutils一套能让你在Windows体验bash的工具集。

解压后只要将usr/local/wbin文件的绝对路径添加进环境变量Path中,即可。

This entry was tagged on #sofeware

Tue 05 April 2016 by

我的Emacs的配置

记录Emacs的相关配置和插件,以备万一。 ~/.emacs/init.el

;;; package --- Emacs Configure
;;; Commentary:
;;; code:
(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))

(package-initialize)
(when (not package-archive-contents)
  (package-refresh-contents))

(when (memq window-system '(mac ns))
  (exec-path-from-shell-initialize))

;; add maunal/ and top dir to load-path
(let ((base "~/.emacs.d/maunal"))
  (add-to-list 'load-path base)
  (dolist (f (directory-files base))
    (let ((name (concat base "/" f)))
      (when (and (file-directory-p name)
                 (not (equal f ".."))
                 (not (equal f ".")))
        (add-to-list 'load-path name)))))


;; BASIC CONFIGURE
;; mouse intergration
(xterm-mouse-mode t)
(global-set-key [mouse-4] '(lambda ()
                             (interactive)
                             (scroll-down 1)))
(global-set-key [mouse-5] '(lambda ()
                             (interactive)
                             (scroll-up 1)))
;; font
;;(set-frame-font "Consolas-16")
(set-face-attribute 'default nil :family "Consolas")
(set-face-attribute 'default nil :height 200)
;; (load-theme 'noctilux t)
(setq-default kill-whole-line t)
(setq require-final-newline t)
(global-linum-mode t)
(setq linum-format "%3d ")
(electric-pair-mode 1)
(fset 'yes-or-no-p 'y-or-n-p)
(global-set-key (kbd "M-q") 'other-window)

;;(load-file "~/.emacs.d/maunal/emacs-for-python/epy-init.el")

;;(add-hook 'python-mode-hook (lambda() (electric-indent-mode -1)))

 ;; Standard Jedi.el setting
(require 'jedi)
(add-hook 'python-mode-hook 'jedi:setup)
(setq jedi:complete-on-dot t)

;; flymake-python-pyflakes
;;(require 'flymake-python-pyflakes)
(require 'flycheck)
(add-hook 'after-init-hook 'global-flycheck-mode)

;; auto pep8 on save
(require 'py-autopep8)
(add-hook 'python-mode-hook 'py-autopep8-enable-on-save)
(setq py-autopep8-options '("--max-line-length=100"))

;; move line and region
(require 'move-lines)
(move-lines-binding)

;;(require 'yaml-mode)
;;(add-to-list 'auto-mode-alist '("\\.yml$" . yaml-mode))
;;(add-to-list 'auto-mode-alist '("\\.sls\\'" . yaml-mode))

;; qucik insert new line
(require 'open-next-line)
;;
;; ace jump mode major function
;;
(autoload
  'ace-jump-mode
  "ace-jump-mode"
  "Emacs quick move minor mode"
  t)
;; you can select the key you prefer to
(define-key global-map (kbd "C-c h") 'ace-jump-mode)
;;
;; enable a more powerful jump back function from ace jump mode
;;
(autoload
  'ace-jump-mode-pop-mark
  "ace-jump-mode"
  "Ace jump back:-)"
  t)
(eval-after-load "ace-jump-mode"
  '(ace-jump-mode-enable-mark-sync))
(define-key global-map (kbd "C-c SPC") 'ace-jump-mode-pop-mark)
;; smex
(require 'smex) ; Not needed if you use package.el
(smex-initialize) ; Can be omitted. This might cause a (minimal) delay
                                        ; when Smex is auto-initialized on its first run.
(global-set-key (kbd "M-x") 'smex)
(global-set-key (kbd "M-X") 'smex-major-mode-commands)
;; This is your old M-x.
(global-set-key (kbd "C-c C-c M-x") 'execute-extended-command)

(require 'sr-speedbar)
(setq sr-speedbar-right-side nil)
(setq sr-speedbar-width 25)
(setq dframe-update-speed t)
(global-set-key (kbd "<f5>") (lambda()
                               (interactive)
                               (sr-speedbar-toggle)))

(defun go-mode-setup ()
  "Prepare for go mode."
  (setq compile-command "go build -v && go test -v && go vet")
  (define-key (current-local-map) "\C-c\C-c" 'compile)
  (go-eldoc-setup)
  (add-hook 'before-save-hook 'gofmt-before-save))
(add-hook 'go-mode-hook 'go-mode-setup)

(require 'go-autocomplete)
(require 'auto-complete-config)
(ac-config-default)

;;; init.el ends here

相关插件

  • jedi : python代码的静态分析工具
  • pycheck : python代码规范检查工具
  • py-autopep8 : python代码自动格式化工具
  • yaml-mode : yaml文件编辑模式
  • sr-speedbar : 侧边栏, F5开启/关闭
  • ace-jump-mode : 代码首字母跳转工具
  • color-theme-github : github的代码主题
  • emacs-for-python : emacs开发python的相关有用套件
  • go-autocomplete : galang的自动补全工具
  • mouse
  • move-lines : 代码行和快的快速移动工具
  • open-next-line : 快速在当前行的上方或下方新建空白行
  • smooth-scroll : 平缓滚动
  • cython-mode : cython模式

This entry was tagged on #Emacs

Mon 28 March 2016 by

实现博客的自动编译发布

使用github、coding和travis-ci实现在线编辑和自动发布!

在线编辑

使用github在线创建和编辑文章。

自动发布

使用travis-ci自动编译和发布到github、coding的相应repo的gh-pages分支

This entry was tagged on #blog and #travis-ci

Sun 27 March 2016 by

Dockerfile参数学习

镜像和容器

容器是镜像的实例。一个镜像可以对应多个容器。每次使用docker run <image>时都会重新创建该镜像的一个容器,我们可以为该命令指定--name 来为所产生的容器指定名字:

docker run -d --name serve ubuntu 这样根据ubuntu这个镜像产生的容器就叫serve

。当容器运行时,我们对容器的修改只会写人到容器的文件系统,而不会影响到对应镜像。所以, 前一次run形成的改动不会影响到后一次run所创建的容器。想要这些修改对image生效, 可以使用docker commit <container>

                ├─ 可写层(container)
内核层 - 镜像层  ├─ 可写层(container)
                ├─ 可写层(container)

内核层 - 镜像层 -可写层(container)

CMD和ENTRYPOINT的区别

首先两者都可以让你在容器运行的时候执行一条命令,而且这两个字段在Dockerfile中只能指定一次,如果指定多次,那么则以最后一次为准。

再来说两者的区别: 假如我们在运行容器的命令docker run <image>中指定了参数。那么CMD指定的命令会被该参数所覆盖, 但是该参数却能为ENTRYPOINT中指定的命令所用。

举个例子:

# Dockerfile
# 镜像名: test
...
CMD ["echo", "Hello, CMD!"]

当我们使用docker run test时,显示Hello, CMD!,但当我们使用docker run test echo hi, everyone!时, 显示hi, everyone!。我们重新指定的echo命令和参数覆盖了原来的echo。 值得注意的是,如果在Dockerfile使用CMD指定命令, 那么在run的时候指定的第一个参数必须时可执行的命令,否则将运行失败!

如果我们使用ENTRYPOINT指定入口命令的话,在docker run命令中指定的参数都会被传递给入口命令.

# Dockerfile
# 镜像名: test
...
ENTRYPOINT ["echo", "Hello, CMD!"]

运行docker run test echo hi, everyone!将得到Hello, CMD! echo hi, everyone!

补充:

其实在run的时候ENTRYPOINT也是可以别覆盖的,通过指定--entrypoint="xxxx"参数就可以做到!

FROM

指定基础镜像,格式通常为FROM <image>:<tag> 例如: From ubuntu:latest 如果本地不存在该镜像,则去Docker Hub中拉取该镜像。

MAINTAINER

为该镜像指定作者, 例如: MAINTAINER importcjj@ele.me

ENV

设定环境变量, 两种方式: ENV \ \ ENV \=\

例如: ENV LANG en_US.UTF-8 ENV PYTHON_VERSION 2.7.6

WORKDIR

切换工作目录,类似于cd, 例如:

# pwd is /p/dir1
RUN ls
# now cd to /p/dir2
WORKDIR ../dir2
# pwd is /p/dir2
RUN ls

RUN

可用于运行普通的命令,有两种方式: RUN \ RUN ["executable", "param1", "param2"]

通常使用第一种

比如运行相关软件包的安装命令,例如: RUN apt-get -y update RUN apt-get -y python RUN apt-get install -y python-pip RUN apt-get install -y python2.7-dev * RUN pip install flask

ADD

将build路径(或者相对位置)的文件,文件夹甚至是远程文件添加至荣区的文件系统的指定位置,可用于设置相关软件的配置文件等例如:

ADD requirements.txt /tmp/requirements.txt
RUN pip install -i /tmp/requirements.txt

VOLUME

将宿主系统的文件夹挂载到容器中指定的文件夹。比如:

VOLUME /data/logs

此命令的局限性在于无法指定宿主系统的某个文件夹被挂载,只能由docker来分配。可用docker run的-v src:dest参数来弥补这一局限性。如果想要查看是哪个文件夹被mount了,使用docker inspect <container>,在返回的json结果中的查看Mounts这个字段的内容。

EXPOSE

指定容器运行时所要监听的端口。虽然配置了EXPOSE, 但我们在运行容器的时候还需要指定端口映射,否则在宿主机中无法访问该端口。 例如: docker run -d -p 127.0.0.1:5000:5000 这样的话我们容器的5000端口就被映射到了宿主机的5000端口。访问宿主机的5000端口,即可访问到容器的5000端口所提供的服务。 同时,多端口的映射也是可以的 -p 1234-1236:1234-1236/tcp

LABEL

为构建的镜像设置标签 LABEL <key>=<value> <key>=<value> <key>=<value> 设置的标签可在 docker inspect <image>给出的JSON结果中看到。

例子(来源于github)

FROM centos:centos6

RUN cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN yum install -y wget tar gcc zlib zlib-devel openssl openssl-devel unzip mysql-devel python-devel

RUN mkdir /opt/logs
RUN mkdir /usr/src/python
WORKDIR /usr/src/python

ENV LANG en_US.UTF-8
ENV PYTHON_VERSION 2.7.6

RUN curl -SL "https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tgz" | tar xvzf - --strip-components=1
RUN ./configure \
    && make \
    && make install \
    && make clean

RUN sed 's/\/usr\/bin\/python/\/usr\/bin\/python2.6/g' /usr/bin/yum > /usr/bin/yum.tmp \
    && mv /usr/bin/yum.tmp /usr/bin/yum \
    && chmod 755  /usr/bin/yum

ADD . /opt/
WORKDIR /opt

RUN tar zxvf scribed.tar.gz \
    && chown -R root:root scribed \
    && rm -f scribed.tar.gz

RUN curl -SL 'https://bootstrap.pypa.io/get-pip.py' | python
RUN pip install -r requirements.txt \
    && rm -f requirements.txt

RUN easy_install virtualenv \
    && easy_install mysql-connector-python \
    && easy_install MySQL-python

RUN easy_install supervisor \
    && echo_supervisord_conf > /etc/supervisord.conf \
    && echo "[include]" >> /etc/supervisord.conf \
    && echo "files = /etc/supervisord.d/*.conf" >> /etc/supervisord.conf \
    && mkdir -p /etc/supervisord.d \
    && cp gunicorn.conf scribed.conf /etc/supervisord.d/ \
    && rm -f gunicorn.conf scribed.conf Dockerfile

更多内容请前往Docker的官方文档

补充

除了FROM, MAINTAINER, 'RUN' 和 'AND四个Dockerfile命令之外, 其他命令都可以在docker run`时被覆盖! 详见Docker官方文档docker run reference

修改Dockerfile的时候,尽量在文件的最后修改(如果允许的话)。这回加快build的速度(缓存的作用:)。因此, 尽量把不轻易变动的部分放在Dockerfile的上方。

如何为正在运行的容器,打开一个新的shell? docker exec -i -t <container_id> bash docker exec -i -t <container_name> bash

This entry was tagged on #Docker and #Dockerfile

Sun 27 March 2016 by

Git中一些容易被忽视的东西

修改commit的message

  1. 修改最近一次commit的message git commit --amend 然后输入想要修改的message
  2. 批量修改commit的message git rebase -i HEAD~n n为一个数字,表示最近n次commit, 将想要修改的commit id前的pick改成reword(r)命令, 然后修改id后的commit message, 保存退出后即可.

合并连续的多个commit为一个commit

使用git rebase -i HEAD~n 把将被合并的commit id前的pick命令改成squash(s)

忽略已被提交过的文件

  1. git rm --cached <filename> 不用担心,这一步只是删除该文件在版本库中的追踪,并不会正真删除磁盘上的物理文件
  2. 更新.gitignore文件, 如果之前已经更新过了,就可以直接跳过这一步了。
  3. add + commit 提交

新建一个orphan(孤儿)分支

git checkout --orphan <name>

使用 git diff 查看修改内容

这里要说的是两种情况:

  1. 文件改动还未加入暂存区(即未git add),这种情况直接使用git diff或者git diff <path查看一个或多个文件的改动情况。
  2. 文件改动已经加入暂存区,这种情况需要使用参数--staged, 即git diff --staged或者git diff --staged <path>来查看暂存区中的文件与代码仓库中的文件差异。

注: 可以使用difftool来代替diff命令从而更加清晰的查看文件改动!

This entry was tagged on #git

Sun 27 March 2016 by

Golang中三种import方式

1. 点方式

有时候会看到如下的方式导入包 import( . "fmt" )

这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调用的fmt.Println("hello world") 可以省略的写成Println("hello world")

2. 别名方式

别名操作顾名思义可以把包命名成另一个用起来容易记忆的名字。

import( f "fmt" ) 别名操作调用包函数时前缀变成了重命名的前缀,即f.Println("hello world")

3. _方式

_操作其实只是引入该包。当导入一个包时,它所有的init()函数就会被执行,但有些时候并非真的需要使用这些包,仅仅是希望它的init()函数被执行而已。这个时候就可以使用_操作引用该包了。即使用_操作引用包是无法通过包名来调用包中的导出函数,而是只是为了简单的调用其init函数()。

比如 import (_ "github.com/mattn/go-sqlite3")只是为了调用该包中的init函数来注册该数据库驱动而已,并需要该包中的其他内容。

This entry was tagged on #golang and #import

Sun 27 March 2016 by

关于Docker的一些杂货

如何在外部获取容器的日志?

可以使用docker logs -f <container>的方式在宿主机显示容器进程所产生的日志,就像我们使用tail那样。但是如果我们需要访问的并不是容器主进程所记录的日志呢?比如说,现在有一个很简单的web应用,我们选择让他运行在Docker容器里面,web服务器使用了gunicorn,它支持使用参数--access-file=filename来将该web应用的访问请求记录到指定的日志中去。当我们需要方便的获取这些访问记录或者实时查看的时候,应该怎么做?

当我们的web应用运行时,gunicorn所记录的日志只存在于docker系统的读写层(容器container),而不会影响到只读层(镜像image)。如果我们想要较为方便的得到容器中的日志文件,可以使用Docker提供的的volume。你可以理解为将宿主系统的文件夹挂载至容器中从而达到实时共享文件夹的目的。

你可以在很多地方指定volume, 比如:

1. Dockerfile 的VOLUME命令
# Dockerfile

RUN mkdir -p /data/logs/webapp
VOLUME /data/logs/webapp

当你使用该Dockerfile去build一个镜像,并使用该镜像来运行了一个容器实例的时候,宿主系统的某个文件夹就会被挂载为该容器的/data/logs/webapp文件夹,至于是哪一个文件夹,我们可以通过docker inspect <container_name>来查看,在返回的JSON中有一个Mounts字段,就像这样:

...
"Mounts": [
        {
            "Name": "1722e5cefd5c077339f69deaaf474c0adea5e3f343c15869802f55cbdeaed743",
            "Source": "/var/lib/docker/volumes/1722e5cefd5c077339f69deaaf474c0adea5e3f343c15869802f55cbdeaed743/_data",
            "Destination": "/data/logs/webapp",
            "Driver": "local",
            "Mode": "",
            "RW": true
        }
    ]
...

当我们在宿主机中向/var/lib/docker/volumes/1722e5cefd5c077339f69deaaf474c0adea5e3f343c15869802f55cbdeaed743/_data中放文件的时候,对应容器的/data/logs/webapp文件夹也会出现这些文件。反之,我们在容器中向该文件夹写日志的时候,在宿主机中也就可以访问这些日志了。

2. docker run 的-v参数

作用与VOLUME相同,但是它允许你指定volume的对应关系:

docker run -v /host/dir/path:/container/dir/path image_name

这样一来,宿主机的/host/dir/path文件夹就被mount到了容器的/container/dir/path,如果容器中原本就有该文件夹, 那么原来的内容会被隐藏。当取消mount后,才可以访问原先的文件。

3. docker run 的--volumes-from参数

这个参数主要用于多个容器共享一个volume。有连个容器A指定了volume, 当我们使用命令docker run --volumes-from A --name B <image>新建容器B时,B就和A共享volume了。

This entry was tagged on #Docker

Sun 27 March 2016 by

MarkDown语法说明

本篇博文参考这里

Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。

标题语法

形式一:

标题1
====(至少一个,同行不能有其他字符)
标题2
----

效果:

标题1

标题2

形式二:

示例:
# 标题1 #(可以闭合,也可以不闭合,闭合的话数量也不一定要相同)
## 标题2
### 标题3
#### 标题4
##### 标题5
###### 标题6

效果:

标题1

标题2

标题3

标题4

标题5
标题6



段落和换行

空格:多个空格或者一个回车符(只能显示一个空格)

多空格:\ 

段内换行:双空格 + 回车符

另起一段:双回车符

多空行: \

分隔线语法

三个以上 *  -  _ 

* * *

***

*****

- - -

---------------------------------------

效果:






加粗和斜体

使用一个 *  _ 来包围词句,相当于用 <em> 标签包围(内部就是这样转化的);
使用两个 *  _ 包起来的话,则相当于使用 <strong>
示例:
**加粗** __加粗__
*斜体* _斜体_

效果:

加粗 加粗
斜体 斜体

列表语法

无序列表

一个星号 / 一个加号 / 一个减号 + 若干个空格 + 列表项内容
条目之间的空行会作用到所有条目
* 条目1         + 条目1        - 条目1
* 条目2    or   + 条目2    or  - 条目1
* 条目3         + 条目3        - 条目1

效果:

  • 条目1
  • 条目2
  • 条目3


有序列表

一个数字 + 一个英文句点 + 若干个空格 + 列表项内容
1. 条目1
2. 条目2
3. 条目3
33.条目4 (数字可以乱写,自动从1开始排列)

效果:

  1. 条目1
  2. 条目2
  3. 条目3
  4. 条目4

注意:

  1. 每个列表条目下都可以包含多个段落,但这些段落都必须缩进 4 个空格或 1 个制表符

  2. 如果要在列表条目内放进引用,那 > 就需要缩进

  3. 如果要在列表项内放代码区块的话,该区块就需要缩进两次,也就是8个空格或是2个制表符

区块引用语法

> 引用d段落
(段落里每一行有可以加 > 也可以只在段首加)

效果:

引用段落


引用可以嵌套
示例:
> 父引用
> 
> > 子引用
> 
> 父引用

效果:

父引用

子引用

父引用

引用内MarkDown语法依然有效
> ## 这是一个标题。
> 
> 1. 这是第一个列表项。
> 2. 这是第二个列表项。
> 
> 这是一个代码例子:
> 
>     printf("Hello, Minmin!\n"); return 0;

效果:

这是一个标题。

  1. 这是第一个列表项。
  2. 这是第二个列表项。

这是一个代码例子:

printf("Hello, Minmin!\n"); return 0;

链接语法

形式一:自动链接(可点击的URL和Email)

如果链接的文字就是一个网络地址或者是邮箱地址直接使用<url>
示例
<https://www.baidu.com>
<importcjj@gmail.com>

效果:
https://www.baidu.com

importcjj@gmail.com


形式二:普通文本链接

[链接文字](链接URL ‘可选的链接title)  注:我的blog有问题,这里的‘ 其实是英文单引号
[链接文字](链接URL "可选的链接title")
[链接文字](链接URL (可选的链接title))
title: 加上title后,你用鼠标移到该链接文字时就会显示title文字,可以把它当作一种提示信息来用
示例:
欢迎光临我的[博客](www.importcjj.com)

效果:

欢迎光临我的博客


形式三:参考式链接

[链接文字][链接id]
\[链接id]: 真正的链接URL "可选的title"           注:去掉反杠!
示例:
[我的博客][id]
\[id]: www.baidu.com "jiaju的博客"

效果: 我的博客

图片语法

![alt文本](https://img10.3lian.com/sc6/show02/67/27/02.jpg)

效果:

alt文本

代码语法

行内代码

`代码`  反引号
`print "hello world"`

代码区块

要在 Markdown 中建立代码区块很简单,只要简单地缩进 4 个空格或是 1 个制表符就可以。

表格语法

简洁写法:

示例:
|姓名  |年龄|性别   |   我们可以指定单元格的对齐方式
|---  |----|----- |   |:---|           靠左
|John |22  |male  |   |---| or |:---:| 居中
|Tom  |20  |male  |   |---:|           靠右
|aliss|24  |female|

效果:

姓名 年龄 性别
John 22 male
Tom 20 male
aliss 24 female

总结

注意:上述乃基本语法,而且由于MarkDown的编辑器各不相同,对于原生MarkDown都有扩展,实际使用的时候情况很多,自己多多尝试就能熟练使用了。

This entry was tagged on #markdown

« Page 2 / 3 »

 

Tags