分类目录归档:Python 基础教程

if __name__ == ‘__main__’ 的作用和原理

1.一句话总结 if __name__ == ‘__main__’ 作用和原理

if __name__ == ‘__main__’ 是为了保护用户不在无意中(比如import)调用到脚本的主函数部分代码。

比如你在另一个脚本中导入了无 if __name__ == ‘__main__’ 保护的脚本(比如导入 test.py),那么该脚本的主函数会在导入时被触发运行,这有时会造成严重的后果。

如果你在无保护脚本中设置了一个自定义类,并将其保存在一个pickle文件中,在另一个脚本中解开它时就会触发无保护脚本的导入,其问题与上面所述的一样。

如果你看不懂这段描述,请看下面的详解:

2.详解

为了更好地理解这个问题的原因,我们需要退一步理解 Python 是如何初始化脚本的,以及这与它的模块导入机制是如何互动的。

每当 Python 解释器读取一个源文件时,它会做两件事。

  • 它设置了一些特殊变量,例如__name__,然后
  • 它执行文件中找到的所有代码。

让我们看看这是如何工作的,以及它与你关于我们在 Python 脚本中经常看到的 __name__ 检查有什么关系。

2.1 代码样例

假设代码在一个叫做foo.py的文件中。

# Suppose this is foo.py.

print("before import")
import math

print("before functionA")
def functionA():
    print("Function A")

print("before functionB")
def functionB():
    print("Function B {}".format(math.sqrt(100)))

print("before __name__ guard")
if __name__ == '__main__':
    functionA()
    functionB()
print("after __name__ guard")

2.2 运行代码

当Python解释器读取源文件时,它首先会定义一些特殊的变量。比如__name__ 虽然它长得很奇怪,前面和后面都有一个下划线_,但是记得变量命名的规则嘛?下划线_是可以出现在变量首字母的。所以,__name__仍然是一个变量,只不过,是解释器自己定义的。

2.3 作为主程序运行

当我们在命令行中使用python foo.py,或者直接在ide(比如pycharm)图形界面里点运行 foo.py,那么这时候,foo.py就是作为主程序运行的。

此时: Python解释器会直接给_name_变量赋值为”_main_

2.2 作为导入的模块运行

如果有另一个程序,叫 main.py,它里面的代码是这样的。

import foo

那么如果我们在命令行中使用 python main.py, 则 main.py 作为主程序运行,而foo.py就是导入的模块。

此时: Python解释器会令 __name__ = "foo"

2.3 执行foo.py文件中的代码

如果使用主程序运行!python foo.py: 输出如下:

    before import
    before functionA
    before functionB
    before __name__ guard
    Function A
    Function B 10.0
    after __name__ guard

如果使用主程序运行import foo: 输出如下:

before import
before functionA
before functionB
before __name__ guard
after __name__ guard

可以明显看到,当使用主程序运行import foo时,没有执行下面语句的内容,因为此时__name__ = 'foo'

if __name__ == '__main__':
    functionA()
    functionB()

3.为什么这样工作?

有时我们想编写一个.py文件,该文件既可以被其他程序和模块导入,也可以作为主程序运行。 例子如下:

  • 这个文件是一个库,可以被其他文件导入。但是我们希望可以在其中运行一些单元测试或演示。
  • 这个文件仅用作主程序,但具有一些单元测试的功能,一些测试框架(类似unittestdoctest)需要导入这个.py文件来测试。我们不希望,它只是因为被导入为模块,就直接运行整个脚本。
  • 这个模块主要用作主程序,但它也为高级用户提供了程序员友好的API

所以,其实就是有的时候希望他在被导入的时候运行一些代码,有的时候希望他作为主程序的时候运行另一些代码。所以需要进行判断。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

pathlib vs os 这两大Python模块谁更好?优势对比

作者:somenzz

来源:Python七号

前段时间,在使用新版本的 Django 时,我发现了 settings.py 的第一行代码从

import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

变成了

from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent

于是我就好奇,os 和 pathlib 同样是标准库,为什么 pathlib 得到了 Django 的青睐?学习了一番 pathlib 之后,发现这是一个非常高效便捷的工具,用它来处理文件系统路径相关的操作最合适不过,集成了很多快捷的功能,提升你的编程效率,那是妥妥的。

接下来让一起看一下,为什么 pathlib 更值得我们使用。

pathlib vs os

话不多说,先看下使用对比:比如说

  1. 打印当前的路径:

使用 os:

In [13]: import os

In [14]: os.getcwd()
Out[14]: '/Users/aaron'

使用 pathlib:

In [15]: from pathlib import Path

In [16]: Path.cwd()
Out[16]: PosixPath('/Users/aaron')
In [17]: print(Path.cwd())
/Users/aaron

使用 print 打印的结果是一样的,但 os.getcwd() 返回的是字符串,而 Path.cwd() 返回的是 PosixPath 类,你还可以对此路径进行后续的操作,会很方便。

  1. 判断路径是否存在:

使用 os:

In [18]: os.path.exists("/Users/aaron/tmp")
Out[18]: True

使用 pathlib:

In [21]: tmp = Path("/Users/aaron/tmp")

In [22]: tmp.exists()
Out[22]: True

可以看出 pathlib 更易读,更面向对象。

  1. 显示文件夹的内容
In [38]: os.listdir("/Users/aaron/tmp")
Out[38]: ['.DS_Store''.hypothesis''b.txt''a.txt''c.py''.ipynb_checkpoints']

In [39]: tmp.iterdir()
Out[39]: <generator object Path.iterdir at 0x7fa3f20d95f0>

In [40]: list(tmp.iterdir())
Out[40]:
[PosixPath('/Users/aaron/tmp/.DS_Store'),
 PosixPath('/Users/aaron/tmp/.hypothesis'),
 PosixPath('/Users/aaron/tmp/b.txt'),
 PosixPath('/Users/aaron/tmp/a.txt'),
 PosixPath('/Users/aaron/tmp/c.py'),
 PosixPath('/Users/aaron/tmp/.ipynb_checkpoints')]

可以看出 Path().iterdir 返回的是一个生成器,这在目录内文件特别多的时候可以大大节省内存,提升效率。

  1. 通配符支持

os 不支持含有通配符的路径,但 pathlib 可以:

In [45]: list(Path("/Users/aaron/tmp").glob("*.txt"))
Out[45]: [PosixPath('/Users/aaron/tmp/b.txt'), PosixPath('/Users/aaron/tmp/a.txt')]
  1. 便捷的读写文件操作

这是 pathlib 特有的:

f = Path('test_dir/test.txt'))
f.write_text('This is a sentence.')
f.read_text()

也可以使用 with 语句:

>>> p = Path('setup.py')
>>> with p.open() as f: f.readline()
...
'#!/usr/bin/env python3\n'
  1. 获取文件的元数据
In [56]: p = Path("/Users/aaron/tmp/c.py")

In [57]: p.stat()
Out[57]: os.stat_result(st_mode=33188, st_ino=35768389, st_dev=16777221, st_nlink=1, st_uid=501, st_gid=20, st_size=20, st_atime=1620633580, st_mtime=1620633578, st_ctime=1620633578)

In [58]: p.parts
Out[58]: ('/''Users''aaron''tmp''c.py')

In [59]: p.parent
Out[59]: PosixPath('/Users/aaron/tmp')

In [60]: p.resolve()
Out[60]: PosixPath('/Users/aaron/tmp/c.py')

In [61]: p.exists()
Out[61]: True

In [62]: p.is_dir()
Out[62]: False

In [63]: p.is_file()
Out[63]: True

In [64]: p.owner()
Out[64]: 'aaron'

In [65]: p.group()
Out[65]: 'staff'

In [66]: p.name
Out[66]: 'c.py'

In [67]: p.suffix
Out[67]: '.py'

In [68]: p.suffixes
Out[68]: ['.py']

In [69]: p.stem
Out[69]: 'c'

  1. 路径的连接 join

相比 os.path.join,使用一个 / 是不是更为直观和便捷?

>>> p = PurePosixPath('foo')
>>> p / 'bar'
PurePosixPath('foo/bar')
>>> p / PurePosixPath('bar')
PurePosixPath('foo/bar')
>>> 'bar' / p
PurePosixPath('bar/foo')

当然,也可以使用 joinpath 方法

>>> PurePosixPath('/etc').joinpath('passwd')
PurePosixPath('/etc/passwd')
>>> PurePosixPath('/etc').joinpath(PurePosixPath('passwd'))
PurePosixPath('/etc/passwd')
>>> PurePosixPath('/etc').joinpath('init.d''apache2')
PurePosixPath('/etc/init.d/apache2')
>>> PureWindowsPath('c:').joinpath('/Program Files')
PureWindowsPath('c:/Program Files')
  1. 路径匹配
>>> PurePath('a/b.py').match('*.py')
True
>>> PurePath('/a/b/c.py').match('b/*.py')
True
>>> PurePath('/a/b/c.py').match('a/*.py')
False

pathlib 出现的背景和要解决的问题

pathlib 目的是提供一个简单的类层次结构来处理文件系统的路径,同时提供路径相关的常见操作。那为什么不使用 os 模块或者 os.path 来实现呢?

许多人更喜欢使用 datetime 模块提供的高级对象来处理日期和时间,而不是使用数字时间戳和 time 模块 API。同样的原因,假如使用专用类表示文件系统路径,也会更受欢迎。

换句话说,os.path 是面向过程风格的,而 pathlib 是面向对象风格的。Python 也在一直在慢慢地从复制 C 语言的 API 转变为围绕各种常见功能提供更好,更有用的抽象。

其他方面,使用专用的类处理特定的需求也是很有必要的,例如 Windows 路径不区分大小写。

在这样的背景下,pathlib 在 Python 3.4 版本加入标准库。

pathlib 的优势和劣势分别是什么

pathlib 的优势在于考虑了 Windows 路径的特殊性,同时提供了带 I/O 操作的和不带 I/O 操作的类,使用场景更加明确,API 调用更加易懂。

先看下 pathlib 对类的划分:

图中的箭头表示继承自,比如 Path 继承自 PurePath,PurePath 表示纯路径类,只提供路径常见的操作,但不包括实际 I/O 操作,相对安全;Path 包含 PurePath 的全部功能,包括 I/O 操作。

PurePath 有两个子类,一个是 PureWindowsPath,表示 Windows 下的路径,不区分大小写,另一个是 PurePosixPath,表示其他系统的路径。有了 PureWindowsPath,你可以这样对路径进行比较:

from pathlib import PureWindowsPath
>>> PureWindowsPath('a') == PureWindowsPath('A')
True

PurePath 可以在任何操作系统上实例化,也就是说与平台无关,你可以在 unix 系统上使用 PureWindowsPath,也可以在 Windows 系统上使用 PurePosixPath,他们还可以相互比较。

>>> from pathlib import PurePosixPath, PureWindowsPath, PosixPath  
>>> PurePosixPath('a') == PurePosixPath('b')
False
>>> PurePosixPath('a') < PurePosixPath('b')
True
>>> PurePosixPath('a') == PosixPath('a')
True
>>> PurePosixPath('a') == PureWindowsPath('a')
False

可以看出,同一个类可以相互比较,不同的类比较的结果是 False。

相反,包含 I/O 操作的类 PosixPath 及 WindowsPath 只能在对应的平台实例化:

In [8]: from pathlib import PosixPath,WindowsPath

In [9]: PosixPath('a')
Out[9]: PosixPath('a')

In [10]: WindowsPath('a')
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-10-cc7a0d86d4ed> in <module>
----> 1 WindowsPath('a')

/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/pathlib.py in __new__(cls, *args, **kwargs)
   1038         self = cls._from_parts(args, init=False)
   1039         if not self._flavour.is_supported:
-> 1040             raise NotImplementedError("cannot instantiate %r on your system"
   1041                                       % (cls.__name__,))
   1042         self._init()

NotImplementedError: cannot instantiate 'WindowsPath' on your system

In [11]:

要说劣势,如果有的话,那就是在选择类时会比较困惑,到底用哪一个呢?其实如果你不太确定的话,用 Path 就可以了,这也是它的名称最短的原因,因为更加常用,短点的名称编写的更快。

适用的场景

如果要处理文件系统相关的操作,选 pathlib 就对了。

一些关键点

获取家目录:

In [70]: from pathlib import Path

In [71]: Path.home()
Out[71]: PosixPath('/Users/aaron')

父目录的层级获取:

>>> p = PureWindowsPath('c:/foo/bar/setup.py')
>>> p.parents[0]
PureWindowsPath('c:/foo/bar')
>>> p.parents[1]
PureWindowsPath('c:/foo')
>>> p.parents[2]
PureWindowsPath('c:/')

获取多个文件后缀:

>>> PurePosixPath('my/library.tar.gar').suffixes
['.tar''.gar']
>>> PurePosixPath('my/library.tar.gz').suffixes
['.tar''.gz']
>>> PurePosixPath('my/library').suffixes
[]


Windows 风格转 Posix:

>>> p = PureWindowsPath('c:\\windows')
>>> str(p)
'c:\\windows'
>>> p.as_posix()
'c:/windows'

获取文件的 uri:

>>> p = PurePosixPath('/etc/passwd')
>>> p.as_uri()
'file:///etc/passwd'
>>> p = PureWindowsPath('c:/Windows')
>>> p.as_uri()
'file:///c:/Windows'

判断是否绝对路径:

>>> PurePosixPath('/a/b').is_absolute()
True
>>> PurePosixPath('a/b').is_absolute()
False

>>> PureWindowsPath('c:/a/b').is_absolute()
True
>>> PureWindowsPath('/a/b').is_absolute()
False
>>> PureWindowsPath('c:').is_absolute()
False
>>> PureWindowsPath('//some/share').is_absolute()
True

文件名若有变化:

>>> p = PureWindowsPath('c:/Downloads/pathlib.tar.gz')
>>> p.with_name('setup.py')
PureWindowsPath('c:/Downloads/setup.py')

是不是非常方便?

技术的底层原理和关键实现

pathlib 并不是基于 str 的实现,而是基于 object 设计的,这样就严格地区分了 Path 对象和字符串对象,同时也用到了一点 os 的功能,比如 os.name,os.getcwd 等,这一点大家可以看 pathlib 的源码了解更多。

最后的话

本文分享了 pathlib 的用法,后面要处理路径相关的操作时,你应该第一时间想到 pathlib,不会用没有关系,搜索引擎所搜索 pathlib 就可以看到具体的使用方法。

虽然 pathlib 比 os 库更高级,更方便并且提供了很多便捷的功能,但是我们仍然需要知道如何使用 os 库,因为 os 库是 Python 中功能最强大且最基本的库之一,但是,在需要一些文件系统操作时,强烈建议使用 pathlib。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

这3种数组内的字典去重方式 你会几种?

你知道吗?如果数组是字典组成的,直接对数组内的字典采用set的方式进行去重,会报错:

test = [{"a": 1}, {"a": 1}, {"a": 3}, {"b": 4}]
test = list(set(test))
>>>TypeError: unhashable type: 'dict'

因为使用set去重的前提是该对象为不可变对象,而字典是可变对象,因此无法使用该方法去重。

那么怎么解决这个问题呢?有三个办法。

1.使用reduce方法

reduce() 函数会对参数序列中元素进行累积。

比如:

from functools import reduce
>>>def add(x, y) :            # 两数相加
...    return x + y
... 
>>>reduce(add, [1,2,3,4,5])   # 计算列表和:1+2+3+4+5
15

上述写法也能用lambda函数简化为:

from functools import reduce
>>> reduce(lambda x, y: x+y, [1,2,3,4,5])  # 使用 lambda 匿名函数
15

因此,我们自己编写一个函数进行数组内的字典去重:

from functools import reduce

data = [{"a": 1}, {"a": 1}, {"a": 3}, {"b": 4}]
result = []
def unduplicate(result, data):
    if data not in result:
        result = result + [data]
    return result

for i in data:
    result = unduplicate(result, i)

>>> result
>>> [{'a': 1}, {'a': 3}, {'b': 4}]

稍显复杂,如果使用reduce函数和lambda函数,代码能简化很多:

def delete_duplicate(data):
    func = lambda x, y: x + [y] if y not in x else x
    data = reduce(func, [[], ] + data)
    return data

>>> delete_duplicate(data)
>>> [{'a': 1}, {'a': 3}, {'b': 4}]

当然, 我也能一行写完这个功能:

data = reduce(lambda x, y: x + [y] if y not in x else x, [[], ] + data)

只不过有可能会被打死在工位上,所以不建议这么干。

2.奇怪的技巧

就如文章开头提到的,字典之所以不能用set去重,是因为它是可变对象。

但是…如果我们把它变成不可变对象呢?

data = [{"a": 1}, {"a": 1}, {"a": 3}, {"b": 4}]
def delete_duplicate(data):
    immutable_dict = set([str(item) for item in data])
    data = [eval(i) for i in immutable_dict]
    return data
>>> delete_duplicate(data)
>>> [{'a': 1}, {'a': 3}, {'b': 4}]

没错,这能成。

1.遍历字典,将每个子项变成字符串存放到数组中,再通过set函数去重。

2.通过eval函数,将去重后的数组里的每个子项重新转化回字典。

如此Python,怎能不好玩?

3.高效的方式

上面讲了两种骚操作,其实都不太建议在实际工作中使用。

一个原因是真的太骚了,怕被打趴在工位上。

另一个原因是,它们在应对较大数据量的时候,性能不太行。

下面是最正统的方式:

data = [dict(t) for t in set([tuple(d.items()) for d in data])]
>>>data
>>>[{'a': 1}, {'b': 2}]

其实和第二种方式一样,是将数组内的每个字典转成元组,也就是不可变对象,再使用set进行去重。去重完毕后再使用dict函数将元组重新组成字典对。

但是,这种方法对于字典内还有字典的数据结构是不适用的,因此对于字典对里还有字典情况的去重,比如:

data2 = [{"a": {"b": "c"}}, {"a": {"b": "c"}}]

这种情况我建议使用第二种方式去重:

data2 = [{"a": {"b": "c"}}, {"a": {"b": "c"}}]
def delete_duplicate_str(data):
    immutable_dict = set([str(item) for item in data])
    data = [eval(i) for i in immutable_dict]
    return data
print(delete_duplicate_str(data2))

>>> [{'a': {'b': 'c'}}]

怎么样,这三种方式你都学会了吗?如果觉得有收获的话记得收藏一下。以后遇到类似的去重场景时可以拿出阅读一下。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 列表去重的4种方式及性能对比

​列表去重是Python中一种常见的处理方式,任何编程场景都可能会遇到需要列表去重的情况。

列表去重的方式有很多,本文将一一讲解他们,并进行性能的对比。

让我们先制造一些简单的数据,生成0到99的100万个随机数:

from random import randrange

DUPLICATES = [randrange(100) for _ in range(1000000)]

接下来尝试这4种去重方式中最简单最直观的一种方法:

1.新建一个数组,遍历原数组,如果值不在新数组里便加入到新数组中。

# 第一种方式
def easy_way():
    unique = []
    for element in DUPLICATES:
        if element not in unique:
            unique.append(element)
    return unique

进入ipython使用timeit计算其去重耗时:

%timeit easy_way()
# 1.16 s ± 137 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

平均耗时在1.16秒左右,但是在这个例子中我们使用了数组作为存储对象,实际上如果我们改成集合存储去重后的结果,性能会快不少:

def easy_way():
    unique = set()
    for element in DUPLICATES:
        if element not in unique:
            unique.add(element)
    return unique
%timeit easy_way()
# 48.4 ms ± 11.6 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

平均耗时在48毫秒左右,改善明显,这是因为集合和数组的内在数据结构完全不同,集合使用了哈希表,因此速度会比列表快许多,但缺点在于无序。

接下来看看第2种方式:

2.直接对数组进行集合转化,然后再转回数组:

# 第二种去重方式
def fast_way()
    return list(set(DUPLICATES))

耗时:

%timeit fast_way()
# 14.2 ms ± 1.73 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

平均耗时14毫秒,这种去重方式是最快的,但正如前面所说,集合是无序的,将数组转为集合后再转为列表,就失去了原有列表的顺序。

如果现在有保留原数组顺序的需要,那么这个方式是不可取的,怎么办呢?

3.保留原有数组顺序的去重

使用dict.fromkeys()函数,可以保留原有数组的顺序并去重:

def save_order():
    return list(dict.fromkeys(DUPLICATES))

当然,它会比单纯用集合进行去重的方式耗时稍微久一点:

%timeit save_order()
# 39.5 ms ± 8.66 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

平均耗时在39.5毫秒,我认为这是可以接受的耗时,毕竟保留了原数组的顺序。

但是,dict.fromkeys()仅在Python3.6及以上才支持。

如果你是Python3.6以下的版本,那么可能要考虑第四种方式了。

4. Python3.6以下的列表保留顺序去重

在Python3.6以下,其实也存在fromkeys函数,只不过它由collections提供:

from collections import OrderedDict
def save_order_below_py36():
    return list(OrderedDict.fromkeys(DUPLICATES))

耗时:

%timeit save_order_below_py36()
# 71.8 ms ± 16.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

平均耗时在72毫秒左右,比 Python3.6 的内置dict.fromkeys()慢一些,这是因为OrderedDict是用纯Python实现的。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 最好用的8个VS Code扩展

1. Python extension for Visual Studio Code

这个扩展是由微软官方提供的,支持但不仅限于以下功能:

  • 通过Pylint或Flake8支持代码检查
  • 在VS Code编辑器中调试代码
  • IntelliSense支持自动完成,代码导航和格式化。
  • 支持Jupyter Notebook,Pytest和Unittest
  • 在编辑器中轻松切换Python环境

2.Python Preview

这个插件很牛皮,能够实时可视化你的代码结果。

不仅如此,还能为VSCode切换各种主题皮肤。

3.Sort lines

这个扩展很有意思,可以给你按字母大小排序(升序、降序),也可以进行排序+去重。而且还能将所有文本打乱顺序。

做短文本分类的训练,清洗数据集的时候,这个工具大有用处。

4.Git Graph

这玩意可是Git神器,堪比Pycharm内的Git管理器。

通过这个扩展,可以清楚地看见当前分支的commit记录和变化,可以通过按钮的方式轻易地创建、切换分支、cherry pick、merge等操作。

对比分支、查看未提交的修改……还有许多可定制的扩展设置。

5.Python Snippets

很多时候,我们用到的代码片段都是类似的,比如for循环、try/catch等等,现在有了这个工具,我们只需要输入命令生成代码片段,然后再进行微调,就能完成功能的开发。

此外,有些时候我们可能会忘记某些内置函数的用法,这个工具也能给你提供示例代码做参考,而不用你再去搜索引擎搜索示例,实在非常方便。

6.Better Comments

这是一个让你能更好地编写注释的工具,它能根据关键词用不同的颜色高亮代码片段。支持以下类型的高亮:

1. 感叹号 “!” 代码警告。
2. 问号“?”代表存留疑问。
3. TODO 代码未来将要进行的操作。
4. @param 参数

此外,它还支持在设置中自定义需要高亮句子的首部关键词。

7.autoDocstring

这个扩展我应该已经推荐了好多次,能够自动生成函数的注释格式,通过tab键快速切换填充块编写相应的注释。

8.Python Indent

你有没有觉得VSCode里对Python的自动缩进有点不准确?甚至可以用“丑”来形容。每次我都喜欢强行矫正VSCode给我做的自动缩进。

经过一番查阅,我终于找到了能纠正VSCode缩进错误的扩展,它就是Python Indent,看看下面的示例,相信你也会安装它。

我们的文章到此就结束啦,如果你喜欢今天的Python 实战教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!


​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 简单实用的日志装饰器

在写代码的时候,往往会漏掉日志这个关键因素,导致功能在使用的时候出错却无法溯源。

其实,只需要写一个非常简单的日志装饰器,我们就能大大提升排查问题的效率。

1.简陋版

写一个装饰器非常简单,因为本质上装饰器就是一个返回函数的“高阶”函数而已:

1.函数作为参数传递进装饰器。
2.装饰器内定义一个函数,处理作为参数传递进来的函数。
3.返回这个装饰器内定义的函数

import datetime


def log(func):
    """
    日志装饰器,简单记录函数的日志

    Args:
        func (function): 函数
    """
    def inner(*args):
        timestamp = str(datetime.datetime.now()).split(".")[0]
        res = func(*args)
        print(f"[{timestamp}] ({func.__name__}) {args} -> {res}")
        return res
    return inner

用一下试试看:

@log
def pluser(a, b):
    return a + b

pluser(1, 2)

效果如下:

虽然这样可以实现我们所需要的功能,但其实有很大的优化空间。

2.普通版

第一版代码中有一个显而易见的问题,装饰器内定义的处理函数不支持kwargs,而在装饰器中支持kwargs仅仅是举手之劳而已。

第二个问题是,生成时间戳的时候采用字符串截取的形式,这种形式过于粗暴。其实可以使用strftime做字符串转换。

修改如下:

import datetime


def log(func):
    """
    日志装饰器,简单记录函数的日志

    Args:
        func (function): 函数
    """
    def inner(*args, **kwargs):
        timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        res = func(*args, **kwargs)
        print(f"[{timestamp}] ({func.__name__}) {args} -> {res}")
        return res
    return inner

似乎优化地差不多了,不过依然存在改进空间。

3.优化版

在前两版代码中,我们使用print进行日志输出,其实这种处理日志的方式并不标准。

使用logging模块控制日志输出是一个更好地选择。

为了使用logging模块记录日志,我们需要先配置好logging相关的选项。

1.首先,生成一个日志记录器,并配置日志等级:

import logging

# 获取日志记录器,配置日志等级
logger = logging.getLogger(__name__)
logger.setLevel('DEBUG')

2.配置日志格式、增加handler控制输出流:

# 默认日志格式
formatter = logging.Formatter("%(asctime)s - [%(levelname)s] - %(message)s")
# 输出到控制台的handler
chlr = logging.StreamHandler()
# 配置默认日志格式
chlr.setFormatter(formatter)

此处可以设置handler所需要处理的日志等级,没有设置则默认使用logger自身的Level,即DEBUG等级。

3.最后,将此handler加入到日志记录器内:

# 日志记录器增加此handler
logger.addHandler(chlr)

logging 完整配置如下:

import logging

# 获取日志记录器,配置日志等级
logger = logging.getLogger(__name__)
logger.setLevel('DEBUG')

# 默认日志格式
formatter = logging.Formatter("%(asctime)s - [%(levelname)s] - %(message)s")
# 输出到控制台的handler
chlr = logging.StreamHandler()
# 配置默认日志格式
chlr.setFormatter(formatter)

# 日志记录器增加此handler
logger.addHandler(chlr)

使用的时候非常简单,就是把print换成logger.debug即可:

def log(func):
    """
    日志装饰器,简单记录函数的日志

    Args:
        func (function): 函数
    """
    def inner(*args, **kwargs):
        timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        res = func(*args, **kwargs)
        logger.debug(f"func: {func.__name__} {args} -> {res}")
        return res
    return inner

效果如下:

这样,一个比较完善的日志装饰器就完成了。

附常用的日志等级配置:

我们的文章到此就结束啦,如果你喜欢今天的Python 实战教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!


​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 什么是元类(metaclasses)?

1.什么是类

在理解元类之前,我们必须先掌握Python中的类(class)。

和大多数语言一样,Python中的类知识用来描述如何“生成一个对象”:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

但是,在Python中,类不仅能用来描述如何生成一个对象,类本身也是对象

在你使用关键词 class 的时候,Python就会执行它,并创建一个对象。

>>> class ObjectCreator(object):
...       pass
...

上述指令在内存中创建了一个“ObjectiveCreator”的对象。

这个对象(类)本身具有创建对象(实例)的能力,因此它也是一个类。你可以对它做以下操作:

1.将其分配给变量
2.复制它
3.为其添加属性
4.将其作为函数参数传递

例如:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

2.动态创建类

由于类是对象,因此你可以像创建任何对象(数组、字典等)一样,随时随地创建类。

你甚至可以在函数里创建类:

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

但是,这样的类并不是很动态,因为你必须自己编写整个类。

使用class关键字时,Python会帮你自动创建此对象,但是,Python同样也提供了一种手动创建的方法,那就是type函数。

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

type函数最经典的用法是返回对象的类型。但是很少人知道,它还能接受参数并手动创建类。

type(name, bases, attrs)

其中

  • name: 类名
  • bases: 元组,父类名
  • attrs: 字典,类属性值

因此你可以这样手动创建类:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

如果你想给它赋予属性,可以这样玩:

>>> class Foo(object):
...       bar = True

等同于

>>> Foo = type('Foo', (), {'bar':True})

用来继承也是可以的:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

可见通过type()函数创建的类和直接写class是完全一样的。

因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。

正常情况下,我们用class来定义类,但是,type()函数也允许我们动态创建类,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同。

Python是通过什么做到这一切的?那就是元类。

3.什么是元类

元类就是用于创建类的“东西”。

你定义类是为了创建对象,Python中所有的类都是对象。元类是用于创建这些对象的。可以看这个例子:

MyClass = MetaClass()
my_object = MyClass()

这有点像套娃。这段代码转化为type就是这样的:

MyClass = type('MyClass', (), {})

因此,我们可以得到一个基本事实,type 本身就是一个元类

其实,就是 type 在幕后创建了Python中所有的类。

通过检查__class__属性,你会看到Python中,一切对象都是基于 type 的:

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

那么,有个有趣的问题,__class__的__class__是什么呢?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

因此,元类只是创建类对象的东西,如果愿意,可以将其称为“类的工厂”。

type 是Python使用的内置元类。不过,你可以创建自己的元类。

3.1 __metaclass__属性

在Python 2中,可以在编写类时添加属性__metaclass__,使用某个元类来创建该类:

class Foo(object):
    __metaclass__ = something...
    [...]

不过,要小心的是,你虽然先写了 class Foo(object),但Foo这个对象尚未被创建,Python将先寻找__metaclass__类,找到后用它来创建Foo类。

如果没有这个__metaclass__类,它将使用 type 来创建类。

因此,类创建的流程是这样的:

1.创建的类中有__metaclass__元类属性吗?

2.如果有,那就用__metaclass__给该类在内存中创建一个类对象。

3.如果Python找不到__metaclass__,它将在MODULE级别查找__metaclass__属性 。

4.如果还是没有,那就使用父类的元类来创建类对象。

现在的问题就是,你可以在__metaclass__中放置些什么代码呢?

答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何继承或使用它的东西。

3.2 Python 3中的元类

设置元类的语法在Python3已改为:

class Foo(object, metaclass=something):
    ...

即不再使用__metaclass__属性,而是在基类参数列表中引入关键字参数。

不过元类的基本工作方式不变。在Python3中,你可以将属性作为关键字参数传递给元类:

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

4.为什么需要元类

元类最主要的一个应用方向是创建API,一个最著名的应用是Django ORM,比如:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

当你这样访问属性的时候:

person = Person(name='bob', age='35')
print(person.age)

它并不会返回models.IntegerField,而是返回了一个整形的数字。

这是因为models.Model引用了一个ModelBase类,该类随后进行了魔术般地操作,使其能够与数据库字段进行挂钩。

这就是元类的作用,Django通过它,完成了系列复杂的幕后工作,将原本非常复杂的事情变得非常简单。

我们的文章到此就结束啦,如果你喜欢今天的Python 实战教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!


​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

我们也有Python学习互助群啦!

最近问我问题的同学越来越多,平台越来越分散,很难进行管理,可能无意中漏了某些同学的问题,请见谅~

为了解决这个问题,我觉得还是搭建一个群,把大家都集中起来比较好。

这样做有几个好处:

一是交流容易、反馈容易,文章有问题大家可以直接@我,这样我就能进行快速的修改。

二是文章发布能被大家及时看到,现在我一般都在早上发布文章,文章有几率会被大家的公众号信息流覆盖掉。

三是更能帮助大家学习Python,有任何问题都能在群里问,我或者其他群友都会尽可能帮你。

四是沟通交流对于我们的学习非常重要,集思广益,各抒己见,人人都尽其所能,你知道我所不知道的,我知道你所不知道的,这样问题就变得容易解决。

群二维码:

另外,再提供一个微信个人号,有什么问题可以直接加我微信问我,有时间的话我会耐心解答的。

如果群二维码过期了,大家也可以直接加我微信,密我“进群”,然后我会拉你进群的。

最后,希望大家能开开心心学Python,学会用Python处理生活中的事~

Python 量化投资实战教程(1) — Backtrader 教程

量化投资系列文章:

Backtrader 教程 — Python 量化投资原来这么简单(1)

Python 量化投资原来这么简单(2) —MACD策略(+26.9%)

Python 量化投资原来这么简单(3) —A股回测MACD策略

Github仓库:https://github.com/Ckend/pythondict-quant


都说 Python 量化投资 非常好用,但是很多人都不知道该怎么做,甚至觉得是非常高深的知识,其实并非如此,任何人都可以在只有一点Python的基础上回测一个简单的策略。

Backtrader是一个基于Python的自动化回溯测试框架,作者是德国人 Daniel Rodriguez,是一个易懂、易上手的量化投资框架。今天我们就来试试用Backtrader进行简单的量化策略回溯。

当然,第一篇文章将会使用最简单的投资策略给大家起个头。通过学习这一篇文章,你将能学会以下这个简单的量化策略:

买入:五日价格移动平均线(MA5)和十日价格移动平均线(MA10)形成均线金叉(MA5上穿MA10)原理:最近处于涨势

卖出: 五日价格移动平均线(MA5)和十日价格移动平均线(MA10)形成均线死叉(MA5下穿MA10)原理:最近处于跌势

这个策略真的有用吗?普通人可能要炒一辈子股才能发现它的实际作用,而使用Python进行量化验证,则能迅速得到答案。

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格 输入Terminal),准备开始输入命令安装依赖。

当然,我更推荐大家用VSCode编辑器,把本文代码Copy下来,在编辑器下方的终端装依赖模块,多舒服的一件事啊:Python 编程的最好搭档—VSCode 详细指南

输入以下命令安装本文所需要的依赖模块:

pip install backtrader

看到 Successfully installed xxx 则说明安装成功。本文完整数据和源代码可在公众号后台回复 量化投资一 进行下载。

2.基础使用

在开始之前,你必须要知道backtrader的数据结构特点:

self.dataclose[0] # 当日的收盘价
self.dataclose[-1] # 昨天的收盘价
self.dataclose[-2] # 前天的收盘价

这一点我在一开始使用的时候也被作者的逻辑震惊了,原来还能这么玩,总而言之,请记住这个特点,否则你可能会完全看不懂策略。

2.1 资金与佣金

Backtrader 初始化模型后,即可通过broker(经纪人)来设定初始资金,如下所示:

# -*- coding:utf-8 -*-
# Python 实用宝典
# 量化投资原来这么简单(1)
# 2020/04/12

import backtrader as bt

if __name__ == '__main__':

    # 初始化模型
    cerebro = bt.Cerebro()
    # 设定初始资金
    cerebro.broker.setcash(100000.0)

    # 策略执行前的资金
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    cerebro.run()

    # 策略执行后的资金
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

现实生活中的股票交易里,每次交易都需要支付一定的佣金,比如万五(交易额每满一万元收取5元佣金)万三等,在Backtrader里你只需要这么设定即可:

cerebro.broker.setcommission(0.005)

设定需要设定每次交易买入的股数,可以这样

cerebro.addsizer(bt.sizers.FixedSize, stake=100)

2.2 加载数据

Backtrader 将数据集称作为 Data Feeds,默认的数据集是yahoo的股票数据,通过以下方式可以加载:

    data = bt.feeds.YahooFinanceCSVData(
        dataname='数据文件所在位置',
        fromdate=datetime.datetime(2000, 1, 1),
        todate=datetime.datetime(2000, 12, 31)
    )

当然,载入自己的数据也是可以的,只不过你需要设定每个列的含义,比如开盘价在第4列,则open=3(从0开始算起),如下所示:

    data = bt.feeds.GenericCSVData(
        dataname='数据文件所在位置',
        datetime=2,
        open=3,
        high=4,
        low=5,
        close=6,
        volume=10,
        dtformat=('%Y%m%d'),
        fromdate=datetime(2010, 1, 1),
        todate=datetime(2020, 4, 12)
    )

下面,咱会使用自己的数据进行回测,这样才够有代入感。

2.3 构建策略

使用backtrader构建策略是一件很简单的事情,你只需要继承backtrader的策略类,并重写部分方法,就能实现策略。比如说重写属于我们自己的log函数:

class TestStrategy(bt.Strategy):
    """
    继承并构建自己的bt策略
    """

    def log(self, txt, dt=None, doprint=False):
        ''' 日志函数,用于统一输出日志格式 '''
        if doprint:
            dt = dt or self.datas[0].datetime.date(0)
            print('%s, %s' % (dt.isoformat(), txt))

最重要的是,重写我们自己的交易策略,比如咱在开头提到的均线金叉死叉策略

class TestStrategy(bt.Strategy):
    """
    继承并构建自己的bt策略
    """

    def next(self):
        # 记录收盘价
        self.log('Close, %.2f' % self.dataclose[0])

        # 是否正在下单,如果是的话不能提交第二次订单
        if self.order:
            return

        # 是否已经买入
        if not self.position:

            # 还没买,如果 MA5 > MA10 说明涨势,买入
            if self.sma5[0] > self.sma10[0]:
                self.log('BUY CREATE, %.2f' % self.dataclose[0])
                self.order = self.buy()

        else:
            # 已经买了,如果 MA5 < MA10 ,说明跌势,卖出
            if self.sma5[0] < self.sma10[0]:
                self.log('SELL CREATE, %.2f' % self.dataclose[0])
                self.order = self.sell()

有用吗?待会儿我们回测后就知道了。

2.4 添加指标

backtrader内置了许多指标的计算方法,比如移动平均线、MACD、RSI等等,我们这一篇文章仅需要移动平均线MA,设置方法如下:

self.sma5 = bt.indicators.SimpleMovingAverage(
            self.datas[0], period=5)

其中,datas[0]是第一个数据集,period是指多少天的移动平均线,比如5,则返回MA5的相关数据。

3.策略回测

为了验证我们开头提到的策略,咱使用了 贵州茅台600519.SH 在2020年1月1日至今(2020/04/12)的股票数据,完整数据和源代码可在公众号后台回复 量化投资一 进行下载。

将数据命名为600519.csv,保存在当前文件夹下,主函数如下:

if __name__ == '__main__':

    # 初始化模型
    cerebro = bt.Cerebro()

    # 构建策略
    strats = cerebro.addstrategy(TestStrategy)
    # 每次买100股
    cerebro.addsizer(bt.sizers.FixedSize, stake=100)

    # 加载数据到模型中
    data = bt.feeds.GenericCSVData(
        dataname='600519.csv',
        fromdate=datetime.datetime(2010, 1, 1),
        todate=datetime.datetime(2020, 4, 12),
        dtformat='%Y%m%d',
        datetime=2,
        open=3,
        high=4,
        low=5,
        close=6,
        volume=10
    )

    cerebro.adddata(data)

    # 设定初始资金和佣金
    cerebro.broker.setcash(1000000.0)
    cerebro.broker.setcommission(0.005)

    # 策略执行前的资金
    print('启动资金: %.2f' % cerebro.broker.getvalue())

    # 策略执行
    cerebro.run()

最后补全策略就完成了,我们的backtrader策略如下,为了公众号的可读性,这里去掉了部分不重要的代码,详细的代码可阅读原文或后台回复量化投资一下载:

class TestStrategy(bt.Strategy):
    """
    继承并构建自己的bt策略
    """

    def log(self, txt, dt=None, doprint=False):
        ''' 日志函数,用于统一输出日志格式 '''
        if doprint:
            dt = dt or self.datas[0].datetime.date(0)
            print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):

        # 初始化相关数据
        self.dataclose = self.datas[0].close
        self.order = None
        self.buyprice = None
        self.buycomm = None

        # 五日移动平均线
        self.sma5 = bt.indicators.SimpleMovingAverage(
            self.datas[0], period=5)
        # 十日移动平均线
        self.sma10 = bt.indicators.SimpleMovingAverage(
            self.datas[0], period=10)

    def notify_order(self, order):
        """
        订单状态处理

        Arguments:
            order {object} -- 订单状态
        """
        if order.status in [order.Submitted, order.Accepted]:
            # 如订单已被处理,则不用做任何事情
            return

        # 检查订单是否完成
        if order.status in [order.Completed]:
            if order.isbuy():
                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
            self.bar_executed = len(self)

        # 订单因为缺少资金之类的原因被拒绝执行
        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('Order Canceled/Margin/Rejected')

        # 订单状态处理完成,设为空
        self.order = None

    def notify_trade(self, trade):
        """
        交易成果
        
        Arguments:
            trade {object} -- 交易状态
        """
        if not trade.isclosed:
            return

        # 显示交易的毛利率和净利润
        self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                 (trade.pnl, trade.pnlcomm), doprint=True)

    def next(self):
        ''' 下一次执行 '''

        # 记录收盘价
        self.log('Close, %.2f' % self.dataclose[0])

        # 是否正在下单,如果是的话不能提交第二次订单
        if self.order:
            return

        # 是否已经买入
        if not self.position:
            # 还没买,如果 MA5 > MA10 说明涨势,买入
            if self.sma5[0] > self.sma10[0]:
                self.order = self.buy()
        else:
            # 已经买了,如果 MA5 < MA10 ,说明跌势,卖出
            if self.sma5[0] < self.sma10[0]:
                self.order = self.sell()

    def stop(self):
        self.log(u'(金叉死叉有用吗) Ending Value %.2f' %
                 (self.broker.getvalue()), doprint=True)

这份代码看起来很长,但其实把注释去掉后,实现的是很简单的逻辑。效果如何?看下图就知道了:

可以看到,我们初始资金是100万,每次交易100股,虽然偶尔有盈利,如果严格按照这个策略执行十年,最后会亏损5万元。当然,现实生活中,有时候情形好你肯定会加仓,情形差你会减仓,而这里暂时只是一个简单的买入卖出策略。

但是这种简单的实现方式,往往最能帮助你理性地分析该策略的合理性,如果说一个策略总是需要你主观地去加仓、减仓,那该策略势必存在问题。真正好的策略,从概率上来讲,简单回测的结果总会是盈利的。

所以这种单纯的、简单的均线金叉死叉策略有用吗? 我认为效果有限。网上策略很多,大家也可以试试别的策略,如果有好用的,记得告诉我(滑稽)。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

快来用Python制作只属于你和ta的聊天渠道吧

是否担心微信的数据流会被监视?是否担心你和ta聊天的小秘密会被保存到某个数据里?没关系,现在我们可以用Python做一个只属于你和ta的聊天渠道,来解除你们心中的担忧。

1.原理简介

在我们今天的教程中,将用到即时通讯的概念,即时通讯允许两人或多人同时使用网络传递文字信息、文字、语音等。即时通讯一般都基于socket连接,socket连接可用于发送或接受数据,一般的组合形式是IP+端口号

也就是说,在我们的例子中,聊天的双方,由一方要承担“服务器 ” 的责任,维持一个socket服务器,等待连接进入;另一方则是“客户端”,在服务器端维持等待状态时即可发送请求,建立连接。

当你和ta想进入“小黑屋 ” 里聊天的时候,只有有一方充当服务器,另一方充当客户端即可,作为“服务器端 ” 的那个人,在微信中将IP和端口号告诉对方,即可构建连接,在小黑屋里聊天,这个小黑屋里的数据不会被任何数据保留(除非你自己做了一个保存的数据)。

2.代码编写

好了,基本原理我们已经讲清楚了。不过,在开始教程之前,你得先安装好了Python,如果还没有安装,可以看这篇文章:https://pythondict.com/python-tutorials/how-to-install-python/

2.1 服务器端

聊天的时候,我们有时候会遇到双方同时发消息的情况。这种聊天方式就叫全双工聊天方式:“服务器”可向“客户端”发送消息,“客户端”也可向“服务端”发送消息,而且允许同时发送消息。

服务器端怎么实现全双工的聊天方式呢?其实很简单,只要用多线程就行了,主线程用于接收客户端的连接,连接成功后新建两个线程:一个用于发送消息,一个用于接收消息:

首先,建立socket服务器:

import socket
import traceback
# 设定ip和端口号
host = ''
port = 51423 
# 建立socket服务器
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind((host,port))
s.listen() 
while True:
    # 等待连接
    try:
        clientsock, clientaddr = s.accept()
    except KeyboardInterrupt:
        raise
    except:
        traceback.print_exc()
        continue 

其中,AF_INET指的是用IPv4进行通信,而SOCK_STREAM指的是TCP协议。端口号你可以随意设定,服务器端的IP地址默认为空即可。

在while循环中不断等待用户的连接。如果有用户连接成功了,我们将进入下一步,分别建立发送和接受线程:

# 建立接收线程
t = _thread.start_new_thread(processRecv, (clientsock,))

# 建立发送线程
r = _thread.start_new_thread(processSend, (clientsock,)) 

clientsock就是我们得到的socket连接,processRecv和processSend分别用于处理接受信息和处理发送信息:

import _thread 
def processRecv(clientsock):
    """
    接受消息
        :param clientsock: 客户端的socket连接
    """
    while True:
        data = clientsock.recv(4096)
        if not len(data):
            break
        print (data.decode('utf-8'))
    clientsock.close()

def processSend(clientsock):
    """
    发送消息
        :param clientsock: 客户端的socket连接
    """
    while True:
        data = input("> ")
        data = data
        clientsock.sendall(data.encode('utf-8'))
    clientsock.close() 

有个小细节要注意,socket连接的sendall函数只支持bytes类型的数据,所以我们要encode(‘utf-8’)。

服务端的所有代码就这样,没错,就是这么简单。

2.2 客户端

客户端则更简单,主线程本身设定为接受消息,那么我们只需要多一个线程用于发送消息即可。客户端的全部代码如下:

#-*-coding:utf-8-*-

import _thread
import sys
from socket import *

def send_message(tcpCliSock):
    """
    发送信息
        :param tcpCliSock: 与服务端的socket连接
    """
    while True:
        message = input('> ')
        if not message:
            break
        tcpCliSock.send(message.encode('utf-8'))

    tcpCliSock.close()

if(len(sys.argv) < 3):
    HOST = 'localhost'
    PORT = 51423
else:
    HOST = sys.argv[1]
    PORT = int(sys.argv[2])

BUFSIZ = 1024
ADDR = (HOST,PORT)

tcpCliSock = socket(AF_INET,SOCK_STREAM)
tcpCliSock.connect(ADDR)

# 建立发送消息的线程
s = _thread.start_new_thread(send_message, (tcpCliSock,))

while True:
    rdata = tcpCliSock.recv(BUFSIZ)
    if not rdata:
        break
    print (rdata.decode('utf-8'))
    
tcpCliSock.close() 

其中,HOST部分填写对方的IP,PORT部分填写端口号。sys.argv用于通过参数输入这两个值,比如我们将客户端文件命名为:client.py, 在cmd中输入:

python client.py 127.0.0.1 51423

能直接传入参数执行脚本,除此之外,其他部分和服务端其实差不多。注意把接受到的数据decode一下(因为我们发的时候encode了)。

3. 改进

实际上,这份代码虽然可以用,但是还是存在许多问题的。比如在你们聊天的时候,突然又有一个人向服务端发送连接请求怎么办?这时候我们需要在服务端加一份拥有验证的代码,要求对方输入聊天室密码后才可建立连接。

这个密码必须是你们双方才知道的密码,任何通过第三方工具传播密码的行为都是不可靠和不安全的。这样才可以防止第三者的偷听。增加一个密码功能其实也不难,这部分交给大家自己去实现啦!

本文完整源代码下载请在公众号后台回复:聊天渠道

我们的文章到此就结束啦,如果你希望我们今天的Python 教程,请持续关注我们,如果对你有帮助,麻烦在下面点一个赞/在看哦有任何问题都可以在下方留言区留言,我们都会耐心解答的!


​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典