注册
环信即时通讯云

环信即时通讯云

单聊、群聊、聊天室...
环信开发文档

环信开发文档

环信FAQ

环信FAQ

集成常见问题及答案
RTE开发者社区

RTE开发者社区

汇聚音视频领域技术干货,分享行业资讯
技术讨论区

技术讨论区

技术交流、答疑
资源下载

资源下载

收集了海量宝藏开发资源
iOS Library

iOS Library

不需要辛辛苦苦的去找轮子, 这里都有
Android Library

Android Library

不需要辛辛苦苦的去找轮子, 这里都有

我发现了 Android 指纹认证 Api 内存泄漏

我发现了 Android 指纹认证 Api 内存泄漏 目前很多市面上的手机基本都有指纹登陆功能。Google 也提供了调用相关功能 API,安全类的App 也基本都在使用。接下来就一起捋一捋今天的主角 BiometricPrompt 先说问题,使用Biome...
继续阅读 »

我发现了 Android 指纹认证 Api 内存泄漏


目前很多市面上的手机基本都有指纹登陆功能。Google 也提供了调用相关功能 API,安全类的App 也基本都在使用。接下来就一起捋一捋今天的主角 BiometricPrompt


先说问题,使用BiometricPrompt 会造成内存泄漏,目前该问题试了 Android 11 到 13 都发生,而且没有什么好的办法。目前想到的最好的方法是漏的少一点。当然谁有好的办法欢迎留言。


问题再现


先看动画


在这里插入图片描述


动画中操作如下



  1. MainAcitivity 跳转到 SecondActivity

  2. SecondActivity 调用 BiometricPrompt 三次

  3. 从SecondActivity 返回到 MainAcitivity


以下是使用 BiometricPrompt 的代码


public fun showBiometricPromptDialog() {
val keyguardManager = getSystemService(
Context.KEYGUARD_SERVICE
) as KeyguardManager;

if (keyguardManager.isKeyguardSecure) {
var biometricPromptBuild = BiometricPrompt.Builder(this).apply {// this is SecondActivity
setTitle("verify")
setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL or BiometricManager.Authenticators.BIOMETRIC_WEAK)
}
val biometricPromp = biometricPromptBuild.build()
biometricPromp.authenticate(CancellationSignal(), mExecutor, object :
BiometricPrompt.AuthenticationCallback() {

})
}
else {
Log.d("TAG", "showLockScreen: isKeyguardSecure is false");
}
}

以上逻辑 biometricPromp 是局部变量,应该没有问题才对。


内存泄漏如下


在这里插入图片描述
可以看到每启动一次生物认证,创建的 BiometricPrompt 都不会被回收。


规避方案:


修改方案也简单


方案一:



  1. biometricPromp 改为全局变量。

  2. this 改为 applicationContext


方案一存在的问题,SecondActivity 可能频繁创建,所以 biometricPromp 还会存在多个实例。


方案二(目前想到的最优方案):



  1. biometricPromp 改为单例

  2. this 改为 applicationContext


修改后,App memory 中只存在一个 biometricPromp ,且没有 Activity 被泄漏。


想到这里,应该会觉得奇怪,biometricPromp 为什么不会被回收?提供的 API 都看过了,没有发现什么方法可以解决这个问题。直觉告诉我这个可能是系统问题,下来分析下BiometricPrompt 吧。


BiometricPrompt 源码分析


在这里插入图片描述


App 相关信息通过 BiometricPrompt 传递到 System 进程,System 进程再通知 SystemUI 显示认证界面。


App 信息传递到 System 进程,应该会使用 Binder。这个查找 BiometricPrompt 使用哪些 Binder。


private final IBiometricServiceReceiver mBiometricServiceReceiver =
new IBiometricServiceReceiver.Stub() {

......
}

源码中发现 IBiometricServiceReceiver 比较可疑,IBiometricServiceReceiver 是匿名内部类,内部是持有 BiometricPrompt 对象的引用。


接下来看下 System Server 进程信息(注:系统是 UserDebug 的手机,才可以查看,买的手机版本是不支持的)


在这里插入图片描述



😂 App 使用优化后(方案二)App 只存在一个 IBiometricServiceReceiver ,而 system 进程中存在三个 IBiometricServiceReceiver 的 binder proxy。 每次启动 BiometricPrompt 都会创建一个。这个就不解释为什么会出现三个binder proxy,感兴趣可以看下面推荐的文章。GC root 是 AuthSession。

再看下 AuthSession 的实例数


在这里插入图片描述


果然 AuthSession 也存在三个。


在这里插入图片描述


这里有个知识点,binder 也是有生命周期的,三个 Proxy 这篇文章也是解释了的。有兴趣的可以了看下。


Binder | 对象的生命周期


一开始,我以为 AuthSession 没有被置空,看下代码,发现 AOSP 的代码,还是比较严谨的,有置空的操作。


细心的同学发现,上图中 AuthSession 没有被任何对象引用,AuthSession 就是 GC Root,哈哈哈。


问题解密


一个实例什么情况可以作为GC Root,有兴趣的同学,可以自行百度,这里就不卖关子了,直接说问题吧。


Binder.linkToDeath()


public void linkToDeath(@NonNull DeathRecipient recipient, int flags) {
}

需要传递 IBinder.DeathRecipient ,这个 DeathRecipient 会被作为 GC root。当调用 unlinkToDeath(@NonNull DeathRecipient recipient, int flags),GC root 才被收回。


AuthSession 初始化的时候,会调用 IBiometricServiceReceiver .linkToDeath。


public final class AuthSession implements IBinder.DeathRecipient {
AuthSession(@NonNull Context context,
......
@NonNull IBiometricServiceReceiver clientReceiver,
......
) {
Slog.d(TAG, "Creating AuthSession with: " + preAuthInfo);
......
try {
mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */);//this 变成 GC root
} catch (RemoteException e) {
Slog.w(TAG, "Unable to link to death");
}

setSensorsToStateUnknown();
}
}

Jni 中 通过 env->NewGlobalRef(object),告诉虚拟机 AuthSession 是 GC Root。


core/jni/android_util_Binder.cpp

static void android_os_BinderProxy_linkToDeath(JNIEnv* env, jobject obj,
jobject recipient, jint flags)
// throws RemoteException
{
if (recipient == NULL) {
jniThrowNullPointerException(env, NULL);
return;
}

BinderProxyNativeData *nd = getBPNativeData(env, obj);
IBinder* target = nd->mObject.get();

LOGDEATH("linkToDeath: binder=%p recipient=%p\n", target, recipient);

if (!target->localBinder()) {
DeathRecipientList* list = nd->mOrgue.get();
sp<JavaDeathRecipient> jdr = new JavaDeathRecipient(env, recipient, list);//java 中 DeathRecipient 会被封装为 JavaDeathRecipient
status_t err = target->linkToDeath(jdr, NULL, flags);
if (err != NO_ERROR) {
// Failure adding the death recipient, so clear its reference
// now.
jdr->clearReference();
signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/);
}
}
}

JavaDeathRecipient(JNIEnv* env, jobject object, const sp<DeathRecipientList>& list)
: mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object)),// object -> DeathRecipient 变为 GC root
mObjectWeak(NULL), mList(list)
{
// These objects manage their own lifetimes so are responsible for final bookkeeping.
// The list holds a strong reference to this object.
LOGDEATH("Adding JDR %p to DRL %p", this, list.get());
list->add(this);

gNumDeathRefsCreated.fetch_add(1, std::memory_order_relaxed);
gcIfManyNewRefs(env);
}

unlinkToDeath 最终会在 Jni 中 通过 env->DeleteGlobalRef(mObject),告诉虚拟机 AuthSession 不是GC root。


virtual ~JavaDeathRecipient()
{
//ALOGI("Removing death ref: recipient=%p\n", mObject);
gNumDeathRefsDeleted.fetch_add(1, std::memory_order_relaxed);
JNIEnv* env = javavm_to_jnienv(mVM);
if (mObject != NULL) {
env->DeleteGlobalRef(mObject);// object -> DeathRecipient GC root 被撤销
} else {
env->DeleteWeakGlobalRef(mObjectWeak);
}
}

解决方式


AuthSession 置空的时候调用 IBiometricServiceReceiver 的 unlinkToDeath 方法。


总结


以上梳理的其实就是 Binder 的造成的内存泄漏。


问题严重性来看,也不算什么大问题,因为调用 BiometricPrompt 的进程被杀,system 进程相关实例也就回收释放了。一般 app 也不太可能出现,常驻进程,而且还频繁调用手机认证的。


这里主要介绍了一种容易被忽略的内存泄漏,Binder.linktoDeath()。
Google issuetracker


参考资料


Binder | 对象的生命周期


作者:Jingle_zhang
来源:juejin.cn/post/7202066794299129914
收起阅读 »

别再忘了锁屏,几行代码写一个人走屏锁小工具

写在前面 之前在公司,毕竟是干安全的,部门有这么一个要求,被发现不锁屏的,请全部门喝奶茶。很不幸,我也出现过忘了锁屏然后被发现的情况。自此之后,我就形成了肌肉记忆,同时也对别人不锁屏很敏感。 为什么要强调锁屏呢?你也不想你的电脑被别人操作吧,也不想自己的信息被...
继续阅读 »

写在前面


之前在公司,毕竟是干安全的,部门有这么一个要求,被发现不锁屏的,请全部门喝奶茶。很不幸,我也出现过忘了锁屏然后被发现的情况。自此之后,我就形成了肌肉记忆,同时也对别人不锁屏很敏感。


为什么要强调锁屏呢?你也不想你的电脑被别人操作吧,也不想自己的信息被别人获取吧。毕竟防人之心不可无。


自打跳槽到新公司之后,每次去厕所的路上就看到有人电脑不锁屏,真的是令我无比的纠结。锁个屏幕有那么难吗?确实很难,有时候一忙就容易忘,于是我就想实现一个离开电脑自动锁屏的程序。


分析


这玩意实现也不难,简单思考一下,就是让电脑检测人在不在电脑前面,那就是要试试捕获摄像头了,然后设置一个间隔时间,每隔一段时间截取图片,做人脸识别,没有人脸了就锁屏就行了。


涉及到摄像头图片处理,直接让人联想到opencv,然后再用python实现上面的一套逻辑,就搞定。


代码


安装opencv的库


pip install opencv-python

直接上代码:


import cv2
import time
import os
import platform

# 检测操作系统
def detect_os():
os_name = platform.system()
if os_name == 'Windows':
return 'windows'
elif os_name == 'Darwin':
return 'mac'
else:
return 'other'

# 执行锁屏命令
def lock_screen(os_type):
if os_type == 'windows':
os.system('rundll32.exe user32.dll, LockWorkStation')
elif os_type == 'mac':
os.system('/System/Library/CoreServices/"Menu Extras"/User.menu/Contents/Resources/CGSession -suspend')


# 初始化摄像头
cap = cv2.VideoCapture(0)

# 载入OpenCV的人脸检测模型
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')

# 无人状态计时器
no_person_timer = 0
# 设定无人状态时间阈值
NO_PERSON_THRESHOLD = 3

# 检测操作系统类型
os_type = detect_os()

while True:
ret, frame = cap.read()
if not ret:
break

# 转换为灰度图像
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.1, 4)

if len(faces) == 0:
no_person_timer += 1
else:
no_person_timer = 0

# 如果超过阈值,则锁屏
if no_person_timer > NO_PERSON_THRESHOLD:
lock_screen(os_type)
no_person_timer = 0

time.sleep(1)

cap.release()

代码里都做好了注释,很简单,因为windows和macOS的锁屏指令不一样,所以做了个简单的系统平台判断。


可以完美执行,就是它得一直调用摄像头,应该也不会有人真的使用这玩意吧,hhh。


作者:银空飞羽
来源:juejin.cn/post/7317824480911458304
收起阅读 »

wordcloud,一个超酷的python库

微信公众号:愤怒的it男,超多Python技术干货文章。 一、简单介绍一下 词云图是文本挖掘中用来表征词频的数据可视化图像,通过它可以很直观地展现文本数据中地高频词,让读者能够从大量文本数据中快速抓住重点。如下图: wordcloud则是一个非常优秀的词云...
继续阅读 »

微信公众号:愤怒的it男,超多Python技术干货文章。



一、简单介绍一下


词云图是文本挖掘中用来表征词频的数据可视化图像,通过它可以很直观地展现文本数据中地高频词,让读者能够从大量文本数据中快速抓住重点。如下图:


图1.png


wordcloud则是一个非常优秀的词云展示python库,它支持自定义词云图的大小、颜色、字体等,甚至可以通过蒙版图片设置词云图的形状。因此,我们可以借助wordcloud轻松生成精美的词云图。


二、安装只需一行命令


pip install wordcloud

三、从一个简单例子开始


from wordcloud import WordCloud

text = "微信公众号:愤怒的it男"

wc = WordCloud(font_path='FZYTK.TTF', repeat=True)
wc.generate(text)
wc.to_file('wordcloud.png')

这里通过WordCloud类设置字体为方正姚体,背景颜色为白色,文本可以重复显示。生成WordCloud对象后,使用generate()方法将“微信公众号:愤怒的it男”生成词云图。最后,使用to_file()方法生成图片文件。


图2.png


四、细说wordcloud


WordCloud作为wordcloud库最核心的类,其主要参数及说明如下:


图3.PNG


这里以wordcloud库官方文档的constitution.txt文件作为数据,覆盖WordCloud类的各种参数设置用法,绘制出一张精美的词云图。


图4.PNG


首先,读入constitution.txt数据,并将数据清洗成空格分隔的长字符串。


import re

with open('constitution.txt') as c:
text = ' '.join([word.group().lower() for word in re.finditer('[a-zA-Z]+', c.read())])

print(text[:500])

图5.PNG


然后,在默认参数设置下,使用WordCloud对象的generate()和to_file()方法生成一张简单的词云图。


from wordcloud import WordCloud
import re

with open('constitution.txt') as c:
text = ' '.join([word.group().lower() for word in re.finditer('[a-zA-Z]+', c.read())])

wc = WordCloud()
wc.generate(text)

wc.to_file('wordcloud.png')

图6.png


以上词云图是在默认参数下生成的,简单粗糙不好看。接下来我们将对WordCloud的各种参数调整设置,不断地对以上词云图进行升级改造。


1、设置图片属性


设置图片宽为600,高为300,放大1.5倍,色彩空间为RGBA,背景颜色为。


from wordcloud import WordCloud
import re

with open('constitution.txt') as c:
text = ' '.join([word.group().lower() for word in re.finditer('[a-zA-Z]+', c.read())])

wc = WordCloud(
width=600,
height=300,
scale=1.5,
mode='RGBA',
background_color=,
)
wc.generate(text)

wc.to_file('wordcloud.png')

图7.png


2、设置文字布局


设置水平比例为1(即全部为水平文字),最多只显示100个词,停用词使用自带的词典(中文需要传入自定义的),相关一致性为0.3,文字布局为非随机,不允许重复词。


from wordcloud import WordCloud
import re

with open('constitution.txt') as c:
text = ' '.join([word.group().lower() for word in re.finditer('[a-zA-Z]+', c.read())])

wc = WordCloud(
width=600,
height=300,
scale=1.5,
mode='RGBA',
background_color=,
prefer_horizontal=1,
max_words=400,
stopwords=,
relative_scaling=0.3,
random_state=4,
repeat=False,
)
wc.generate(text)

wc.to_file('wordcloud.png')

图8.png


3、设置字体属性


设置字体为‘JOKERMAN.TTF’,最小字号为2,最大字号为150。


from wordcloud import WordCloud
import re

with open('constitution.txt') as c:
text = ' '.join([word.group().lower() for word in re.finditer('[a-zA-Z]+', c.read())])

wc = WordCloud(
width=600,
height=300,
scale=1.5,
mode='RGBA',
background_color=,
prefer_horizontal=1,
max_words=400,
stopwords=,
relative_scaling=0.3,
random_state=4,
repeat=False,
font_path='JOKERMAN.TTF',
min_font_size=2,
max_font_size=150,
)
wc.generate(text)

wc.to_file('wordcloud.png')

图9.png


4、设置蒙版


图10.PNG


设置微信公众号【愤怒的it男】头像的黑白图片为蒙版图片。


from PIL import Image
from wordcloud import WordCloud
import numpy as np
import re

mask_picture = np.array(Image.open('angry_it_man_mask.png'))

with open('constitution.txt') as c:
text = ' '.join([word.group().lower() for word in re.finditer('[a-zA-Z]+', c.read())])

wc = WordCloud(
width=600,
height=300,
scale=1.5,
mode='RGBA',
background_color=,
prefer_horizontal=1,
max_words=400,
stopwords=,
relative_scaling=0.3,
random_state=4,
repeat=False,
font_path='JOKERMAN.TTF',
min_font_size=2,
max_font_size=150,
mask=mask_picture,
)
wc.generate(text)

wc.to_file('wordcloud.png')

图11.png



微信公众号:愤怒的it男,超多Python技术干货文章。



作者:愤怒的it男
来源:juejin.cn/post/7317214007572807731
收起阅读 »

Python中级知识梳理

1. 文件操作 Python中的文件操作通常使用内置的open()函数来打开文件。以下是一个简单的示例: with open("file.txt", "r") as f: content = f.read() print(content) 在...
继续阅读 »

image.png


1. 文件操作


Python中的文件操作通常使用内置的open()函数来打开文件。以下是一个简单的示例:


with open("file.txt", "r") as f:
content = f.read()
print(content)

在这个示例中,我们打开了名为"file.txt"的文件,并将其读入变量content中,最后将其打印出来。


open()函数的第一个参数是文件名,第二个参数是打开文件的模式。以下是一些常用的模式:



  • "r":只读模式

  • "w":写入模式(会覆盖已有文件)

  • "a":追加模式(不会覆盖已有文件)


2. 正则表达式


正则表达式是一种强大的工具,可以帮助我们从文本中提取信息或进行文本替换。Python中可以使用内置的re模块来进行正则表达式操作。以下是一个示例:


import re

text = "The quick brown fox jumps over the lazy dog."
pattern = r"fox"
matches = re.findall(pattern, text)
print(matches)

在这个示例中,我们定义了一个正则表达式模式r"fox",然后使用re.findall()函数来查找匹配该模式的所有字符串。最后,我们将匹配的结果打印出来。


3. 异常处理


在编写程序时,经常需要处理可能出现的错误或异常情况。Python中可以使用tryexcept语句来实现异常处理。以下是一个简单的示例:


try:
x = 1 / 0
except ZeroDivisionError:
print("Error: division by zero")

在这个示例中,我们尝试计算1除以0,这将引发一个ZeroDivisionError异常。我们使用tryexcept语句来捕获该异常并打印出一条错误消息。


4. 面向对象编程(Object-Oriented Programming)


面向对象编程是一种重要的编程范式,Python是一种面向对象的语言。以下是一个简单的示例:


class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def say_hello(self):
print("Hello, my name is " + self.name)

person = Person("Alice", 25)
person.say_hello()

在这个示例中,我们定义了一个名为Person的类,并定义了一个构造函数__init__()来初始化对象的属性。我们还定义了一个名为say_hello()的方法,用于打印出问候语。最后,我们创建一个Person对象,并调用say_hello()方法。
Python是一种面向对象编程语言,它支持面向对象编程的三个基本概念:封装、继承和多态。面向对象编程的核心思想是将数据和操作数据的方法封装在一起,形成一个对象。


示例代码


class Animal:
def __init__(self, name, age):
self.name = name
self.age = age

def say_hello(self):
print(f'{self.name} is saying hello')

class Cat(Animal):
def __init__(self, name, age, color):
super().__init__(name, age)
self.color = color

def catch_mouse(self):
print(f'{self.name} is catching mouse')

cat = Cat('Tom', 2, 'White')
cat.say_hello()
cat.catch_mouse()

输出结果


Tom is saying hello
Tom is catching mouse

5. Python高级特性


5.1 生成器(Generator)


生成器是一种特殊的函数,可以在函数执行期间多次返回值,并且可以保留当前执行状态,等待下一次调用。它们是高效的迭代器,可以用于处理大量数据或无限流数据,同时也能节省内存。


示例代码


pythonCopy code
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a + b

f = fib()
for i in range(10):
print(next(f))

输出结果


Copy code
0
1
1
2
3
5
8
13
21
34

5.2 装饰器(Decorator)


装饰器是一种函数,用于修改其他函数的行为。它们提供了一种简单的方式来修改函数,而无需修改函数的原始定义。装饰器的常见用途包括添加日志、计时、缓存等功能。


示例代码


pythonCopy code
import time

def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__} executed in {end - start} seconds')
return result
return wrapper

@timer
def calculate_sum(n):
return sum(range(n+1))

print(calculate_sum(100000000))

输出结果


Copy code
calculate_sum executed in 4.150076866149902 seconds
5000000050000000

作者:SandySY
来源:juejin.cn/post/7224335234010234935
收起阅读 »

你的flask服务开启https了吗?

一、你的flask服务开启https了吗? 1.事件起因 计划做文心一言插件,我购买了服务器,开始按照我的想象部署插件,结果工作不错,但是最后一步图片显示不正常。哭晕了,如下图所示。 仔细阅读,发现返回图片地址什么的都很正常啊,也可以访问得到,但是为什么就不...
继续阅读 »

一、你的flask服务开启https了吗?


1.事件起因


计划做文心一言插件,我购买了服务器,开始按照我的想象部署插件,结果工作不错,但是最后一步图片显示不正常。哭晕了,如下图所示。


e3dde086617a68f9a585f78f8bfdc20.png
仔细阅读,发现返回图片地址什么的都很正常啊,也可以访问得到,但是为什么就不能看到呢,很奇怪。


经多方排查,最终确定是图片跨域导致无法显示。


2.解决思路


知道是跨域问题就好了,因为文心一言是https访问,所以提供服务的也需要https,那么就开始作了(解决)。


二、解决办法


1.无效1.0解决办法


知道要https那我就加https了,直接百度解决办法。



  • Flask(更具体地说其实是Werkzeug),支持使用即时证书,这对于通过HTTPS快速提供应用程序非常有用,而且不会搞乱系统的证书。你只有需要做的就是将 ssl_context ='adhoc' 添加到程序的 app.run() 调用中。遗憾的是,Flask CLI无法使用此选项。举个例子,下面是官方文档中的“Hello,World” Flask应用程序,并添加了TLS加密:

  • 安装库 pip install pyopenssl


from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
return "Hello World!"

if __name__ == "__main__":
app.run(ssl_context='adhoc')


这样启动起来就是https了,但是访问问题依旧,图片仍然没有显示。。。。。。仔细看来浏览器说是假的ssl,那就继续解决。


2.无效2.0自签名证书解决办法


所谓的自签名证书是使用与同一证书关联的私钥生成签名的证书,就是自己动手丰衣足食。


微信截图_20231126163515.png



  • 生成证书

  • flask加载证书


openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
return "Hello World!"

if __name__ == "__main__":
app.run(ssl_context=('cert.pem', 'key.pem'))
复制代码

然后看结果,依然无效,因为是自签名。。。。。。


3.0终极解决办法


后来发现要真正的证书,那么你必须要有域名,才会发给你,就是说证书和域名是绑定的,就跟户籍一样,户籍都没有说什么学区房,没人理你。



  • 为此我花了8块买了一个cn一年的域名,并且进行了实名。

  • 在腾讯云申请ssl证书,参考地址 cloud.tencent.com/document/pr…

  • 申请地址 console.cloud.tencent.com/ssl
    申请时必须先证明该证书属于你,需要按提示加入cname,进行验证。申请完毕略等一会就会通过,下载证书即可,具体包含以下几个问题件:


微信截图_20231126164111.png



  • 加载证书
    因为有4个文件,没有详细写,因此我测试了几次,最终成功。


if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', ssl_context=( 'erniebotplugins.cn_bundle.crt','erniebotplugins.cn.key'), port=8081)

三、最终效果


用上最终大招后,最终成功,具体效果如下。


微信截图_20231126164250.png


可见现在ssl非常普遍,不安全别的网站都懒得搭理你。


作者:Livingbody
来源:juejin.cn/post/7305235970285568011
收起阅读 »

如何实现一个下班倒计时程序

Hello伙伴们,好几天不见啦。最近也是晚上打球太累,加上一直在研究mybatis的多租户问题,简直是没有太多的精力了。正好周六的晚上有一点点的空隙,就是洗完澡之后,顿时觉得整个人轻松下来了。有伙伴跟我一样的感受吗? 话不多说,现在我们来开始今天的主题:《如何...
继续阅读 »

Hello伙伴们,好几天不见啦。最近也是晚上打球太累,加上一直在研究mybatis的多租户问题,简直是没有太多的精力了。正好周六的晚上有一点点的空隙,就是洗完澡之后,顿时觉得整个人轻松下来了。有伙伴跟我一样的感受吗?


话不多说,现在我们来开始今天的主题:《如何实现一个桌面倒计时程序》。


身为打工人,一定是想着下班的那一刻吧。就像我昨天和我的伙伴开玩笑说:一个月就盼望着发工资的那一天。shigen找到了一段程序来实现下班倒计时,一起来看看实现的效果吧:


倒计时应用


页面上动态的显示当前时间和剩余时间,假设shigen的文章要在今天的23点写完,那么我还剩2小时25分钟的准备时间。是不是挺神奇的,另外,还可以实现到点了自动关机。人走电脑关,看看老板还有什么理由让你去加班🤫🤫🤫。


那就上今天的代码吧:


 # -*- encoding: utf-8 -*-
 __date__ = '2023/11/18 19:27:08'
 
 """
 距离下班时间倒计时
 """

 
 import time
 from tkinter import *
 
 
 def refresh_current_time():
     """刷新当前时间"""
     clock_time = time.strftime('%Y-%m-%d %H:%M:%S')
     curr_time.config(text=clock_time)
     curr_time.after(1000, refresh_current_time)
 
 
 def refresh_down_time():
     """刷新倒计时时间"""
     # 当前时间戳
     now_time = int(time.time())
     # 下班时间时分秒数据过滤
     work_hour_val = int(work_hour.get())
     if work_hour_val > 23:
         down_label.config(text='小时的区间为(00-23)')
         return
     work_minute_val = int(work_minute.get())
     if work_minute_val > 59:
         down_label.config(text='分钟的区间为(00-59)')
         return
     work_second_val = int(work_second.get())
     if work_second_val > 59:
         down_label.config(text='秒数的区间为(00-59)')
         return
     # 下班时间转为时间戳
     work_date = str(work_hour_val) + ':' + str(work_minute_val) + ':' + str(work_second_val)
     work_str_time = time.strftime('%Y-%m-%d ') + work_date
     time_array = time.strptime(work_str_time, "%Y-%m-%d %H:%M:%S")
     work_time = time.mktime(time_array)
     if now_time > work_time:
         down_label.config(text='已过下班时间')
         return
     # 距离下班时间剩余秒数
     diff_time = int(work_time - now_time)
     while diff_time > -1:
         # 获取倒计时-时分秒
         down_minute = diff_time // 60
         down_second = diff_time % 60
         down_hour = 0
         if down_minute > 60:
             down_hour = down_minute // 60
             down_minute = down_minute % 60
         # 刷新倒计时时间
         down_time = str(down_hour).zfill(2) + '时' + str(down_minute).zfill(2) + '分' + str(down_second).zfill(2) + '秒'
         down_label.config(text=down_time)
         tk_obj.update()
         time.sleep(1)
         if diff_time == 0:
             # 倒计时结束
             down_label.config(text='已到下班时间')
             # 自动关机,定时一分钟关机,可以取消
             # down_label.config(text='下一分钟将自动关机')
             # os.system('shutdown -s -f -t 60')
             break
         diff_time -= 1
 
 
 # 程序主入口
 if __name__ == "__main__":
     # 设置页面数据
     tk_obj = Tk()
     tk_obj.geometry('400x280')
     tk_obj.resizable(0, 0)
     tk_obj.config(bg='white')
     tk_obj.title('倒计时应用')
     Label(tk_obj, text='下班倒计时', font='宋体 20 bold', bg='white').pack()
     # 设置当前时间
     Label(tk_obj, font='宋体 15 bold', text='当前时间:', bg='white').place(x=50, y=60)
     curr_time = Label(tk_obj, font='宋体 15', text='', fg='gray25', bg='white')
     curr_time.place(x=160, y=60)
     refresh_current_time()
     # 设置下班时间
     Label(tk_obj, font='宋体 15 bold', text='下班时间:', bg='white').place(x=50, y=110)
     # 下班时间-小时
     work_hour = StringVar()
     Entry(tk_obj, textvariable=work_hour, width=2, font='宋体 12').place(x=160, y=115)
     work_hour.set('18')
     # 下班时间-分钟
     work_minute = StringVar()
     Entry(tk_obj, textvariable=work_minute, width=2, font='宋体 12').place(x=185, y=115)
     work_minute.set('00')
     # 下班时间-秒数
     work_second = StringVar()
     Entry(tk_obj, textvariable=work_second, width=2, font='宋体 12').place(x=210, y=115)
     work_second.set('00')
     # 设置剩余时间
     Label(tk_obj, font='宋体 15 bold', text='剩余时间:', bg='white').place(x=50, y=160)
     down_label = Label(tk_obj, font='宋体 23', text='', fg='gray25', bg='white')
     down_label.place(x=160, y=155)
     down_label.config(text='00时00分00秒')
     # 开始计时按钮
     Button(tk_obj, text='START', bd='5', command=refresh_down_time, bg='green', font='宋体 10 bold').place(x=150, y=220)
     tk_obj.mainloop()

代码就是简简单单的204行,要实现到点自动关机的伙伴可以把63-64行的代码注释打开即可。


那最后总结一下吧,为什么shigen会选取这个程序作为今天的分享呢?



  1. 跨平台。首先python是跨平台的,其次tkinter也是跨平台的,意味着在常见的操作系统都可以执行这个代码,实现倒计时的效果;

  2. 新思路。其实shigen之前也做了一个类似的桌面时钟效果,做的更加酷炫一点的话,其实可以当作屏保了;

  3. 小工具的改造。其实shigen的mac上也有很多的小工具,但是都是在命令行执行的,改在了GUI界面岂不是更加的nice和方便,也实现傻瓜式操作。

作者:shigen01
来源:juejin.cn/post/7302348032543899660
收起阅读 »

Python枚举类:定义、使用和最佳实践

枚举(Enum)是一种有助于提高代码可读性和可维护性的数据类型,允许我们为一组相关的常量赋予有意义的名字。 在Python中,枚举类(Enum)提供了一种简洁而强大的方式来定义和使用枚举。 一、枚举类 1.1 什么是枚举类? 枚举类是一种特殊的数据类型,用于表...
继续阅读 »


枚举(Enum)是一种有助于提高代码可读性和可维护性的数据类型,允许我们为一组相关的常量赋予有意义的名字。


在Python中,枚举类(Enum)提供了一种简洁而强大的方式来定义和使用枚举。


一、枚举类


1.1 什么是枚举类?


枚举类是一种特殊的数据类型,用于表示一组具有离散取值的常量。它将常量与有意义的名字关联起来,使得代码更易读、更易维护。枚举类的每个成员都有一个唯一的名称和一个关联的值。


枚举类的典型用例包括表示颜色、方向、状态、星期几等常量值。使用枚举可以增强代码的可读性,减少硬编码的风险。


1.2 Python中的枚举类


在Python中,使用内置模块enum来创建和使用枚举类。


enum模块提供了Enum类,允许定义自己的枚举类型。


二、定义和使用枚举类


2.1 定义枚举类


要定义一个枚举类,需要导入Enum类并创建一个继承自它的子类。在子类中,我们定义枚举成员,并为每个成员分配一个名称和一个关联的值。


示例代码:


from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

在这个示例中,定义一个名为Color的枚举类,它有三个成员:REDGREENBLUE,每个成员都有一个整数值与之关联。


2.2 访问枚举成员


定义枚举类,可以通过成员名来访问枚举成员。例如:


print(Color.RED)    # 输出:Color.RED
print(Color.GREEN)  # 输出:Color.GREEN

2.3 获取枚举成员的值


要获取枚举成员的关联值,可以使用成员的value属性。例如:


print(Color.RED.value)    # 输出:1
print(Color.GREEN.value)  # 输出:2

2.4 比较枚举成员


枚举成员可以使用相等运算符进行比较。可以直接比较枚举成员,而不必比较它们的值。例如:


color1 = Color.RED
color2 = Color.GREEN

print(color1 == color2)  # 输出:False

2.5 迭代枚举成员


使用for循环来迭代枚举类的所有成员。例如,要打印所有颜色的名称和值:


for color in Color:
    print(f"{color.name}: {color.value}")

2.6 将值映射到枚举成员


根据枚举成员的值来获取成员本身,可以通过枚举类的__members__属性来实现。


例如,要根据值获取Color枚举成员:


value = 2
color = Color(value)
print(color)  # 输出:Color.GREEN

三、枚举的最佳实践


第三部分:枚举的最佳实践


枚举是一种有用的数据类型,但在使用时需要遵循一些最佳实践,以确保代码的可读性和可维护性。


3.1 使用枚举代替魔术数字


在代码中使用枚举来代替魔术数字(不明确的常量值)可以增加代码的可读性。枚举为常量提供了有意义的名字,使得代码更容易理解。


例如,使用枚举来表示星期几:


from enum import Enum

class Weekday(Enum):
    MONDAY = 1
    TUESDAY = 2
    WEDNESDAY = 3
    THURSDAY = 4
    FRIDAY = 5
    SATURDAY = 6
    SUNDAY = 7

3.2 避免硬编码


尽量避免在代码中硬编码枚举成员的值。如果需要使用枚举成员的值,最好使用枚举成员本身而不是其值。这可以提高代码的可读性,使得代码更容易维护。


例如,避免这样的写法:


if day == 1:  # 避免硬编码
    print("Today is Monday")

而使用枚举成员:


if day == Weekday.MONDAY:  # 更具表现力
    print("Today is Monday")

3.3 使用枚举成员的名称


枚举成员的名称通常应该使用大写字母,以便与常规变量和函数名称区分开。这是一种约定,有助于提高代码的可读性。例如,使用RED而不是red


3.4 考虑枚举成员的值类型


枚举成员的值通常是整数,但根据上下文和需求,可以选择不同的值类型,如字符串。选择适当的值类型可以使代码更具表现力。


3.5 考虑用法和上下文


在定义枚举时,考虑其用法和上下文。命名枚举成员和选择合适的值应该反映其在应用程序中的含义和用途。这有助于其他开发人员更容易理解和使用枚举。


3.6 枚举的不可变性


枚举成员是不可变的,一旦创建就不能更改其值。这有助于确保枚举成员的稳定性,并防止意外的修改。


遵循这些最佳实践可以帮助你有效地使用枚举,提高代码的可读性和可维护性。枚举是一种强大的工具,可以在代码中代替魔术数字,并提供有意义的常量名称。


总结


Python的枚举类是一种强大的工具,用于表示一组相关的常量,并提高代码的可读性和可维护性。通过枚举,我们可以为常量赋予有意义的名称,避免硬编码的值,以及更容易进行比较和迭代。


在实际编程中,枚举类可以提供一种清晰、可维护且更具表现力的方式来处理常量值。


作者:涛哥聊Python
来源:juejin.cn/post/7294150742112895002
收起阅读 »

136. 只出现一次的数字

题目 题解 考察的是位运算 —— 异或(^),相同为 0,不同为 1 1^0 = 1,1^1 = 0 则直接对数据所有元素执行 ^ 操作,最终的就是结果 class&nbs...
继续阅读 »

题目





题解





  • 考察的是位运算 —— 异或(^),相同为 0,不同为 1



  • 1^0 = 1,1^1 = 0



  • 则直接对数据所有元素执行 ^ 操作,最终的就是结果


class Solution {
    public int singleNumber(int[] nums) {

        int res = 0;

        for (int num : nums) {
            res = res ^ num;
        }

        return res;
    }
}

作者:程序员小航
来源:mdnice.com/writing/7bb65e0150154b28b4777a1ea6e2784b
收起阅读 »

【JD京东抢购】茅台抢购逻辑

直接进入正文。京东抢购模式有很多种。 普通商品无货,定时查询库存蹲抢普通商品定时发售(库存由0变为有货),定时提前构造订单请求抢预售商品(需要先预约),可以加入购物车,通过购物车结算。这种用常规购物车结算订单接口就行,当然也可以用抢购接口。 这种体现为可以加...
继续阅读 »

直接进入正文。京东抢购模式有很多种。


  1. 普通商品无货,定时查询库存蹲抢
  2. 普通商品定时发售(库存由0变为有货),定时提前构造订单请求抢
  3. 预售商品(需要先预约),可以加入购物车,通过购物车结算。这种用常规购物车结算订单接口就行,当然也可以用抢购接口。


这种体现为可以加购,抢购时候显示两个按钮,加入购物车(黄色)和立即购买(淡绿色)。



  • 预售商品(需要先预约),无法加入购物车,电脑端无法预约,必须手机端预约。这种采用marathon.jd.com/seckillnew/… 接口完成抢购,有完整流程验证和tokenKey(sign),sk验证。


这种体现为 无法加入购物车,必须手机端才能预约,可购买时候只显示一个红色按钮立即抢购



逻辑参考GitHub大佬给出的思路。


第一步:获取跳转链接


跳转链接是指形如:un.m.jd.com/cgi-bin/app… 的链接,获取该链接,还需要一个前置步骤,即获取token和拼接url。先说获取token,获取token是通过genToken接口获取的,然后将获取到的tokenKey和url拼接起来,得到跳转链接。


第二步:访问跳转链接


拿到跳转链接后,直接将该跳转链接仍给浏览器即可,浏览器会经过两次302跳转得到sekill.action链接,从而渲染出提交订单页面,此时我们需要模拟点击“提交订单”按钮,实现抢购。(可以使用Selenium、Pyppeteer或Playwright等类库 来模拟浏览器)


访问跳转连接,及提交订单的时候需要提供移动端的APP参数抓包获取。Android抓包较为简单,IOS的也不麻烦,就是步骤多了一些。


然后提取Hades头的信息组成以下参数

        query_params = {
"functionId": "genToken",
"clientVersion": "12.0.8",
"build": "168782",
"client": "apple",
"d_brand": "apple",
"d_model": "iPhone11,4",
"osVersion": "16.5",
"screen": "1284*2778",
"partner": "apple",
"aid": self.aid,
"eid": self.eid,
"sdkVersion": "29",
"lang": "zh_CN",
# 'harmonyOs': '0',
"uuid": self.uuid,
"area": "4_51026_58465_0",
"networkType": "wifi",
"wifiBssid": self.wifiBssid,
"uts": self.uts,
"uemps": "0-0-0",
"ext": '{"prstate":"0","pvcStu":"1"}',
# 'ef': '1',
# 'ep': json.dumps(ep, ensure_ascii=False, separators=(',', ':')),
}

这种仅仅是前面所需的参数。具体方法还是需要使用这些参数来获取用户的个人信息拿到跳转连接


如:

    def get_appjmp(self, token_params):
headers = {"user-agent": self.ua}
appjmp_url = token_params["url"]
params = {
"to": "https://divide.jd.com/user_routing?skuId=%s" % self.skuId,
"tokenKey": token_params["tokenKey"],
}

response = self.s.get(
url=appjmp_url,
params=params,
allow_redirects=False,
verify=False,
headers=headers,
)
print("Get Appjmp跳转链接-------------->%s" % response.headers["Location"])
return response.headers["Location"]

get_appjmp(self, token_params) 函数接受一个名为 token_params 的参数。


然后发送相关请求后携带参数得到跳转链接。

  • headers 是一个字典,包含了请求头中的 "User-Agent" 字段,用于模拟浏览器的用户代理。

  • appjmp_url 是一个变量,它存储了 token_params 字典中的 "url" 键所对应的值。

  • params 是一个字典,其中包含两个键值对:

  • "to" 键对应的值是一个字符串,使用了 %s 占位符,用于生成跳转链接中的 skuId 参数。

  • "tokenKey" 键对应的值是一个字符串,使用了 token_params 字典中的 "tokenKey" 键所对应的值。

  • 通过调用 self.s.get() 方法发起一个 GET 请求,传入以下参数:

  • url 参数是 appjmp_url,表示要访问的链接地址。

  • params 参数是之前定义的 params 字典,用于添加请求参数。

  • allow_redirects 参数设置为 False,禁止自动重定向。

  • verify 参数设置为 False,跳过 SSL 证书验证。

  • headers 参数是之前定义的 headers 字典,用于设置请求头。

  • 最后,打印获取到的跳转链接的响应头中的 "Location" 字段值,并将其返回。


抢购返回解决无非就是



{'errorMessage': '很遗憾没有抢到,再接再厉哦。', 'orderId': 0, 'resultCode': 90016, 'skuId': 0, 'success': False}




{'errorMessage': '很遗憾没有抢到,再接再厉哦。', 'orderId': 0, 'resultCode': 90008, 'skuId': 0, 'success': False}





根据其他作者的推测 推测返回 90008 是京东的风控机制,代表这次请求直接失败,不参与抢购。

小白信用越低越容易触发京东的风控。


具体代码可参考GitHub地址


感谢GitHub作者@geeeeeeeek @jd-seckill等


作者:狗头大军之江苏分军
链接:https://juejin.cn/post/7280740005571362816
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

电视剧里的代码真能运行吗?

大家好,欢迎来到 Crossin的编程教室 ! 前几天,后台老有小伙伴留言“爱心代码”。这不是Crossin很早之前发过的内容嘛,怎么最近突然又被人翻出来了?后来才知道 ,原来是一部有关程序员的青春偶像剧《点燃我,温暖你》在热播,而剧中有一段关于期中考试要用程...
继续阅读 »

大家好,欢迎来到 Crossin的编程教室 !


前几天,后台老有小伙伴留言“爱心代码”。这不是Crossin很早之前发过的内容嘛,怎么最近突然又被人翻出来了?后来才知道


,原来是一部有关程序员的青春偶像剧《点燃我,温暖你》在热播,而剧中有一段关于期中考试要用程序画一个爱心的桥段。


于是出于好奇,Crossin就去看了这一集(第5集,不用谢)。这一看不要紧,差点把刚吃的鸡腿给喷出来--槽点实在太多了!


忍不住做了个欢乐吐槽向的代码解读视频,在某平台上被顶到了20个w的浏览,也算蹭了一波人家电视剧的热度吧……


下面是图文版,给大家分析下剧中出现的“爱心”代码,并且来复刻一下最后男主完成的酷炫跳动爱心。


剧中代码赏析


1. 首先是路人同学的代码:



虽然剧中说是“C语言期中考试”,但这位同学的代码名叫 draw2.py,一个典型的 Python 文件,再结合截图中的 pen.forward、pen.setpos 等方法来看,应该是用 turtle 海龟作图库来画爱心。那效果通常是这样的:


import turtle as t
t.color('red')
t.setheading(50)
t.begin_fill()
t.circle(-100, 170)
t.circle(-300, 40)
t.right(38)
t.circle(-300, 40)
t.circle(-100, 170)
t.end_fill()
t.done()



而不是剧中那个命令行下用1组成的不规则的图形。


2. 然后是课代表向路人同学展示的优秀代码:



及所谓的效果:



这确实是C语言代码了,但文件依然是以 .py 为后缀,并且 include 前面没有加上 #,这显然是没法运行的。


里面的内容是可以画出爱心的,用是这个爱心曲线公式:



然后遍历一个15*17的方阵,计算每个坐标是在曲线内还是曲线外,在内部就输出#或*,外部就是-


用python改写一下是这样的:


for y in range(9, -6, -1):
for x in range(-8, 9):
print('*##*'[(x+10)%4] if (x*x+y*y-25)**3 < 25*x*x*y*y*y else '-', end=' ')
print()

效果:



稍微改一下输出,还能做出前面那个全是1的效果:


for y in range(9, -6, -1):
for x in range(-8, 9):
print('1' if (x*x+y*y-25)**3 < 25*x*x*y*y*y else ' ', end=' ')
print()


但跟剧中所谓的效果相去甚远。


3. 最后是主角狂拽酷炫D炸天的跳动爱心:



代码有两个片段:




但这两个片段也不C语言,而是C++,且两段并不是同一个程序,用的方法也完全不一样。


第一段代码跟前面一种思路差不多,只不过没有直接用一条曲线,而是上半部用两个圆形,下半部用两条直线,围出一个爱心。



改写成 Python 代码:


size = 10
for x in range(size):
for y in range(4*size+1):
dist1 = ((x-size)**2 + (y-size)**2) ** 0.5
dist2 = ((x-size)**2 + (y-3*size)**2) ** 0.5
if dist1 < size + 0.5 or dist2 < size + 0.5:
print('V', end=' ')
else:
print(' ', end=' ')
print()

for x in range(1, 2*size):
for y in range(x):
print(' ', end=' ')
for y in range(4*size+1-2*x):
print('V', end=' ')
print()

运行效果:



第二段代码用的是基于极坐标的爱心曲线,是遍历角度来计算点的位置。公式是:



计算出不同角度对应的点坐标,然后把它们连起来,就是一个爱心。


from math import pi, sin, cos
import matplotlib.pyplot as plt
no_pieces = 100
dt = 2*pi/no_pieces
t = 0
vx = []
vy = []
while t <= 2*pi:
vx.append(16*sin(t)**3)
vy.append(13*cos(t)-5*cos(2*t)-2*cos(3*t)-cos(4*t))
t += dt
plt.plot(vx, vy)
plt.show()

效果:



代码中循环时用到的2π是为了保证曲线长度足够绕一个圈,但其实长一点也无所谓,即使 π=100 也不影响显示效果,只是相当于同一条曲线画了很多遍。所以剧中代码里写下35位小数的π,还被女主用纸笔一字不落地抄写下来,实在是让程序员无法理解的迷惑行为。



但不管写再多位的π,上述两段代码都和最终那个跳动的效果差了五百只羊了个羊。


跳动爱心实现


作为一个总是在写一些没什么乱用的代码的编程博主,Crossin当然也不会放过这个机会,下面就来挑战一下用 Python 实现最终的那个效果。


1. 想要绘制动态的效果,必定要借助一些库的帮助,不然代码量肯定会让你感动得想哭。这里我们将使用之前 羊了个羊游戏 里用过的 pgzero 库。然后结合最后那个极坐标爱心曲线代码,先绘制出曲线上离散的点。


import pgzrun
from math import pi, sin, cos

no_p = 100
dt = 2*3/no_p
t = 0
x = []
y = []
while t <= 2*3:
x.append(16*sin(t)**3)
y.append(13*cos(t)-5*cos(2*t)-2*cos(3*t)-cos(4*t))
t += dt

def draw():
screen.clear()
for i in range(len(x)):
screen.draw.filled_rect(Rect((x[i]*10+400, -y[i]*10+300), (4, 4)), 'pink')

pgzrun.go()


2. 把点的数量增加,同时沿着原点到每个点的径向加一个随机数,并且这个随机数是按照正态分布来的(半个正态分布),大概率分布在曲线上,向曲线内部递减。这样,就得到这样一个随机分布的爱心效果。


...
no_p = 20000
...
while t <= 2*pi:
l = 10 - abs(random.gauss(10, 2) - 10)
x.append(l*16*sin(t)**3)
y.append(l*(13*cos(t)-5*cos(2*t)-2*cos(3*t)-cos(4*t)))
t += dt
...


3. 下面就是让点动起来,这步是关键,也有一点点复杂。为了方便对于每个点进行控制,这里将每个点自定义成了一个Particle类的实例。


从原理上来说,就是给每个点加一个缩放系数,这个系数是根据时间变化的正弦函数,看起来就会像呼吸的节律一样。


class Particle():
def __init__(self, pos, size, f):
self.pos = pos
self.pos0 = pos
self.size = size
self.f = f

def draw(self):
screen.draw.filled_rect(Rect((10*self.f*self.pos[0] + 400, -10*self.f*self.pos[1] + 300), self.size), 'hot pink')

def update(self, t):
df = 1 + (2 - 1.5) * sin(t * 3) / 8
self.pos = self.pos0[0] * df, self.pos0[1] * df

...

t = 0
def draw():
screen.clear()
for p in particles:
p.draw()

def update(dt):
global t
t += dt
for p in particles:
p.update(t)


4. 剧中爱心跳动时,靠中间的点波动的幅度更大,有一种扩张的效果。所以再根据每个点距离原点的远近,再加上一个系数,离得越近,系数越大。


class Particle():
...
def update(self, t):
df = 1 + (2 - 1.5 * self.f) * sin(t * 3) / 8
self.pos = self.pos0[0] * df, self.pos0[1] * df


5. 最后再用同样的方法画一个更大一点的爱心,这个爱心不需要跳动,只要每一帧随机绘制就可以了。


def draw():
...
t =
0
while t < 2*pi:
f = random.gauss(1.1, 0.1)
x = 16*sin(t)**3
y = 13*cos(t)-5*cos(2*t)-2*cos(3*t)-cos(4*t)
size = (random.uniform(0.5,2.5), random.uniform(0.5,2.5))
screen.draw.filled_rect(Rect((10*f*x + 400, -10*f*y + 300), size), 'hot pink')
t += dt * 3


合在一起,搞定!



总结一下,就是在原本的基础爱心曲线上加上一个正态分布的随机量、一个随时间变化的正弦函数和一个跟距离成反比的系数,外面再套一层更大的随机爱心,就得到类似剧中的跳动爱心效果。


但话说回来,真有人会在考场上这么干吗?


除非真的是超级大学霸,不然就是食堂伙食太好--


吃太饱撑的……



代码已开源:https://gitee.com/crossin/easy-py/tree/master/221114%20%E7%88%B1%E5%BF%83%E4%BB%A3%E7%A0%81



作者:Crossin先生
来源:juejin.cn/post/7168388057631031332
收起阅读 »

当程序员纠结中午应该吃什么,那就用pygame来解决吧

写多了kotlin和android,最近想搞点小东西,于是拿出了长期没有宠爱的python,打算搞个小项目 想想应该写什么,对了,该吃饭了,诶,刚好,写一个能随机选择吃什么的小程序吧,只需要点击按钮,就会随机出现菜谱,然后再点一下,就会得出今天吃什么的结论 思...
继续阅读 »

写多了kotlin和android,最近想搞点小东西,于是拿出了长期没有宠爱的python,打算搞个小项目


想想应该写什么,对了,该吃饭了,诶,刚好,写一个能随机选择吃什么的小程序吧,只需要点击按钮,就会随机出现菜谱,然后再点一下,就会得出今天吃什么的结论


思路是这样的,读入一个txt文件,文件中写满想吃的东西,做到数据和代码区分,然后开始写UI,UI通过按钮点击随机展示美食即可


麻辣香锅
糖醋排骨
红烧肉
...

    import pygame
import random

class App:
   def __init__(self):
       # 初始化 Pygame
       pygame.init()

       # 创建窗口和设置标题
       self.window_size = (600, 300)
       self.window = pygame.display.set_mode(self.window_size)
       pygame.display.set_caption("What to Eat Today")

       # 设置字体对象
       self.font = pygame.font.Font('myfont.ttf', 32)

       # 加载菜单数据
       self.menu = []
       with open("menu.txt", "r") as f:
           for line in f:
               line = line.strip()
               if line != "":
                   self.menu.append(line)
               print(line) # 打印数据

if __name__ == "__main__":
   app = App()


运行一下


image-20230828201918635.png


nice,文件已经读入


这个时候的UI是一闪而过的,因为程序瞬间就执行完毕了,ok,那么我们就需要用一个循环维持UI窗口,然后设置开始选择按键,以及键盘控制按键,同时设置变量


today_menu表示今天吃的东西,


btn_start_stop表示按键文字,


cur_menu表示正处于随机中的美食,当我们按下开始按键时,cur_menu开始变换,当我们按下关闭按键时,cur_menu的数据就赋值到today_menu中,


show_wheel表示当前正处于随机中,还是已经结束了


只要增加一个无限循环,一切就会好起来的



       # 随机选择一道菜
       self.today_menu = ""
       self.btn_start_stop = "start"
       self.cur_menu = ""

       # 游戏主循环
       self.running = True
       self.show_wheel = False


       # 开关程序
       while self.running:
           for event in pygame.event.get():
               if event.type == pygame.QUIT:
                   self.running = False
               elif event.type == pygame.MOUSEBUTTONDOWN:
               ...

               # 增加一个elif 按键s,show_wheel为true, 按下q, show_wheel为false
               elif event.type == pygame.KEYDOWN:
               ...

运行结果


image-20230828202631700.png


现在已经有了窗口,接下来需要在上面画东西了


所用到的就是draw函数



   def draw(self):
       # 绘制界面
       self.window.fill((255, 255, 255))

       # 绘制菜单
       menu_surface = self.font.render(f"Today's Menu: {self.today_menu}", True, (0, 0, 0))
       ...

       # 绘制按钮
       button_size = min(self.window_size[0] // 4, self.window_size[1] // 4)
    ...

       btn_start = self.font.render(f"{self.btn_start_stop}", True, (0, 0, 0))
        # 缩小start 文字字号 以适应按钮
       btn_start = pygame.transform.scale(btn_start, (button_size // 3 * 2, button_size // 3 * 2))
       self.window.blit(btn_start, (button_x + button_size // 2 - btn_start.get_width() // 2, button_y + button_size // 2 - btn_start.get_height() // 2))

       # 滚轮动画
       ...
       pygame.display.update()

运行


image-20230828202741990.png


上面的代码仅仅能够展示一个静态的页面,


虽然界面平平无奇,似乎只有两行字?不然,实际上暗藏玄🐔,只要我们加上这段,



       # 绘制滚轮动画
       if self.show_wheel:
           wheel_size = min(self.window_size) // 2
           wheel_x = self.window_size[0] // 2 - wheel_size // 2
           wheel_y = self.window_size[1] // 2 - wheel_size // 2
           wheel_rect = pygame.Rect(wheel_x, wheel_y, wheel_size, wheel_size)
...

           # 随机选择并显示菜谱
           menu_index = random.randint(0, len(self.menu) - 1)
           menu_surface = self.font.render(self.menu[menu_index], True, (0, 0, 0))
           self.window.blit(menu_surface, (wheel_x + wheel_size // 2 - menu_surface.get_width() // 2, wheel_y + wheel_size // 2 - menu_surface.get_height() // 2))
           self.cur_menu = self.menu[menu_index]

当我们点击“start"


QQ20230828-203131-HD.gif


发现中间的菜谱动了起来,动了起来!都是大家爱吃的,只需要点击右边的stop就可以固定结果!


真正麻烦的在于那个滚轮动画,可以想见,我们需要额外的一个无限循环,每一帧都要修改cur_menu,同时更新动画中的菜谱,然后点击stop后,将cur_menu赋值给到today_menu,最麻烦的不是这些逻辑,而是位置,滚轮动画的位置设置为窗口正中间,然后画了两条线,看起来更好看,有一种,狙击枪瞄准的感觉


最后,进行简单优化,比如设置定时关闭等,全代码如下,如果你也不知道吃什么,就用这段代码 + 在同目录自定义一个txt文件,把自己想吃的写上去吧



import pygame
import random

class App:
   def __init__(self):
       # 初始化 Pygame
       pygame.init()

       # 创建窗口和设置标题
       self.window_size = (600, 300)
       self.window = pygame.display.set_mode(self.window_size)
       pygame.display.set_caption("What to Eat Today")

       # 设置字体对象
       self.font = pygame.font.Font('myfont.ttf', 32)

       # 加载菜单数据
       self.menu = []
       with open("menu.txt", "r") as f:
           for line in f:
               line = line.strip()
               if line != "":
                   self.menu.append(line)

       # 随机选择一道菜
       self.today_menu = ""
       self.btn_start_stop = "start"
       self.cur_menu = ""

       # 游戏主循环
       self.running = True
       self.show_wheel = False
       self.wheel_count = 0     # 记录滚轮动画播放的帧数


       # 开关程序
       while self.running:
           for event in pygame.event.get():
               if event.type == pygame.QUIT:
                   self.running = False
               elif event.type == pygame.MOUSEBUTTONDOWN:
                   if not self.show_wheel:
                       self.show_wheel = True
                       self.wheel_count = 0  # 点击按钮后重置计数器为0
                       self.btn_start_stop = "stop"
                   else:
                       self.show_wheel = False
                       self.today_menu = self.cur_menu  # 点击停止赋值
                       self.btn_start_stop = "start"

               # 增加一个elif 按键s,show_wheel为true, 按下q, show_wheel为false
               elif event.type == pygame.KEYDOWN:
                   if event.key == pygame.K_s:  # 按下 s 键
                       self.show_wheel = True
                       self.wheel_count = 0  # 重置计数器为0
                   elif event.key == pygame.K_q:  # 按下 q 键
                       self.show_wheel = False
                       self.today_menu = self.cur_menu  # 停止赋值

           self.draw()

   def draw(self):
       # 绘制界面
       self.window.fill((255, 255, 255))

       # 绘制菜单
       menu_surface = self.font.render(f"Today's Menu: {self.today_menu}", True, (0, 0, 0))
       menu_x = self.window_size[0] // 2 - menu_surface.get_width() // 2
       menu_y = self.window_size[1] // 2 - menu_surface.get_height() // 2
       self.window.blit(menu_surface, (menu_x, menu_y))

       # 绘制按钮
       button_size = min(self.window_size[0] // 4, self.window_size[1] // 4)
       button_x = self.window_size[0] - button_size - 20
       button_y = self.window_size[1] - button_size - 20
       button_rect = pygame.Rect(button_x, button_y, button_size, button_size)
       pygame.draw.circle(self.window, (255, 0, 0), (button_x + button_size // 2, button_y + button_size // 2), button_size // 2)
       pygame.draw.rect(self.window, (255, 255, 255), button_rect.inflate(-button_size // 8, -button_size // 8))

       btn_start = self.font.render(f"{self.btn_start_stop}", True, (0, 0, 0))
        # 缩小start 文字字号 以适应按钮
       btn_start = pygame.transform.scale(btn_start, (button_size // 3 * 2, button_size // 3 * 2))
       self.window.blit(btn_start, (button_x + button_size // 2 - btn_start.get_width() // 2, button_y + button_size // 2 - btn_start.get_height() // 2))

       # 绘制滚轮动画
       if self.show_wheel:
           wheel_size = min(self.window_size) // 2
           wheel_x = self.window_size[0] // 2 - wheel_size // 2
           wheel_y = self.window_size[1] // 2 - wheel_size // 2
           wheel_rect = pygame.Rect(wheel_x, wheel_y, wheel_size, wheel_size)
           pygame.draw.circle(self.window, (0, 0, 0), (wheel_x + wheel_size // 2, wheel_y + wheel_size // 2), wheel_size // 2)
           pygame.draw.circle(self.window, (255, 255, 255), (wheel_x + wheel_size // 2, wheel_y + wheel_size // 2), wheel_size // 2 - 10)
           pygame.draw.line(self.window, (0, 0, 0), (wheel_x + 10, wheel_y + wheel_size // 2), (wheel_x + wheel_size - 10, wheel_y + wheel_size // 2))
           pygame.draw.line(self.window, (0, 0, 0), (wheel_x + wheel_size // 2, wheel_y + 10), (wheel_x + wheel_size // 2, wheel_y + wheel_size - 10))

           # 随机选择并显示菜谱
           menu_index = random.randint(0, len(self.menu) - 1)
           menu_surface = self.font.render(self.menu[menu_index], True, (0, 0, 0))
           self.window.blit(menu_surface, (wheel_x + wheel_size // 2 - menu_surface.get_width() // 2, wheel_y + wheel_size // 2 - menu_surface.get_height() // 2))
           self.cur_menu = self.menu[menu_index]
           # 播放一定帧数后停止动画
           self.wheel_count += 1
           if self.wheel_count > 500:
               self.show_wheel = False
               self.today_menu = self.cur_menu  # 自动停止赋值
       pygame.display.update()

if __name__ == "__main__":
   app = App()
作者:小松漫步
来源:juejin.cn/post/7272257829770887223


收起阅读 »

如何去除图片马赛克?

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究! 一、前言 对图像不了解的人时常妄想去除马赛克是可以实现的,严格意义来说这确实是无法实现的。而深度学习是出现,让去除马赛克成为可能。 为了理解去除马赛克有多难,我们需要...
继续阅读 »

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!



一、前言


对图像不了解的人时常妄想去除马赛克是可以实现的,严格意义来说这确实是无法实现的。而深度学习是出现,让去除马赛克成为可能。


为了理解去除马赛克有多难,我们需要知道马赛克是什么。观感上,马赛克就是方块感。当我们观察图像像素时, 马赛克表现为下图的情况:



原图右下角有十字,而添加马赛克后右下角一片都变成了同一像素,如果我们没保留原图,那么我们无法还原,也不知道是否还原了原图。因为原图已经被破坏了,这也是为什么马赛克是不可修复的。


那神经网络又是如何让修复成为可能呢?其实无论什么方式的修复,都是一种估计,而不是真正的修复。神经网络去除马赛克的操作其实是生成马赛克那部分内容,然后替代马赛克,从而达到修复的效果。


这种修复并不是还原,而是想象。假如我们对一张人脸打了马赛克,神经网络可以去除马赛克,但是去除后的人脸不再是原来那个人了。


二、实现原理


2.1 自编码器


图像修复的方法有很多,比如自编码器。自编码器是一种自监督模型,结构简单,不需要人为打标,收敛迅速。其结构如图:



编码器部分就是用于下采样的卷积网络,编码器会把图片编码成一个向量,而解码器则利用转置卷积把编码向量上采样成和原图大小一致的图片,最后我们把原图和生成结果的MSE作为损失函数进行优化。当模型训练好后,就可以用编码器对图片进行编码。


2.2 自编码器去除马赛克


那自编码器和去除马赛克有什么联系呢?其实非常简单,就是原本我们是输入原图,期望解码器能输出原图。这是出于我们希望模型学习如何编码图片的原图。而现在我们想要模型去除马赛克,此时我们要做的就是把马赛克图片作为输入,而原图作为输出,这样来训练就可以达到去除马赛克的效果了:



关于关于这种实现可以参考:juejin.cn/post/721068…


2.3 自编码器的问题


自编码器有个很明显的问题,就是图片经过编码器后会损失信息,而解码器的结果自然也会存在一些问题。这样既达不到去除马赛克的功能,连还原的原图都有一些模糊。


这里可以利用FPN的思想来改进,当自编码器加入FPN后,就得到了UNet网络结构。


2.4 UNet网络


UNet结构和自编码器类似,是一个先下再上的结构。和自编码器不同的时,UNet会利用编码器的每个输出,将各个输出与解码器的输入进行concatenate,这样就能更好地保留原图信息。其结构如下图:



UNet原本是用于图像分割的网络,这里我们用它来去除马赛克。


在UNet中,有几个部分我们分别来看看。


2.4.1 ConvBlock


在UNet中,有大量连续卷积的操作,这里我们作为一个Block(蓝色箭头),它可以实现为一个层,用PyTorch实现如下:


class ConvBlock(nn.Module):
def __init__(self, in_channels, out_channels):
super().__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels, out_channels, 3, 1, 1),
nn.BatchNorm2d(out_channels),
nn.ReLU(),
nn.Conv2d(out_channels, out_channels, 3, 1, 1),
nn.BatchNorm2d(out_channels),
nn.ReLU()
)

def forward(self, inputs):
return self.model(inputs)

这里其实就是两次卷积操作,这里的目的是提取当前感受野的特征。


2.4.2 ConvDown


经过连续卷积后,会使用卷积网络对图片进行下采样,这里把stride设置为2即可让图片缩小为原来的1/2。我们同样可以实现为层:


class ConvDown(nn.Module):
def __init__(self, channels):
super().__init__()
self.model = nn.Sequential(
nn.Conv2d(channels, channels, 3, 2, 1),
nn.BatchNorm2d(channels),
nn.ReLU()
)

def forward(self, inputs):
return self.model(inputs)

这里只有一个卷积,而且stride被设置为了2。


2.4.3 ConvUp


接下来是解码器部分,这里多了一个上采用的操作,我们可以用转置卷积完成,代码如下:


class ConvUp(nn.Module):
def __init__(self, channels):
super().__init__()
self.model = nn.Sequential(
nn.ConvTranspose2d(channels, channels // 2, 2, 2),
nn.BatchNorm2d(channels // 2),
nn.ReLU()
)

def forward(self, inputs):
return self.model(inputs)

上面是层可以把图片尺寸扩大为2倍,同时把特征图数量缩小到1/2。这里缩小特征图的操作是为了concatenate操作,后面详细说。


三、完整实现


首先,导入需要用的模块:


import os
import random
import torch
from torch import nn
from torch import optim
from torch.utils import data
import matplotlib.pyplot as plt
from torchvision import transforms
from torchvision.transforms import ToTensor
from PIL import Image, ImageDraw, ImageFilter
from torchvision.utils import make_grid

下面开始具体实现。


3.1 创建Dataset


首先创建本次任务需要的数据集,分布大致相同的图片即可,代码如下:


class ReConstructionDataset(data.Dataset):
def __init__(self, data_dir=r"G:/datasets/lbxx", image_size=64):
self.image_size = image_size
# 图像预处理
self.trans = transforms.Compose([
transforms.Resize(image_size),
transforms.CenterCrop(image_size),
transforms.ToTensor(),
# transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
# 保持所有图片的路径
self.image_paths = []
# 读取根目录,把所有图片路径放入image_paths
for root, dirs, files in os.walk(data_dir):
for file in files:
self.image_paths.append(os.path.join(root, file))

def __getitem__(self, item):
# 读取图片,并预处理
image = Image.open(self.image_paths[item])
return self.trans(self.create_blur(image)), self.trans(image)

def __len__(self):
return len(self.image_paths)


@staticmethod
def create_blur(image, return_mask=False, box_size=200):
mask = Image.new('L', image.size, 255)
draw = ImageDraw.Draw(mask)
upper_left_corner = (random.randint(0, image.size[0] - box_size), random.randint(0, image.size[1] - box_size))
lower_right_corner = (upper_left_corner[0] + box_size, upper_left_corner[1] + box_size)
draw.rectangle([lower_right_corner, upper_left_corner], fill=0)
masked_image = Image.composite(image, image.filter(ImageFilter.GaussianBlur(15)), mask)
if return_mask:
return masked_image, mask
else:
return masked_image

Dataset的实现与以往基本一致,实现init、getitem、len方法,这里我们还实现了一个create_blur方法,该方法用于生成矩形马赛克(实际上是高斯模糊)。下面是create_blur方法生成的图片:



3.2 网络构建


这里我们需要使用前面的几个子单元,先实现编码器,代码如下:


class UNetEncoder(nn.Module):
def __init__(self):
super().__init__()
self.blk0 = ConvBlock(3, 64)
self.down0 = ConvDown(64)
self.blk1 = ConvBlock(64, 128)
self.down1 = ConvDown(128)
self.blk2 = ConvBlock(128, 256)
self.down2 = ConvDown(256)
self.blk3 = ConvBlock(256, 512)
self.down3 = ConvDown(512)
self.blk4 = ConvBlock(512, 1024)

def forward(self, inputs):
f0 = self.blk0(inputs)
d0 = self.down0(f0)
f1 = self.blk1(d0)
d1 = self.down1(f1)
f2 = self.blk2(d1)
d2 = self.down2(f2)
f3 = self.blk3(d2)
d3 = self.down3(f3)
f4 = self.blk4(d3)
return f0, f1, f2, f3, f4

这里就是ConvBlok和ConvDown的n次组合,最终会得到一个1024×4×4的特征图。在forward中,我们返回了5个ConvBlok返回的结果,因为在解码器中我们需要全部使用。


接下来是解码器部分,这里与编码器相反,代码如下:


class UNetDecoder(nn.Module):
def __init__(self):
super().__init__()
self.up3 = ConvUp(1024)
self.blk3 = ConvBlock(1024, 512)
self.up2 = ConvUp(512)
self.blk2 = ConvBlock(512, 256)
self.up1 = ConvUp(256)
self.blk1 = ConvBlock(256, 128)
self.up0 = ConvUp(128)
self.blk0 = ConvBlock(128, 64)
self.last_conv = nn.Conv2d(64, 3, 3, 1, 1)

def forward(self, inputs):
f0, f1, f2, f3, f4 = inputs
u3 = self.up3(f4)
df2 = self.blk3(torch.concat((f3, u3), dim=1))
u2 = self.up2(df2)
df1 = self.blk2(torch.concat((f2, u2), dim=1))
u1 = self.up1(df1)
df0 = self.blk1(torch.concat((f1, u1), dim=1))
u0 = self.up0(df0)
f = self.blk0(torch.concat((f0, u0), dim=1))
return torch.tanh(self.last_conv(f))

解码器的inputs为编码器的5组特征图,在forward时需要与上采样结果concatenate。


最后,整个网络组合起来,代码如下:


class ReConstructionNetwork(nn.Module):

def __init__(self):
super().__init__()
self.encoder = UNetEncoder()
self.decoder = UNetDecoder()

def forward(self, inputs):
fs = self.encoder(inputs)
return self.decoder(fs)

3.3 网络训练


现在各个部分都完成了,可以开始训练网络:


device = "cuda" if torch.cuda.is_available() else "cpu"


def train(model, dataloader, optimizer, criterion, epochs):
model = model.to(device)
for epoch in range(epochs):
for iter, (masked_images, images) in enumerate(dataloader):
masked_images, images = masked_images.to(device), images.to(device)
outputs = model(masked_images)
loss = criterion(outputs, images)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (iter + 1) % 100 == 1:
print("epoch: %s, iter: %s, loss: %s" % (epoch + 1, iter + 1, loss.item()))
with torch.no_grad():
outputs = make_grid(outputs)
img = outputs.cpu().numpy().transpose(1, 2, 0)
plt.imshow(img)
plt.show()
torch.save(model.state_dict(), '../outputs/reconstruction.pth')


if __name__ == '__main__':
dataloader = data.DataLoader(ReConstructionDataset(r"G:\datasets\lbxx"), 64)
unet = ReConstructionNetwork()
optimizer = optim.Adam(auto_encoder.parameters(), lr=0.0002)
criterion = nn.MSELoss()
train(unet, dataloader, optimizer, criterion, 20)

训练完成后,就可以用来去除马赛克了,代码如下:



dataloader = data.DataLoader(ReConstructionDataset(r"G:\datasets\lbxx"), 64, shuffle=True)
unet = ReConstructionNetwork().to(device)
unet.load_state_dict(torch.load('../outputs/reconstruction.pth'))
for masked_images, images in dataloader:
masked_images, images = masked_images.to(device), images.to(device)
with torch.no_grad():
outputs = unet(masked_images)
outputs = torch.concatenate([images, masked_images, outputs], dim=-1)
outputs = make_grid(outputs)
img = outputs.cpu().numpy().transpose(1, 2, 0)
img = cv2.normalize(img, , 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
Image.fromarray(img).show()


下面是生成结果。左侧为原图,中间为添加马赛克后的图片,右侧则是去除马赛克后的结果:



整体来说效果比较不错。本文的方法不只可以用来去除马赛克,还可以完成图像重构。比如老化的图片、被墨汁污染的图片等,都可以用本文的方法完成重构。另外,本文的数据有限,实现效果并不通用,有需求的读者可以移步CodeFormer项目:github.com/sczhou/Code…


作者:ZackSock
来源:juejin.cn/post/7265310874488520765
收起阅读 »

python复数类型的使用及介绍

在Python中,复数类型是用来表示具有实部和虚部的数值。复数由实部和虚部组成,形式为 a + bj,其中 a 是实部,b 是虚部,j 是虚数单位。 要创建一个复数,可以使用 complex() 函数,并提供实部和虚部作为参数。例如: z =&n...
继续阅读 »

在Python中,复数类型是用来表示具有实部和虚部的数值。复数由实部和虚部组成,形式为 a + bj,其中 a 是实部,b 是虚部,j 是虚数单位。


要创建一个复数,可以使用 complex() 函数,并提供实部和虚部作为参数。例如:


z = complex(23)
print(z)  # 输出:(2+3j)

这里,z 是一个复数,实部为2,虚部为3。


可以通过 .real 属性来访问复数的实部,通过 .imag 属性来访问复数的虚部。例如:


print(z.real)  # 输出:2.0
print(z.imag)  # 输出:3.0

可以使用运算符来对复数进行算术运算,包括加法、减法、乘法和除法。例如:


z1 = complex(23)
z2 = complex(45)

addition = z1 + z2
subtraction = z1 - z2
multiplication = z1 * z2
division = z1 / z2

print(addition)      # 输出:(6+8j)
print(subtraction)   # 输出:(-2-2j)
print(multiplication) # 输出:(-7+22j)
print(division)      # 输出:(0.5609756097560976+0.0487804878048781j)

Python还提供了一些内置函数和方法来处理复数,例如 abs() 函数用于计算复数的绝对值,cmath 模块提供了一些数学函数,如求幅度和相位角。


复数的使用可以在需要处理虚数或使用复数运算的情况下非常有用,例如在工程、物理或数学领域。


作者:小小绘
来源:mdnice.com/writing/2cd491bb2ae749d195b965325ba408f3
收起阅读 »

Python中列表的惭怍方法

Python中的列表是一种非常常用的数据结构,它可以存储多个元素,并且可以进行各种操作。下面是关于列表操作的一些基本方法:列表的生成:使用方括号 [] 来创建一个空列表:my_list = []使用方括号 [] 并在其中添加元素来创建一个非空列表:my_lis...
继续阅读 »

Python中的列表是一种非常常用的数据结构,它可以存储多个元素,并且可以进行各种操作。下面是关于列表操作的一些基本方法:

  1. 列表的生成:

    • 使用方括号 [] 来创建一个空列表:my_list = []

    • 使用方括号 [] 并在其中添加元素来创建一个非空列表:my_list = [1, 2, 3]

    • 使用列表生成式来生成列表:my_list = [x for x in range(5)]

  2. 列表的增加和删除:

    • 使用 append() 方法在列表末尾添加一个元素:my_list.append(4)

    • 使用 insert() 方法在指定位置插入一个元素:my_list.insert(0, 0)

    • 使用 extend() 方法将另一个列表的元素添加到当前列表末尾:my_list.extend([5, 6, 7])

    • 使用 remove() 方法删除列表中的指定元素:my_list.remove(3)

    • 使用 pop() 方法删除并返回列表中指定位置的元素:my_list.pop(0)

  3. 列表的遍历和循环:

    • 使用 for 循环遍历列表中的每个元素:

      for item in my_list:
          print(item)
    • 使用 enumerate() 函数同时获取元素的索引和值:

      for index, item in enumerate(my_list):
        print(index, item)
    • 使用 while 循环根据条件遍历列表:

      i = 0
      while i < len(my_list):
        print(my_list[i])
        i += 1
    • 使用 range() 函数和 len() 函数结合来遍历列表的索引:

        for i in range(len(my_list)):
            print(my_list[i])

希望这些例子能帮助你更好地理解列表的操作方法。如果有任何问题,请随时提问。

作者:orangewu
来源:mdnice.com/writing/3a3a6e2f2a5c4763a2c8901e205f446c
收起阅读 »

😋贪心算法

贪心算法 贪心算法是一种寻找最优解的算法思想,它通过局部最优选择来达到全局最优解。在贪心算法中,每一步都会做出当前状态下的最优选择,并且假设做出这样的选择后,剩余的问题可以被简化为一个更小的子问题。 与动态规划不同,贪心算法不需要保存子问题的解,因此通常需要更...
继续阅读 »

贪心算法


贪心算法是一种寻找最优解的算法思想,它通过局部最优选择来达到全局最优解。在贪心算法中,每一步都会做出当前状态下的最优选择,并且假设做出这样的选择后,剩余的问题可以被简化为一个更小的子问题。


与动态规划不同,贪心算法不需要保存子问题的解,因此通常需要更少的空间和时间。


贪心算法通常采用一种贪心的策略,即在每一步选择当前看起来最优的选择,希望最终得到全局最优解。但是,在某些情况下,局部最优解并不能保证一定能够导致全局最优解。由于贪心算法一旦做出选择就不能更改。贪心算法只是一种近似算法。


贪心算法通常需要满足贪心选择性质和最优子结构性质,否则它可能会导致错误的结果。


在使用贪心算法时,我们需要仔细考虑问题的特点和贪心选择的合理性,并尽可能地证明贪心算法的正确性。如果无法证明贪心算法的正确性,我们需要考虑使用其他算法来解决问题。


贪心算法常见的应用场景包括:



  • 贪心选择性质:在求解最优解的过程中,每一步的选择只与当前状态有关,不受之前选择的影响。

  • 最优子结构性质:问题的最优解可以被分解为若干个子问题的最优解,即子问题的最优解可以推导出原问题的最优解。

  • 无后效性:某个状态以前的过程不会影响以后的状态,只与当前状态有关。


举个反例🌰:279. 完全平方数


给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。


完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,149 和 16 都是完全平方数,而 3 和 11 不是。


 


示例 1:


输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4

示例 2:


输入: n = 13
输出: 2
解释: 13 = 4 + 9

 


提示:



  • 1<=n<=1041 <= n <= 10^4


错误做法:


class Solution:
def numSquares(self, n: int) -> int:
count = 0
while n != 0:
c = int(n**(1/2))
n -= c**2
count += 1
return count

输入12的时候答案是4,也就是12 = 9 + 1 + 1 + 1


实际上应该是答案为312 = 4 + 4 + 4


这个函数使用的是贪心算法的思想,每次都选择当前能用的最大完全平方数来减去 n,直到 n 减为 0。


在每一步中,选择最大的完全平方数来减去 n,可以确保所需的完全平方数的数量最小,因为如果我们选择了小的完全平方数,那么我们需要更多的完全平方数才能表示 n。


但是它并没有证明贪心策略的正确性,也没有提供正确性的证明。我们已经提供反例,证明这玩意儿是错的了。贪心算法的正确性得不到保证,所以本题不能用贪心算法。


正确答案:


class Solution:
def numSquares(self, n: int) -> int:
dp = [float('inf')]*(n+1)
dp[0] = 0
for i in range(1,n+1):
j = 1
while j*j <= i:
dp[i] = min(dp[i],dp[i-j*j]+1)
j+=1
return dp[-1]

这个代码使用了动态规划来解决完全平方数问题,它的时间复杂度为 O(nn)O(n\sqrt{n}),空间复杂度为 O(n)O(n)




  • i=0 时,不需要任何完全平方数。




  • 对于 i>0 的情况,我们枚举从 1i 中的每个完全平方数 j*j,然后计算 dp[i-j*j]+1 的值,这个值表示在将 i-j*j 分解成完全平方数之和的基础上再加上一个完全平方数 j*j。我们需要使 dp[i-j*j]+1 的值最小,因此我们可以得出状态转移方程:




dp[i]=min(dp[i],dp[ijj]+1)dp[i] = min(dp[i], dp[i-j * j]+1)

最后,dp[n] 的值就是将 n 分解成完全平方数之和所需的最小个数。


该代码正确地解决了完全平方数问题,可以得到全局最优解。


55. 跳跃游戏


给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。


数组中的每个元素代表你在该位置可以跳跃的最大长度。


判断你是否能够到达最后一个下标。


 


示例 1:


输入: nums = [2,3,1,1,4]
输出: true
解释: 可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

示例 2:


输入: nums = [3,2,1,0,4]
输出: false
解释: 无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

 


提示:



  • 1 <= nums.length <= 3 * 104

  • 0 <= nums[i] <= 105


class Solution:
def canJump(self, nums: List[int]) -> bool:
maxlen = 0
for i,n in enumerate(nums):
if maxlen < i:
return False
maxlen = max(maxlen,i+n)
return maxlen >= len(nums) -1

这段代码实现了一个非常经典的贪心算法,用于判断能否从数组的起点跳到终点。


具体思路是,用 maxlen 记录当前能到达的最远位置,遍历数组中的每个位置,如果当前位置大于 maxlen,说明无法到达该位置,直接返回 False。否则,更新 maxlen 为当前位置能够到达的最远位置。


这个算法的贪心策略是,在每个位置上都选择能够到达的最远位置。由于跳跃的步数只能是整数,所以如果当前位置能到达的最远位置小于当前位置,那么就无法到达该位置。


这个算法的时间复杂度是 O(n)O(n),空间复杂度是 O(1)O(1)


45. 跳跃游戏 II


给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]


每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:



  • 0 <= j <= nums[i] 

  • i + j < n


返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]


 


示例 1:


输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
  从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

示例 2:


输入: nums = [2,3,0,1,4]
输出: 2

 


提示:



  • 1 <= nums.length <= 104

  • 0 <= nums[i] <= 1000

  • 题目保证可以到达 nums[n-1]


class Solution:
def jump(self, nums) -> int:
minstep = 0
i = len(nums) - 1
while i > 0:
for j,n in enumerate(nums):
if j+n >= i:
minstep += 1
i = j
break
return minstep

该算法的时间复杂度为 O(n2)O(n^2),其中 nn 为数组的长度。


在最坏情况下,每个元素都需要遍历一遍,以找到它们能够到达的最远距离,这需要 O(n)O(n) 的时间复杂度。同时,每次找到能够到达 ii 的最远距离时,都需要遍历从 00i1i-1 的所有元素,以找到能够到达 ii 的最小步数,这也需要 O(n)O(n) 的时间复杂度。因此,总时间复杂度为 O(n2)O(n^2)


该算法的空间复杂度为 O(1)O(1),因为它只使用了常数级别的额外空间。


优化——从前往后跳:


这个算法是一个基于贪心策略的解法,跟之前的从前往后跳的贪心算法类似,不过稍微做了一些改进,可以将时间复杂度降低到 O(n)O(n)


算法的核心思想是维护一个区间 [0, end],在这个区间内每个位置所能跳到的最远距离都是 i + nums[i],其中 i 是当前位置,nums[i] 是当前位置所能跳的最远距离。维护的时候,我们不断更新能够到达的最远距离 maxlen,当 i 到达区间的末尾 end 时,说明需要跳一步,并将 end 更新为 maxlen


这个算法的时间复杂度为 O(n)O(n),空间复杂度为 O(1)O(1)


class Solution:
def jump(self, nums):
n = len(nums)
maxlen = end = 0
step = 0
for i in range(n - 1):
maxlen = max(maxlen, i + nums[i])
if i == end:
end = maxlen
step += 1
return step
作者:Ann
来源:juejin.cn/post/7262231954191859770

收起阅读 »

python计算质数的几种方法

因为要学着写渗透工具,这几天都在上python编程基础课,听得我打瞌睡,毕竟以前学过嘛。 最后sherry老师留了作业,其中一道题是这样的: 题目:编写python程序找出10-30之间的质数。 太简单了,我直接给出答案: Prime = [11, 13, 1...
继续阅读 »

因为要学着写渗透工具,这几天都在上python编程基础课,听得我打瞌睡,毕竟以前学过嘛。
最后sherry老师留了作业,其中一道题是这样的:


题目:编写python程序找出10-30之间的质数。


太简单了,我直接给出答案:


Prime = [11, 13, 17, 19, 23, 29]
print(Prime)

输出结果:


[11, 13, 17, 19, 23, 29]

当然,这样做肯定会在下节课被sherry老师公开处刑的,所以说还是要根据上课时学的知识写个算法。


1.穷举法


回想一下上课时学了变量、列表、循环语句之类的东西,sherry老师还亲自演示了多重死循环是怎么搞出来的(老师是手滑了还是业务不熟练啊),所以我们还是要仔细思考一下不要重蹈覆辙。


思路:首先要构造一个循环,遍历所有符合条件的自然数,然后一个一个验证是否为质数,最后把符合条件的质数列出来。


# 最开始编的穷举法,简单粗暴,就是性能拉跨。
# P=因数,N=自然数
import time

t0 = time.time() # 开始时间
Min = 10 # 范围最小值
Max = 30 # 范围最大值
Result = [] # 结果

for N in range(Min, Max): # 给自然数来个遍历
for P in range(2, N):
if (N % P == 0): # 判断是否有因数
break # 有因数那就不是质数,跳出循环
else:
Result.append(N)

print('计算', Min, '到', Max, '之间的质数')
print(Min, '到', Max, '之间的质数序列:', Result)
print(Min, '到', Max, '之间的质数个数:', len(Result))
print('计算耗时:', time.time() - t0, '秒')

执行结果(计算耗时是最后加上去的):


2023-05-28-22-35-46.png


到这里作业就搞定了。然后把其他几道题也做完了,发现很无聊,就又切回来想搞点事。这么点计算量,0秒真的有点少,不如趁这个机会烤一烤笔记本的性能,所以直接在Min和Max的值后面加几个0。试试100000-200000。


2023-05-28-23-02-03.png


很尴尬,直接卡住了,这代码有点拉跨啊,完全不符合我的风格。
倒了杯咖啡,终于跑完了。


2023-05-28-23-01-07.png


这个也太夸张,一定是哪里出了问题,很久以前用C写的代码我记得也没那么慢啊。反正周末挺闲的,不如仔细研究一下。


2.函数(CV)大法


为了拓宽一下思路,我决定借鉴一下大佬的代码。听说函数是个好东西,所以就CV了两个函数。


一个函数判断质数,另一个求范围内的所有质数,把它们拼一起,是这个样子:


# 网上学来的,自定义两个函数,但是数值稍微大点就卡死了。
import time

t0 = time.time()
Min = 100000 # 范围最小值
Max = 200000 # 范围最大值


def is_prime(n): return 0 not in [n % i for i in range(2, n//2+1)] # 判断是否为质数


def gen_prime(a, b): return [n for n in range(
a, b+1) if 0 not in [n % i for i in range(2, n//2+1)]] # 输出范围内的质数


print('计算', Min, '到', Max, '之间的质数')
print(Min, '到', Max, '之间的质数序列:', gen_prime(Min, Max))
print('计算耗时:', time.time() - t0, '秒')

稍微改动了一下,还是100000-200000,我们试试看。


2023-05-28-23-08-35.png


嗯,一运行风扇就开始啸叫,CPU都快烤炸了。看来CV大法也不行啊。
经过漫长的烤机,这次结果比上次还惨,300多秒,这两个函数本质上还是穷举法,看来这条路也走不通。


3.穷举法改


我们可以分析一下穷举法的代码,看看有没有什么改进的方法。
首先,通过九年义务教育掌握的数学知识,我们知道,质数中只有2是偶数,所以计算中可以把偶数忽略掉,只计算奇数,工作量立马减半!
其次,在用因数P判断N是否为质数时,如果P足够大的话,比如说PxP>=N的时候,那么后面的循环其实是重复无意义的。因为假设PxQ>=N,那么P和Q必然有一个小于sqrt(N),只需要计算P<=sqrt(N)的情况就行了。


因为2作为唯一一个偶数,夹在循环里面处理起来很麻烦,所以放在开头处理掉。最终的代码如下:


# 优化后的代码,减少了一些无意义的循环,比以前快多了。
import time

t0 = time.time()
Min = 100000 # 范围最小值
Max = 200000 # 范围最大值
Prime = [2, 3] # 质数列表
Result = [] # 结果
Loop = 0 # 计算循环次数

if Min <= 2:
Result.append(2)
if Min <= 3:
Result.append(3) # 先把2这个麻烦的偶数处理掉
for N in range(5, Max, 2):
for P in range(3, int(N**0.5)+2, 2): # 只计算到根号N
Loop += 1
if (N % P == 0):
break
else:
Prime.append(N)
if N > Min:
Result.append(N)

print('计算', Min, '到', Max, '之间的质数')
print(Min, '到', Max, '之间的质数序列:', Result)
print(Min, '到', Max, '之间的质数个数:', len(Result))
print('循环次数:', Loop)
print('计算耗时:', time.time() - t0, '秒')

2023-05-28-23-09-54.png


代码量虽然多了,但是效果还是很明显,100000-200000才0.4秒,快了不知道多少,看来我们的思路是对的。
我决定再加到1000000-5000000,看看能不能撑住。因为输出太多了控制台会卡死,所以改一下,只输出最后一个质数。


2023-05-28-23-19-12.png


总共花了64秒,看来还是有点费劲。


4.穷举法魔改


我们再来分析一下,如果我们用于判断的因数,不是用奇数列表,而是用生成的Prime列表里面的质数,因为质数的个数远远少于奇数,所以第二个循环会少一些工作量呢?可以试试看。但是因为这个改动,需要加一些判断语句进去,所以节省的时间比较有限。


# 别看这个代码比较长,但是跑到1000万也不会卡死,而且还很快。
import time

t0 = time.time()
Min = 1000000 # 范围最小值
Max = 5000000 # 范围最大值
Prime = [2, 3] # 质数列表
Result = [] # 结果
Loop = 0 # 计算循环次数

if Min <= 2:
Result.append(2)
if Min <= 3:
Result.append(3)
for N in range(5, Max, 2):
M = int(N**0.5) # 上限为根号N
for P in range(len(Prime)): # 在质数列表Prime中遍历
Loop += 1
L = Prime[P+1]
if (N % L == 0):
break
elif L >= M: # 上限大于根号N,判断为质数并跳出循环
Prime.append(N)
if N > Min:
Result.append(N)
break

print('计算', Min, '到', Max, '之间的质数')
print('最后一个质数:', Result[-1])
print(Min, '到', Max, '之间的质数个数:', len(Result))
print('循环次数:', Loop)
print('计算耗时:', time.time() - t0, '秒')

还是1000000-5000000再试试看


2023-05-28-23-25-29.png


这次耗时22秒,时间又缩短了一大半,但是好像已经没多少改进的空间了,感觉穷举法已经到头了,需要新的思路。


5.埃氏筛法


其实初中数学我们就学过埃氏筛法:
如果P是质数,那么大于P的N的倍数一定不是质数。把所有的合数排除掉,那么剩下的就都是质数了。
我们可以生成一个列表用来储存数字是否是质数,初始阶段都是质数,每次得出一个质数就将它的倍数全部标记为合数。


# 速度已经起飞了。
import time

t0 = time.time()
Min = 1000000 # 范围最小值
Max = 2000000 # 范围最大值
Loop = 0 # 计算循环次数
Result = [] # 结果

Natural = [True for P in range(Max)] # 自然数列表标记为True
for P in range(2, Max):
if Natural[P]: # 标记如果为True,就是质数
if P >= Min:
Result.append(P) # 添加范围之内的质数
for N in range(P*2, Max, P): # 将质数的倍数的标记改为False
Loop += 1
Natural[N] = False

print('计算', Min, '到', Max, '之间的质数')
print('最后一个质数:', Result[-1])
print(Min, '到', Max, '之间的质数个数:', len(Result))
print('循环次数:', Loop)
print('计算耗时:', time.time() - t0, '秒')

2023-05-29-00-11-23.png


1.6秒,比最高级的穷举法还要快上10多倍,这是数学的胜利。
再试试1-50000000。


1.png


很不错,只需要20秒。因为筛法的特性,忽略内存的影响,数值越大,后面的速度反而越快了。


6.欧拉筛法


我们可以仔细分析一下,上面的埃氏筛法在最后标记的时候,还是多算了一些东西,N会重复标记False,比如77,既是7的倍数又是11的倍数,这样会被标记两次,后面的大合数会重复标记多次,浪费了算力,所以标记的时候要排除合数。另外就是P*N大于Max时,后面的计算已经无意义了,也要跳出来。把这些重复的动作排除掉,就是欧拉筛法,也叫线性筛。


# 最终版,优化了很多细节。
import time

t0 = time.time()
Min = 1 # 范围最小值
Max = 50000000 # 范围最大值
Loop = 0 # 计算循环次数
Prime = [2]
Result = [] # 结果

if Min <= 2:
Result.append(2)
Limit = int(Max/3)+1
Natural = [True for P in range(Max+1)] # 自然数列表标记为True
for P in range(3, Max+1, 2):
if Natural[P]: # 标记如果为True,就是质数
Prime.append(P)
if P >= Min:
Result.append(P)
if P > Limit: # 超过Limit不需要再筛了,直接continue
continue
for N in Prime: # 将质数的倍数的标记改为False
Loop += 1
if P*N > Max: # 超过Max就无意义了,直接break
break
Natural[P * N] = False
if P % N == 0: # 判断是否为合数
break

print('计算', Min, '到', Max, '之间的质数')
print('最后一个质数:', Result[-1])
print(Min, '到', Max, '之间的质数个数:', len(Result))
print('循环次数:', Loop)
print('计算耗时:', time.time() - t0, '秒')

(因为之前的版本缩进错了,所以更新了这段代码)


2.png


同样的条件下耗时11.46秒。这是因为多了一个列表和几行判断语句,加上python的解释型特性,所以实际上并不会快好几倍,但是总体效率还是有50%左右的提升。


好了,这次把老师课堂上讲的变量、列表、循环语句什么的都用上了,算是现买现卖、活学活用吧。我觉得这次的作业怎么说也能拿满分吧,sherry老师记得下次上课夸夸我。


作者:ReisenSS
来源:juejin.cn/post/7238199999732695097
收起阅读 »

微信图片防撤回

了解需求 实际生活中,由于好奇朋友撤回的微信图片信息,但直接去要又怎会是我的性格呢。由此萌生出做一个微信防撤回程序(已向朋友说明)。 当前网络上其实存在一些微信防撤回程序,不过担心不正规软件存在漏洞,泄漏个人信息,这里也就不考虑此种方法。 解决方案 思路 由于...
继续阅读 »

了解需求


实际生活中,由于好奇朋友撤回的微信图片信息,但直接去要又怎会是我的性格呢。由此萌生出做一个微信防撤回程序(已向朋友说明)。


当前网络上其实存在一些微信防撤回程序,不过担心不正规软件存在漏洞,泄漏个人信息,这里也就不考虑此种方法。


解决方案


思路


由于当前微信不支持微信网页版登陆,因此使用itchat的方法不再适用。


后来了解到电脑端微信图片会先存储在本地,撤回后图片再从本地删除,因此只要在撤回前将微信本地图片转移到新文件夹即可。


在此使用Python的watchdog包来监视文件系统事件,例如文件被创建、修改、删除、移动,我们只需监听创建文件事件即可。


安装watchdog包:    pip install watchdog
我的python环境为python3.9版本

实现


1.首先进行文件创建事件监听,在监听事件发生后的事件处理对象为复制微信图片到新文件夹。具体代码如下。


需要注意的是微信在2022.05前,图片存储在images目录下;在2022.05后,图片存储在MsgAttach目录下,并按微信对象分别进行存储。


# 第一步:加载路径,并实时读取JPG信息
import os
import shutil
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

def mycopyfile(srcfile,dst_dir):
if not os.path.isfile(srcfile):
print ("%s not exist!"%(srcfile))
else:
fpath,fname=os.path.split(srcfile) # 分离文件名和路径
if fname.endswith('.jpg') or fname.endswith('.png') or fname.endswith('.dat'):
dst_path = os.path.join(dst_dir, fname)
shutil.copy(srcfile, dst_path) # 复制文件

class MyEventHandler(FileSystemEventHandler):
# 文件移动
# def on_moved(self, event):
# print("文件移动触发")
# print(event)


def on_created(self, event):
# print("文件创建触发")
print(event)
mycopyfile(event.src_path, dst_dir)


# def on_deleted(self, event):
# print("文件删除触发")
# print(event)
#
# def on_modified(self, event):
# print("文件编辑触发")
# print(event)

if __name__ == '__main__':

dst_dir = r"E:\03微信防撤回\weixin" #TODO:修改为自己的保存文件目录
if not os.path.exists(dst_dir):
os.makedirs(dst_dir)

observer = Observer() # 创建观察者对象
file_handler = MyEventHandler() # 创建事件处理对象
listen_dir = r"C:\Users\hc\Documents\WeChat" #TODO:修改为自己的监听目录
observer.schedule(file_handler, listen_dir, True) # 向观察者对象绑定事件和目录
observer.start() # 启动
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()

2.由于微信保存文件以.dat格式保存,因此需要对微信文件格式进行解码,具体解码代码如下。


# weixin_Image.dat 破解
# JPG 16进制 FF D8 FF
# PNG 16进制 89 50 4e 47
# GIF 16进制 47 49 46 38
# 微信.bat 16进制 a1 86----->jpg ab 8c----jpg dd 04 --->png
# 自动计算异或 值
import os

into_path = r'E:\03微信防撤回\weixin' # 微信image文件路径
out_path = r'E:\03微信防撤回\image'

def main(into_path, out_path):

dat_list = Dat_files(into_path) # 把路径文件夹下的dat文件以列表呈现
lens = len(dat_list)
if lens == 0:
print('没有dat文件')
exit()

num = 0
for dat_file in dat_list: # 逐步读取文件
num += 1
temp_path = into_path + '/' + dat_file # 拼接路径:微信图片路径+图片名
dat_file_name = dat_file[:-4] # 截取字符串 去掉.dat
imageDecode(temp_path, dat_file_name, out_path) # 转码函数
value = int((num / lens) * 100) # 显示进度
print('正在处理--->{}%'.format(value))


def Dat_files(file_dir):
"""
:param file_dir: 寻找文件夹下的dat文件
:return: 返回文件夹下dat文件的列表
"""

dat = []
for files in os.listdir(file_dir):
if os.path.splitext(files)[1] == '.dat':
dat.append(files)
return dat

def imageDecode(temp_path, dat_file_name, out_path):
dat_read = open(temp_path, "rb") # 读取.bat 文件
xo, j = Format(temp_path) # 判断图片格式 并计算返回异或值 函数

if j == 1:
mat = '.png'
elif j == 2:
mat = '.gif'
else:
mat = '.jpg'

out = out_path + '/' + dat_file_name + mat # 图片输出路径
png_write = open(out, "wb") # 图片写入
dat_read.seek(0) # 重置文件指针位置

for now in dat_read: # 循环字节
for nowByte in now:
newByte = nowByte ^ xo # 转码计算
png_write.write(bytes([newByte])) # 转码后重新写入


def Format(f):
"""
计算异或值
各图片头部信息
png:89 50 4e 47
gif: 47 49 46 38
jpeg:ff d8 ff
"""

dat_r = open(f, "rb")

try:
a = [(0x89, 0x50, 0x4e), (0x47, 0x49, 0x46), (0xff, 0xd8, 0xff)]
for now in dat_r:
j = 0
for xor in a:
j = j + 1 # 记录是第几个格式 1:png 2:gif 3:jpeg
i = 0
res = []
now2 = now[:3] # 取前三组判断
for nowByte in now2:
res.append(nowByte ^ xor[i])
i += 1
if res[0] == res[1] == res[2]:
return res[0], j
except:
pass
finally:
dat_r.close()


# 运行
if __name__ == '__main__':
main(into_path, out_path)
复制代码
作者:空气猫
来源:juejin.cn/post/7221376169370583101
>
收起阅读 »

python简单实现校园网自动认证,赶紧部署到你的路由器上吧

2023-05-20:使用python库来实现定时任务,不再依赖系统的定时任务,部署起来容易100倍 原文链接 python实现校园网自动认证 - 歌梦罗 - 努力生活,努力热爱 (gmero.com) 说在前面 大部分校园网都需要认证才能够上网,而且就...
继续阅读 »

2023-05-20:使用python库来实现定时任务,不再依赖系统的定时任务,部署起来容易100倍




原文链接 python实现校园网自动认证 - 歌梦罗 - 努力生活,努力热爱 (gmero.com)



说在前面


大部分校园网都需要认证才能够上网,而且就算你保持在线隔一段时间也会将你强行踢下线,导致游戏中断,自己挂在校园网的web程序宕机等头疼的问题。


本文主要是使用python与主机的计划任务来实现校园网的自动认证,需要注意的是,我们学校的校园网认证系统与认证方式可能与你们学校的不一样,所以主要是分享我的实现思路,以供大家参考参考。


准备工作:



  • 一台电脑

  • 一台用于挂载自动认证脚本的服务器(也可以是你的电脑,或者不那么硬的路由器之类的)


了解校园网认证的过程



这是最繁琐的一步,在网上见过的这么多认证的教程,感觉我们学校是最复杂的



其实总结来说这一步就是反复的在浏览器f12里观察你在认证的时候都经过了哪些程序,我们学校的认证流程大概是这样的



我们需要传入以下参数进行认证,其中后三项传空值,因为是本地认证后面脚本就懒得加密密码了,把passwordEncrypt传false,password直接明文就可以了,可能不安全,但校园网无所谓。



看起来感觉实现很容易对不对?python随便几行request就能实现了的。但有以下几个问题:



  • 重复的认证是无效的,不会重新计算在线时间(到点还是踢你)

  • 高频的认证可能导致莫名奇妙的bug,比如明明已经认证成功了而认证服务器那边确觉得你没有认证,导致认证界面直接卡死(因为123.123.123.123在你已经接入互联网的情况下是无法访问的)

  • 多次认证还可能导致出现验证码,目前脚本还无法处理验证码


于是我们在程序中需要判断是否已经认证了,在已经认证的情况下运行程序需要先登出来重置在线时间从而推迟强制下线的时间(于是,我们又需要找到两个请求:一个是判断认证情况的,一个是退出登录的)


经过疯狂的F12和api调试之后,找到了172.208.2.102/eportal/InterFace.do?method=logout172.208.2.102/eportal/InterFace.do?method=getUserInfo两个api来进行登出和判断操作


python实现


认证流程用python的requests库很容易就能实现,定时任务用schedule来创建也很容易。


import re
import time
import urllib.parse
from urllib.parse import urlparse

import requests
import schedule

apiUrl = "http://172.208.2.102/eportal/InterFace.do"
authUrl = apiUrl + "?method=login"
logoutUrl = apiUrl + "?method=logout"
getQueryUrl = "http://123.123.123.123/"
getUserInfoUrl = apiUrl + "?method=getOnlineUserInfo"
webService = "中国电信"


# 判断是否已经认证了
def is_authed():
   try:
       resp = requests.get(getUserInfoUrl, timeout=3)
       if resp.status_code != 200:
           # 网络问题直接退出
           print("判断认证状态失败")
           return False
       else:
           json_data = resp.json()
           result = json_data["result"]
           return result != "fail"
   except requests.RequestException:
       print("判断认证状态失败: 请求失败")
       return False


# 获取query认证信息
def get_query_str():
   try:
       resp = requests.get(getQueryUrl, timeout=8)
       if resp.status_code != 200:
           print("获取query信息失败")
           return False
       pattern = "href='(.*)'"
       match = re.search(pattern, resp.text)

       if match:
           url = urlparse(match.group(1))
           return url.query
       else:
           return
   except requests.RequestException:
       print("获取query信息失败: 请求失败")
       return False


# 认证
def do_auth():
   query_str = get_query_str()
   if not query_str:
       return False
   # 表单数据
   data = {
       "userId": "871390441",
       "password": "yourpassword",
       "service": urllib.parse.quote(webService),
       "queryString": query_str,
       "passwordEncrypt": "false",
       "operatorPwd": ,
       "operatorUserId": ,
       "validcode":
  }

   try:
       resp = requests.post(authUrl, data)
       if resp.status_code == 200 and resp.json()["result"] == "success":
           print("认证成功")
           return True
       else:
           print("认证失败")
           return False
   except requests.RequestException:
       print("认证失败: 请求失败")
       return False


# 退出登录
def do_logout():
   resp = requests.get(logoutUrl)

   if resp.status_code == 200:
       if resp.json()["result"] == "success":
           print("退出登录成功")
           return True
       else:
           print("退出登录失败: " + resp.json()["message"])
           return False
   else:
       print("退出登录失败: 网络错误")
       return False


# 一次认证流程
def auth_job():
   print("\n====校园网自动认证开始====")
   if is_authed():
       if do_logout():
           do_auth()
   else:
       do_auth()
   print("====校园网自动认证结束====\n")


if __name__ == '__main__':
   auth_job()
   # 定时任务
   schedule.every().day.at("12:00").do(auth_job)
   schedule.every().day.at("00:00").do(auth_job)

   while True:
       schedule.run_pending()
       time.sleep(1)


代码部分有了思路之后其实就很简单了,接下来就是最重要的部署环节了。


部署到服务器


我这里以部署到我的破烂x86linux服务器上为例(windows就更简单了,直接运行python程序即可),采取docker部署的方式


首先写Dockerfile, 这里我就不解释了,不懂的话找一找资料吧


FROM python:3.11-slim-bullseye

WORKDIR /app

ADD . /app

RUN pip3 config set global.index-url http://mirrors.aliyun.com/pypi/simple && \
  pip3 config set install.trusted-host mirrors.aliyun.com && \
  pip install --upgrade pip &&\
  pip3 install requests &&\
  pip3 install schedule


CMD python3 -u main.py

然后在当前文件夹输入命令来建立镜像和运行容器,很简单对不对


docker build -t autoauth:v1 . 
docker run --restart always --name myautoauth autoauth:v1

写在最后


以上操作在我这是完美运行的,需要一些折腾,但你能看完我这篇博客说明你肯定也是个喜欢折腾的人吧。上面的一些命名方法路径之类的不一定是绝对的。


作者:歌梦罗
来源:juejin.cn/post/7234864940799213625
收起阅读 »

快速生成定制化的Word文档:Python实践指南

1.1. 前言 众所周知,安服工程师又叫做Word工程师,在打工或者批量SRC的时候,如果产出很多,又需要一个一个的写报告的情况下会非常的折磨人,因此查了一些相关的资料,发现使用python的docxtpl库批量写报告效果很不错,记录一下。 1.2. 介绍 d...
继续阅读 »

1.1. 前言


众所周知,安服工程师又叫做Word工程师,在打工或者批量SRC的时候,如果产出很多,又需要一个一个的写报告的情况下会非常的折磨人,因此查了一些相关的资料,发现使用python的docxtpl库批量写报告效果很不错,记录一下。


1.2. 介绍


docxtpl 是一个用于生成 Microsoft Word 文档的模板引擎库,它结合了 docx 模块和 Jinja2 模板引擎,使用户能够使用 Microsoft Word 模板文件并在其中填充动态数据。它提供了一种方便的方式来生成个性化的 Word 文档,并支持条件语句、循环语句和变量等控制结构,以满足不同的文档生成需求。


官方GitHub地址:github.com/elapouya/py…


官方文档地址:docxtpl.readthedocs.io/en/latest/



简单来说:就是创建一个类似Jinja2语法的模板文档,然后往里面动态填充内容就可以了



安装:


pip3 install docxtpl

1.3. 基础使用


from docxtpl import DocxTemplate

doc = DocxTemplate("test.docx")
context = {'whoami': "d4m1ts"}
doc.render(context)
doc.save("generated_doc.docx")

其中,test.docx内容如下:


test.docx


生成后的结果如下:


gen


1.4. 案例介绍


1.4.1. 需求假设


写一份不考虑美观的漏扫报告,需要有统计结果图和漏洞详情,每个漏洞包括漏洞名、漏洞地址、漏洞等级、漏洞危害、复现过程、修复建议六个部分。


1.4.2. 模板文档准备


编写的模板文档如下,使用到了常见的iffor赋值等,保存为template.docx,后续只需要向里面填充数据即可。


template


1.4.3. 数据结构分析


传入数据需要一串json字符串,因此我们根据模板文档梳理好json结构,然后传入即可。


梳理好的数据结构如下:


{
"饼图": "111",
"柱状图": "222",
"漏洞简报": [
{
"漏洞名": "测试漏洞名1",
"漏洞等级": "高危"
}
],
"漏洞详情": [
{
"漏洞名": "测试漏洞名1",
"漏洞地址": "http://blog.gm7.org/",
"漏洞等级": "高危",
"漏洞危害": "危害XXX",
"复现过程": "先xxx,再xxx,最后xxx",
"修复建议": "更新到最新版本即可"
}
]
}

编写代码测试一下可行性:


from docxtpl import DocxTemplate

doc = DocxTemplate("template.docx")
context = {
"饼图": "111",
"柱状图": "222",
"漏洞简报": [
{
"漏洞名": "测试漏洞名1",
"漏洞等级": "高危"
},
{
"漏洞名": "测试漏洞名2",
"漏洞等级": "严重"
},
{
"漏洞名": "测试漏洞名2",
"漏洞等级": "中危"
}
],
"漏洞详情": [
{
"漏洞名": "测试漏洞名1",
"漏洞地址": "http://blog.gm7.org/",
"漏洞等级": "高危",
"漏洞危害": "危害XXX",
"复现过程": "先xxx,再xxx,最后xxx",
"修复建议": "更新到最新版本即可"
},
{
"漏洞名": "测试漏洞名2",
"漏洞地址": "http://bblog.gm7.org/",
"漏洞等级": "严重",
"漏洞危害": "危害XXX",
"复现过程": "先xxx,再xxx,最后xxx",
"修复建议": "更新到最新版本即可"
},
{
"漏洞名": "测试漏洞名3",
"漏洞地址": "http://cblog.gm7.org/",
"漏洞等级": "中危",
"漏洞危害": "危害XXX",
"复现过程": "先xxx,再xxx,最后xxx",
"修复建议": "更新到最新版本即可"
}
]
}

doc.render(context)
doc.save("generated_doc.docx")

很好,达到了预期的效果。


res


1.4.4. 加入图表


在上面的过程中,内容几乎是没问题了,但是图表还是没有展示出来。生成图表我们使用plotly这个库,并将生成内容写入ByteIO


相关代码如下:


import plotly.graph_objects as go
from io import BytesIO

def generatePieChart(title: str, labels: list, values: list, colors: list):
"""
生成饼图
https://juejin.cn/post/6911701157647745031#heading-3
https://juejin.cn/post/6950460207860449317#heading-5

:param title: 饼图标题
:param labels: 饼图标签
:param values: 饼图数据
:param colors: 饼图每块的颜色
:return:
"""

# 基础饼图
fig = go.Figure(data=[go.Pie(
labels=labels,
values=values,
hole=.4, # 中心环大小
insidetextorientation="horizontal"
)])
# 更新颜色
fig.update_traces(
textposition='inside', # 文本显示位置
hoverinfo='label+percent', # 悬停信息
textinfo='label+percent', # 饼图中显示的信息
textfont_size=15,
marker=dict(colors=colors)
)
# 更新标题
fig.update_layout(
title={ # 设置整个标题的名称和位置
"text": title,
"y": 0.96, # y轴数值
"x": 0.5, # x轴数值
"xanchor": "center", # x、y轴相对位置
"yanchor": "top"
}
)
image_io = BytesIO()
fig.write_image(image_io, format="png")
return image_io


def generateBarChart(title: str, x: list, y: list):
"""
生成柱状图
https://cloud.tencent.com/developer/article/1817208
https://blog.csdn.net/qq_25443541/article/details/115999537
https://blog.csdn.net/weixin_45826022/article/details/122912484

:param title: 标题
:param x: 柱状图标签
:param y: 柱状图数据
:return:
"""

# x轴长度最为18
b = x
x = []
for i in b:
if len(i) >= 18:
x.append(f"{i[:15]}...")
else:
x.append(i)

# 基础柱状图
fig = go.Figure(data=[go.Bar(
x=x,
y=y,
text=y,
textposition="outside",
marker=dict(color=["#3498DB"] * len(y)),
width=0.3
)])
# 更新标题
fig.update_layout(
title={ # 设置整个标题的名称和位置
"text": title,
"y": 0.96, # y轴数值
"x": 0.5, # x轴数值
"xanchor": "center", # x、y轴相对位置
"yanchor": "top"
},
xaxis_tickangle=-45, # 倾斜45度
plot_bgcolor='rgba(0,0,0,0)' # 背景透明
)
fig.update_xaxes(
showgrid=False
)
fig.update_yaxes(
zeroline=True,
zerolinecolor="#17202A",
zerolinewidth=1,
showgrid=True,
gridcolor="#17202A",
showline=True
)

image_io = BytesIO()
fig.write_image(image_io, format="png")
return image_io

1.4.5. 最终结果


要插入图片内容,代码语法如下:


myimage = InlineImage(tpl, image_descriptor='test_files/python_logo.png', width=Mm(20), height=Mm(10))

完整代码如下:


from docxtpl import DocxTemplate, InlineImage
from docx.shared import Mm
import plotly.graph_objects as go
from io import BytesIO


def generatePieChart(title: str, labels: list, values: list, colors: list):
"""
生成饼图
https://juejin.cn/post/6911701157647745031#heading-3
https://juejin.cn/post/6950460207860449317#heading-5

:param title: 饼图标题
:param labels: 饼图标签
:param values: 饼图数据
:param colors: 饼图每块的颜色
:return:
"""

# 基础饼图
fig = go.Figure(data=[go.Pie(
labels=labels,
values=values,
hole=.4, # 中心环大小
insidetextorientation="horizontal"
)])
# 更新颜色
fig.update_traces(
textposition='inside', # 文本显示位置
hoverinfo='label+percent', # 悬停信息
textinfo='label+percent', # 饼图中显示的信息
textfont_size=15,
marker=dict(colors=colors)
)
# 更新标题
fig.update_layout(
title={ # 设置整个标题的名称和位置
"text": title,
"y": 0.96, # y轴数值
"x": 0.5, # x轴数值
"xanchor": "center", # x、y轴相对位置
"yanchor": "top"
}
)
image_io = BytesIO()
fig.write_image(image_io, format="png")
return image_io


def generateBarChart(title: str, x: list, y: list):
"""
生成柱状图
https://cloud.tencent.com/developer/article/1817208
https://blog.csdn.net/qq_25443541/article/details/115999537
https://blog.csdn.net/weixin_45826022/article/details/122912484

:param title: 标题
:param x: 柱状图标签
:param y: 柱状图数据
:return:
"""

# x轴长度最为18
b = x
x = []
for i in b:
if len(i) >= 18:
x.append(f"{i[:15]}...")
else:
x.append(i)

# 基础柱状图
fig = go.Figure(data=[go.Bar(
x=x,
y=y,
text=y,
textposition="outside",
marker=dict(color=["#3498DB"] * len(y)),
width=0.3
)])
# 更新标题
fig.update_layout(
title={ # 设置整个标题的名称和位置
"text": title,
"y": 0.96, # y轴数值
"x": 0.5, # x轴数值
"xanchor": "center", # x、y轴相对位置
"yanchor": "top"
},
xaxis_tickangle=-45, # 倾斜45度
plot_bgcolor='rgba(0,0,0,0)' # 背景透明
)
fig.update_xaxes(
showgrid=False
)
fig.update_yaxes(
zeroline=True,
zerolinecolor="#17202A",
zerolinewidth=1,
showgrid=True,
gridcolor="#17202A",
showline=True
)

image_io = BytesIO()
fig.write_image(image_io, format="png")
return image_io


doc = DocxTemplate("template.docx")
context = {
"饼图": InlineImage(doc, image_descriptor=generatePieChart(
title="漏洞数量",
labels=["严重", "高危", "中危", "低危"],
values=[1, 1, 1, 0],
colors=["#8B0000", "red", "orange", "aqua"]
), width=Mm(130)),
"柱状图": InlineImage(doc, image_descriptor=generateBarChart(
title="漏洞类型",
x=["测试漏洞名1", "测试漏洞名2", "测试漏洞名3"],
y=[1, 1, 1]
), width=Mm(130)),
"漏洞简报": [
{
"漏洞名": "测试漏洞名1",
"漏洞等级": "高危"
},
{
"漏洞名": "测试漏洞名2",
"漏洞等级": "严重"
},
{
"漏洞名": "测试漏洞名2",
"漏洞等级": "中危"
}
],
"漏洞详情": [
{
"漏洞名": "测试漏洞名1",
"漏洞地址": "http://blog.gm7.org/",
"漏洞等级": "高危",
"漏洞危害": "危害XXX",
"复现过程": "先xxx,再xxx,最后xxx",
"修复建议": "更新到最新版本即可"
},
{
"漏洞名": "测试漏洞名2",
"漏洞地址": "http://bblog.gm7.org/",
"漏洞等级": "严重",
"漏洞危害": "危害XXX",
"复现过程": "先xxx,再xxx,最后xxx",
"修复建议": "更新到最新版本即可"
},
{
"漏洞名": "测试漏洞名3",
"漏洞地址": "http://cblog.gm7.org/",
"漏洞等级": "中危",
"漏洞危害": "危害XXX",
"复现过程": "先xxx,再xxx,最后xxx",
"修复建议": "更新到最新版本即可"
}
]
}

doc.render(context)
doc.save("generated_doc.docx")

结果如下:


result


作者:初始安全
来源:juejin.cn/post/7233597845919662139
收起阅读 »

python干饭神器---今天吃什么?python告诉你

一、前言 hello,大家好,我是干饭大王,打工人每天最幸福的时刻莫过于干饭了 😎,然而最纠结的时刻莫过于今天吃什么呢? 😂,于是乎我用python写了一个干饭神器,今天分享给大家,别忘了给我点赞 收藏 评论哟~ 话不多说,先看效果图: 还有隐藏 “福利”...
继续阅读 »

一、前言


hello,大家好,我是干饭大王,打工人每天最幸福的时刻莫过于干饭了 😎,然而最纠结的时刻莫过于今天吃什么呢? 😂,于是乎我用python写了一个干饭神器,今天分享给大家,别忘了给我点赞 收藏 评论哟~


话不多说,先看效果图:
在这里插入图片描述
在这里插入图片描述


还有隐藏 “福利” 帮你的完成减肥大业:


在这里插入图片描述


在本篇博客中,我们将会介绍如何使用Python编写一个带有界面的可切换的轮播图程序。轮播图程序可以从指定的文件夹中读取图片,并可以自动切换到下一张图片。如果图片比较大,则可以进行缩放以适应窗口大小。


二、准备工作


在开始编写程序之前,我们需要安装以下依赖项:



  • tkinter:用于创建GUI界面。

  • Pillow:用于处理图片。


tkinter是python一个内置集成库,Pillow可以使用pip命令来安装:


pip install tkinter Pillow

当安装完成后,我们就可以开始编写程序了。


三、编写代码


说了折磨多,你们是不是非常期待我的代码呀,看代码之前,别忘了先点个关注哟~,接下来,进入代码编写环节


2.1 导入所需的库


首先,我们需要导入所需的库:


import os
import tkinter as tk
from PIL import ImageTk, Image

其中,os库用于读取指定目录下的文件列表;tkinter库用于创建GUI界面;Pillow库用于处理图片。


2.2 创建GUI界面


初始化窗口,并设置窗口的标题,以及窗口的初始大小。


mywindow=tk.Tk()
mywindow.title('今天吃什么')
mywindow.minsize(500,500)

2.3 设置窗口居中显示


我们首先 winfo_screenwidthwinfo_screenheight 函数获取屏幕的尺寸(screenwidth和screenheight),并根据屏幕尺寸计算了窗口的大小和位置。最后使用 geometry 函数 将应用程序窗口设置到屏幕中央。


def getWindowsSize(width, height, screen_width, screen_height):
x = int(screen_width / 2 - width / 2)
y = int(screen_height / 2 - height / 2)
return '{}x{}+{}+{}'.format(width, height, x, y)


screenwidth = mywindow.winfo_screenwidth()
# 得到屏幕宽度
screenheight = mywindow.winfo_screenheight()

print("screenheight:%s,screenheight:%S ",screenheight,screenwidth)

# x tkinter窗口距离屏幕左边距离
mywindow_x = 650
# y tkinter窗口距离屏幕上边距离
mywindow_y = 575
mywindow.geometry(getWindowsSize(mywindow_x,mywindow_y,screenwidth,screenheight))

2.4 设置切换按钮和当前食物的label 以及图片展示 切换方法


photoDefault 用来读取默认首图的图片,imageNameList用来存放 imgs文件下的所有文件路径,nextChoice() 用于当你不满意第一次随机的食物,点击 不满意?换一个 按钮时切换下一个食物。mylab 用来展示图片,eatlab 用来展示食物的中文名,mybut 用于切换。


photoDefault = ImageTk.PhotoImage(Image.open("imgs/今天吃点啥.gif"))

path = 'imgs/'
imagNamelist = os.listdir(path)

def nextChoice():
# 重新排序文件
random.shuffle(imagNamelist)
imagePath=imagNamelist[0]
print('今天吃',imagePath)
finalImg = Image.open("imgs/"+imagePath)
photo = ImageTk.PhotoImage(finalImg.resize((400, 400)))
mylab.config(image = photo)
mylab.image = photo
mybut.config(text='不满意?换一个')
eatlab.config(text=imagePath.split(".")[0])




mylab=tk.Label(master=mywindow,
image= photoDefault
)
mylab.pack()

eatlab=tk.Label(master=mywindow,
text='今天吃点啥?',
fg='white',
bg='green',
font=('微软雅黑',14),
width=20,
height=3
)
eatlab.pack()

mybut=tk.Button(mywindow,
text='点我决定',
font=('微软雅黑',12),
width=15,
height=3,
command=nextChoice
)
mybut.pack()

mywindow.mainloop()

2.5 完整代码


import os
import tkinter as tk
import random
from PIL import Image, ImageTk

mywindow=tk.Tk()
mywindow.title('今天吃什么')
mywindow.minsize(500,500)

def getWindowsSize(width, height, screen_width, screen_height):
x = int(screen_width / 2 - width / 2)
y = int(screen_height / 2 - height / 2)
return '{}x{}+{}+{}'.format(width, height, x, y)


screenwidth = mywindow.winfo_screenwidth()
# 得到屏幕宽度
screenheight = mywindow.winfo_screenheight()

print("screenheight:%s,screenheight:%S ",screenheight,screenwidth)

# x tkinter窗口距离屏幕左边距离
# mywindow_x = mywindow.winfo_x()
# # y tkinter窗口距离屏幕上边距离
# mywindow_y = mywindow.winfo_y()

# x tkinter窗口距离屏幕左边距离
mywindow_x = 650
# y tkinter窗口距离屏幕上边距离
mywindow_y = 575
mywindow.geometry(getWindowsSize(mywindow_x,mywindow_y,screenwidth,screenheight))


photoDefault = ImageTk.PhotoImage(Image.open("imgs/今天吃点啥.gif"))

path = 'imgs/'
imagNamelist = os.listdir(path)

def nextChoice():
# 重新排序文件
random.shuffle(imagNamelist)
imagePath=imagNamelist[0]
print('今天吃',imagePath)
finalImg = Image.open("imgs/"+imagePath)
photo = ImageTk.PhotoImage(finalImg.resize((400, 400)))
mylab.config(image = photo)
mylab.image = photo
mybut.config(text='不满意?换一个')
eatlab.config(text=imagePath.split(".")[0])




mylab=tk.Label(master=mywindow,
image= photoDefault
)
mylab.pack()

eatlab=tk.Label(master=mywindow,
text='今天吃点啥?',
fg='white',
bg='green',
font=('微软雅黑',14),
width=20,
height=3
)
eatlab.pack()

mybut=tk.Button(mywindow,
text='点我决定',
font=('微软雅黑',12),
width=15,
height=3,
command=nextChoice
)
mybut.pack()

mywindow.mainloop()

四、打包exe运行程序,方便在其他windows电脑上使用


4.1 安装pyinstaller打包程序


要想打包成windows下可以运行的 exe 可以使用pip安装如下依赖包,


pip3 install pyinstaller

4.2 执行打包


进入我们的源代码目录,比如我的就是:D:\project\opensource\t-open\python-labs\src\eat , 然后执行如下命令即可, eat2.py 是我们写的python源代码,eatSomething.exe 是我们最后打完包的程序名称。


pyinstaller -F -w eat2.py -n eatSomething.exe

在这里插入图片描述


4.3 移动img文件,运行程序


打包完成后 会在 dist目录生成 eatSomething.exe 应用程序,由于打包的时候我们并没有把 imgs 文件夹的图片拷贝进去,所以我们可以手动把 imgs 全部拷贝到 dist 文件夹下,当然你也可以 把你喜欢的食物照片放到 imgs 文件夹下面。
在这里插入图片描述
在这里插入图片描述
然后我们就可以双击 eatSomething.exe
在这里插入图片描述


祝大家不在纠结今天吃什么,做一个果断 坚毅 的人✌️✌️✌️
如果你喜欢的话,就不要吝惜你的一键三连了~ 🤝🤝🤝
谢谢大家!


这里是文章的表白神器所有代码+图片,对文章不是太懂得小伙伴们可以自取一下哟:今天吃点啥的源代码地址:



  1. 码云地址gitee.com/T-OPEN/pyth…

  2. 源代码和程序下载地址download.csdn.net/download/we…


作者:TOPEN
来源:juejin.cn/post/7231526222634582071
收起阅读 »

python-实现地铁延误告警

在深圳地铁延误、临停n次之后 终于让我不得不又new了一个py文件😭😭 这次主要记录的是一个延误告警的开发过程 一、实现逻辑 使用库:requests,time,zmail,re 实现逻辑: 1、抓取深圳地铁微博的文章 2、判断是否有延误相关的内容 3、判断时...
继续阅读 »

在深圳地铁延误、临停n次之后


终于让我不得不又new了一个py文件😭😭


这次主要记录的是一个延误告警的开发过程


一、实现逻辑


使用库:requests,time,zmail,re


实现逻辑:


1、抓取深圳地铁微博的文章


2、判断是否有延误相关的内容


3、判断时间是否是今天

4、通知方式:邮件


5、定时执行任务


二、抓取深圳地铁微博(一中1~3)



def goout_report():
url ="https://weibo.com/ajax/statuses/mymblog"
# url ="https://weibo.com/szmcservice/statuses/mymblog"
data = {"uid":2311331195,"page":1,"feature":0}
headers={
"accept":"application/json, text/plain, */*",
"accept-encoding":"gzip, deflate, br",
"accept-language":"zh-CN,zh;q=0.9",
"referer":"https://weibo.com/szmcservice?tabtype=feed",
"cookie":"SUB=_2AkMV8LtUf8NxqwJRmf8XzmLgaY9wywjEieKjrEqPJRMxHRl-yT92ql0ctRB6PnCVuU8iqV308mSwZuO-G9gDVwYDBUdc; SUBP=0033WrSXqPxfM72-Ws9jqgMF55529P9D9WFpwsXV4nqgkyH.bEVfx-Xw; login_sid_t=c6bbe5dc58bf01c49b0209c29fadc800; cross_origin_proto=SSL; _s_tentry=passport.weibo.com; Apache=4724569630281.133.1655452763512; SINAGLOBAL=4724569630281.133.1655452763512; ULV=1655452763517:1:1:1:4724569630281.133.1655452763512:; wb_view_log=1920*10801; XSRF-TOKEN=1YMvL3PsAm21Y3udZWs5LeX3; WBPSESS=xvhb-0KtQV-0lVspmRtycws5Su8i9HTZ6dAejg6GXKXDqr8m6IkGO6gdtA5nN5IMNb5JZ1up7qJoFXFyoP2RSQSYXHY1uLzykpOFENQ07VthB0G9WHKwRCMWdaof42zB4mOkdTEeX_N9-m1x6Cpm3pmPsC1YhmTwqH8RGwXmYkI=",
"referer":"https://weibo.com/szmcservice",
"x-requested-with": "XMLHttpRequest",
"x-xsrf-token":"1YMvL3PsAm21Y3udZWs5LeX3",
"sec-ch-ua":'Not A;Brand";v="99", "Chromium";v="102", "Google Chrome";v="102',
"sec-ch-ua-platform":"Windows",
"sec-fetch-dest": "empty",
}
text = requests.get(url,headers=headers,params=data,verify=False).json()['data']['list']
today_date = time.ctime()[:10]
for i in range(1,5):
time_post = text[i]['created_at'][:10]
content = str(text[i]).split("'text': '")[1].split(", 'textLength'")[0]
tp=""
if '延误' in content and time_post == today_date:
# mail(content)
text = re.findall(">(.*?)<|>(.*?)\\",content)
for i in text:
for j in i:
if j!="":


                       tp=tp+j

        mail(tp)
break
else:
continue



三、邮件通知,代码如下


def mail(content):
mail = {
'subject': '别墨迹了!地铁又双叒叕延误啦', #邮件标题
'content_text': content, # 邮件内容
}
server = zmail.server('自己的邮箱', '密码',smtp_host="smtp.qq.com",
smtp_port=465) #此处用的qq邮箱、授权码
server.send_mail('收件人邮箱', mail)

ps:需去QQ邮箱网页版-设置-账户-开启smtp服务、获取授权码


四、定时执行任务


1、Jenkins比较合适项目的一个定时执行,


可参考如下:


jenkins环境: jenkins环境部署踩坑记


git环境:Mac-git环境搭建


2、windows-计算机管理比较合适脚本的执行,具体步骤如下,




  • windows键+R输入compmgmt.msc可进入计算机管理界面


    图片




  • 点击上图“创建任务”后如图,


    “常规”界面上输入任务名称、选项二,


    这样锁屏也会自动执行脚本


    图片




  • 点击“触发器”-新建进入新建触发器界面


    这个界面可设置任务执行时间、执行频率、任务重复间隔、延迟时间等等


    图片




  • 点击“操作”-新建跳到如图-新建操作界面


    这个界面可在“程序或脚本”输入框设置脚本运行程序,比如python.exe


    在“添加参数”输入框设置需要运行脚本路径(包含脚本名)


    在“起始于”输入框设置脚本执行路径(一般可为脚本目录)


    图片




  • 其他选项卡也可以看看,


    全部填写完可以点击“创建任务”界面上的“确定”按钮,


    然后在列表中找到新建的任务点击可查看,


    图片




  • 实时执行测试的话可以点击上图“运行”按钮


    或者右击任务-运行即可


    任务执行结果如下:




图片


作者:WAF910
来源:juejin.cn/post/7231074060788613175
收起阅读 »

《左手上篮》之弹幕含‘’坤‘’量分析?!

对不起 别骂了别骂了我有错,但是我不认。哈哈哈 本来就是想爬一下最近比较火的国产动漫《左手上篮》,我是一个篮球爱好者 ,也是一个篮球迷,有这种篮球的国漫怎么会放过呢,所以我也想搞点事情分析分析弹幕,其实我有想过一个比较好的题目《左手上篮》--我们的灌篮高手,其...
继续阅读 »

对不起


别骂了别骂了我有错,但是我不认。哈哈哈


本来就是想爬一下最近比较火的国产动漫《左手上篮》,我是一个篮球爱好者 ,也是一个篮球迷,有这种篮球的国漫怎么会放过呢,所以我也想搞点事情分析分析弹幕,其实我有想过一个比较好的题目《左手上篮》--我们的灌篮高手,其实没开始爬之前我一直是这么想的,但是当我真正去爬的时候发现一个这样的弹幕‘‘123,背带裤’’事情就开始变得不一样了,我想正常的一板一眼的做弹幕数据分析是不是太无聊了,所以我决定做弹幕的含坤量分析,这就是我标题的来源。


爬取弹幕


image.png


上面这个就是我的爬虫代码了,其实非常简单,就是一个请求头,一个request函数,然后在爬的视频网站找到你要的json包,给他请求解析下来,然后写到我们的CSV文件中。下面就是我爬到的数据大概有两万条弹幕


image.png


数据处理


接着就是对爬到的数据进行中文分词,把弹幕用jieba分好词,大概有80万条小数据,我做的第一个处理是把他做一个词云图,通过对停用词的不同限定,做了几个版本的词云图,为什么做了几个版本呢,其实是被迫的,本来我早就开始这个项目了,就是在这里被卡了很久,不然早就做完了,主要就是stylecloud这个库不太熟悉,所以一直画不出来,第一个词云图其实是我用fineBI做的,直到今天有空了,所以好好研究了下,终于不报错了谢天谢地。其实画的还是很粗糙,大家将就看吧。


屏幕截图 2023-03-16 140620.png


image.png


image.png


含坤量分析


接着有趣的来了,我们来看一下我们鸡哥在这些弹幕里的含量,首先我们在弹幕中把含坤的弹幕统计出来,words = ["坤", "背带裤", "小黑子", "ikun", "蔡徐坤", "只因", "鸡", "鸡你太美"],这些都是我们的含坤的弹幕类型,我们对弹幕进行筛选,有这些词的我们就把他放到一起去。


image.png


大概有多少呢?如图所示一共236条。接着我们对他进行数据的可视化,我分别做了柱状图和一个饼状图。


image.png


image.png


OK,最后一步含坤量的计算最后的结果是:


0.02906%


怎么算的呢?其实很简单就是用我们筛选出来的词比上我们全部的词。


image.png


结尾


以上内容,如有雷同纯属巧合,如有冒犯就是你对。


作者:HeteroCat
来源:juejin.cn/post/7221521544496873528
收起阅读 »

python | 写一个记仇本

最近背着老婆买了一个switch卡带,这货居然给丈母娘讲,害得我被丈母娘说还小了,不买奶粉买游戏,太气人了,我连夜用python写了个《记仇本》,画个圈圈把她记下来。 本片文章,主要关注于python代码,而html和css将暂时被忽略。 记仇本展示 如题所...
继续阅读 »

最近背着老婆买了一个switch卡带,这货居然给丈母娘讲,害得我被丈母娘说还小了,不买奶粉买游戏,太气人了,我连夜用python写了个《记仇本》,画个圈圈把她记下来。


本片文章,主要关注于python代码,而htmlcss将暂时被忽略。



记仇本展示


如题所述,项目已经写好了,是基于local_storage存储在本地的项目,地址如下:



该项目运行时是基于brython, 你可能想问,为什么不使用原生python来编写网页呢,这个有个误区是,网页是由html代码编写的,而原生python想要操作DOM非常难,所以brython正是为这个来操作的。


初始打开页面,因为没有数据展示,所以只有一个增加按钮。



当我们点击【画个圈圈记下来】按钮后,会刷新为新增页面,例如:



此时,我们只需要输入信息,比如 记老婆的仇,缘由为 买switch游戏透露给丈母娘,还得被骂。



此时点击记仇,就可以存储到页面上了。



此时若点击已原谅,则可以删除该记录。


brython 之 local_storage


你可能细心发现了,哎,关掉了浏览器,下次打开,怎么还会有记录在上面呢,这是因为用了local_storage,那么,什么是local_storage呢?


哎,我们使用的是brython中的local_storage但是,它可不是python定义的哦,而是HTML 5提供的存储数据的API之一,可以在浏览器中保持键值对数据块。


现在来展示使用一下brython存储和删除的操作。


导入库:


from browser.local_storage import storage

存储数据,例如键值信息juejinName存储为pdudo


storage[juejinName] = "pdudo"

查询的话,直接使用storage[变量]就好,若为空,则返回


v = storage[juejinName]

循环所有的key,需要引入window库,再使用for...in来完成


from browser import window
for key in window.localStorage:
print(key)

也可以直接使用for...in来遍历storage


而删除数据呢?只需要像删除字典一下


del storage[juejinName]

storage是不是操作起来和字典非常类似呢?减少了开发者的学习成本。


上述案例,已经放到下面的链接中了:



制作项目


有了上述前置条件后,我们再看该项目,便可以总结为 针对localStorage的增删查,首先当页面加载出来的时候,我们需要先遍历一下localstorage数据,从而映射为一个table,例如:


  for key in window.localStorage:
tr = html.TR()
datas = json.loads(storage[key])

delBtn = html.BUTTON("已原谅")
delBtn.dataset["id"] = datas["id"]
delBtn.className = "confirm-btn"
delBtn.bind("click",delGrudges)

td = html.TD(delBtn+" "+time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(datas["id"]))))
tr <= td

for tdVal in datas["whos"],datas["Text"]:
td = html.TD(tdVal)
tr <= td

tb <= tr

userWindows <= tb

上述代码是遍历localStorage,而后在定义删除按钮,等将其他值组合完毕后,全部加载进table中,而后再页面上显示。


而添加数据呢?


def saveGrudges(ev):
getWhoVal = document["whos"].value
getTextVal = document["textArea"].value

if getWhoVal == "" or getTextVal == "":
return

document["saveBtn"].unbind("click")


ids = int(time.time())
datas = {
"id": ids,
"whos": getWhoVal,
"Text": getTextVal
}

storage[str(ids)] = json.dumps(datas)

上述代码,显示获取inputtextarea框中的值,再判断是否用户没有输入,我们将数据组装为一个字典,而后转换为字符串,再存入localstage中。


还有其他操作,这个可以直接看代码说明,brython很简单的。


总结


这篇文章,是善用localStorage来作为键值对存储,以此来保证打开和关闭浏览器,不会对数据产生影响,整个项目就是围绕这个localStorage增删改查来操作的。


作者:真的不能告诉你我的名字
来源:juejin.cn/post/7222229682027462693
收起阅读 »

看我如何用定值 Cookie 实现反爬

本文分享自华为云社区《我是怎么用一个特殊Cookie,限制住别人的爬虫的》,作者: 梦想橡皮擦 。 Cookie 生成 由于本案例需要用到一个特定的 Cookie ,所以我们需要提前将其生成,你可以直接设置一个固定的字符串,也可以使用 Python 加密模块来...
继续阅读 »

本文分享自华为云社区《我是怎么用一个特殊Cookie,限制住别人的爬虫的》,作者: 梦想橡皮擦 。


Cookie 生成


由于本案例需要用到一个特定的 Cookie ,所以我们需要提前将其生成,你可以直接设置一个固定的字符串,也可以使用 Python 加密模块来加密一段文本,例如本案例加密 梦想橡皮擦


下面是一个示例代码,展示了如何使用 Python 的 hashlib 模块进行加密:


import hashlib

# 要加密的文本
text = "梦想橡皮擦"

# 使用 sha256 算法进行加密
encrypted_text = hashlib.sha256(text.encode()).hexdigest()

print(encrypted_text)

在这个例子中,我们使用了 hashlib 模块中的 sha256 算法对文本进行加密。这个算法生成了一个长度为 64 位的十六进制哈希值,用于表示加密后的文本。



注意,这个算法只能用于加密文本,而不能用于解密。因此,一旦文本被加密,就无法恢复成原来的文本,即不可逆加密/单项加密。



Python Flask 框架生成 Cookie


在 Python 的 Flask 框架中,可以使用 make_response 函数和 set_cookie 方法来生成一个 Cookie。


例如,下面的代码片段展示了如何在 Flask 中设置一个名为 story 的 Cookie,并将它的值设为前文建立的加密串。


from flask import Flask, make_response

app = Flask(__name__)

@app.route('/')
def index():
resp = make_response('Setting a cookie')
encrypted_text = hashlib.sha256(text.encode()).hexdigest()
resp.set_cookie('story', encrypted_text)

return resp

在这个例子中,我们使用 make_response() 函数创建了一个响应对象,然后使用 set_cookie() 方法来设置 cookie。最后,我们将响应对象返回给客户端。


注意,上面的代码仅创建了一个简单的 Cookie,它只有名称和值两个部分。你还可以使用其他可选参数来设置 Cookie 的其他属性,例如过期时间、域名等。


接下来为大家在补充一下 make_response() 相关知识。


Flask make_response 加载模板


在 Flask 中,你可以使用 make_response() 函数和模板系统来生成带有模板的响应。


下面是一个示例代码,展示了如何使用 make_response() 函数加载模板:


from flask import Flask, make_response, render_template

app = Flask(__name__)

@app.route('/')
def index():
# 加载模板并渲染
rendered_template = render_template('index.html', title='梦想橡皮擦')
# 使用 make_response 函数创建响应
resp = make_response(rendered_template)
return resp

在这个例子中,我们首先使用 Flask 的 render_template() 函数加载并渲染了名为 index.html 的模板。然后我们使用 make_response() 函数创建了一个响应对象,并将渲染后的模板作为响应的内容。最后,我们返回了这个响应对象给客户端。



注意,你需要在 Flask 应用的模板目录中存在名为 index.html 的模板文件,才能正常使用上述代码。



然后我们将该视图函数补充完整,代码在 app/routes.py 文件中。


@app.route('/')
@app.route('/index')
def index():
item = {
"msg": "后台传递信息"
}
# 访问首页生成一个 Cookie 值,该值用于访问特定页面
rendered_template = render_template('index.html', title='梦想橡皮擦')
resp = make_response(rendered_template)
text = "梦想橡皮擦"

# 使用 sha256 算法进行加密
encrypted_text = hashlib.sha256(text.encode()).hexdigest()
resp.set_cookie('story', encrypted_text)
return resp

此时当我们访问爬虫训练场首页的时候,就会在 Cookie 中写入一个加密之后的字符串。

通过开发者工具,可以查看到响应头。



最后一步,就是在 Python Flask 框架中判断刚刚的 Cookie 值,如果存在则响应数据,否则返回 403。


Flask 判断指定 cookie 是否存在


在 Python 的 Flask 框架中,你可以使用 request.cookies 属性来判断指定的 Cookie 是否存在。


例如,下面的代码片段展示了如何判断一个名为 story 的 Cookie 是否存在:


from flask import Flask, request

app = Flask(__name__)

@app.route('/')
def index():
if 'story' in request.cookies:
# 如果存在 'story' cookie,则执行相应操作
# ...
else:
# 如果不存在 'story' cookie,则执行相应操作
# ...

将代码补充完整,文件是 app/antispider/index.py


@antispider.route('/cookie_demo')
def cookie_demo():
if 'story' in request.cookies:
# 如果存在 'story' cookie,则执行相应操作
# ...
return render_template("antispider/cookie_demo.html")
else:
return "没有权限", 403

补充知识点


在 Python 的 Flask 框架中,除了使用 set_cookie() 方法设置 cookie 以外,还有其他几种操作 cookie 的方法。


下面是一些常用的操作 cookie 的方法:




  • 设置 cookie 的值:你可以使用 set_cookie() 方法来设置 cookie 的值。例如:


    from flask import Flask, make_response


    app = Flask(name)


    @app.route('/')
    def index():
    resp = make_response('Setting a cookie')
    resp.set_cookie('user', 'xiangpica')
    return resp




  • 获取 cookie 的值:你可以使用 request.cookies字典来获取 cookie 的值。例如:


    from flask import Flask, request


    app = Flask(name)


    @app.route('/')
    def index():
    user = request.cookies.get('user')
    return user




  • 删除 cookie:你可以使用 set_cookie() 方法并将 cookie 的过期时间设为过去的时间来删除 cookie。例如:


    from flask import Flask, make_response


    app = Flask(name)


    @app.route('/')
    def index():
    resp = make_response('Deleting a cookie')
    resp.set_cookie('user', '', expires=0)
    return resp




点击关注,第一时间了解华为云新鲜技术~


作者:华为云开发者联盟
来源:juejin.cn/post/7217665415710244921
收起阅读 »

为什么 Python、Go 和 Rust 都不支持三元运算符?

在编程时,我们经常要作条件判断,并根据条件的结果选择执行不同的语句块。在许多编程语言中,最常见的写法是三元运算符,但是,Python 并不支持三元运算符,无独有偶,两个最热门的新兴语言 Go 和 Rust 也不支持! 为什么 Python 不支持三元运算符呢?...
继续阅读 »

在编程时,我们经常要作条件判断,并根据条件的结果选择执行不同的语句块。在许多编程语言中,最常见的写法是三元运算符,但是,Python 并不支持三元运算符,无独有偶,两个最热门的新兴语言 Go 和 Rust 也不支持!


为什么 Python 不支持三元运算符呢?本文将主要分析 Python 在设计条件选择语法时的过程,科普为什么它会采用现今的与众不同的实现方案,同时,我们也将考察为什么其它语言也要抛弃传统的三元运算符。


在开篇之前,我再声明一下:就像“Python为什么”系列的大部分文章一样,本文关注的仅是一个很小的语法点,但它并不是“茴香豆有几种写法”那种毫无意义的话题。因为,细微之处见真功夫,深入研究语言设计背后的原因、历史和哲学,可以让我们在编程时有更加清晰和自由的思维。


什么是三元运算符?


三元运算符通常指的是“?:”,其语法形式为:condition ? expression1 : expression2,如果 condition 为真,则取 expression1,若不为真,则取 expression2。


语法简化形式“a ? b : c”,可以读成“如果 a 条件成立,则为 b,否则为 c”。


三元运算符是对普通一重 if-else 结构的简化,常用于在一条语句中同时实现条件判断和取值操作。


// 常规 if-else 
if (a > b) {
result = x;
} else {
result = y;
}

// 简化后的写法
result = a > b ? x : y;

采用了这种语法设计的编程语言有很多,比如 C、C#、C++、Java、JavaScript、PHP、Perl、Ruby、Swift 等等。毫无争议,它就是编程语言界的主流设计方案(至今仍是)。


这种语法非常简洁高效,代码的可读性也很强(如果你不是第一次接触的话),深得很多人的喜欢。


但是,它并非毫无缺点。Python 是这种语法设计的最著名的挑战者,接下来,我们将看看为什么 Python 要另辟蹊径。


Python 社区的投票


Python 发布于 1991 年,但在接下来的 15 年里,除了 if-else 语法外,它并不支持三元运算符和其它条件表达式。而且,在 2006 年引入条件表达式前,社区对此进行了漫长而曲折的争论,可以说这是一个设计得很艰难的语法了。


最初,由于时常有人请求添加 if-then-else(三元)表达式,因此在 2003 年 2 月,PEP 308 – Conditional Expressions 被提了出来,目的是让社区选出一个让多数人支持的方案。


PEP-308


很快,除了少部分人希望啥也不做外,社区里出现了好几种方案:


(1)使用标点符号构建的三元运算符


即常规的三元运算符,跟前文介绍的语法一样:


<condition> ? <expression1> : <expression2>

这个方案的呼声挺高,有开发者甚至已提交了实现代码。但是,Guido 给出了两个反对的理由:冒号在 Python 中已经有许多用途(即使它实际上不会产生歧义,因为问号需要匹配冒号);对于不习惯 C 衍生语言的人来说,理解起来很困难。


(2)使用现有和新的关键字构建


引入新的“then”关键字,结合现有的“else”关键字:


<condition> then <expression1> else <expression2>

它的优点是简单明了、不需要括号、不改变现有关键字的语义,不大可能与语句混淆,而且不需要重载冒号。缺点是引入新关键字的实现成本较高。


(3)其它思路


跟上一种方案的思路相似,但没有上述两类方案的支持度高。


(if <condition>: <expression1> else: <expression2>)
<condition> and <expression1> else <expression2>
<expression1> if <condition> else <expression2>
cond(<condition>, <expression1>, <expression2>)

值得一提的是(if <condition>: <expression1> else: <expression2>) ,它是常规 if-else 语法的扁平化,容易理解,但缺点是需要使用圆括号,容易跟生成器表达式混淆,而且需要解释器对冒号做特殊化处理。


另外值得一提的是<expression1> if <condition> else <expression2>,它是 PEP-308 最早版本的推荐方案,但是这种不将条件放在首位的风格让一些人感觉不舒服,而且,当“expression1”很长的时候,很容易就忽略掉它的条件。


当时参与投票的全部设计方案:



总体上,开发者们希望引入某种形式的 if-then-else 表达式,但投票后却没有哪种方案能取得绝对的优势。概括起来,分歧的问题主要有:是否用标点符号、是否复用关键字、是否复用圆括号、是否引入新关键字、是否引入新语法……


由于得票太分散,因此,这个 PEP 在当时被拒绝了。PEP 中写道:“Python 的一个设计原则是在不确定采取哪条路线时,则保持现状。


and-or 用于条件选择的问题


以上的投票事件发生在 2004 年 3 月,但是,在 PEP 被拒绝后,相关话题的讨论并未平息,因为大家总想找一种简洁的方式来替换“if-else“。


时间到了 2005 年 9 月,邮件组中有人提议在 Py3.0 中变更"and"与"or"操作符的逻辑,提议将"and" 和 "or" 运算符简化成始终返回布尔值,而不是返回最后一个被求值的参数。


之所以发起这个提议,原因是他使用了<condition> and <expression1> or <expression2>的方式来实现条件判断与选择。但是这种写法在 Python 中的行为跟有些语言并不一样,使用不严谨的话,可能会酿成 Bug!


看看下面的两个例子,你觉得它们会得到什么结果呢?


a = True and True or "Python猫"

b = True and False or "Python猫"

对于<condition> and <expression1> or <expression2> ,若 condition 为假,则会直接对 expression2 求值并返回结果;若 condition 为真,则先对 expression1 求值,若也为真,则不会继续对 expression2 求值,若 expression1 不为真,则对 expression2 求值。


因此,上述例子得到的 a 是“True”,而 b 会得到“Python猫”。


本系列的《Python 为什么能支持任意的真值判断? 》介绍过 Python 在真值判断的特殊之处,运用到以上结构中,将出现更不易察觉的问题。比如,该邮件的作者就是遇到了“expression1”为复数“0+4i”,这个数的真值判断为 False,因此导致最后返回的不是预期的“expression1”,而是“expression2”!


在没有更好的方案前,“and-or”是比较常见的条件选择写法,PEP-308 也提及了它,也指出了当“expression1”为假的情况,还认为这种方案是丑陋和令人费解的。


这封邮件再次引发了社区对条件选择语法的讨论,大佬们纷纷登场。


以我现在的视角分析,其实就是开发者们不满足于“if-else”的现状,但是当时流行的“and-or”写法并不够好,因此,大家期望 Python 设计出新的规范性语法,来解决这个痛点。


与众不同的条件表达式


在经过 10 天的邮件讨论后,Guido van Rossum 最终决定添加一个条件表达式,语法形式为X if C else Y 。因此,PEP-308 被重开和更新,并很快就在次年的 2.5 版本中实现了。


前文已提到过这个让一些人感觉不舒服的方案了,因为它没有将条件判断逻辑放在最前面。


那么,为什么最后的胜者会是它呢?这是不是最优的设计呢?


不可否认,起到决定性作用的原因是 Guido。由于社区在一年半前投票时没有形成多数意见,因此他行使 BDFL (终身仁慈独裁者)的决策权力,裁定出一个他认为是最佳的方案。


X if C else Y 非常易于理解,可读性高。它延续了“明确优于隐式”的风格,使用了直观口语化的“if-else”,而不是引入可能引起混淆的标点符号,就像 Python 选择“and”和“or”两个单词,而不是“&&”和“||”两个符号,它们有着异曲同工之妙。


虽然调整后的语法顺序让人不太习惯,但其实这样的实现却大有好处。首先,它只需复用“if-else”两个关键字,而不需要引入“then”、“when”和其它语法要素,也不像(if <condition>: <expression1> else: <expression2>) 那样的繁琐。


其次,为了验证X if C else Y 的有效性,Guido 排查了标准库中所有“and-or”组合的写法,发现那些C and X or Y 写法都可以被X if C else Y 替换掉。标准库的情况,证明了这新的语法是可行的。


最后,在 PEP-308 提及的原因外,我还想补充一点。据观察,我发现很多时候我们有一个已初始化的变量,然后需要在出现某个条件时,更新变量的值。在这种情况下,“else”部分可以被省略,非常便捷。


my_str = ""
# 中间存在其它代码逻辑
# 当 condition 为真时,变量会被重新赋值
my_str = "Python猫" if condition

回顾这段历史,我们可以梳理出一条线索:Python 没有设计三元运算符“?:”,主要是因为它不符合 Python 明确直观的设计风格。最后采用X if C else Y 这种设计,主要的意图其实是消除“and-or”写法的隐患,这种设计简明易读,而且还有<expression> if <condition> 简化写法的妙用。


总体而言,Python 设计者非常看重可读性与可维护性,不采用三元运算符而创造条件表达式语法,这是一个经过了开放讨论、谨慎评估与权衡取舍的结果。


Go、Rust 为什么不支持三元运算符?


考察完 Python 的设计原因后,我们再来考察“反派阵营”中两门最热门的语言。


首先是 Go 语言,官网的 FAQ 专门列出了一个问题:“Why does Go not have the ?: operator?”。


Go 语言不支持“?:”运算符,而是推荐使用原生的“if-else”写法。文档的解释很简短,只有一段话:



Go 语言没有 ?: 运算符,因为语言的设计者们经常看到它被用来创建难以理解的复杂表达式。虽然 if-else 形式比较长,但是它无疑更清晰易懂。一个语言只需要一个条件控制流结构



接着是 Rust 语言,它的官方文档中似乎没有任何关于不支持三元运算符的解释。但在查阅资料后,我发现它也有一段特殊的故事,非常有意思:在 2011 年 6 月时,Rust 曾经引入过三元运算符(#565),然而半年后,设计者意识到这个特性是多余的,因此又把它移除了(#1698#4632)!


为什么三元运算符在 Rust 是多余的呢?因为它的 if 语法并不像其它语言是“语句(statement)”,而是一个“表达式(expression)”,这意味着你可以直接将 if 表达式赋值给变量:


// 若条件为真,得到 5,否则 6
let number = if condition { 5 } else { 6 };

这种语法形式足够简单明了,不就是将大家都熟悉的“if-else”直接用于赋值么,太方便了,替换成三元运算符的话,确实有点画蛇添足之感。


另外,Rust 使用花括号划分代码块,因此上例的花括号内可以包含多条表达式,也支持换行,例如这个例子:


let x = 42;
let result = if x > 50 {
println!("x is greater than 50");
x * 2 // 这是一个表达式,将返回的值赋给 result
} else {
println!("x is less than or equal to 50");
x / 2 // 也是一个表达式,将返回的值赋给 result
};

这种用法,Python 是不可能做到的。最关键的区别在于,Rust 的 if 是表达式而不是语句。


这两个概念的区别是:



  • 表达式(expression)通常指的是由变量、常量、运算符等组成的一个可求值的代码片段,它的求值结果可以用到其它表达式或语句中。

  • 语句(statement)通常指的是完成某个任务的单个指令或一组指令,例如赋值语句、条件语句、循环语句等,它没有返回值(或者为空),不能用于赋值操作。


除了 Rust 外,还有一些编程语言中的 if 是表达式而不是语句,例如 Kotlin、Scala、F#、Swift,它们在理论上也不需要使用三元运算符。(题外话:Swift 是个例外,它也有三元运算符。Kotlin 有“?:”运算符,注意两个符号是连在一起的,val result = a ?: b 表示:如果 a 不为 null,则赋值给 result ;否则将 b 赋给 result


由于有这种语言设计层面的区别,因此在面对“是否要支持三元运算符”这个问题时,Rust 和 Python/Go 的思考角度有着天然不同的起点。知道了这种区别后,我们对编程语言会有更明晰地认知。


回到本文的问题:为什么有些编程语言不采用主流的三元运算符语法呢?


不可否认,“?:”确实是一种简洁好用的设计,然而,标点符号的负面影响是过于抽象,可读性并不及“if-else”那样强。另外,不同语言的设计风格与使用习惯,也会导致不同的选择。


Python 在经过一番波折后,最后设计出了与众不同的条件表达式。Go 语言明确表示不支持三元运算符。Rust 先设计后舍去,主要的原因在于 if 表达式的语言基础。


考察完这三个热门语言后,我相信你已收获了一个满意的答案。如果是这样,请点赞支持一下本文吧!


最后,本文出自“Python为什么”系列,全部文章已归档在 Github 上,欢迎 star

作者:豌豆花下猫
来源:juejin.cn/post/7217755581847846967
和提 issue。

收起阅读 »

大喊一声Fuck!代码就能跑了是什么体验?

大喊一声Fuck!代码就能跑了是什么体验? 1 前言 大家好,我是心锁,23届准毕业生。 程序员的世界,最多的不是代码,而是💩山和bug。 近期我在学习过程中,在github找到了这么一个项目,能在我们输错命令之后,大喊一声Fuck即可自动更正命令,据说喊得越...
继续阅读 »

大喊一声Fuck!代码就能跑了是什么体验?


1 前言


大家好,我是心锁,23届准毕业生。


程序员的世界,最多的不是代码,而是💩山和bug。


近期我在学习过程中,在github找到了这么一个项目,能在我们输错命令之后,大喊一声Fuck即可自动更正命令,据说喊得越大声效果越好。


c37237b03e45fed8c2828c6f7abb93b9


2 项目基本介绍


thefuck是一个基于Python编写的项目,它能够自动纠正你在命令行中输入的错误命令。如果你输错了一个命令,只需要在命令行中输入“fuck”,thefuck就会自动纠正你的错误。该项目支持众多的终端和操作系统,包括Linux、macOS和Windows。


43885f5e1f8c7ff2b3392d297c855609


2.1 环境要求



  • python环境(3.4+)


2.2 安装方式


thefuck支持brew安装,非常方便,在macOS和Linux上都可以通过brew安装。


brew install thefuck

也支持通过pip安装,便携性可以说是一流了。


pip3 install thefuck

2.3 配置环境变量


建议将下边的代码配置在环境变量中(.bash_profile.bashrc.zshrc),不要问为什么,问就是有经验。


eval $(thefuck --alias)
eval $(thefuck --alias FUCK)
eval $(thefuck --alias fuck?)
eval $(thefuck --alias fuck?)

接着运行source ~/.bashrc(或其他配置文件,如.zshrc)确认更改立即可用。


3 使用效果


Untitled


03cf7e926946b7d8a3da902841c3c5b1


4 thefuck的工作原理


thefuck的工作原理非常简单。当你输入一个错误的命令时,thefuck会根据你输入的命令和错误提示自动推测你想要输入的正确命令,并将其替换为正确的命令。thefuck能够自动推测正确的命令是因为它内置了大量的规则,这些规则能够帮助thefuck智能地纠正错误的命令。


所以,该项目开放了自定义规则。


4.1 创建自己的规则


如果thefuck内置的规则不能够满足你的需求,你也可以创建自己的规则。thefuck的规则是由普通的Python函数实现的。你可以在~/.config/thefuck/rules目录下创建一个Python脚本,然后在其中定义你的规则函数。


以创建一个名为my_rule的规则为例,具体步骤如下:


4.1.1 创建rule.py文件


~/.config/thefuck/rules目录下创建一个Python脚本,比如my_rules.py


4.1.2 遵循的规则


在自定义脚本中,必须实现以下两个函数,match显然是用来匹配命令是否吻合的函数,而get_new_command则会在match函数返回True时触发。


match(command: Command) -> bool
get_new_command(command: Command) -> str | list[str]

同时可以包含可选函数,side_effect的作用是开启一个副作用,即除了允许原本的命令外,你可以在side_effect做更多操作。


side_effect(old_command: Command, fixed_command: str) -> None

5 yarn_uninstall_to_remove


以创建一个名为yarn_uninstall_to_remove的规则为例,该规则会在我们错误使用yarn uninstall …命令时,自动帮助我们修正成yarn remove … 。具体步骤如下:


5.1 创建yarn_uninstall_to_move.py文件


~/.config/thefuck/rules目录下创建一个Python脚本,yarn_uninstall_to_remove.py


5.2 编写代码


from thefuck.utils import for_app

@for_app('yarn')
def match(command):
return 'uninstall' in command.script

def get_new_command(command):
return command.script.replace('uninstall', 'remove')

priority=1 # 优先级,数字越小优先级越高

5.3 效果


Untitled


6 总结


世界之大,无奇不有。不得不说的是,伴随着AI的逐渐发展,类似这种项目未来一定是优先接入AI者才可以继续发展。


友情提示,喊fuck的时候先设置后双击control打开听写功能,喊完再点击一下control完成输入。


Untitled





作者:源心锁
来源:juejin.cn/post/7213651072145244221
收起阅读 »

30个Python操作小技巧

1、列表推导列表的元素可以在一行中进行方便的循环。numbers = [1, 2, 3, 4, 5, 6, 7, 8]even_numbers = [number for number in numbers if number % 2 == 0]print(e...
继续阅读 »

1、列表推导

列表的元素可以在一行中进行方便的循环。

numbers = [1, 2, 3, 4, 5, 6, 7, 8]
even_numbers = [number for number in numbers if number % 2 == 0]
print(even_numbers)

输出:

 [1,3,5,7]

同时,也可以用在字典上。

dictionary = {'first_num': 1, 'second_num': 2,
             'third_num': 3, 'fourth_num': 4}
oddvalues = {key: value for (key, value) in dictionary.items() if value % 2 != 0}
print(oddvalues)Output: {'first_num': 1, 'third_num': 3}

2、枚举函数

枚举是一个有用的函数,用于迭代对象,如列表、字典或文件。该函数生成一个元组,其中包括通过对象迭代获得的值以及循环计数器(从0的起始位置)。当您希望根据索引编写代码时,循环计数器很方便。

sentence = 'Just do It'
length = len(sentence)
for index, element in enumerate(sentence):
   print('{}: {}'.format(index, element))
    if index == 0:
       print('The first element!')
   elif index == length - 1:
       print('The last element!')

3、通过函数返回多个值

在设计函数时,我们经常希望返回多个值。这里我们将介绍两种典型的方法:

方法一

最简单的方式就是返回一个tuple。

get_student 函数,它根据员工的ID号以元组形式返回员工的名字和姓氏。

# returning a tuple.
def get_student(id_num):
   if id_num == 0:
       return 'Taha', 'Nate'
   elif id_num == 1:
       return 'Jakub', 'Abdal'
   else:
       raise Exception('No Student with this id: {}'.format(id_num))

Student = get_student(0)
print('first_name: {}, last_name: {}'.format(Student[0], Student[1]))

方法二、

返回一个字典类型。因为字典是键、值对,我们可以命名返回的值,这比元组更直观。

# returning a dictionary
def get_data(id_num):
   if id_num == 0:
       return {'first_name': 'Muhammad', 'last_name': 'Taha', 'title': 'Data Scientist', 'department': 'A', 'date_joined': '20200807'}
   elif id_num == 1:
       return {'first_name': 'Ryan', 'last_name': 'Gosling', 'title': 'Data Engineer', 'department': 'B', 'date_joined': '20200809'}
   else:
       raise Exception('No employee with this id: {}'.format(id_num))
employee = get_data(0)
print('first_name: {},nlast_name: {},ntitle: {},ndepartment: {},ndate_joined: {}'.format(
   employee['first_name'], employee['last_name'], employee['title'], employee['department'], employee['date_joined']))

4、像数学一样比较多个数字

如果你有一个值,并希望将其与其他两个值进行比较,则可以使用以下基本数学表达式:1<x<30。

你也许经常使用的是这种

1<x and x<30

在python中,你可以这么使用

x = 5
print(1<x<30)

5、将字符串转换为字符串列表:

当你输入 "[[1, 2, 3],[4, 5, 6]]" 时,你想转换为列表,你可以这么做。

import ast
def string_to_list(string):
return ast.literal_eval(string)
string = "[[1, 2, 3],[4, 5, 6]]"
my_list = string_to_list(string)
print(my_list)

6、对于Else方法

Python 中 esle 特殊的用法。

number_List = [1, 3, 8, 9,1]

for number in number_List:
if number % 2 == 0:
print(number)
break
else:
print("No even numbers!!")

7、在列表中查找n个最大或n个最小的元素

使用 heapq 模块在列表中查找n个最大或n个最小的元素。

import heapq
numbers = [80, 25, 68, 77, 95, 88, 30, 55, 40, 50]
print(heapq.nlargest(5, numbers))
print(heapq.nsmallest(5, numbers))

8、在不循环的情况下重复整个字符串

value = "Taha"
print(value * 5)
print("-" * 21)

9、从列表中查找元素的索引

cities= ['Vienna', 'Amsterdam', 'Paris', 'Berlin']
print(cities.index('Berlin'))

10、在同一行中打印多个元素?

print("Analytics", end="")
print("Vidhya")
print("Analytics", end=" ")
print("Vidhya")
print('Data', 'science', 'blogathon', '12', sep=', ')

输出

AnalyticsVidhya
Analytics Vidhya
Data, science, blogathon, 12

11、把大数字分开以便于阅读

有时,当你试图打印一个大数字时,传递整数真的很混乱,而且很难阅读。然后可以使用下划线,使其易于阅读。

print(5_000_000_000_000)

print(7_543_291_635)

输出:

5000000000000
7543291635

12、反转列表的切片

切片列表时,需要传递最小、最大和步长。要以相反的顺序进行切片,只需传递负步长。让我们来看一个例子:

sentence = "Data science blogathon"
print(sentence[21:0:-1])

输出

nohtagolb ecneics ata

13、 “is” 和 “==” 的区别。

如果要检查两个变量是否指向同一个对象,则需要使用“is”

但是,如果要检查两个变量是否相同,则需要使用“==”。

list1 = [7, 9, 4]
list2 = [7, 9, 4]
print(list1 == list2)
print(list1 is list2)
list3 = list1
print(list3 is list1)

输出

True
False
True

14、在一行代码中合并两个词典。

first_dct = {"London": 1, "Paris": 2}
second_dct = {"Tokyo": 3, "Seol": 4}
merged = {**first_dct, **second_dct}
print(merged)

输出

{‘London’: 1, ‘Paris’: 2, ‘Tokyo’: 3, ‘Seol’: 4}

15、识别字符串是否以特定字母开头

sentence = "Analytics Vidhya"
print(sentence.startswith("b"))
print(sentence.startswith("A"))

16、获得字符的Unicode

print(ord("T"))
print(ord("A"))
print(ord("h"))
print(ord("a"))

17、获取字典的键值对

cities = {'London': 1, 'Paris': 2, 'Tokyo': 3, 'Seol': 4}
for key, value in cities.items():
print(f"Key: {key} and Value: {value}")

18、在列表的特定位置添加值

cities = ["London", "Vienna", "Rome"]
cities.append("Seoul")
print("After append:", cities)
cities.insert(0, "Berlin")
print("After insert:", cities)

输出:

[‘London’, ‘Vienna’, ‘Rome’, ‘Seoul’] After insert: [‘Berlin’, ‘London’, ‘Vienna’, ‘Rome’, ‘Seoul’]

19、Filter() 函数

它通过在其中传递的特定函数过滤特定迭代器,并且返回一个迭代器。

mixed_number = [8, 15, 25, 30,34,67,90,5,12]
filtered_value = filter(lambda x: x > 20, mixed_number)
print(f"Before filter: {mixed_number}")
print(f"After filter: {list(filtered_value)}")

输出:

Before filter: [8, 15, 25, 30, 34, 67, 90, 5, 12]
After filter: [25, 30, 34, 67, 90]

20、创建一个没有参数个数限制的函数

def multiplication(*arguments):
mul = 1
for i in arguments:
mul = mul * i
return mul
print(multiplication(3, 4, 5))
print(multiplication(5, 8, 10, 3))
print(multiplication(8, 6, 15, 20, 5))

输出:

60
1200
72000

21、一次迭代两个或多个列表

capital = ['Vienna', 'Paris', 'Seoul',"Rome"]
countries = ['Austria', 'France', 'South Korea',"Italy"]
for cap, country in zip(capital, countries):
print(f"{cap} is the capital of {country}")

22、检查对象使用的内存大小

import sys
mul = 5*6
print(sys.getsizeof(mul))

23、 Map() 函数

map() 函数用于将特定函数应用于给定迭代器。

values_list = [8, 10, 6, 50]
quotient = map(lambda x: x/2, values_list)
print(f"Before division: {values_list}")
print(f"After division: {list(quotient)}")

24、计算 item 在列表中出现的次数

可以在 list 上调用 count 函数。

cities= ["Amsterdam", "Berlin", "New York", "Seoul", "Tokyo", "Paris", "Paris","Vienna","Paris"]
print("Paris appears", cities.count("Paris"), "times in the list")

25、在元组或列表中查找元素的索引

cities_tuple = ("Berlin", "Paris", 5, "Vienna", 10)
print(cities_tuple.index("Paris"))
cities_list = ['Vienna', 'Paris', 'Seoul',"Amsterdam"]
print(cities_list.index("Amsterdam"))

26、2个 set 进行 join 操作

set1 = {'Vienna', 'Paris', 'Seoul'}
set2 = {"Tokyo", "Rome",'Amsterdam'}
print(set1.union(set2))

27、根据频率对列表的值进行排序

from collections import Counter
count = Counter([7, 6, 5, 6, 8, 6, 6, 6])
print(count)
print("Sort values according their frequency:", count.most_common())

输出:

Counter({6: 5, 7: 1, 5: 1, 8: 1})
Sort values according their frequency: [(6, 5), (7, 1), (5, 1), (8, 1)]

28、从列表中删除重复值

cities_list = ['Vienna', 'Paris', 'Seoul',"Amsterdam","Paris","Amsterdam","Paris"]
cities_list = set(cities_list)
print("After removing the duplicate values from the list:",list(cities_list))

29、找出两个列表之间的差异

cities_list1 = ['Vienna', 'Paris', 'Seoul',"Amsterdam", "Berlin", "London"]
cities_list2 = ['Vienna', 'Paris', 'Seoul',"Amsterdam"]
cities_set1 = set(cities_list1)
cities_set2 = set(cities_list2)
difference = list(cities_set1.symmetric_difference(cities_set2))
print(difference)

30、将两个不同的列表转换为一个字典

number = [1, 2, 3]
cities = ['Vienna', 'Paris', 'Seoul']
result = dict(zip(number, cities))
print(result)

作者:程序员学长
来源:juejin.cn/post/7126728825274105886

收起阅读 »

Python办公软件自动化,5分钟掌握openpyxl操作

今天给大家分享一篇用openpyxl操作Excel的文章。各种数据需要导入Excel?多个Excel要合并?目前,Python处理Excel文件有很多库,openpyxl算是其中功能和性能做的比较好的一个。接下来我将为大家介绍各种Excel操作。打开Excel...
继续阅读 »

今天给大家分享一篇用openpyxl操作Excel的文章。

各种数据需要导入Excel?多个Excel要合并?目前,Python处理Excel文件有很多库,openpyxl算是其中功能和性能做的比较好的一个。接下来我将为大家介绍各种Excel操作。

打开Excel文件

新建一个Excel文件


打开现有Excel文件


打开大文件时,根据需求使用只读或只写模式减少内存消耗。


获取、创建工作表

获取当前活动工作表:


创建新的工作表:


使用工作表名字获取工作表:


获取所有的工作表名称:


保存

保存到流中在网络中使用:



单元格
单元格位置作为工作表的键直接读取:


为单元格赋值:


多个单元格 可以使用切片访问单元格区域:


使用数值格式:


使用公式:


合并单元格时,除左上角单元格外,所有单元格都将从工作表中删除:


行、列
可以单独指定行、列、或者行列的范围:


可以使用Worksheet.iter_rows()方法遍历行:


同样的Worksheet.iter_cols()方法将遍历列:


遍历文件的所有行或列,可以使用Worksheet.rows属性:


Worksheet.columns属性:


使用Worksheet.append()或者迭代使用Worksheet.cell()新增一行数据:


插入操作比较麻烦。可以使用Worksheet.insert_rows()插入一行或几行:


Worksheet.insert_cols()操作类似。Worksheet.delete_rows()Worksheet.delete_cols()用来批量删除行和列。

只读取值
使用Worksheet.values属性遍历工作表中的所有行,但只返回单元格值:


Worksheet.iter_rows()Worksheet.iter_cols()可以设置values_only参数来仅返回单元格的值:



作者:Sinchard | 来源:python中文社区

收起阅读 »

Py大蟒蛇搞机器人

itchat使用教程itchat是一个开源的微信个人号接口,使用python调用微信从未如此简单。使用不到三十行的代码,你就可以完成一个能够处理所有信息的微信机器人。首先,在终端安装一下itchat:#pip是pyth的包管理工具也就是pyth的应用商店专门用...
继续阅读 »

itchat使用教程

itchat是一个开源的微信个人号接口,使用python调用微信从未如此简单。使用不到三十行的代码,你就可以完成一个能够处理所有信息的微信机器人。

首先,在终端安装一下itchat:

#pip是pyth的包管理工具也就是pyth的应用商店专门用来安装和卸载库
pip install itchat

1.登录

  1. login() - 每次运行程序都需要扫二维码

  2. login(hotReload==True) - 下次登录不用再扫二维码

  3. auto_login(loginCallback=登录成功回调函数, exitCallback=退出登录回调函数)

2.退出登录

  1. logout() - 强制退出登录

3.获取好友信息

  1. get_friends(update=True) - 获取所有的好友信息

  2. get_chatrooms() - 获取群组

  3. get_mps() - 获取公众号

  4. get_msg() - 获取消息列表

  5. get_head_img() - 获取个人头像

4.发送消息

send(msg=消息内容, toUserName=用户名)

1).msg的值会因为消息类型不同而不同:

  • 文本消息 - 引号中直接写要发送的文字内容

  • 发送文件 - @fil@文件路径

  • 发送图片 - @img@图片路径

  • 发送视频 - @vid@视频路径

2).toUserName: 发送对象,如果不填就发送给自己

5.接收消息

想要自动接收消息,需要先对不同类型的消息进行注册,如果没有注册,对应类型的消息将不会被接收.

注册的方式如下:

@itchat.msg_register(消息类型,isFriendChat=True, isGroupChat=True,isMpChat=True)

def 函数名(msg):
#接收到对应的消息会自动执行的代码段
  #msg.download(msg['FileName'])   #这个同样是下载文件的方式
  #msg['Text'](msg['FileName'])     #下载文件

1)消息类型:

参数类型Text键值
TEXT文本文本内容(文字消息)
CARD名片推荐人字典(推荐人的名片)
SHARING分享分享名称(分享的音乐或者文章等)
RECORDING语音下载方法
ATTACHMENT附件下载方法
VIDEO小视频下载方法
FRIENDS好友邀请添加好友所需参数
SYSTEM系统消息更新内容的用户或群聊的UserName组成的列表
MAP地图位置文本(位置分享)
NOTE通知通知文本(消息撤回等)
PICTURE图片/表情下载方法

来源:blog.csdn.net/weixin_46014553/article/details/110200748

收起阅读 »

Python-可变和不可变类型

1. 不可变类型不可变类型,内存中的数据不允许被修改(一旦被定义,内存中分配了小格子,就不能再修改内容了):数字类型int,bool,float,complex,long(2,x)字符串str元组tuple2. 可变类型可变类型,内存中的数据可以被修改(可以通...
继续阅读 »

1. 不可变类型

不可变类型,内存中的数据不允许被修改(一旦被定义,内存中分配了小格子,就不能再修改内容了):

  • 数字类型intboolfloatcomplexlong(2,x)

  • 字符串str

  • 元组tuple

2. 可变类型

可变类型,内存中的数据可以被修改(可以通过变量名调用方法来修改列表和字典内部的内容,而内存地址不发生变化):

  • 列表list

  • 字典dict(注:字典中的key只能使用不可变类型的数据)

注:给变量赋新值的时候,只是改变了变量的引用地址,不是修改之前的内容

  1. 可变类型的数据变化,是通过方法来实现的

  2. 如果给一个可变类型的变量,复制了一个新的数据,引用会修改(变量从之前的数据上撕下来,贴到新赋值的数据上)

3. 代码演示

# 新建列表
a = [1, 2, 3]
print("列表a:", a)
print("列表a的地址:", id(a))
print("*"*50)
# 追加元素
a.append(999)
print("列表a:", a)
print("列表a的地址:", id(a))
print("*"*50)
# 移除元素
a.remove(2)
print("列表a:", a)
print("列表a的地址:", id(a))
print("*"*50)
# 清空列表
a.clear()
print("列表a:", a)
print("列表a的地址:", id(a))
print("*"*50)
# 将空列表赋值给变量a
a = []
print("列表a的地址:", id(a))   # 通过输出可以看出地址发生了变化
print("*"*50)
# 新建字典
d = {"name": "xiaoming"}
print("字典d为:", d)
print("字典d的地址:", id(d))
print("*"*50)
# 追加键值对
d["age"] = 18
print("字典d为:", d)
print("字典d的地址:", id(d))
print("*"*50)
# 删除键值对
d.pop("age")
print("字典d为:", d)
print("字典d的地址:", id(d))
print("*"*50)
# 清空所有键值对
d.clear()
print("字典d为:", d)
print("字典d的地址:", id(d))
print("*"*50)
# 对d赋值空字典
d = {}
print("字典d为:", d)
print("字典d的地址:", id(d))
print("*"*50)

4. 运行结果

可变类型(列表和字典)的数据变化,是通过方法(比如append,remove,pop等)来实现的,不会改变地址。而重新赋值后地址会改变。具体运行结果如下图所示:




作者:ZacheryZHANG
来源:juejin.cn/post/7100423532655411213

收起阅读 »

2022年最值得收藏的 25 个 Python 文本处理案例!

目录1提取PDF内容2提取Word内容3提取Web网页内容4读取json数据5读取CSV数据6删除字符串中的标点符号7使用NLTK删除停用词8使用TextBlob更正拼写9使用NLTK和TextBlob的词标记化10使用NLTK提取句子单词或短语的词干列表11...
继续阅读 »

1提取 PDF 内容

  1. # pip install PyPDF2 安装 PyPDF2
  2. import PyPDF2
  3. from PyPDF2 import PdfFileReader
  4.  
  5. # Creating a pdf file object.
  6. pdf = open("test.pdf", "rb")
  7.  
  8. # Creating pdf reader object.
  9. pdf_reader = PyPDF2.PdfFileReader(pdf)
  10.  
  11. # Checking total number of pages in a pdf file.
  12. print("Total number of Pages:", pdf_reader.numPages)
  13.  
  14. # Creating a page object.
  15. page = pdf_reader.getPage(200)
  16.  
  17. # Extract data from a specific page number.
  18. print(page.extractText())
  19.  
  20. # Closing the object.
  21. pdf.close()

2提取 Word 内容

  1. # pip install python-docx 安装 python-docx
  2.  
  3.  
  4. import docx
  5.  
  6.  
  7. def main():
  8.      try:
  9.      doc = docx.Document('test.docx') # Creating word reader object.
  10.      data = ""
  11.      fullText = []
  12.      for para in doc.paragraphs:
  13.          fullText.append(para.text)
  14.          data = '\n'.join(fullText)
  15.  
  16.      print(data)
  17.  
  18.      except IOError:
  19.      print('There was an error opening the file!')
  20.      return
  21.  
  22.  
  23. if __name__ == '__main__':
  24.      main()

3提取 Web 网页内容

  1. # pip install bs4 安装 bs4
  2.  
  3. from urllib.request import Request, urlopen
  4. from bs4 import BeautifulSoup
  5.  
  6. req = Request('http://www.cmegroup.com/trading/products/#sortField=oi&sortAsc=false&venues=3&page=1&cleared=1&group=1',
  7.          headers={'User-Agent': 'Mozilla/5.0'})
  8.  
  9. webpage = urlopen(req).read()
  10.  
  11. # Parsing
  12. soup = BeautifulSoup(webpage, 'html.parser')
  13.  
  14. # Formating the parsed html file
  15. strhtm = soup.prettify()
  16.  
  17. # Print first 500 lines
  18. print(strhtm[:500])
  19.  
  20. # Extract meta tag value
  21. print(soup.title.string)
  22. print(soup.find('meta', attrs={'property':'og:description'}))
  23.  
  24. # Extract anchor tag value
  25. for x in soup.find_all('a'):
  26.      print(x.string)
  27.  
  28. # Extract Paragraph tag value
  29. for x in soup.find_all('p'):
  30.      print(x.text)

4读取 Json 数据

  1. import requests
  2. import json
  3.  
  4. = requests.get("https://support.oneskyapp.com/hc/en-us/article_attachments/202761727/example_2.json")
  5. res = r.json()
  6.  
  7. # Extract specific node content.
  8. print(res['quiz']['sport'])
  9.  
  10. # Dump data as string
  11. data = json.dumps(res)
  12. print(data)

5读取 CSV 数据

  1. import csv
  2.  
  3. with open('test.csv','r') as csv_file:
  4.      reader =csv.reader(csv_file)
  5.      next(reader) # Skip first row
  6.      for row in reader:
  7.      print(row)

6删除字符串中的标点符号

  1. import re
  2. import string
  3.  
  4. data = "Stuning even for the non-gamer: This sound track was beautiful!\
  5. It paints the senery in your mind so well I would recomend\
  6. it even to people who hate vid. game music! I have played the game Chrono \
  7. Cross but out of all of the games I have ever played it has the best music! \
  8. It backs away from crude keyboarding and takes a fresher step with grate\
  9. guitars and soulful orchestras.\
  10. It would impress anyone who cares to listen!"
  11.  
  12. # Methood 1 : Regex
  13. # Remove the special charaters from the read string.
  14. no_specials_string = re.sub('[!#?,.:";]', '', data)
  15. print(no_specials_string)
  16.  
  17.  
  18. # Methood 2 : translate()
  19. # Rake translator object
  20. translator = str.maketrans('', '', string.punctuation)
  21. data = data.translate(translator)
  22. print(data)

7使用 NLTK 删除停用词

  1. from nltk.corpus import stopwords
  2.  
  3.  
  4. data = ['Stuning even for the non-gamer: This sound track was beautiful!\
  5. It paints the senery in your mind so well I would recomend\
  6. it even to people who hate vid. game music! I have played the game Chrono \
  7. Cross but out of all of the games I have ever played it has the best music! \
  8. It backs away from crude keyboarding and takes a fresher step with grate\
  9. guitars and soulful orchestras.\
  10. It would impress anyone who cares to listen!']
  11.  
  12. # Remove stop words
  13. stopwords = set(stopwords.words('english'))
  14.  
  15. output = []
  16. for sentence in data:
  17.      temp_list = []
  18.      for word in sentence.split():
  19.      if word.lower() not in stopwords:
  20.          temp_list.append(word)
  21.      output.append(' '.join(temp_list))
  22.  
  23.  
  24. print(output)

8使用 TextBlob 更正拼写

  1. from textblob import TextBlob
  2.  
  3. data = "Natural language is a cantral part of our day to day life, and it's so antresting to work on any problem related to langages."
  4.  
  5. output = TextBlob(data).correct()
  6. print(output)

9使用 NLTK 和 TextBlob 的词标记化

  1. import nltk
  2. from textblob import TextBlob
  3.  
  4.  
  5. data = "Natural language is a central part of our day to day life, and it's so interesting to work on any problem related to languages."
  6.  
  7. nltk_output = nltk.word_tokenize(data)
  8. textblob_output = TextBlob(data).words
  9.  
  10. print(nltk_output)
  11. print(textblob_output)

Output:

['Natural', 'language', 'is', 'a', 'central', 'part', 'of', 'our', 'day', 'to', 'day', 'life', ',', 'and', 'it', "'s", 'so', 'interesting', 'to', 'work', 'on', 'any', 'problem', 'related', 'to', 'languages', '.']
['Natural', 'language', 'is', 'a', 'central', 'part', 'of', 'our', 'day', 'to', 'day', 'life', 'and', 'it', "'s", 'so', 'interesting', 'to', 'work', 'on', 'any', 'problem', 'related', 'to', 'languages']

10使用 NLTK 提取句子单词或短语的词干列表

  1. from nltk.stem import PorterStemmer
  2.  
  3. st = PorterStemmer()
  4. text = ['Where did he learn to dance like that?',
  5.      'His eyes were dancing with humor.',
  6.      'She shook her head and danced away',
  7.      'Alex was an excellent dancer.']
  8.  
  9. output = []
  10. for sentence in text:
  11.      output.append(" ".join([st.stem(i) for i in sentence.split()]))
  12.  
  13. for item in output:
  14.      print(item)
  15.  
  16. print("-" * 50)
  17. print(st.stem('jumping'), st.stem('jumps'), st.stem('jumped'))

Output:

where did he learn to danc like that?
hi eye were danc with humor.
she shook her head and danc away
alex wa an excel dancer.
--------------------------------------------------
jump jump jump

11使用 NLTK 进行句子或短语词形还原

  1. from nltk.stem import WordNetLemmatizer
  2.  
  3. wnl = WordNetLemmatizer()
  4. text = ['She gripped the armrest as he passed two cars at a time.',
  5.      'Her car was in full view.',
  6.      'A number of cars carried out of state license plates.']
  7.  
  8. output = []
  9. for sentence in text:
  10.      output.append(" ".join([wnl.lemmatize(i) for i in sentence.split()]))
  11.  
  12. for item in output:
  13.      print(item)
  14.  
  15. print("*" * 10)
  16. print(wnl.lemmatize('jumps', 'n'))
  17. print(wnl.lemmatize('jumping', 'v'))
  18. print(wnl.lemmatize('jumped', 'v'))
  19.  
  20. print("*" * 10)
  21. print(wnl.lemmatize('saddest', 'a'))
  22. print(wnl.lemmatize('happiest', 'a'))
  23. print(wnl.lemmatize('easiest', 'a'))

Output:

She gripped the armrest a he passed two car at a time.
Her car wa in full view.
A number of car carried out of state license plates.
**********
jump
jump
jump
**********
sad
happy
easy

12使用 NLTK 从文本文件中查找每个单词的频率

  1. import nltk
  2. from nltk.corpus import webtext
  3. from nltk.probability import FreqDist
  4.  
  5. nltk.download('webtext')
  6. wt_words = webtext.words('testing.txt')
  7. data_analysis = nltk.FreqDist(wt_words)
  8.  
  9. # Let's take the specific words only if their frequency is greater than 3.
  10. filter_words = dict([(m, n) for m, n in data_analysis.items() if len(m) > 3])
  11.  
  12. for key in sorted(filter_words):
  13.      print("%s: %s" % (key, filter_words[key]))
  14.  
  15. data_analysis = nltk.FreqDist(filter_words)
  16.  
  17. data_analysis.plot(25, cumulative=False)

Output:

[nltk_data] Downloading package webtext to
[nltk_data]     C:\Users\amit\AppData\Roaming\nltk_data...
[nltk_data]   Unzipping corpora\webtext.zip.
1989: 1
Accessing: 1
Analysis: 1
Anyone: 1
Chapter: 1
Coding: 1
Data: 1
...

13从语料库中创建词云

  1. import nltk
  2. from nltk.corpus import webtext
  3. from nltk.probability import FreqDist
  4. from wordcloud import WordCloud
  5. import matplotlib.pyplot as plt
  6.  
  7. nltk.download('webtext')
  8. wt_words = webtext.words('testing.txt') # Sample data
  9. data_analysis = nltk.FreqDist(wt_words)
  10.  
  11. filter_words = dict([(m, n) for m, n in data_analysis.items() if len(m) > 3])
  12.  
  13. wcloud = WordCloud().generate_from_frequencies(filter_words)
  14.  
  15. # Plotting the wordcloud
  16. plt.imshow(wcloud, interpolation="bilinear")
  17.  
  18. plt.axis("off")
  19. (-0.5, 399.5, 199.5, -0.5)
  20. plt.show()


收起阅读 »

Python 中的万能之王 Lambda 函数

Python 提供了非常多的库和内置函数。有不同的方法可以执行相同的任务,而在 Python 中,有个万能之王函数:lambda 函数,它可以以不同的方式在任何地方使用。今天将和大家一起研究下这个万能之王!Lambda 函数简介Lambda函数也被称为匿名(没...
继续阅读 »

Python 提供了非常多的库和内置函数。有不同的方法可以执行相同的任务,而在 Python 中,有个万能之王函数:lambda 函数,它可以以不同的方式在任何地方使用。今天将和大家一起研究下这个万能之王!

Lambda 函数简介

Lambda函数也被称为匿名(没有名称)函数,它直接接受参数的数量以及使用该参数执行的条件或操作,该参数以冒号分隔,并返回最终结果。为了在大型代码库上编写代码时执行一项小任务,或者在函数中执行一项小任务,便在正常过程中使用lambda函数。
lambda argument_list:expersion
argument_list是参数列表,它的结构与Python中函数(function)的参数列表是一样的

a,b
a=1,b=2
*args
**kwargs
a,b=1,*args

....

expression是一个关于参数的表达式,表达式中出现的参数需要在argument_list中有定义,并且表达式只能是单行的。

1
None
a+b
sum(a)
1 if a >10 else 0
[i for i in range(10)]
...

普通函数和Lambda函数的区别

  1. 没有名称
    Lambda函数没有名称,而普通操作有一个合适的名称。

  2. Lambda函数没有返回值
    使用def关键字构建的普通函数返回值或序列数据类型,但在Lambda函数中返回一个完整的过程。假设我们想要检查数字是偶数还是奇数,使用lambda函数语法类似于下面的代码片段。

    b = lambda x: "Even" if x%2==0 else "Odd" b(9)

  3. 函数只在一行中
    Lambda函数只在一行中编写和创建,而在普通函数的中使用缩进

  4. 不用于代码重用
    Lambda函数不能用于代码重用,或者不能在任何其他文件中导入这个函数。相反,普通函数用于代码重用,可以在外部文件中使用。

为什么要使用Lambda函数?
一般情况下,我们不使用Lambda函数,而是将其与高阶函数一起使用。高阶函数是一种需要多个函数来完成任务的函数,或者当一个函数返回任何另一个函数时,可以选择使用Lambda函数。

什么是高阶函数?
通过一个例子来理解高阶函数。假设有一个整数列表,必须返回三个输出。

  • 一个列表中所有偶数的和

  • 一个列表中所有奇数的和

  • 一个所有能被三整除的数的和
    首先假设用普通函数来处理这个问题。在这种情况下,将声明三个不同的变量来存储各个任务,并使用一个for循环处理并返回结果三个变量。该方法常规可正常运行。

现在使用Lambda函数来解决这个问题,那么可以用三个不同的Lambda函数来检查一个待检验数是否是偶数,奇数,还是能被三整除,然后在结果中加上一个数。

def return_sum(func, lst):
result = 0
for i in lst:
  #if val satisfies func
  if func(i):
    result = result + i
return result
lst = [11,14,21,56,78,45,29,28]
x = lambda a: a%2 == 0
y = lambda a: a%2 != 0
z = lambda a: a%3 == 0
print(return_sum(x, lst))
print(return_sum(y, lst))
print(return_sum(z, lst))

这里创建了一个高阶函数,其中将Lambda函数作为一个部分传递给普通函数。其实这种类型的代码在互联网上随处可见。然而很多人在使用Python时都会忽略这个函数,或者只是偶尔使用它,但其实这些函数真的非常方便,同时也可以节省更多的代码行。接下来我们一起看看这些高阶函数。

Python内置高阶函数

Map函数

map() 会根据提供的函数对指定序列做映射。

Map函数是一个接受两个参数的函数。第一个参数 function 以参数序列中的每一个元素调用 function 函数,第二个是任何可迭代的序列数据类型。返回包含每次 function 函数返回值的新列表。

map(function, iterable, ...)
Map函数将定义在迭代器对象中的某种类型的操作。假设我们要将数组元素进行平方运算,即将一个数组的每个元素的平方映射到另一个产生所需结果的数组。

arr = [2,4,6,8] 
arr = list(map(lambda x: x*x, arr))
print(arr)

我们可以以不同的方式使用Map函数。假设有一个包含名称、地址等详细信息的字典列表,目标是生成一个包含所有名称的新列表。

students = [
          {"name": "John Doe",
            "father name": "Robert Doe",
            "Address": "123 Hall street"
            },
          {
            "name": "Rahul Garg",
            "father name": "Kamal Garg",
            "Address": "3-Upper-Street corner"
          },
          {
            "name": "Angela Steven",
            "father name": "Jabob steven",
            "Address": "Unknown"
          }
]
print(list(map(lambda student: student['name'], students)))
>>> ['John Doe', 'Rahul Garg', 'Angela Steven']

上述操作通常出现在从数据库或网络抓取获取数据等场景中。

Filter函数

Filter函数根据给定的特定条件过滤掉数据。即在函数中设定过滤条件,迭代元素,保留返回值为True 的元素。Map 函数对每个元素进行操作,而 filter 函数仅输出满足特定要求的元素。

假设有一个水果名称列表,任务是只输出那些名称中包含字符“g”的名称。

fruits = ['mango', 'apple', 'orange', 'cherry', 'grapes'] 
print(list(filter(lambda fruit: 'g' in fruit, fruits)))

filter(function or None, iterable) --> filter object

返回一个迭代器,为那些函数或项为真的可迭代项。如果函数为None,则返回为真的项。

Reduce函数

这个函数比较特别,不是 Python 的内置函数,需要通过from functools import reduce 导入。Reduce 从序列数据结构返回单个输出值,它通过应用一个给定的函数来减少元素。

reduce(function, sequence[, initial]) -> value

将包含两个参数的函数(function)累计应用于序列(sequence)的项,从左到右,从而将序列reduce至单个值。

如果存在initial,则将其放在项目之前的序列,并作为默认值时序列是空的。

假设有一个整数列表,并求得所有元素的总和。且使用reduce函数而不是使用for循环来处理此问题。

from functools import reduce
lst = [2,4,6,8,10]
print(reduce(lambda x, y: x+y, lst))
>>> 30

还可以使用 reduce 函数而不是for循环从列表中找到最大或最小的元素。

lst = [2,4,6,8]
# 找到最大元素
print(reduce(lambda x, y: x if x>y else y, lst))
# 找到最小元素
print(reduce(lambda x, y: x if x<y else y, lst))

高阶函数的替代方法

列表推导式

其实列表推导式只是一个for循环,用于添加新列表中的每一项,以从现有索引或一组元素创建一个新列表。之前使用map、filter和reduce完成的工作也可以使用列表推导式完成。然而,相比于使用Map和filter函数,很多人更喜欢使用列表推导式,也许是因为它更容易应用和记忆。

同样使用列表推导式将数组中每个元素进行平方运算,水果的例子也可以使用列表推导式来解决。

arr = [2,4,6,8]
arr = [i**2 for i in arr]
print(arr)
fruit_result = [fruit for fruit in fruits if 'g' in fruit]
print(fruit_result)

字典推导式

与列表推导式一样,使用字典推导式从现有的字典创建一个新字典。还可以从列表创建字典。

假设有一个整数列表,需要创建一个字典,其中键是列表中的每个元素,值是列表中的每个元素的平方。

lst = [2,4,6,8]
D1 = {item:item**2 for item in lst}
print(D1)
>>> {2: 4, 4: 16, 6: 36, 8: 64}
# 创建一个只包含奇数元素的字典
arr = [1,2,3,4,5,6,7,8]
D2 = {item: item**2 for item in arr if item %2 != 0}
print(D2)
>>> {1: 1, 3: 9, 5: 25, 7: 49}

一个简单应用

如何快速找到多个字典的公共键

方法一

dl = [d1, d2, d3] # d1, d2, d3为字典,目标找到所有字典的公共键
[k for k in dl[0] if all(map(lambda d: k in d, dl[1:]))]

dl = [{1:'life', 2: 'is'}, 
{1:'short', 3: 'i'},
{1: 'use', 4: 'python'}]
[k for k in dl[0] if all(map(lambda d: k in d, dl[1:]))]
# 1

解析

# 列表表达式遍历dl中第一个字典中的键
[k for k in dl[0]]
# [1, 2]

# lambda 匿名函数判断字典中的键,即k值是否在其余字典中
list(map(lambda d: 1 in d, dl[1:]))
# [True, True]
list(map(lambda d: 2 in d, dl[1:]))
#[False, False]

# 列表表达式条件为上述结果([True, True])全为True,则输出对应的k值
#1

方法二

# 利用集合(set)的交集操作
from functools import reduce
# reduce(lambda a, b: a*b, range(1,11)) # 10!
reduce(lambda a, b: a & b, map(dict.keys, dl))

写在最后
目前已经学习了Lambda函数是什么,以及Lambda函数的一些使用方法。随后又一起学习了Python中的高阶函数,以及如何在高阶函数中使用lambda函数。除此之外,还学习了高阶函数的替代方法:在列表推导式和字典推导式中执行之前操作。虽然这些方法看似简单,或者说你之前已经见到过这类方法,但你很可能很少使用它们。你可以尝试在其他更加复杂的函数中使用它们,以便使代码更加简洁。

作者:编程学习网
来源:https://juejin.cn/post/7084062324981497870

收起阅读 »

爬了下知乎神回复,笑死人了~

都说知乎出人才,爬虫爬了下知乎上的回答,整理了80条超级搞笑的神回复,已经笑趴😂1Q: 你随身携带或佩戴最久的那件东西是什么?对你有什么特殊的意义?A: 眼镜,因为瞎2Q: 有哪些东西你以为很贵,但其实很便宜?A: 大学刚毕业的我。3Q: 如何看待「当你买 i...
继续阅读 »
都说知乎出人才,爬虫爬了下知乎上的回答,整理了80条超级搞笑的神回复,已经笑趴😂


1

Q: 你随身携带或佩戴最久的那件东西是什么?对你有什么特殊的意义?

A: 眼镜,因为瞎


2

Q: 有哪些东西你以为很贵,但其实很便宜?

A: 大学刚毕业的我。


3

Q: 如何看待「当你买 iPhone 4 的时候,他买了冰箱」这段话?

A: 这暗示了,在你连iPhone都买不起的时候,他就买了房子。

世界真是不公平呀!


4

Q: 哪些因素会阻止未来粮食产量的增加?

A: 崔永元,,,


5

Q: 为什么程序员不应该会修电脑?

A: 范冰冰需要会修电视机吗?


6

Q: 有哪些主角颜值低、穷、能力弱,配角颜值高、富、能力强的影视或游戏作品?

A: 人生


7

Q: 室友要花 9.6 万元参加一个操盘手培训值得吗?

A: 非常值得! 
一次被骗9万是很少见的体验


8

Q: 深夜食堂在中国的可行性如何?

A: 在我天朝,他们被称为“午夜大排档”


9

Q: 有哪些品牌名字起得失败得让人崩溃?为什么?

A: 海伦凯勒太阳眼镜。


10

Q: 中国程序员是否偏爱「冲锋衣+牛仔裤+运动鞋」的衣着?如果是,为何会形成这样的潮流?

A: 穿那么好看给程序看吗?


11

Q: 你读过的书中,有哪些让你觉得惊艳的开头?

A: abandon


12

Q: 有哪些动漫角色穿着暴露无比,却没有给人「卖肉」的感觉?

A: 海尔兄弟


13

Q: 为什么每次圣斗士出招前都要大喊一下招式?

A: 函数要先声明,才能调用。


14

Q: 体育史上有哪些后果严重的『冤案』?

A: 如果说02年韩国一路杀到四强都不算的话那找不到别的了!


15

Q: 知乎上极力推崇读书的人为什么不把上知乎的时间都用来读书?

A: 独学而无友,则孤陋而寡闻


16

Q: 有哪些时候你发现赚钱如此容易,过后你还会那么觉得么?

A: 刷知乎的时候


17

Q: 董明珠为什么选择自己为格力代言?

A: 估计他们认为老干妈好卖,是因为老干妈把自己印在瓶子上面?


18

Q: 哪些盈利模式令你拍案叫绝?

A: 卖生男秘方,不准全额退钱。


19

Q: 售价上万的马桶,商家却为什么强调节水效果?

A: 因為節水不是為了省錢。


20

Q: 中国的部分学生宿舍是不是反人类的?

A: 在北京四环内650能住一年的地方
除了大学宿舍,哪怕树洞你再找个给我看看啊。


21

Q: 上厕所忘了拉拉链,出来被女同事看到并且笑了,如何优雅的圆场?

A: 国之利器,本不可以示人,诸位一窥门径,何其幸哉。


22

Q: 祈求代码不出 bug 该拜哪个神仙?

A: 拜雍正,专治八阿哥。


23

Q: 颜值真的有那么重要吗?

A: 同样是互联网巨头 李彦宏是老公 而马云只能当爸爸


24

Q: 为什么人常会在黑夜里,变得矫情万分?

A: 要渲染的图像少了,CPU就有空闲来思考人生了。


25

Q: 你第一次跳槽是什么原因?后悔吗?

A: 我上班就是为了钱,他非得和我谈理想,可我的理想是不上班…


26

Q: 员工辞职最主要的原因是什么?

A: 钱少事多离家远,位低权轻责任重。


27

Q: 水平极高的出租车司机能够有多神奇的驾技?

A: 在停车的一瞬间再多跳一块钱。


28

Q: 面试的时候被人问到为什么你没有去清华大学,你该怎么回答?

A: “去了,但保安没让进。


29

Q: 哪一个瞬间你觉得自己应该离职了?

A: 当然是身体原因了:
胃不好,饼太大,吃不下。
腰不好,锅太沉,背不动。


30

Q: 接了阿里 offer 后毁约会被拉黑吗?

A: 得打个电话,让对方拥抱变化。


31

Q: 有哪些事情人们在二次元可以接受,而在三次元则不可接受?

A: 没鼻子。


32

Q: 《新世纪福音战士》 TV 版第 25、26 集为什么大量运用意识流手法?它在试图表达什么?

A:
“我们没钱了。


33

Q: 如何评价《火影忍者》的大结局?

A:
辛苦了,我们知道你编不下去了。


34

Q: 你曾经被哪些书名骗了?

A: 血泪史啊,有本书叫《制服诱惑》!你妹的是动词啊!


35

Q: 你是否曾经被一本书所改变与(或)感动?甚至被改变人生观?

A: 《五年高考 三年模拟》


36

Q: 如何评价 bilibili 永远不对正版新番播放视频贴片广告的声明?

A: 其实我说吧,只要广告也可以发弹幕,就算看两分钟也无所谓……


37

Q: 男生在【日常打扮】中如何才能穿出二次元的感觉,同时显得得体又不怪异?

A: 穿女装就好了。


38

Q: 如何看待「爱狗人士」欲强行带走玉林当地活狗?

A: 作为爱钱人士,我一直不敢随便抱走别人的钱。看了这新闻,我非常羡慕爱狗人士能随便抱走别人的狗。


39

Q: 为何图书馆不能穿拖鞋?

A: 以防翻书舔手指的和看书抠脚丫的打起来。


40

Q: 去厦门鼓浪屿旅游,不能错过什么?

A: 回厦门的船。


41

Q: 现代有哪些不如古代的技术?

A: 蹴鞠


42

Q: 在杭州和喜欢的姑娘在一起有哪些好玩的地方?

A: 南山路橘子水晶酒店、九里云松酒店、湖滨凯悦酒店、西湖四季酒店、灵隐安曼法云酒店。


43

Q: 原始人为什么会画壁画?

A: 说明「装修」这种冲动是基因里的本能。


44

Q: 有哪些地方让你觉得「一定要跟最喜欢的人来这里」?

A: 民政局


45

Q: 有没有一部电影让你在深夜中痛哭?

A: 《肉蒲团》。当时我12岁,半夜偷看被我爸发现,被揍哭。


46

Q: 无神论的各位一般从哪里获得精神力量?

A: deadline


47

Q: IT 界有哪些有意思的短笑话?

A: winrarsetup.rar


48

Q: 为什么科技水平提高了,人却没有更轻松?

A:
因为科技只管吃穿住行,不管贪嗔痴妒。


49

Q: IT 工程师被叫「码农」时是否会不舒服?

A: 我们好歹还是人,产品和设计已经是狗了……


50

Q: 外国也有地域歧视吗?

A: 在上海,一老外给我说,他打心眼里瞧不起北京老外。


51

Q: 什么原因让你一直单身?

A: 我还没找到自己,如何去找另一半?


52

Q: 如果恋爱不牵手,不接吻,不上床,就不是恋爱,爱一个人的表现真的要这些身体接触吗?

A: 当然了,不然你觉得为啥“爱情”和“受精”长那么像。


53

Q: 你会怎么写三行情书?

A: 我们化学了
我们物理了
我们生物了


54

Q: 男朋友别人面前很正经,在我面前很二怎么办?

A: 真爱


55

Q: 你一直暗恋的人突然反过来向你表白是一种什么样子的体验?

A: 还想继续睡,然后努力想怎样才能把梦续上去。


56

Q: 怎么看待女朋友的蓝颜?

A: 蓝颜蓝颜,加点黄色就绿了


57

Q: 你有哪些「异性暗示跟你交往,你却木讷地错过了」的经验?

A: 大一时候一女生坐我自行车,我义正言辞地说:“手放老实点。


58

Q: 拒绝了我的人是以什么心态偶尔来我空间的?

A: 触屏手机就这点不好


59

Q: 女朋友有什么用处?

A: 让你四处躁动的心、鸡鸡和不知道怎么花的钱有个温暖着落。


60

Q: 当我有话不直说的时候,为什么男友总是不明白我的心思?

A: 题主,你猜猜我的回答是什么?

难道要我直接回答你才能明白?


61

Q: 平时开玩笑开习惯了,结果跟女生表白人家以为我是开玩笑呢,怎么办?

A: 她才没误会呢
只是她比较善良


62

Q: 怎样拒绝女生的告白?

A: 对不起,我是个好人。


63

Q: 女朋友痛经,男方该怎么做会显得贴心?

A: 跟老婆说:“来,掐老公小鸡鸡,老公陪你一起疼!


64

Q: 你会在意你的恋人有异性闺蜜吗?

A: 女人永远不会明白男人为什么怀疑她跟别的男人的友谊,因为男人太了解男人了!


65

Q: 如何成为“交际花”?

A: 善解人异
善解人疑
善解人意
善解人衣


66

Q: 怎么反驳“胸小不要说话”?

A: “怎么,怕我揭你短?


67

Q: 如何优雅地拒绝他人的表白?

A: 我知道了,你先回去等消息吧~


68

Q: 在哪里可更高几率遇见高质量男生?

A: 两会的时候可以去逛逛,每个地方牛逼的大叔都在那


69

Q: 为什么那么多人说自己寂寞、孤单、想找个男/女朋友,却还是单身?

A: 因为不仅自己丑,
还嫌别人长得丑。


70

Q: 做备胎是什么感觉?

A: 每一句话都是密码


71

Q: 滚床单来不及卸妆怎么办?

A: 别啊,我要的就是阿凡达


72

Q: 男生坚持每天给女生说晚安,持续一年会怎样?

A: ie天天都问我要不要把它设定为默认浏览器,已经好几年了


73

Q: 前女友分手时给我发「掌上珊瑚怜不得,应教移作上阳花」应该怎么回答?

A: 海誓山盟皆笑话,愿卿怜惜手中瓜。


74

Q: 女生送青蛙玩具给男生,代表什么意思?

A: 我送你個青蛙,你好歹還我點蝌蚪吧


75

Q: 女生的长相重要吗?

A: 重要,一般姑娘撒个娇就能解决的问题我都得靠武力。


76

Q: 你为什么还单身?

A: 因为单身比较专注,单身使人进步~


77

Q: 为什么绝大部分女生拒绝男生表白后都希望做朋友,这是什么心态?

A: 跟你客气一下啊,要不还能说什么,“我们不合适,我们还是做仇人吧”


78

Q: 如何用歌词表白?

A: 总想对你表白,我的心情是多么豪迈。


79

Q: 表白被拒绝时你在想什么?

A: 不愧是我看上的妹子,眼光果然不错~


80

Q: 有个女生帮了我的忙,我想感谢她但不希望她男友误会,我该怎么做?

A: 你可以给她送个锦旗。

作者丨shenzhongqiang
来源丨Python与数据分析(ID:PythonML)

收起阅读 »

写了个自动批改小孩作业的代码(下)

接:写了个自动批改小孩作业的代码(上)2.4 切割图像上帝说要有光,就有了光。于是,当光投过来时,物体的背后就有了影。我们就知道了,有影的地方就有东西,没影的地方是空白。这就是投影。这个简单的道理放在图像切割上也很实用。我们把文字的像素做个投影,这样我们就知道...
继续阅读 »

接:写了个自动批改小孩作业的代码(上)

2.4 切割图像

上帝说要有光,就有了光。

于是,当光投过来时,物体的背后就有了影。

我们就知道了,有影的地方就有东西,没影的地方是空白。


这就是投影。

这个简单的道理放在图像切割上也很实用。

我们把文字的像素做个投影,这样我们就知道某个区间有没有文字,并且知道这个区间文字是否集中。

下面是示意图:


2.4.1 投影大法

最有效的方法,往往都是用循环实现的。

要计算投影,就得一个像素一个像素地数,查看有几个像素,然后记录下这一行有N个像素点。如此循环。


首先导入包:

import numpy as np
import cv2
from PIL import Image, ImageDraw, ImageFont
import PIL
import matplotlib.pyplot as plt
import os
import shutil
from numpy.core.records import array
from numpy.core.shape_base import block
import time

比如说要看垂直方向的投影,代码如下:

# 整幅图片的Y轴投影,传入图片数组,图片经过二值化并反色
def img_y_shadow(img_b):
  ### 计算投影 ###
  (h,w)=img_b.shape
  # 初始化一个跟图像高一样长度的数组,用于记录每一行的黑点个数
  a=[0 for z in range(0,h)]
  # 遍历每一列,记录下这一列包含多少有效像素点
  for i in range(0,h):          
      for j in range(0,w):      
          if img_b[i,j]==255:    
              a[i]+=1  
  return a

最终得到是这样的结构:[0, 79, 67, 50, 50, 50, 109, 137, 145, 136, 125, 117, 123, 124, 134, 71, 62, 68, 104, 102, 83, 14, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, ……38, 44, 56, 106, 97, 83, 0, 0, 0, 0, 0, 0, 0]表示第几行总共有多少个像素点,第1行是0,表示是空白的白纸,第2行有79个像素点。

如果我们想要从视觉呈现出来怎么处理呢?那可以把它立起来拉直画出来。


# 展示图片
def img_show_array(a):
  plt.imshow(a)
  plt.show()
   
# 展示投影图, 输入参数arr是图片的二维数组,direction是x,y轴
def show_shadow(arr, direction = 'x'):

  a_max = max(arr)
  if direction == 'x': # x轴方向的投影
      a_shadow = np.zeros((a_max, len(arr)), dtype=int)
      for i in range(0,len(arr)):
          if arr[i] == 0:
              continue
          for j in range(0, arr[i]):
              a_shadow[j][i] = 255
  elif direction == 'y': # y轴方向的投影
      a_shadow = np.zeros((len(arr),a_max), dtype=int)
      for i in range(0,len(arr)):
          if arr[i] == 0:
              continue
          for j in range(0, arr[i]):
              a_shadow[i][j] = 255

  img_show_array(a_shadow)

我们来试验一下效果:

我们将上面的原图片命名为question.jpg放到代码同级目录。

# 读入图片
img_path = 'question.jpg'
img=cv2.imread(img_path,0)
thresh = 200
# 二值化并且反色
ret,img_b=cv2.threshold(img,thresh,255,cv2.THRESH_BINARY_INV)

二值化并反色后的变化如下所示:


上面的操作很有作用,通过二值化,过滤掉杂色,通过反色将黑白对调,原来白纸区域都是255,现在黑色都是0,更利于计算。

计算投影并展示的代码:

img_y_shadow_a = img_y_shadow(img_b)
show_shadow(img_y_shadow_a, 'y') # 如果要显示投影

下面的图是上面图在Y轴上的投影


从视觉上看,基本上能区分出来哪一行是哪一行。

2.4.2 根据投影找区域

最有效的方法,往往还得用循环来实现。

上面投影那张图,你如何计算哪里到哪里是一行,虽然肉眼可见,但是计算机需要规则和算法。

# 图片获取文字块,传入投影列表,返回标记的数组区域坐标[[左,上,右,下]]
def img2rows(a,w,h):
   
  ### 根据投影切分图块 ###
  inLine = False # 是否已经开始切分
  start = 0 # 某次切分的起始索引
  mark_boxs = []
  for i in range(0,len(a)):        
      if inLine == False and a[i] > 10:
          inLine = True
          start = i
      # 记录这次选中的区域[左,上,右,下],上下就是图片,左右是start到当前
      elif i-start >5 and a[i] < 10 and inLine:
          inLine = False
          if i-start > 10:
              top = max(start-1, 0)
              bottom = min(h, i+1)
              box = [0, top, w, bottom]
              mark_boxs.append(box)
               
  return mark_boxs

通过投影,计算哪些区域在一定范围内是连续的,如果连续了很长时间,我们就认为是同一区域,如果断开了很长一段时间,我们就认为是另一个区域。


通过这项操作,我们就可以获得Y轴上某一行的上下两个边界点的坐标,再结合图片宽度,其实我们也就知道了一行图片的四个顶点的坐标了mark_boxs存下的是[坐,上,右,下]。


如果调用如下代码:

(img_h,img_w)=img.shape
row_mark_boxs = img2rows(img_y_shadow_a,img_w,img_h)
print(row_mark_boxs)

我们获取到的是所有识别出来每行图片的坐标,格式是这样的:[[0, 26, 596, 52], [0, 76, 596, 103], [0, 130, 596, 155], [0, 178, 596, 207], [0, 233, 596, 259], [0, 282, 596, 311], [0, 335, 596, 363], [0, 390, 596, 415]]

2.4.3 根据区域切图片

最有效的方法,最终也得用循环来实现。这也是计算机体现它强大的地方。

# 裁剪图片,img 图片数组, mark_boxs 区域标记
def cut_img(img, mark_boxs):

img_items = [] # 存放裁剪好的图片
for i in range(0,len(mark_boxs)):
img_org = img.copy()
box = mark_boxs[i]
# 裁剪图片
img_item = img_org[box[1]:box[3], box[0]:box[2]]
img_items.append(img_item)
return img_items

这一步骤是拿着方框,从大图上用小刀划下小图,核心代码是img_org[box[1]:box[3], box[0]:box[2]]图片裁剪,参数是数组的[上:下,左:右],获取的数据还是二维的数组。

如果保存下来:

# 保存图片
def save_imgs(dir_name, imgs):

  if os.path.exists(dir_name):
      shutil.rmtree(dir_name)
  if not os.path.exists(dir_name):    
      os.makedirs(dir_name)

  img_paths = []
  for i in range(0,len(imgs)):
      file_path = dir_name+'/part_'+str(i)+'.jpg'
      cv2.imwrite(file_path,imgs[i])
      img_paths.append(file_path)
   
  return img_paths

# 切图并保存
row_imgs = cut_img(img, row_mark_boxs)
imgs = save_imgs('rows', row_imgs) # 如果要保存切图
print(imgs)

图片是下面这样的:


2.4.4 循环可去油腻

还是循环。横着行我们掌握了,那么针对每一行图片,我们竖着切成三块是不是也会了,一个道理。


需要注意的是,横竖是稍微有区别的,下面是上图的x轴投影。


横着的时候,字与字之间本来就是有空隙的,然后块与块也有空隙,这个空隙的度需要掌握好,以便更好地区分出来是字的间距还是算式块的间距。

幸好,有种方法叫膨胀。

膨胀对人来说不积极,但是对于技术来说,不管是膨胀(dilate),还是腐蚀(erode),只要能达到目的,都是好的。

kernel=np.ones((3,3),np.uint8)  # 膨胀核大小
row_img_b=cv2.dilate(img_b,kernel,iterations=6) # 图像膨胀6次

膨胀之后再投影,就很好地区分出了块。


根据投影裁剪之后如下图所示:


同理,不膨胀可截取单个字符。


这样,这是一块区域的字符。

一行的,一页的,通过循环,都可以截取出来。

有了图片,就可以识别了。有了位置,就可以判断识别结果的关系了。

下面提供一些代码,这些代码不全,有些函数你可能找不到,但是思路可以参考,详细的代码可以去我的github去看。

def divImg(img_path, save_file = False):

  img_o=cv2.imread(img_path,1)
  # 读入图片
  img=cv2.imread(img_path,0)
  (img_h,img_w)=img.shape
  thresh = 200
  # 二值化整个图,用于分行
  ret,img_b=cv2.threshold(img,thresh,255,cv2.THRESH_BINARY_INV)

  # 计算投影,并截取整个图片的行
  img_y_shadow_a = img_y_shadow(img_b)
  row_mark_boxs = img2rows(img_y_shadow_a,img_w,img_h)
  # 切行的图片,切的是原图
  row_imgs = cut_img(img, row_mark_boxs)
  all_mark_boxs = []
  all_char_imgs = []
  # ===============从行切块======================
  for i in range(0,len(row_imgs)):
      row_img = row_imgs[i]
      (row_img_h,row_img_w)=row_img.shape
      # 二值化一行的图,用于切块
      ret,row_img_b=cv2.threshold(row_img,thresh,255,cv2.THRESH_BINARY_INV)
      kernel=np.ones((3,3),np.uint8)
      #图像膨胀6次
      row_img_b_d=cv2.dilate(row_img_b,kernel,iterations=6)
      img_x_shadow_a = img_x_shadow(row_img_b_d)
      block_mark_boxs = row2blocks(img_x_shadow_a, row_img_w, row_img_h)
      row_char_boxs = []
      row_char_imgs = []
      # 切块的图,切的是原图
      block_imgs = cut_img(row_img, block_mark_boxs)
      if save_file:
          b_imgs = save_imgs('cuts/row_'+str(i), block_imgs) # 如果要保存切图
          print(b_imgs)
      # =============从块切字====================
      for j in range(0,len(block_imgs)):
          block_img = block_imgs[j]
          (block_img_h,block_img_w)=block_img.shape
          # 二值化块,因为要切字符图片了
          ret,block_img_b=cv2.threshold(block_img,thresh,255,cv2.THRESH_BINARY_INV)
          block_img_x_shadow_a = img_x_shadow(block_img_b)
          row_top = row_mark_boxs[i][1]
          block_left = block_mark_boxs[j][0]
          char_mark_boxs,abs_char_mark_boxs = block2chars(block_img_x_shadow_a, block_img_w, block_img_h,row_top,block_left)
          row_char_boxs.append(abs_char_mark_boxs)
          # 切的是二值化的图
          char_imgs = cut_img(block_img_b, char_mark_boxs, True)
          row_char_imgs.append(char_imgs)
          if save_file:
              c_imgs = save_imgs('cuts/row_'+str(i)+'/blocks_'+str(j), char_imgs) # 如果要保存切图
              print(c_imgs)
      all_mark_boxs.append(row_char_boxs)
      all_char_imgs.append(row_char_imgs)


  return all_mark_boxs,all_char_imgs,img_o

最后返回的值是3个,all_mark_boxs是标记的字符位置的坐标集合。[左,上,右,下]是指某个字符在一张大图里的坐标,打印一下是这样的:

[[[[19, 26, 34, 53], [36, 26, 53, 53], [54, 26, 65, 53], [66, 26, 82, 53], [84, 26, 101, 53], [102, 26, 120, 53], [120, 26, 139, 53]], [[213, 26, 229, 53], [231, 26, 248, 53], [249, 26, 268, 53], [268, 26, 285, 53]], [[408, 26, 426, 53], [427, 26, 437, 53], [438, 26, 456, 53], [456, 26, 474, 53], [475, 26, 492, 53]]], [[[20, 76, 36, 102], [38, 76, 48, 102], [50, 76, 66, 102], [67, 76, 85, 102], [85, 76, 104, 102]], [[214, 76, 233, 102], [233, 76, 250, 102], [252, 76, 268, 102], [270, 76, 287, 102]], [[411, 76, 426, 102], [428, 76, 445, 102], [446, 76, 457, 102], [458, 76, 474, 102], [476, 76, 493, 102], [495, 76, 511, 102]]]]

它是有结构的。它的结构是:


all_char_imgs这个返回值,里面是上面坐标结构对应位置的图片。img_o就是原图了。

2.5 识别

循环,循环,还是TM循环!

对于识别,2.3 预测数据已经讲过了,那次是对于2张独立图片的识别,现在我们要对整张大图切分后的小图集合进行识别,这就又用到了循环。

翠花,上代码!

all_mark_boxs,all_char_imgs,img_o = divImg(path,save)
model = cnn.create_model()
model.load_weights('checkpoint/char_checkpoint')
class_name = np.load('class_name.npy')

# 遍历行
for i in range(0,len(all_char_imgs)):
  row_imgs = all_char_imgs[i]
  # 遍历块
  for j in range(0,len(row_imgs)):
      block_imgs = row_imgs[j]
      block_imgs = np.array(block_imgs)
      results = cnn.predict(model, block_imgs, class_name)
      print('recognize result:',results)

上面代码做的就是以块为单位,传递给神经网络进行预测,然后返回识别结果。

针对这张图,我们来进行裁剪和识别。


看底部的最后一行

recognize result: ['1', '0', '12', '2', '10']
recognize result: ['8', '12', '6', '10']
recognize result: ['1', '0', '12', '7', '10']

结果是索引,不是真实的字符,我们根据字典10: '=', 11: '+', 12: '-', 13: '×', 14: '÷'转换过来之后结果是:

recognize result: ['1', '0', '-', '2', '=']
recognize result: ['8', '-', '6', '=']
recognize result: ['1', '0', '-', '7', '=']

和图片是对应的:


2.6 计算并反馈

循环……

我们获取到了10-2=、8-6=2,也获取到了他们在原图的位置坐标[左,上,右,下],那么怎么把结果反馈到原图上呢?

往往到这里就剩最后一步了。

再来温习一遍需求:作对了,能打对号;做错了,能打叉号;没做的,能补上答案。

实现分两步走:计算(是作对做错还是没错)和反馈(把预期结果写到原图上)。

2.6.1 计算 python有个函数很强大,就是eval函数,能计算字符串算式,比如直接计算eval("5+3-2")。

所以,一切都靠它了。

# 计算数值并返回结果  参数chars:['8', '-', '6', '=']
def calculation(chars):
  cstr = ''.join(chars)
  result = ''
  if("=" in cstr): # 有等号
      str_arr = cstr.split('=')
      c_str = str_arr[0]
      r_str = str_arr[1]
      c_str = c_str.replace("×","*")
      c_str = c_str.replace("÷","/")
      try:
          c_r = int(eval(c_str))
      except Exception as e:
          print("Exception",e)

      if r_str == "":
          result = c_r
      else:
          if str(c_r) == str(r_str):
              result = "√"
          else:
              result = "×"

  return result

执行之后获得的结果是:

recognize result: ['8', '×', '4', '=']
calculate result: 32
recognize result: ['2', '-', '1', '=', '1']
calculate result: √
recognize result: ['1', '0', '-', '5', '=']
calculate result: 5

2.6.2 反馈

有了结果之后,把结果写到图片上,这是最后一步,也是最简单的一步。

但是实现起来,居然很繁琐。

得找坐标吧,得计算结果呈现的位置吧,我们还想标记不同的颜色,比如对了是绿色,错了是红色,补齐答案是灰色。

下面代码是在一个图img上,把文本内容text画到(left,top)位置,以特定颜色和大小。

# 绘制文本
def cv2ImgAddText(img, text, left, top, textColor=(255, 0, 0), textSize=20):
  if (isinstance(img, np.ndarray)): # 判断是否OpenCV图片类型
      img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
  # 创建一个可以在给定图像上绘图的对象
  draw = ImageDraw.Draw(img)
  # 字体的格式
  fontStyle = ImageFont.truetype("fonts/fangzheng_shusong.ttf", textSize, encoding="utf-8")
  # 绘制文本
  draw.text((left, top), text, textColor, font=fontStyle)
  # 转换回OpenCV格式
  return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)

结合着切图的信息、计算的信息,下面代码提供思路参考:

# 获取切图标注,切图图片,原图图图片
all_mark_boxs,all_char_imgs,img_o = divImg(path,save)
# 恢复模型,用于图片识别
model = cnn.create_model()
model.load_weights('checkpoint/char_checkpoint')
class_name = np.load('class_name.npy')

# 遍历行
for i in range(0,len(all_char_imgs)):
  row_imgs = all_char_imgs[i]
  # 遍历块
  for j in range(0,len(row_imgs)):
      block_imgs = row_imgs[j]
      block_imgs = np.array(block_imgs)
      # 图片识别
      results = cnn.predict(model, block_imgs, class_name)
      print('recognize result:',results)
      # 计算结果
      result = calculation(results)
      print('calculate result:',result)
      # 获取块的标注坐标
      block_mark = all_mark_boxs[i][j]
      # 获取结果的坐标,写在块的最后一个字
      answer_box = block_mark[-1]
      # 计算最后一个字的位置
      x = answer_box[2]
      y = answer_box[3]
      iw = answer_box[2] - answer_box[0]
      ih = answer_box[3] - answer_box[1]
      # 计算字体大小
      textSize = max(iw,ih)
      # 根据结果设置字体颜色
      if str(result) == "√":
          color = (0, 255, 0)
      elif str(result) == "×":
          color = (255, 0, 0)
      else:
          color = (192, 192,192)
      # 将结果写到原图上
      img_o = cv2ImgAddText(img_o, str(result), answer_box[2], answer_box[1],color, textSize)
# 将写满结果的原图保存
cv2.imwrite('result.jpg', img_o)

结果是下面这样的:


注意

  1. 同级新建fonts文件夹里拷贝一些字体文件,从这里找C:\Windows\Fonts,几十个就行。

  2. get_character_pic.py 生成字体

  3. cnn.py 训练数据

  4. main.py 裁剪指定图片并识别,素材图片新建imgs文件夹,在imgs/question.png下,结果文件保存在imgs/result.png。

  5. 注意如果识别不成功,很可能是question.png的字体你没有训练(这幅图的字体是方正书宋简体,但是你只训练了楷体),这时候可以使用楷体自己编一个算式图。

原文:https://juejin.cn/post/7006732549451939847

收起阅读 »

写了个自动批改小孩作业的代码(上)

最近一些软件的搜题、智能批改类的功能要下线。昨晚我做了一个梦,梦见我实现了这个功能,如下图所示:功能简介:作对了,能打对号;做错了,能打叉号;没做的,能补上答案。二、实现步骤其实,搞定两点就成,第一是能识别数字,第二是能切分数字。前者是图像识别,后者是图像切割...
继续阅读 »

一、亮出效果

最近一些软件的搜题、智能批改类的功能要下线。

退1024步讲,要不要自己做一个自动批改的功能啊?万一哪天孩子要用呢!

昨晚我做了一个梦,梦见我实现了这个功能,如下图所示:


功能简介:作对了,能打对号;做错了,能打叉号;没做的,能补上答案。

醒来后,我环顾四周,赶紧再躺下,希望梦还能接上。

二、实现步骤

基本思路

其实,搞定两点就成,第一是能识别数字,第二是能切分数字。

首先得能认识5是5,这是前提条件,其次是能找到5、6、7、8这些数字区域的位置。

前者是图像识别,后者是图像切割

  • 对于图像识别,一般的套路是下面这样的(CNN卷积神经网络):


  • 对于图像切割,一般的套路是下面的这样(横向纵向投影法):


既然思路能走得通,那么咱们先搞图像识别。准备数据->训练数据并保存模型->使用训练模型预测结果

2.1 准备数据

对于男友,找一个油嘴滑舌的花花公子,不如找一个闷葫芦IT男,亲手把他培养成你期望的样子。

咱们不用什么官方的mnist数据集,因为那是官方的,不是你的,你想要添加±×÷它也没有。

有些通用的数据集,虽然很强大,很方便,但是一旦放到你的场景中,效果一点也不如你的愿。

只有训练自己手里的数据,然后自己用起来才顺手。更重要的是,我们享受创造的过程。

假设,我们只给口算做识别,那么我们需要的图片数据有如下几类:

索引:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
字符:0 1 2 3 4 5 6 7 8 9 = + - × ÷

如果能识别这些,基本上能满足整数的加减乘除运算了。

好了,图片哪里来?!

是啊,图片哪里来?

吓得我差点从梦里醒来,500万都规划好该怎么花了,居然双色球还没有选号!

梦里,一个老者跟我说,图片要自己生成。我问他如何生成,他呵呵一笑,消失在迷雾中……

仔细一想,其实也不难,打字我们总会吧,生成数字无非就是用代码把字写在图片上。

字之所以能展示,主要是因为有字体的支撑。

如果你用的是windows系统,那么打开KaTeX parse error: Undefined control sequence: \Windows at position 3: C:\̲W̲i̲n̲d̲o̲w̲s̲\Fonts这个文件夹,你会发现好多字体。


我们写代码调用这些字体,然后把它打印到一张图片上,是不是就有数据了。

而且这些数据完全是由我们控制的,想多就多,想少就少,想数字、字母、汉字、符号都可以,今天你搞出来数字识别,也就相当于你同时拥有了所有识别!想想还有点小激动呢!

看看,这就是打工和创业的区别。你用别人的数据相当于打工,你是不用操心,但是他给你什么你才有什么。自己造数据就相当于创业,虽然前期辛苦,你可以完全自己把握节奏,需要就加上,没用就去掉。

2.1.1 准备字体

建一个fonts文件夹,从字体库里拷一部分字体放进来,我这里是拷贝了13种字体文件。


好的,准备工作做好了,肯定很累吧,休息休息休息,一会儿再搞!

2.1.2 生成图片

代码如下,可以直接运行。

from __future__ import print_function
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
import os
import shutil
import time

# %% 要生成的文本
label_dict = {0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '=', 11: '+', 12: '-', 13: '×', 14: '÷'}

# 文本对应的文件夹,给每一个分类建一个文件
for value,char in label_dict.items():
  train_images_dir = "dataset"+"/"+str(value)
  if os.path.isdir(train_images_dir):
      shutil.rmtree(train_images_dir)
  os.makedirs(train_images_dir)

# %% 生成图片
def makeImage(label_dict, font_path, width=24, height=24, rotate = 0):

  # 从字典中取出键值对
  for value,char in label_dict.items():
      # 创建一个黑色背景的图片,大小是24*24
      img = Image.new("RGB", (width, height), "black"
      draw = ImageDraw.Draw(img)
      # 加载一种字体,字体大小是图片宽度的90%
      font = ImageFont.truetype(font_path, int(width*0.9))
      # 获取字体的宽高
      font_width, font_height = draw.textsize(char, font)
      # 计算字体绘制的x,y坐标,主要是让文字画在图标中心
      x = (width - font_width-font.getoffset(char)[0]) / 2
      y = (height - font_height-font.getoffset(char)[1]) / 2
      # 绘制图片,在那里画,画啥,什么颜色,什么字体
      draw.text((x,y), char, (255, 255, 255), font)
      # 设置图片倾斜角度
      img = img.rotate(rotate)
      # 命名文件保存,命名规则:dataset/编号/img-编号_r-选择角度_时间戳.png
      time_value = int(round(time.time() * 1000))
      img_path = "dataset/{}/img-{}_r-{}_{}.png".format(value,value,rotate,time_value)
      img.save(img_path)
       
# %% 存放字体的路径
font_dir = "./fonts"
for font_name in os.listdir(font_dir):
  # 把每种字体都取出来,每种字体都生成一批图片
  path_font_file = os.path.join(font_dir, font_name)
  # 倾斜角度从-1010度,每个角度都生成一批图片
  for k in range(-10, 10, 1): 
      # 每个字符都生成图片
      makeImage(label_dict, path_font_file, rotate = k)

上面纯代码不到30行,相信大家应该能看懂!看不懂不是我的读者。

核心代码就是画文字。

draw.text((x,y), char, (255, 255, 255), font)

翻译一下就是:使用某字体在黑底图片的(x,y)位置写白色的char符号。

核心逻辑就是三层循环。


如果代码你运行的没有问题,最终会生成如下结果:



好了,数据准备好了。总共15个文件夹,每个文件夹下对应的各种字体各种倾斜角的字符图片3900个(字符15类×字体13种×角度20个),图片的大小是24×24像素。

有了数据,我们就可以再进行下一步了,下一步是训练使用数据。

2.2 训练数据

2.2.1 构建模型

你先看代码,外行感觉好深奥,内行偷偷地笑。

# %% 导入必要的包 
import tensorflow as tf
import numpy as np
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential
import pathlib
import cv2

# %% 构建模型
def create_model():
  model = Sequential([
      layers.experimental.preprocessing.Rescaling(1./255, input_shape=(24, 24, 1)),
      layers.Conv2D(24,3,activation='relu'),
      layers.MaxPooling2D((2,2)),
      layers.Conv2D(64,3, activation='relu'),
      layers.MaxPooling2D((2,2)),
      layers.Flatten(),
      layers.Dense(128, activation='relu'),
      layers.Dense(15)]
  )
   
  model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

  return model

这个模型的序列是下面这样的,作用是输入一个图片数据,经过各个层揉搓,最终预测出这个图片属于哪个分类。


这么多层都是干什么的,有什么用?和衣服一样,肯定是有用的,内衣、衬衣、毛衣、棉衣各有各的用处。

2.2.2 卷积层 Conv2D

各个职能部门的调查员,搜集和整理某单位区域内的特定数据。我们输入的是一个图像,它是由像素组成的,这就是R e s c a l i n g ( 1. / 255 , i n p u t s h a p e = ( 24 , 24 , 1 ) ) Rescaling(1./255, input_shape=(24, 24, 1))Rescaling(1./255,input shape=(24,24,1))中,input_shape输入形状是24*24像素1个通道(彩色是RGB 3个通道)的图像。


卷积层代码中的定义是Conv2D(24,3),意思是用3*3像素的卷积核,去提取24个特征。

我把图转到地图上来,你就能理解了。以我大济南的市中区为例子。


卷积的作用就相当于从地图的某级单位区域中收集多组特定信息。比如以小区为单位去提取住宅数量、车位数量、学校数量、人口数、年收入、学历、年龄等等24个维度的信息。小区相当于卷积核。

提取完成之后是这样的。


第一次卷积之后,我们从市中区得到N个小区的数据。

卷积是可以进行多次的。

比如在小区卷积之后,我们还可在小区的基础上再来一次卷积,在卷积就是街道了。


通过再次以街道为单位卷积小区,我们就从市中区得到了N个街道的数据。

这就是卷积的作用。

通过一次次卷积,就把一张大图,通过特定的方法卷起来,最终留下来的是固定几组有目的数据,以此方便后续的评选决策。这是评选一个区的数据,要是评选济南市,甚至山东省,也是这么卷积。这和现实生活中评选文明城市、经济强省也是一个道理。

2.2.3 池化层 MaxPooling2D

说白了就是四舍五入。

计算机的计算能力是强大的,比你我快,但也不是不用考虑成本。我们当然希望它越快越好,如果一个方法能省一半的时间,我们肯定愿意用这种方法。

池化层干的就是这个事情。池化的代码定义是这样的M a x P o o l i n g 2 D ( ( 2 , 2 ) ) MaxPooling2D((2,2))MaxPooling2D((2,2)),这里是最大值池化。其中(2,2)是池化层的大小,其实就是在2*2的区域内,我们认为这一片可以合成一个单位。

再以地图举个例子,比如下面的16个格子里的数据,是16个街道的学校数量。


为了进一步提高计算效率,少计算一些数据,我们用2*2的池化层进行池化。


池化的方格是4个街道合成1个,新单位学校数量取成员中学校数量最大(也有取最小,取平均多种池化)的那一个。池化之后,16个格子就变为了4个格子,从而减少了数据。

这就是池化层的作用。

2.2.4 全连接层 Dense

弱水三千,只取一瓢。

在这里,它其实是一个分类器。

我们构建它时,代码是这样的D e n s e ( 15 ) Dense(15)Dense(15)。

它所做的事情,不管你前面是怎么样,有多少维度,到我这里我要强行转化为固定的通道。

比如识别字母a~z,我有500个神经元参与判断,但是最终输出结果就是26个通道(a,b,c,……,y,z)。

我们这里总共有15类字符,所以是15个通道。给定一个输入后,输出为每个分类的概率。


注意:上面都是二维的输入,比如24×24,但是全连接层是一维的,所以代码中使用了l a y e r s . F l a t t e n ( ) layers.Flatten()layers.Flatten()将二维数据拉平为一维数据([[11,12],[21,22]]->[11,12,21,22])。

对于总体的模型,调用m o d e l . s u m m a r y ( ) model.summary()model.summary()打印序列的网络结构如下:

_________________________________________________________________
Layer (type)                 Output Shape             Param #   
=================================================================
rescaling_2 (Rescaling)     (None, 24, 24, 1)         0         
_________________________________________________________________
conv2d_4 (Conv2D)           (None, 22, 22, 24)       240       
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 11, 11, 24)       0         
_________________________________________________________________
conv2d_5 (Conv2D)           (None, 9, 9, 64)         13888     
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 4, 4, 64)         0         
_________________________________________________________________
flatten_2 (Flatten)         (None, 1024)             0         
_________________________________________________________________
dense_4 (Dense)             (None, 128)               131200    
_________________________________________________________________
dense_5 (Dense)             (None, 15)               1935      
=================================================================
Total params: 147,263
Trainable params: 147,263
Non-trainable params: 0
_________________________________________________________________

我们看到conv2d_5 (Conv2D) (None, 9, 9, 64) 经过2*2的池化之后变为max_pooling2d_5 (MaxPooling2 (None, 4, 4, 64)。(None, 4, 4, 64) 再经过F l a t t e n FlattenFlatten拉成一维之后变为(None, 1024),经过全连接变为(None, 128)再一次全连接变为(None, 15),15就是我们的最终分类。这一切都是我们设计的。

m o d e l . c o m p i l e model.compilemodel.compile就是配置模型的几个参数,这个现阶段记住就可以。

2.2.5 训练数据

执行就完了。

# 统计文件夹下的所有图片数量
data_dir = pathlib.Path('dataset')
# 从文件夹下读取图片,生成数据集
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
  data_dir, # 从哪个文件获取数据
  color_mode="grayscale", # 获取数据的颜色为灰度
  image_size=(24, 24), # 图片的大小尺寸
  batch_size=32 # 多少个图片为一个批次
)
# 数据集的分类,对应dataset文件夹下有多少图片分类
class_names = train_ds.class_names
# 保存数据集分类
np.save("class_name.npy", class_names)
# 数据集缓存处理
AUTOTUNE = tf.data.experimental.AUTOTUNE
train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
# 创建模型
model = create_model()
# 训练模型,epochs=10,所有数据集训练10
model.fit(train_ds,epochs=10)
# 保存训练后的权重
model.save_weights('checkpoint/char_checkpoint')

执行之后会输出如下信息:

Found 3900 files belonging to 15 classes. 
Epoch 1/10 122/122 [=========] - 2s 19ms/step - loss: 0.5795 - accuracy: 0.8615 
Epoch 2/10 122/122 [=========] - 2s 18ms/step - loss: 0.0100 - accuracy: 0.9992 
Epoch 3/10 122/122 [=========] - 2s 19ms/step - loss: 0.0027 - accuracy: 1.0000 
Epoch 4/10 122/122 [=========] - 2s 19ms/step - loss: 0.0013 - accuracy: 1.0000 
Epoch 5/10 122/122 [=========] - 2s 20ms/step - loss: 8.4216e-04 - accuracy: 1.0000 
Epoch 6/10 122/122 [=========] - 2s 18ms/step - loss: 5.5273e-04 - accuracy: 1.0000 
Epoch 7/10 122/122 [=========] - 3s 21ms/step - loss: 4.0966e-04 - accuracy: 1.0000 
Epoch 8/10 122/122 [=========] - 2s 20ms/step - loss: 3.0308e-04 - accuracy: 1.0000 
Epoch 9/10 122/122 [=========] - 3s 23ms/step - loss: 2.3446e-04 - accuracy: 1.0000 
Epoch 10/10 122/122 [=========] - 3s 21ms/step - loss: 1.8971e-04 - accuracy: 1.0000

我们看到,第3遍时候,准确率达到100%了。最后结束的时候,我们发现文件夹checkpoint下多了几个文件:

char_checkpoint.data-00000-of-00001
char_checkpoint.index
checkpoint

上面那几个文件是训练结果,训练保存之后就不用动了。后面可以直接用这些数据进行预测。

2.3 预测数据

终于到了享受成果的时候了。

# 设置待识别的图片
img1=cv2.imread('img1.png',0
img2=cv2.imread('img2.png',0
imgs = np.array([img1,img2])
# 构建模型
model = create_model()
# 加载前期训练好的权重
model.load_weights('checkpoint/char_checkpoint')
# 读出图片分类
class_name = np.load('class_name.npy')
# 预测图片,获取预测值
predicts = model.predict(imgs) 
results = [] # 保存结果的数组
for predict in predicts: #遍历每一个预测结果
  index = np.argmax(predict) # 寻找最大值
  result = class_name[index] # 取出字符
  results.append(result)
print(results)

我们找两张图片img1.png,img2.png,一张是数字6,一张是数字8,两张图放到代码同级目录下,验证一下识别效果如何。

图片要通过cv2.imread('img1.png',0) 转化为二维数组结构,0参数是灰度图片。经过处理后,图片转成的数组是如下所示(24,24)的结构:


我们要同时验证两张图,所以把两张图再组成imgs放到一起,imgs的结构是(2,24,24)。

下面是构建模型,然后加载权重。通过调用predicts = model.predict(imgs)将imgs传递给模型进行预测得出predicts。

predicts的结构是(2,15),数值如下面所示:

[[ 16.134243 -12.10675 -1.1994154 -27.766754 -43.4324 -9.633694 -12.214878 1.6287893 2.562174 3.2222707 13.834648 28.254173 -6.102874 16.76582 7.2586184] [ 5.022571 -8.762314 -6.7466817 -23.494259 -30.170597 2.4392672 -14.676962 5.8255725 8.855118 -2.0998626 6.820853 7.6578817 1.5132296 24.4664 2.4192357]]

意思是有2个预测结果,每一个图片的预测结果有15种可能。

然后根据 index = np.argmax(predict) 找出最大可能的索引。

根据索引找到字符的数值结果是[‘6’, ‘8’]。

下面是数据在内存中的监控:


可见,我们的预测是准确的。

下面,我们将要把图片中数字切割出来,进行识别了。

之前我们准备了数据,训练了数据,并且拿图片进行了识别,识别结果正确。

到目前为止,看来问题不大……没有大问题,有问题也大不了。

下面就是把图片进行切割识别了。

下面这张大图片,怎么把它搞一搞,搞成单个小数字的图片。


续:写了个自动批改小孩作业的代码(下)

原文:https://juejin.cn/post/7006732549451939847

收起阅读 »

研究生写脚本抢HPV九价疫苗:被采取强制措施,后果严重

近日,江西省南昌市公安局网安部门报道了一起涉嫌破坏计算机信息系统罪的案件。嫌疑人刘某被采取刑事强制措施,案件还在进一步办理中。1、贴心男友为爱写代码适用于16-26岁女性的HPV九价疫苗让许多年轻女性十分焦虑,经常出现“一苗难求”的现象,这不仅催生了各式各样的...
继续阅读 »
近日,江西省南昌市公安局网安部门报道了一起涉嫌破坏计算机信息系统罪的案件。嫌疑人刘某被采取刑事强制措施,案件还在进一步办理中。

1、贴心男友为爱写代码
适用于16-26岁女性的HPV九价疫苗让许多年轻女性十分焦虑,经常出现“一苗难求”的现象,这不仅催生了各式各样的黄牛,甚至还有专业技术和团队为此走上违法犯罪的道路。
江西南昌某大学研究生刘某在得知女友因约不上HPV九价疫苗感到烦恼时,决定替女友排忧解难。2021年11月4日,他登录南昌某医院APP,帮女友代抢,没想到女友大半年都没抢到的疫苗,他一次便预约成功!
高兴之余,刘某便发了个小红书进行炫耀。不料引来许多同城网民私聊,询问能不能帮忙代抢,面对高额佣金,小刘在评估自己的编程技术后,决定用“特殊手段”开启发财道路。
他使用编码程序编写了如下代码,并在各大平台发布代抢信息。


2、事情败露
然而没过多久,原本畅通无阻的发财之路却被医院方面觉察到了异常。
医院的工作人员发现,其院九价疫苗预约成功患者大部分都是通过黄牛途径取得挂号的,且其医院系统存在被破坏干扰的痕迹,遂立即前往南昌市公安局网安部门报案。
警方迅速立案侦查,锁定犯罪嫌疑人刘某。经警方工作,2021年12月26日晚,刘某前往大队投案自首,并对其违法行为供认不讳。
目前,刘某因涉嫌破坏计算机信息系统罪已被公安机关依法采取刑事强制措施,案件还在进一步办理中。

3、网友:高端的犯罪只需要最普通的脚本?
1月18日,话题#研究生编代码有偿帮抢HPV九价疫苗#登上微博热搜,阅读讨论数达2.1亿次。有网友认为这就是典型的“知识改变命运,没点学问,连个疫苗都抢不到”。
外行看热闹,内行看门道。不少细心的程序员发现刘某用的代码竟然是vbs!
网友@老李滔滔不绝:一看到findcolor,这是安卓上的点击助手啊,档次未免太低了点,大概率是按键精灵的vbs脚本~
网友@左横有撇:这啥脚本?写个Python不香吗
网友@压电蜂鸣片:如果只是脚本autojs之类的,能算破坏计算机信息系统罪吗?秒杀器也不犯法啊,只是程序帮人点罢了
网友@胖胖的我:Github上就有代抢脚本的开源项目。而且作者是免费分享交流。已经修改了好几版了。怕不是就是照搬过来修改了一下。
网友@哈喽:好家伙,还是VB代码
网友@奋斗啊:但凡学过Python的应该都会整这种APP吧
网友@1米65的高大男子:虽然但是,计算机男朋友真香
你会自己写代码抢东西么?欢迎参与投票~

参考链接:

https://mp.weixin.qq.com/s/Umq6UjeKD0kwgyZgVA28zA

https://weibo.com/5044281310/LbhRgewXz

    整理 | 王晓曼

收起阅读 »

我去!爬虫遇到字体反爬,哭了

今天准备爬取某某点评店铺信息时,遇到了『字体』反爬。比如这样的: 还有这样的: 可以看到这些字体已经被加密(反爬) 竟然遇到这种情况,那辰哥就带大家如何去解决这类反爬(字体反爬类) 01 网页分析在开始分析反爬之前,先简单的介绍一下背景(爬取的网页) 辰...
继续阅读 »

今天准备爬取某某点评店铺信息时,遇到了『字体』反爬。比如这样的:


img

还有这样的:


img

可以看到这些字体已经被加密反爬


竟然遇到这种情况,那辰哥就带大家如何去解决这类反爬(字体反爬类


01 网页分析

在开始分析反爬之前,先简单的介绍一下背景(爬取的网页)


img

辰哥爬取的某某点评的店铺信息。一开始查看网页源码是这样的


img

这种什么也看不到,咱们换另一种方式:通过程序直接把整个网页源代码保存下来


img

获取到的网页源码如下:


img

比如这里看到评论数(4位数)都有对应着一个编号(相同的数字编号相同),应该是对应着网站的字体库


下一步,我们需要找到这个网站的字体库。


02 获取字体库

这里的字体库建议在目标网站里面去获取,因为不同的网站的字体库是不一样,导致解码还原的字体也会不一样。


1、抓包获取字体库


img

在浏览器network里面可以看到一共有三种字体库。(三种字体库各有不同的妙用,后面会有解释


img

把字体库链接复制在浏览器里面打开,就可以把字体库下载到本地。


2、查看字体库


这里使用FontCreator的工具查看字体库。


下载地址:


https://www.high-logic.com/font-editor/fontcreator/download

这里需要注册,邮箱验证才能下载,不过辰哥已经下载了,可以在公众号回复:FC,获取安装包。


安装之后,把刚刚下载的字体库在FontCreator中打开


img

可以看到字体的内容以及对应的编号


比如数字7对应F399数字8对应F572 ,咱们在原网页和源码对比,是否如此???


img

可以看到,真是一模一样对应着解码就可以还原字体。


3、为什么会有三个字体库


img

在查看加密字体的CSS样式时,方式有css内容是这样的


img

字体库1:d35c3812.woff 对应解码class为 shopNum


字体库2:084c9fff.woff 对应解码class为 reviewTag和address


字体库3:73f5e6f3.woff 对应解码class为 tagName


也就是说,字体所属的不同class标签,对应的解密字体库是不一样的,辰哥这里不得不说一句:太鸡贼了


img

咱们这里获取的评论数,clas为shopNum,需要用到字体库d35c3812.woff


03 代码实现解密

1、加载字体库


既然我们已经知道了字体反爬的原理,那么我们就可以开始编程实现解密还原。


加载字体库的Python库包是:fontTools ,安装命令如下:


pip install fontTools

img

将字体库的内容对应关系保存为xml格式


img

code和name是一一对应关系


img

img

可以看到网页源码中的编号后四位对应着字体库的编号。


因此我们可以建立应该字体对应集合


img

建立好映射关系好,到网页源码中去进行替换


img

img

这样我们就成功的将字体反爬处理完毕。后面提取内容大家基本都没问题。


2、完整代码


img

输出结果:


img

可以看到加密的数字全部都还原了。


04 小结

辰哥在本文中主要讲解了如此处理字体反爬问题,并以某某点评为例去实战演示分析。辰哥在文中处理的数字类型,大家可以尝试去试试中文如何解决。


作者:Python研究者
来源:https://juejin.cn/post/6970933428145356831

收起阅读 »

python协程(超详细)

1、迭代1.1 迭代的概念使用for循环遍历取值的过程叫做迭代,比如:使用for循环遍历列表获取值的过程# Python 中的迭代for value in [2, 3, 4]:    print(value)1.2 可迭代对象标准概念:在类...
继续阅读 »



1、迭代

1.1 迭代的概念

使用for循环遍历取值的过程叫做迭代,比如:使用for循环遍历列表获取值的过程

# Python 中的迭代
for value in [2, 3, 4]:
   print(value)

1.2 可迭代对象

标准概念:在类里面定义__iter__方法,并使用该类创建的对象就是可迭代对象

简单记忆:使用for循环遍历取值的对象叫做可迭代对象, 比如:列表、元组、字典、集合、range、字符串

1.3 判断对象是否是可迭代对象

# 元组,列表,字典,字符串,集合,range都是可迭代对象
from collections import Iterable
# 如果解释器提示警告,就是用下面的导入方式
# from collections.abc import Iterable

# 判断对象是否是指定类型
result = isinstance((3, 5), Iterable)
print("元组是否是可迭代对象:", result)

result = isinstance([3, 5], Iterable)
print("列表是否是可迭代对象:", result)

result = isinstance({"name": "张三"}, Iterable)
print("字典是否是可迭代对象:", result)

result = isinstance("hello", Iterable)
print("字符串是否是可迭代对象:", result)

result = isinstance({3, 5}, Iterable)
print("集合是否是可迭代对象:", result)

result = isinstance(range(5), Iterable)
print("range是否是可迭代对象:", result)

result = isinstance(5, Iterable)
print("整数是否是可迭代对象:", result)

# 提示: 以后还根据对象判断是否是其它类型,比如以后可以判断函数里面的参数是否是自己想要的类型
result = isinstance(5, int)
print("整数是否是int类型对象:", result)

class Student(object):
   pass

stu = Student()
result = isinstance(stu, Iterable)

print("stu是否是可迭代对象:", result)

result = isinstance(stu, Student)

print("stu是否是Student类型的对象:", result)

1.4 自定义可迭代对象

在类中实现__iter__方法

自定义可迭代类型代码

from collections import Iterable
# 如果解释器提示警告,就是用下面的导入方式
# from collections.abc import Iterable

# 自定义可迭代对象: 在类里面定义__iter__方法创建的对象就是可迭代对象
class MyList(object):

   def __init__(self):
       self.my_list = list()

   # 添加指定元素
   def append_item(self, item):
       self.my_list.append(item)

   def __iter__(self):
       # 可迭代对象的本质:遍历可迭代对象的时候其实获取的是可迭代对象的迭代器, 然后通过迭代器获取对象中的数据
       pass

my_list = MyList()
my_list.append_item(1)
my_list.append_item(2)
result = isinstance(my_list, Iterable)

print(result)

for value in my_list:
   print(value)

执行结果:

Traceback (most recent call last):
True
 File "/Users/hbin/Desktop/untitled/aa.py", line 24, in <module>
   for value in my_list:
TypeError: iter() returned non-iterator of type 'NoneType'

通过执行结果可以看出来,遍历可迭代对象依次获取数据需要迭代器

总结

在类里面提供一个__iter__创建的对象是可迭代对象,可迭代对象是需要迭代器完成数据迭代的

2、迭代器

2.1 自定义迭代器对象

自定义迭代器对象: 在类里面定义__iter____next__方法创建的对象就是迭代器对象

from collections import Iterable
from collections import Iterator

# 自定义可迭代对象: 在类里面定义__iter__方法创建的对象就是可迭代对象
class MyList(object):

   def __init__(self):
       self.my_list = list()

   # 添加指定元素
   def append_item(self, item):
       self.my_list.append(item)

   def __iter__(self):
       # 可迭代对象的本质:遍历可迭代对象的时候其实获取的是可迭代对象的迭代器, 然后通过迭代器获取对象中的数据
       my_iterator = MyIterator(self.my_list)
       return my_iterator


# 自定义迭代器对象: 在类里面定义__iter__和__next__方法创建的对象就是迭代器对象
class MyIterator(object):

   def __init__(self, my_list):
       self.my_list = my_list

       # 记录当前获取数据的下标
       self.current_index = 0

       # 判断当前对象是否是迭代器
       result = isinstance(self, Iterator)
       print("MyIterator创建的对象是否是迭代器:", result)

   def __iter__(self):
       return self

   # 获取迭代器中下一个值
   def __next__(self):
       if self.current_index < len(self.my_list):
           self.current_index += 1
           return self.my_list[self.current_index - 1]
       else:
           # 数据取完了,需要抛出一个停止迭代的异常
           raise StopIteration


my_list = MyList()
my_list.append_item(1)
my_list.append_item(2)
result = isinstance(my_list, Iterable)

print(result)

for value in my_list:
   print(value)

运行结果:

True
MyIterator创建的对象是否是迭代器: True
1
2

2.2 iter()函数与next()函数

  1. iter函数: 获取可迭代对象的迭代器,会调用可迭代对象身上的__iter__方法

  2. next函数: 获取迭代器中下一个值,会调用迭代器对象身上的__next__方法

# 自定义可迭代对象: 在类里面定义__iter__方法创建的对象就是可迭代对象
class MyList(object):

   def __init__(self):
       self.my_list = list()

   # 添加指定元素
   def append_item(self, item):
       self.my_list.append(item)

   def __iter__(self):
       # 可迭代对象的本质:遍历可迭代对象的时候其实获取的是可迭代对象的迭代器, 然后通过迭代器获取对象中的数据
       my_iterator = MyIterator(self.my_list)
       return my_iterator


# 自定义迭代器对象: 在类里面定义__iter__和__next__方法创建的对象就是迭代器对象
# 迭代器是记录当前数据的位置以便获取下一个位置的值
class MyIterator(object):

   def __init__(self, my_list):
       self.my_list = my_list

       # 记录当前获取数据的下标
       self.current_index = 0

   def __iter__(self):
       return self

   # 获取迭代器中下一个值
   def __next__(self):
       if self.current_index < len(self.my_list):
           self.current_index += 1
           return self.my_list[self.current_index - 1]
       else:
           # 数据取完了,需要抛出一个停止迭代的异常
           raise StopIteration

# 创建了一个自定义的可迭代对象
my_list = MyList()
my_list.append_item(1)
my_list.append_item(2)

# 获取可迭代对象的迭代器
my_iterator = iter(my_list)
print(my_iterator)
# 获取迭代器中下一个值
# value = next(my_iterator)
# print(value)

# 循环通过迭代器获取数据
while True:
   try:
       value = next(my_iterator)
       print(value)
   except StopIteration as e:
       break

2.3 for循环的本质

遍历的是可迭代对象

  • for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。

遍历的是迭代器

  • for item in Iterator 循环的迭代器,不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。

2.4 迭代器的应用场景

我们发现迭代器最核心的功能就是可以通过next()函数的调用来返回下一个数据值。如果每次返回的数据值不是在一个已有的数据集合中读取的,而是通过程序按照一定的规律计算生成的,那么也就意味着可以不用再依赖一个已有的数据集合,也就是说不用再将所有要迭代的数据都一次性缓存下来供后续依次读取,这样可以节省大量的存储(内存)空间。

举个例子,比如,数学中有个著名的斐波拉契数列(Fibonacci),数列中第一个数为0,第二个数为1,其后的每一个数都可由前两个数相加得到:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

现在我们想要通过for...in...循环来遍历迭代斐波那契数列中的前n个数。那么这个斐波那契数列我们就可以用迭代器来实现,每次迭代都通过数学计算来生成下一个数。

class Fibonacci(object):

   def __init__(self, num):
       # num:表示生成多少fibonacci数字
       self.num = num
       # 记录fibonacci前两个值
       self.a = 0
       self.b = 1
       # 记录当前生成数字的索引
       self.current_index = 0

   def __iter__(self):
       return self

   def __next__(self):
       if self.current_index < self.num:
           result = self.a
           self.a, self.b = self.b, self.a + self.b
           self.current_index += 1
           return result
       else:
           raise StopIteration


fib = Fibonacci(5)
# value = next(fib)
# print(value)

for value in fib:
   print(value)

执行结果:

0
1
1
2
3

小结

迭代器的作用就是是记录当前数据的位置以便获取下一个位置的值

3、生成器

3.1 生成器的概念

生成器是一类特殊的迭代器,它不需要再像上面的类一样写__iter__()和__next__()方法了, 使用更加方便,它依然可以使用next函数和for循环取值

3.2 创建生成器方法1

  • 第一种方法很简单,只要把一个列表生成式的 [ ] 改成 ( )

my_list = [i * 2 for i in range(5)]
print(my_list)

# 创建生成器
my_generator = (i * 2 for i in range(5))
print(my_generator)

# next获取生成器下一个值
# value = next(my_generator)
#
# print(value)
for value in my_generator:
   print(value)

执行结果:

[0, 2, 4, 6, 8]
<generator object <genexpr> at 0x101367048>
0
2
4
6
8

3.3 创建生成器方法2

在def函数里面看到有yield关键字那么就是生成器

def fibonacci(num):
   a = 0
   b = 1
   # 记录生成fibonacci数字的下标
   current_index = 0
   print("--11---")
   while current_index < num:
       result = a
       a, b = b, a + b
       current_index += 1
       print("--22---")
       # 代码执行到yield会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行
       yield result
       print("--33---")


fib = fibonacci(5)
value = next(fib)
print(value)
value = next(fib)
print(value)

value = next(fib)
print(value)

# for value in fib:
#     print(value)

在使用生成器实现的方式中,我们将原本在迭代器__next__方法中实现的基本逻辑放到一个函数中来实现,但是将每次迭代返回数值的return换成了yield,此时新定义的函数便不再是函数,而是一个生成器了。

简单来说:只要在def中有yield关键字的 就称为 生成器

3.4 生成器使用return关键字

def fibonacci(num):
a = 0
b = 1
# 记录生成fibonacci数字的下标
current_index = 0
print("--11---")
while current_index < num:
result = a
a, b = b, a + b
current_index += 1
print("--22---")
# 代码执行到yield会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行
yield result
print("--33---")
return "嘻嘻"

fib = fibonacci(5)
value = next(fib)
print(value)
# 提示: 生成器里面使用return关键字语法上没有问题,但是代码执行到return语句会停止迭代,抛出停止迭代异常

# return 和 yield的区别
# yield: 每次启动生成器都会返回一个值,多次启动可以返回多个值,也就是yield可以返回多个值
# return: 只能返回一次值,代码执行到return语句就停止迭代

try:
value = next(fib)
print(value)
except StopIteration as e:
# 获取return的返回值
print(e.value)

提示:

  • 生成器里面使用return关键字语法上没有问题,但是代码执行到return语句会停止迭代,抛出停止迭代异常

3.5 yield和return的对比

  • 使用了yield关键字的函数不再是函数,而是生成器。(使用了yield的函数就是生成器)

  • 代码执行到yield会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行

  • 每次启动生成器都会返回一个值,多次启动可以返回多个值,也就是yield可以返回多个值

  • return只能返回一次值,代码执行到return语句就停止迭代,抛出停止迭代异常

3.6 使用send方法启动生成器并传参

send方法启动生成器的时候可以传参数

def gen():
   i = 0
   while i<5:
       temp = yield i
       print(temp)
       i+=1

执行结果:

In [43]: f = gen()

In [44]: next(f)
Out[44]: 0

In [45]: f.send('haha')
haha
Out[45]: 1

In [46]: next(f)
None
Out[46]: 2

In [47]: f.send('haha')
haha
Out[47]: 3

In [48]:

**注意:如果第一次启动生成器使用send方法,那么参数只能传入None,一般第一次启动生成器使用next函数

小结

  • 生成器创建有两种方式,一般都使用yield关键字方法创建生成器

  • yield特点是代码执行到yield会暂停,把结果返回出去,再次启动生成器在暂停的位置继续往下执行

4、协程

4.1 协程的概念

协程,又称微线程,纤程,也称为用户级线程,在不开辟线程的基础上完成多任务,也就是在单线程的情况下完成多任务,多个任务按照一定顺序交替执行 通俗理解只要在def里面只看到一个yield关键字表示就是协程

协程是也是实现多任务的一种方式

协程yield的代码实现

简单实现协程

import time

def work1():
   while True:
       print("----work1---")
       yield
       time.sleep(0.5)

def work2():
   while True:
       print("----work2---")
       yield
       time.sleep(0.5)

def main():
   w1 = work1()
   w2 = work2()
   while True:
       next(w1)
       next(w2)

if __name__ == "__main__":
   main()

运行结果:

----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
...省略...

小结

协程之间执行任务按照一定顺序交替执行

5、greenlet

5.1 greentlet的介绍

为了更好使用协程来完成多任务,python中的greenlet模块对其封装,从而使得切换任务变的更加简单

使用如下命令安装greenlet模块:

pip3 install greenlet

使用协程完成多任务

import time
import greenlet


# 任务1
def work1():
for i in range(5):
print("work1...")
time.sleep(0.2)
# 切换到协程2里面执行对应的任务
g2.switch()


# 任务2
def work2():
for i in range(5):
print("work2...")
time.sleep(0.2)
# 切换到第一个协程执行对应的任务
g1.switch()


if __name__ == '__main__':
# 创建协程指定对应的任务
g1 = greenlet.greenlet(work1)
g2 = greenlet.greenlet(work2)

# 切换到第一个协程执行对应的任务
g1.switch()

运行效果

work1...
work2...
work1...
work2...
work1...
work2...
work1...
work2...
work1...
work2...

6、gevent

6.1 gevent的介绍

greenlet已经实现了协程,但是这个还要人工切换,这里介绍一个比greenlet更强大而且能够自动切换任务的第三方库,那就是gevent。

gevent内部封装的greenlet,其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO

安装

pip3 install gevent

6.2 gevent的使用

import gevent

def work(n):
   for i in range(n):
       # 获取当前协程
       print(gevent.getcurrent(), i)

g1 = gevent.spawn(work, 5)
g2 = gevent.spawn(work, 5)
g3 = gevent.spawn(work, 5)
g1.join()
g2.join()
g3.join()

运行结果

<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 0
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 0
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 0
<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 1
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 1
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 1
<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 2
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 2
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 2
<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 3
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 3
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 3
<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 4
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 4
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 4

可以看到,3个greenlet是依次运行而不是交替运行

6.3 gevent切换执行

import gevent

def work(n):
   for i in range(n):
       # 获取当前协程
       print(gevent.getcurrent(), i)
       #用来模拟一个耗时操作,注意不是time模块中的sleep
       gevent.sleep(1)

g1 = gevent.spawn(work, 5)
g2 = gevent.spawn(work, 5)
g3 = gevent.spawn(work, 5)
g1.join()
g2.join()
g3.join()

运行结果

<Greenlet at 0x7fa70ffa1c30: f(5)> 0
<Greenlet at 0x7fa70ffa1870: f(5)> 0
<Greenlet at 0x7fa70ffa1eb0: f(5)> 0
<Greenlet at 0x7fa70ffa1c30: f(5)> 1
<Greenlet at 0x7fa70ffa1870: f(5)> 1
<Greenlet at 0x7fa70ffa1eb0: f(5)> 1
<Greenlet at 0x7fa70ffa1c30: f(5)> 2
<Greenlet at 0x7fa70ffa1870: f(5)> 2
<Greenlet at 0x7fa70ffa1eb0: f(5)> 2
<Greenlet at 0x7fa70ffa1c30: f(5)> 3
<Greenlet at 0x7fa70ffa1870: f(5)> 3
<Greenlet at 0x7fa70ffa1eb0: f(5)> 3
<Greenlet at 0x7fa70ffa1c30: f(5)> 4
<Greenlet at 0x7fa70ffa1870: f(5)> 4
<Greenlet at 0x7fa70ffa1eb0: f(5)> 4

6.4 给程序打补丁

import gevent
import time
from gevent import monkey

# 打补丁,让gevent框架识别耗时操作,比如:time.sleep,网络请求延时
monkey.patch_all()


# 任务1
def work1(num):
   for i in range(num):
       print("work1....")
       time.sleep(0.2)
       # gevent.sleep(0.2)

# 任务1
def work2(num):
   for i in range(num):
       print("work2....")
       time.sleep(0.2)
       # gevent.sleep(0.2)



if __name__ == '__main__':
   # 创建协程指定对应的任务
   g1 = gevent.spawn(work1, 3)
   g2 = gevent.spawn(work2, 3)

   # 主线程等待协程执行完成以后程序再退出
   g1.join()
   g2.join()

运行结果

work1....
work2....
work1....
work2....
work1....
work2....

6.5 注意

  • 当前程序是一个死循环并且还能有耗时操作,就不需要加上join方法了,因为程序需要一直运行不会退出

示例代码

import gevent
import time
from gevent import monkey

# 打补丁,让gevent框架识别耗时操作,比如:time.sleep,网络请求延时
monkey.patch_all()


# 任务1
def work1(num):
   for i in range(num):
       print("work1....")
       time.sleep(0.2)
       # gevent.sleep(0.2)

# 任务1
def work2(num):
   for i in range(num):
       print("work2....")
       time.sleep(0.2)
       # gevent.sleep(0.2)



if __name__ == '__main__':
   # 创建协程指定对应的任务
   g1 = gevent.spawn(work1, 3)
   g2 = gevent.spawn(work2, 3)

   while True:
       print("主线程中执行")
       time.sleep(0.5)

执行结果:

主线程中执行work1....work2....work1....work2....work1....work2....主线程中执行主线程中执行主线程中执行..省略..
  • 如果使用的协程过多,如果想启动它们就需要一个一个的去使用join()方法去阻塞主线程,这样代码会过于冗余,可以使用gevent.joinall()方法启动需要使用的协程

    实例代码

 import time
import gevent

def work1():
   for i in range(5):
       print("work1工作了{}".format(i))
       gevent.sleep(1)

def work2():
   for i in range(5):
       print("work2工作了{}".format(i))
       gevent.sleep(1)


if __name__ == '__main__':
   w1 = gevent.spawn(work1)
   w2 = gevent.spawn(work2)
   gevent.joinall([w1, w2])  # 参数可以为list,set或者tuple

7、进程、线程、协程对比

7.1 进程、线程、协程之间的关系

  • 一个进程至少有一个线程,进程里面可以有多个线程

  • 一个线程里面可以有多个协程

关系图.png

7.2 进程、线程、线程的对比

  1. 进程是资源分配的单位

  2. 线程是操作系统调度的单位

  3. 进程切换需要的资源最大,效率很低

  4. 线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)

  5. 协程切换任务资源很小,效率高

  6. 多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发

小结

  • 进程、线程、协程都是可以完成多任务的,可以根据自己实际开发的需要选择使用

  • 由于线程、协程需要的资源很少,所以使用线程和协程的几率最大

  • 开辟协程需要的资源最少

作者:y大壮
来源:https://juejin.cn/post/6971037591952949256

收起阅读 »

女友半夜加班发自拍 python男友用30行代码发现惊天秘密

事情是这样的接到女朋友今晚要加班的电话如下 ↓ ↓ ↓敏感的小哥哥心生疑窦,难道会有原谅帽然后python撸了一段代码 分析照片小哥哥崩溃之余 大呼上当小哥哥将发给自己的照片原图下载下来并使用python写了一个脚本读取到了照片拍摄的详细的地址详细到了具体的街...
继续阅读 »



事情是这样的

正准备下班的python开发小哥哥

接到女朋友今晚要加班的电话

并给他发来一张背景模糊的自拍照

如下 ↓ ↓ ↓

敏感的小哥哥心生疑窦,难道会有原谅帽
然后python撸了一段代码 分析照片

分析下来 emmm
拍摄地址居然在 XXX酒店

小哥哥崩溃之余 大呼上当

python分析照片

小哥哥将发给自己的照片原图下载下来
并使用python写了一个脚本
读取到了照片拍摄的详细的地址
详细到了具体的街道和酒店名称

引入exifread模块

首先安装python的exifread模块,用于照片分析
pip install exifread 安装exfriead模块

PS C:\WINDOWS\system32> pip install exifread
Collecting exifread
Downloading ExifRead-2.3.2-py3-none-any.whl (38 kB)
Installing collected packages: exifread
Successfully installed exifread-2.3.2
PS C:\WINDOWS\system32> pip install json

GPS经纬度信息

其实我们平时拍摄的照片里,隐藏了大量的私密信息
包括 拍摄时间、极其精确 具体的GPS信息。
下面是通过exifread模块,来读取照片内的经纬度信息。

#读取照片的GPS经纬度信息
def find_GPS_image(pic_path):
GPS = {}
date = ''
with open(pic_path, 'rb') as f:
tags = exifread.process_file(f)
for tag, value in tags.items():
#纬度
if re.match('GPS GPSLatitudeRef', tag):
GPS['GPSLatitudeRef'] = str(value)
#经度
elif re.match('GPS GPSLongitudeRef', tag):
GPS['GPSLongitudeRef'] = str(value)
#海拔
elif re.match('GPS GPSAltitudeRef', tag):
GPS['GPSAltitudeRef'] = str(value)
elif re.match('GPS GPSLatitude', tag):
try:
match_result = re.match('\[(\w*),(\w*),(\w.*)/(\w.*)\]', str(value)).groups()
GPS['GPSLatitude'] = int(match_result[0]), int(match_result[1]), int(match_result[2])
except:
deg, min, sec = [x.replace(' ', '') for x in str(value)[1:-1].split(',')]
GPS['GPSLatitude'] = latitude_and_longitude_convert_to_decimal_system(deg, min, sec)
elif re.match('GPS GPSLongitude', tag):
try:
match_result = re.match('\[(\w*),(\w*),(\w.*)/(\w.*)\]', str(value)).groups()
GPS['GPSLongitude'] = int(match_result[0]), int(match_result[1]), int(match_result[2])
except:
deg, min, sec = [x.replace(' ', '') for x in str(value)[1:-1].split(',')]
GPS['GPSLongitude'] = latitude_and_longitude_convert_to_decimal_system(deg, min, sec)
elif re.match('GPS GPSAltitude', tag):
GPS['GPSAltitude'] = str(value)
elif re.match('.*Date.*', tag):
date = str(value)
return {'GPS_information': GPS, 'date_information': date}

百度API将GPS转地址

这里需要使用调用百度API,将GPS经纬度信息转换为具体的地址信息。

这里,你需要一个调用百度API的ak值,这个可以注册一个百度开发者获得,当然,你也可以使用博主的这个ak

调用之后,就可以将拍摄时间、拍摄详细地址都解析出来。

def find_address_from_GPS(GPS):
secret_key = 'zbLsuDDL4CS2U0M4KezOZZbGUY9iWtVf'
if not GPS['GPS_information']:
return '该照片无GPS信息'
#经纬度信息
lat, lng = GPS['GPS_information']['GPSLatitude'], GPS['GPS_information']['GPSLongitude']
baidu_map_api = "http://api.map.baidu.com/geocoder/v2/?ak={0}&callback=renderReverse&location={1},{2}s&output=json&pois=0".format(
secret_key, lat, lng)
response = requests.get(baidu_map_api)
#百度API转换成具体的地址
content = response.text.replace("renderReverse&&renderReverse(", "")[:-1]
print(content)
baidu_map_address = json.loads(content)
#将返回的json信息解析整理出来
formatted_address = baidu_map_address["result"]["formatted_address"]
province = baidu_map_address["result"]["addressComponent"]["province"]
city = baidu_map_address["result"]["addressComponent"]["city"]
district = baidu_map_address["result"]["addressComponent"]["district"]
location = baidu_map_address["result"]["sematic_description"]
return formatted_address,province,city,district,location

if __name__ == '__main__':
GPS_info = find_GPS_image(pic_path='C:/女友自拍.jpg')
address = find_address_from_GPS(GPS=GPS_info)
print("拍摄时间:" + GPS_info.get("date_information"))
print('照片拍摄地址:' + str(address))

老王得到的结果是这样的

照片拍摄地址:('云南省红河哈尼族彝族自治州弥勒县', '云南省', '红河哈尼族彝族自治州', '弥勒县', '湖泉酒店-A座东南128米')

云南弥勒湖泉酒店,这明显不是老王女友工作的地方,老王搜索了一下,这是一家温泉度假酒店。

顿时就明白了

{"status":0,"result":{"location":{"lng":103.41424699999998,"lat":24.410461020097278},
"formatted_address":"云南省红河哈尼族彝族自治州弥勒县",
"business":"",
"addressComponent":{"country":"中国",
"country_code":0,
"country_code_iso":"CHN",
"country_code_iso2":"CN",
"province":"云南省",
"city":"红河哈尼族彝族自治州",
"city_level":2,"district":"弥勒县",
"town":"","town_code":"","adcode":"532526",
"street_number":"",
"direction":"","distance":""},
"sematic_description":"湖泉酒店-A座东南128米",
"cityCode":107}}

拍摄时间:2021:5:03 20:05:32
照片拍摄地址:('云南省红河哈尼族彝族自治州弥勒县', '云南省', '红河哈尼族彝族自治州', '弥勒县', '湖泉酒店-A座东南128米')

完整代码如下

import exifread
import re
import json
import requests
import os

#转换经纬度格式
def latitude_and_longitude_convert_to_decimal_system(*arg):
"""
经纬度转为小数, param arg:
:return: 十进制小数
"""
return float(arg[0]) + ((float(arg[1]) + (float(arg[2].split('/')[0]) / float(arg[2].split('/')[-1]) / 60)) / 60)

#读取照片的GPS经纬度信息
def find_GPS_image(pic_path):
GPS = {}
date = ''
with open(pic_path, 'rb') as f:
tags = exifread.process_file(f)
for tag, value in tags.items():
#纬度
if re.match('GPS GPSLatitudeRef', tag):
GPS['GPSLatitudeRef'] = str(value)
#经度
elif re.match('GPS GPSLongitudeRef', tag):
GPS['GPSLongitudeRef'] = str(value)
#海拔
elif re.match('GPS GPSAltitudeRef', tag):
GPS['GPSAltitudeRef'] = str(value)
elif re.match('GPS GPSLatitude', tag):
try:
match_result = re.match('\[(\w*),(\w*),(\w.*)/(\w.*)\]', str(value)).groups()
GPS['GPSLatitude'] = int(match_result[0]), int(match_result[1]), int(match_result[2])
except:
deg, min, sec = [x.replace(' ', '') for x in str(value)[1:-1].split(',')]
GPS['GPSLatitude'] = latitude_and_longitude_convert_to_decimal_system(deg, min, sec)
elif re.match('GPS GPSLongitude', tag):
try:
match_result = re.match('\[(\w*),(\w*),(\w.*)/(\w.*)\]', str(value)).groups()
GPS['GPSLongitude'] = int(match_result[0]), int(match_result[1]), int(match_result[2])
except:
deg, min, sec = [x.replace(' ', '') for x in str(value)[1:-1].split(',')]
GPS['GPSLongitude'] = latitude_and_longitude_convert_to_decimal_system(deg, min, sec)
elif re.match('GPS GPSAltitude', tag):
GPS['GPSAltitude'] = str(value)
elif re.match('.*Date.*', tag):
date = str(value)
return {'GPS_information': GPS, 'date_information': date}

#通过baidu Map的API将GPS信息转换成地址。
def find_address_from_GPS(GPS):
"""
使用Geocoding API把经纬度坐标转换为结构化地址。
:param GPS:
:return:
"""
secret_key = 'zbLsuDDL4CS2U0M4KezOZZbGUY9iWtVf'
if not GPS['GPS_information']:
return '该照片无GPS信息'
lat, lng = GPS['GPS_information']['GPSLatitude'], GPS['GPS_information']['GPSLongitude']
baidu_map_api = "http://api.map.baidu.com/geocoder/v2/?ak={0}&callback=renderReverse&location={1},{2}s&output=json&pois=0".format(
secret_key, lat, lng)
response = requests.get(baidu_map_api)
content = response.text.replace("renderReverse&&renderReverse(", "")[:-1]
print(content)
baidu_map_address = json.loads(content)
formatted_address = baidu_map_address["result"]["formatted_address"]
province = baidu_map_address["result"]["addressComponent"]["province"]
city = baidu_map_address["result"]["addressComponent"]["city"]
district = baidu_map_address["result"]["addressComponent"]["district"]
location = baidu_map_address["result"]["sematic_description"]
return formatted_address,province,city,district,location
if __name__ == '__main__':
GPS_info = find_GPS_image(pic_path='C:/Users/pacer/desktop/img/5.jpg')
address = find_address_from_GPS(GPS=GPS_info)
print("拍摄时间:" + GPS_info.get("date_information"))
print('照片拍摄地址:' + str(address))

作者:LexSaints
来源:https://juejin.cn/post/6967563349609414692

收起阅读 »

使用 Python 程序实现摩斯密码翻译器

算法加密解密执行摩斯密码对照表输出:.--- ..- . .--- .. -. -....- .... .- .. -.-- --- -. --.JUEJIN-HAIYONG.. .-.. --- ...- . -.-- --- ..-I LOVE YOU作...
继续阅读 »

摩斯密码是一种将文本信息作为一系列通断的音调、灯光或咔嗒声传输的方法,无需特殊设备,熟记的小伙伴即可直接翻译。它以电报发明者Samuel F. B. Morse的名字命名。

算法

算法非常简单。英语中的每个字符都被一系列“点”和“破折号”代替,或者有时只是单数的“点”或“破折号”,反之亦然。

加密

  1. 在加密的情况下,我们一次一个地从单词中提取每个字符(如果不是空格),并将其与存储在我们选择的任何数据结构中的相应摩斯密码匹配(如果您使用 python 编码,字典可以变成在这种情况下非常有用)

  2. 将摩斯密码存储在一个变量中,该变量将包含我们编码的字符串,然后我们在包含结果的字符串中添加一个空格。

  3. 在用摩斯密码编码时,我们需要在每个字符之间添加 1 个空格,在每个单词之间添加 2 个连续空格。

  4. 如果字符是空格,则向包含结果的变量添加另一个空格。我们重复这个过程,直到我们遍历整个字符串

解密

  1. 在解密的情况下,我们首先在要解码的字符串末尾添加一个空格(这将在后面解释)。

  2. 现在我们继续从字符串中提取字符,直到我们没有任何空间。

  3. 一旦我们得到一个空格,我们就会在提取的字符序列(或我们的莫尔斯电码)中查找相应的英语字符,并将其添加到将存储结果的变量中。

  4. 请记住,跟踪空间是此解密过程中最重要的部分。一旦我们得到 2 个连续的空格,我们就会向包含解码字符串的变量添加另一个空格。

  5. 字符串末尾的最后一个空格将帮助我们识别莫尔斯电码字符的最后一个序列(因为空格充当提取字符并开始解码它们的检查)。

执行

Python 提供了一种称为字典的数据结构,它以键值对的形式存储信息,这对于实现诸如摩尔斯电码之类的密码非常方便。我们可以将摩斯密码表保存在字典中,其中 (键值对)=>(英文字符-莫尔斯电码) 。明文(英文字符)代替密钥,密文(摩斯密码)形成相应密钥的值。键的值可以从字典中访问,就像我们通过索引访问数组的值一样,反之亦然。

摩斯密码对照表

# 实现摩斯密码翻译器的 Python 程序

'''
VARIABLE KEY
'cipher' -> '存储英文字符串的摩斯翻译形式'
'decipher' -> '存储摩斯字符串的英文翻译形式'
'citext' -> '存储单个字符的摩斯密码'
'i' -> '计算摩斯字符之间的空格'
'message' -> '存储要编码或解码的字符串
'''

# 表示摩斯密码图的字典
MORSE_CODE_DICT = { 'A':'.-', 'B':'-...',
'C':'-.-.', 'D':'-..', 'E':'.',
'F':'..-.', 'G':'--.', 'H':'....',
'I':'..', 'J':'.---', 'K':'-.-',
'L':'.-..', 'M':'--', 'N':'-.',
'O':'---', 'P':'.--.', 'Q':'--.-',
'R':'.-.', 'S':'...', 'T':'-',
'U':'..-', 'V':'...-', 'W':'.--',
'X':'-..-', 'Y':'-.--', 'Z':'--..',
'1':'.----', '2':'..---', '3':'...--',
'4':'....-', '5':'.....', '6':'-....',
'7':'--...', '8':'---..', '9':'----.',
'0':'-----', ', ':'--..--', '.':'.-.-.-',
'?':'..--..', '/':'-..-.', '-':'-....-',
'(':'-.--.', ')':'-.--.-'}

# 根据摩斯密码图对字符串进行加密的函数
def encrypt(message):
cipher = ''
for letter in message:
if letter != ' ':

# 查字典并添加对应的摩斯密码
# 用空格分隔不同字符的摩斯密码
cipher += MORSE_CODE_DICT[letter] + ' '
else:
# 1个空格表示不同的字符
# 2表示不同的词
cipher += ' '

return cipher

# 将字符串从摩斯解密为英文的函数
def decrypt(message):

# 在末尾添加额外空间以访问最后一个摩斯密码
message += ' '

decipher = ''
citext = ''
for letter in message:

# 检查空间
if (letter != ' '):

# 计数器来跟踪空间
i = 0

# 在空格的情况下
citext += letter

# 在空间的情况下
else:
# 如果 i = 1 表示一个新字符
i += 1

# 如果 i = 2 表示一个新词
if i == 2 :

# 添加空格来分隔单词
decipher += ' '
else:

# 使用它们的值访问密钥(加密的反向)
decipher += list(MORSE_CODE_DICT.keys())[list(MORSE_CODE_DICT
.values()).index(citext)]
citext = ''

return decipher

# 硬编码驱动函数来运行程序
def main():
message = "JUEJIN-HAIYONG"
result = encrypt(message.upper())
print (result)

message = ".--- ..- . .--- .. -. -....- .... .- .. -.-- --- -. --."
result = decrypt(message)
print (result)

message = "I LOVE YOU"
result = encrypt(message.upper())
print (result)

message = ".. .-.. --- ...- . -.-- --- ..-"
result = decrypt(message)
print (result)

# 执行主函数
if __name__ == '__main__':
main()

输出:

.--- ..- . .--- .. -. -....- .... .- .. -.-- --- -. --.
JUEJIN-HAIYONG
.. .-.. --- ...- . -.-- --- ..-
I LOVE YOU

作者:海拥
来源:https://juejin.cn/post/6990223674758397960

收起阅读 »

Python编程需要遵循的一些规则v2

Python编程需要遵循的一些规则v2使用 pylintpylint 是一个在 Python 源代码中查找 bug 的工具. 对于 C 和 C++ 这样的强类型静态语言来说, 这些 bug 通常由编译器来捕获. 由于 Python 的动态特性, 有些警告可能不...
继续阅读 »



Python编程需要遵循的一些规则v2

使用 pylint

pylint 是一个在 Python 源代码中查找 bug 的工具. 对于 C 和 C++ 这样的强类型静态语言来说, 这些 bug 通常由编译器来捕获. 由于 Python 的动态特性, 有些警告可能不对. 不过虚报的情况应该比较少. 确保对你的代码运行 pylint. 在 CI 流程中加入 pylint 检查的步骤. 抑制不准确的警告, 以便其他正确的警告可以暴露出来。

自底向上编程

自底向上编程(bottom up): 从最底层,依赖最少的地方开始设计结构及编写代码, 再编写调用这些代码的逻辑, 自底向上构造程序.

  • 采取自底向上的设计方式会让代码更少以及开发过程更加敏捷.

  • 自底向上的设计更容易产生符合单一责任原则(SRP) 的代码.

  • 组件之间的调用关系清晰, 组件更易复用, 更易编写单元测试案例.

如:需要编写调用外部系统 API 获取数据来完成业务逻辑的代码.

  • 应该先编写一个独立的模块将调用外部系统 API 获取数据的接口封装在一些函数中, 然后再编写如何调用这些函数 来完成业务逻辑.

  • 不可以先写业务逻辑, 然后在需要调用外部 API 时再去实现相关代码, 这会产生调用 API 的代码直 接耦合在业务逻辑中的代码.

防御式编程

使用 assert 语句确保程序处于的正确状态 不要过度使用 assert, 应该只用于确保核心的部分.

注意 assert 不能代替运行时的异常, 不要忘记 assert 语句可能会被解析器忽略.

assert 语句通常可用于以下场景:

  • 确保公共类或者函数被正确地调用 例如一个公共函数可以处理 list 或 dict 类型参数, 在函数开头使用 assert isinstance(param, (list, dict))确保函数接受的参数是 list 或 dict

  • assert 用于确保不变量. 防止需求改变时引起代码行为的改变

if target == x:
  run_x_code()
elif target == y:
  run_y_code()
else:
  run_z_code()

假设该代码上线时是正确的, target 只会是 x, y, z 三种情况, 但是稍后如果需求改变了, target 允许 w 的 情况出现. 当 target 为 w 时该代码就会错误地调用 run_z_code, 这通常会引起糟糕的后果.

  • 使用 assert 来确保不变量

assert target in (x, y, z)
if target == x:
  run_x_code()
elif target == y:
  run_y_code()
else:
  assert target == z
  run_z_code()

不使用 assert 的场景:

  • 不使用 assert 在校验用户输入的数据, 需要校验的情况下应该抛出异常

  • 不将 assert 用于允许正常失败的情况, 将 assert 用于检查不允许失败的情况.

  • 用户不应该直接看到 AssertionError, 如果用户可以看到, 将这种情况视为一个 BUG

避免使用 magic number

赋予特殊的常量一个名字, 避免重复地直接使用它们的字面值. 合适的时候使用枚举值 Enum.

使用常量在重构时只需要修改一个地方, 如果直接使用字面值在重构时将修改所有使用到的地方.

  • 建议

GRAVITATIONAL_CONSTANT = 9.81

def get_potential_energy(mass, height):
  return mass * height * GRAVITATIONAL_CONSTANT

class ConfigStatus:
  ENABLED = 1
  DISABLED = 0

Config.objects.filter(enabled=ConfigStatus.ENABLED)
  • 不建议

def get_potential_energy(mass, height):
  return mass * height * 9.81

# Django ORM
Config.objects.filter(enabled=1)

处理字典 key 不存在时的默认值

使用 dict.setdefault 或者 defaultdict

# group words by frequency
words = [(1, 'apple'), (2, 'banana'), (1, 'cat')]
frequency = {}

dict.setdefault

  • 建议

for freq, word in words:
  frequency.setdefault(freq, []).append(word)

或者使用 defaultdict

from collections import defaultdict

frequency = defaultdict(list)

for freq, word in words:
  frequency[freq].append(word)
  • 不建议

for freq, word in words:
  if freq not in frequency:
      frequency[freq] = []
  frequency[freq].append(word)

注意在 Python 3 中 map filter 返回的是生成器而不是列表, 在隋性计算方面有所区别

禁止使用 import *

原则上禁止避免使用 import *, 应该显式地列出每一个需要导入的模块

使用 import * 会污染当前命名空间的变量, 无法找到变量的定义是来哪个模块, 在被 import 的模块上的改动可 能会在预期外地影响到其它模块, 可能会引起难以排查的问题.

在某些必须需要使用或者是惯用法 from foo import * 的场景下, 应该在模块 foo 的末尾使用 all 控制被导出的变量.

# foo.py
CONST_VALUE = 1
class Apple:
  ...

__all__ = ("CONST_VALUE", "Apple")

# bar.py
# noinspection PyUnresolvedReferences
from foo import *

作者:未来现相
来源:https://mp.weixin.qq.com/s/QinR-bHolVlr0z8IyhCqfg

收起阅读 »

Python对象的浅拷贝与深拷贝

在讲我们深浅拷贝之前,我们需要先区分一下拷贝和赋值的概念。看下面的例子a = [1,2,3]赋值:b = a拷贝:b = a.copy()上面的两行代码究竟有什么不同呢?带着这个问题,继续 看了上面这张图,相信大家已经对直接赋值和拷贝有了一个比较清楚的认识...
继续阅读 »

在讲我们深浅拷贝之前,我们需要先区分一下拷贝和赋值的概念。看下面的例子

a = [1,2,3]

赋值:

b = a

拷贝:

b = a.copy()

上面的两行代码究竟有什么不同呢?带着这个问题,继续

Python对象的浅拷贝与深拷贝_递归



看了上面这张图,相信大家已经对直接赋值和拷贝有了一个比较清楚的认识。

直接赋值:复制一个对象的引用给新变量
拷贝:复制一个对象到新的内存地址空间,并且将新变量引用到复制后的对象

我们的深浅拷贝只是对于可变对象来讨论的。 不熟悉的朋友需要自己去了解可变对象与不可变对象哦。

1 对象的嵌套引用

a = { "list": [1,2,3] }

上面的代码,在内存中是什么样子的呢?请看下图:

Python对象的浅拷贝与深拷贝_递归_02



原来,在我们的嵌套对象中,子对象也是一个引用。

2 浅拷贝

Python对象的浅拷贝与深拷贝_python_03



如上图所示,我们就可以很好的理解什么叫做浅拷贝了。

浅拷贝:只拷贝父对象,不会拷贝对象的内部的子对象。内部的子对象指向的还是同一个引用

上面 的 a 和 c 是一个独立的对象,但他们的子对象还是指向统一对象

2.1 浅拷贝的方法

  • .copy()

a = {"list": [1,2,3] }
b = a.copy()
  • copy模块

import copy
a = {"list": [1,2,3] }
b = copy.copy(a)
  • 列表切片[:]

a = [1,2,3,[1,2,3]]
b = a[1:]
  • for循环

a = [1,2,3,[1,2,3]]
b = []
for i in a:
  b.append(i)

2.2 浅拷贝的影响

a = {"list":[1,2,3]}
b = a.copy()
a["list"].append(4)

print(a)
# {'list': [1, 2, 3, 4]}

print(b)
# {'list': [1, 2, 3, 4]}

在上面的例子中,我们明明只改变 a 的子对象,却发现 b 的子对象也跟着改变了。这样在我们的程序中也许会引发很多的BUG。

3 深拷贝

上面我们知道了什么是浅拷贝,那我们的深拷贝就更好理解了。

Python对象的浅拷贝与深拷贝_python_04



深拷贝:完全拷贝了父对象及其子对象,两者已经完成没有任何关联,是完全独立的。

import copy
a = {"list":[1,2,3]}
b = copy.deepcopy(a)
a["list"].append(4)

print(a)
# {'list': [1, 2, 3, 4]}

print(b)
# {'list': [1, 2, 3,]}

上面的例子中,我们再次修改 a 的子对象对 b 已经没有任何影响

4 手动实现一个深拷贝

主要采用递归的方法解决问题。判断拷贝的每一项子对象是否为引用对象。如果是就采用递归的方式将子对象进行复制。

def deepcopy(instance):
  if isinstance(instance, dict):
      return {k:deepcopy(v) for k,v in instance.items() }
   
  elif isinstance(instance, list):
      return [deepcopy(x) for x in instance]
   
  else:
      return instance

a = {"list": [1,2,3]}
b = deepcopy(a)

print(a)
# {'list': [1, 2, 3]}

print(b)
# {'list': [1, 2, 3]}

a["list"].append(4)
print(a)
# {'list': [1, 2, 3, 4]}

print(b)
# {'list': [1, 2, 3]}

创作不易,且读且珍惜。如有错漏还请海涵并联系作者修改,内容有参考,如有侵权,请联系作者删除。如果文章对您有帮助,还请动动小手,您的支持是我最大的动力。

作者:趣玩Python
来源:https://blog.51cto.com/u_14666251/4716452 收起阅读 »

Python内存驻留机制

驻留下面举例介绍python中的驻留机制。 python内存驻留知道结果是什么吗?下面是执行结果:TrueFalseTrueTrue整型驻留执行结果:FalseTrueTrueTrue因为启动时,Python 将一个 -5~256 之间整数列表预加载(缓存)到...
继续阅读 »



字符串驻留机制在许多面向对象编程语言中都支持,比如Java、python、Ruby、PHP等,它是一种数据缓存机制,对不可变数据类型使用同一个内存地址,有效的节省了空间,本文主要介绍Python的内存驻留机制。

驻留

字符串驻留就是每个字符串只有一个副本,多个对象共享该副本,驻留只针对不可变数据类型,比如字符串,布尔值,数字等。在这些固定数据类型处理中,使用驻留可以有效节省时间和空间,当然在驻留池中创建或者插入新的内容会消耗一定的时间。

下面举例介绍python中的驻留机制。
python内存驻留

在Python对象及内存管理机制一文中介绍了python的参数传递以及以及内存管理机制,来看下面一段代码:

l1 = [1, 2, 3, 4]
l2 = [1, 2, 3, 4]
l3 = l2
print(l1 == l2)
print(l1 is l2)
print(l2 == l3)
print(l2 is l3)

知道结果是什么吗?下面是执行结果:

True
False
True
True

l1和l2内容相同,却指向了不同的内存地址,l2和l3之间使用等号赋值,所以指向了同一个对象。因为列表是可变对象,每创建一个列表,都会重新分配内存,列表对象是没有“内存驻留”机制的。下面来看不可变数据类型的驻留机制。

整型驻留

Jupyter或者控制台交互环境中执行下面代码:

a1 = 300
b1 = 300
c1 = b1
print(a1 is b1)
print(c1 is b1)

a2 = 200
b2 = 200
c2 = b2
print(a2 is b2)
print(c2 is b2)

执行结果:

False
True
True
True

可以发现a1和b1指向了不同的地址,a2和b2指向了相同的地址,这是为什么呢?

因为启动时,Python 将一个 -5~256 之间整数列表预加载(缓存)到内存中,我们在这个范围内创建一个整数对象时,python会自动引用缓存的对象,不会创建新的整数对象。

浮点型不支持:

a = 1.0
b = 1.0
print(a is b)
print(a == b)

# 结果
# False
# True

如果上面的代码在非交互环境,也就是将代码作为python脚本运行的结果是什么呢?(运行环境为python3.7)

True
True
True
True
True
True

全为True,没有明确的限定临界值,都进行了驻留操作。这是因为使用不同的环境时,代码的优化方式不同。

字符串驻留

Jupyter或者控制台交互环境中:

  • 满足标识符命名规范的字符串都会被驻留,长度不限。

  • 空字符串会驻留

  • 使用乘法得到的字符串且满足标识符命名规范的字符串:长度小于等于20会驻留(peephole优化),Python 3.7改为4096(AST优化器)。

  • 长度为1的特殊字符(ASCII 字符中的)会驻留

  • 空元组或者只有一个元素且元素范围为-5~256的元组会驻留

满足标识符命名规范的字符:

a = 'Hello World'
b = 'Hello World'
print(a is b)

a = 'Hello_World'
b = 'Hello_World'
print(a is b)

结果:

False
True

乘法获取字符串(运行环境为python3.7)

a = 'aa'*50
b = 'aa'*50
print(a is b)

a = 'aa'*5000
b = 'aa'*5000
print(a is b)

结果:

True
False

在非交互环境中:

  • 默认字符串都会驻留

  • 使用乘法运算得到的字符串与在控制台相同

  • 元组类型(元组内数据为不可变数据类型)会驻留

  • 函数、类、变量、参数等的名称以及关键字都会驻留

注意:字符串是在编译时进行驻留,也就是说,如果字符串的值不能在编译时进行计算,将不会驻留。比如下面的例子:

letter = 'd'
a = 'Hello World'
b = 'Hello World'
c = 'Hello Worl' + 'd'
d = 'Hello Worl' + letter
e = " ".join(['Hello','World'])

print(id(a))
print(id(b))
print(id(c))
print(id(d))
print(id(e))

在交互环境执行结果如下:

1696903309168
1696903310128
1696903269296
1696902074160
1696903282800

都指向不同的内存。

python 3.7 非交互环境执行结果:

1426394439728
1426394439728
1426394439728
1426394571504
1426394571440

发现d和e指向不同的内存,因为d和e不是在编译时计算的,而是在运行时计算的。前面的a = 'aa'*50是在编译时计算的。

强行驻留

除了上面介绍的python默认的驻留外,可以使用sys模块中的intern()函数来指定驻留内容

import sys
letter_d = 'd'
a = sys.intern('Hello World')
b = sys.intern('Hello World')
c = sys.intern('Hello Worl' + 'd')
d = sys.intern('Hello Worl' + letter)
e = sys.intern(" ".join(['Hello','World']))

print(id(a))
print(id(b))
print(id(c))
print(id(d))
print(id(e))

结果:

1940593568304
1940593568304
1940593568304
1940593568304
1940593568304

使用intern()后,都指向了相同的地址。

总结

本文主要介绍了python的内存驻留,内存驻留是python优化的一种策略,注意不同运行环境下优化策略不一样,不同的python版本也不相同。注意字符串是在编译时进行驻留。

作者:测试开发小记
来源:https://blog.51cto.com/u_15441270/4714515

收起阅读 »

能让你更早下班的Python垃圾回收机制

人生苦短,只谈风月,谈什么垃圾回收。能让你更早下班的Python垃圾回收机制_内存空间据说上图是某语言的垃圾回收机制。。。我们写过C语言、C++的朋友都知道,我们的C语言是没有垃圾回收这种说法的。手动分配、释放内存都需要我们的程序员自己完成。不管是“内存泄漏”...
继续阅读 »



人生苦短,只谈风月,谈什么垃圾回收。

能让你更早下班的Python垃圾回收机制_内存空间

能让你更早下班的Python垃圾回收机制_内存空间

据说上图是某语言的垃圾回收机制。。。

我们写过C语言、C++的朋友都知道,我们的C语言是没有垃圾回收这种说法的。手动分配、释放内存都需要我们的程序员自己完成。不管是“内存泄漏” 还是野指针都是让开发者非常头疼的问题。所以C语言开发这个讨论得最多的话题就是内存管理了。但是对于其他高级语言来说,例如Java、C#、Python等高级语言,已经具备了垃圾回收机制。这样可以屏蔽内存管理的复杂性,使开发者可以更好的关注核心的业务逻辑。

对我们的Python开发者来说,我们可以当甩手掌柜。不用操心它怎么回收程序运行过程中产生的垃圾。但是这毕竟是一门语言的内心功法,难道我们甘愿一辈子做一个API调参侠吗?

1.什么是垃圾?

当我们的Python解释器在执行到定义变量的语法时,会申请内存空间来存放变量的值,而内存的容量是有限的,这就涉及到变量值所占用内存空间的回收问题。

当一个对象或者说变量没有用了,就会被当做“垃圾“。那什么样的变量是没有用的呢?

a = 10000

当解释器执行到上面这里的时候,会划分一块内存来存储 10000 这个值。此时的 10000 是被变量 a 引用的

a = 30000

当我们修改这个变量的值时,又划分了一块内存来存 30000 这个值,此时变量a引用的值是30000。

这个时候,我们的 10000 已经没有变量引用它了,我们也可以说它变成了垃圾,但是他依旧占着刚才给他的内存。那我们的解释器,就要把这块内存地盘收回来。

2.内存泄露和内存溢出

上面我们了解了什么是程序运行过程中的“垃圾”,那如果,产生了垃圾,我们不去处理,会产生什么样的后果呢?试想一下,如果你家从不丢垃圾,产生的垃圾就堆在家里会怎么呢?

  1. 家里堆满垃圾,有个美女想当你对象,但是已经没有空间给她住了。

  2. 你还能住,但是家里的垃圾很占地方,而且很浪费空间,慢慢的,总有一天你的家里会堆满垃圾

上面的结果其实就是计算机里面让所有程序员都闻风丧胆的问题,内存溢出和内存泄露,轻则导致程序运行速度减慢,重则导致程序崩溃。

内存溢出:程序在申请内存时,没有足够的内存空间供其使用,出现 out of memory

内存泄露:程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光

3.引用计数

前面我们提到过垃圾的产生的是因为,对象没有再被其他变量引用了。那么,我们的解释器究竟是怎么知道一个对象还有没有被引用的呢?

答案就是:引用计数。python内部通过引用计数机制来统计一个对象被引用的次数。当这个数变成0的时候,就说明这个对象没有被引用了。这个时候它就变成了“垃圾”。

这个引用计数又是何方神圣呢?让我们看看代码

text = "hello,world"

上面的一行代码做了哪些工作呢?

  • 创建字符串对象:它的值是hello,world

  • 开辟内存空间:在对象进行实例化的时候,解释器会为对象分配一段内存地址空间。把这个对象的结构体存储在这段内存地址空间中。

我们再来看看这个对象的结构体

typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;

熟悉c语言或者c++的朋友,看到这个应该特别熟悉,他就是结构体。这是因为我们Python官方的解释器是CPython,它底层调用了很多的c类库与接口。所以一些底层的数据是通过结构体进行存储的。看不懂的朋友也没有关系。

这里,我们只需要关注一个参数:ob_refcnt

这个参数非常神奇,它记录了这个对象的被变量引用的次数。所以上面 hello,world 这个对象的引用计数就是 1,因为现在只有text这个变量引用了它。

3.1 变量初始化赋值:

text = "hello,world"

能让你更早下班的Python垃圾回收机制_内存空间_02

3.2 变量引用传递:

new_text = text

能让你更早下班的Python垃圾回收机制_垃圾回收_03

3.3 删除第一个变量:

del text

能让你更早下班的Python垃圾回收机制_垃圾回收机制_04

3.4 删除第二个变量:

del new_text

能让你更早下班的Python垃圾回收机制_垃圾回收_05

此时 “hello,world” 对象的引用计数为:0,被当成了垃圾。下一步,就该被我们的垃圾回收器给收走了。

能让你更早下班的Python垃圾回收机制_python_06

4.引用计数如何变化

上面我们了解了什么是引用计数。那这个参数什么时候会发生变化呢?

4.1 引用计数加一的情况

  • 对象被创建

a = "hello,world"
  • 对象被别的变量引用(赋值给一个变量)

b = a
  • 对象被作为元素,放在容器中(比如被当作元素放在列表中)

list = []
list.append(a)
  • 对象作为参数传递给函数

func(a)

4.2 引用计数减一

  • 对象的引用变量被显示销毁

del a
  • 对象的引用变量赋值引用其他对象

a = "hello, Python"   # a的原来的引用对象:a = "hello,world"
  • 对象从容器中被移除,或者容器被销毁(例:对象从列表中被移除,或者列表被销毁)

del list
list.remove(a)
  • 一个引用离开了它的作用域

func():
  a = "hello,world"
  return

func() # 函数执行结束以后,函数作用域里面的局部变量a会被释放

4.3 查看对象的引用计数

如果要查看对象的引用计数,可以通过内置模块 sys 提供的 getrefcount 方法去查看。

import sys
a = "hello,world"
print(sys.getrefcount(a))

注意:当使用某个引用作为参数,传递给 getrefcount() 时,参数实际上创建了一个临时的引用。因此,getrefcount() 所得到的结果,会比期望的多 1

5.垃圾回收机制

其实Python的垃圾回收机制,我们前面已经说得差不多了。

Python通过引用计数的方法来说实现垃圾回收,当一个对象的引用计数为0的时候,就进行垃圾回收。但是如果只使用引用计数也是有点问题的。所以,python又引进了标记-清除和分代收集两种机制。

Python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略。

前面的引用计数我们已经了解了,那这个标记-清除跟分代收集又是什么呢?

5.1 引用计数机制缺点

Python语言默认采用的垃圾收集机制是“引用计数法 ”,该算法最早George E. Collins在1960的时候首次提出,50年后的今天,该算法依然被很多编程语言使用。

引用计数法:每个对象维护一个 ob_refcnt 字段,用来记录该对象当前被引用的次数,每当新的引用指向该对象时,它的引用计数ob_refcnt加1,每当该对象的引用失效时计数ob_refcnt减1,一旦对象的引用计数为0,该对象立即被回收,对象占用的内存空间将被释放。

缺点:

  1. 需要额外的空间维护引用计数

  2. 无法解决循环引用问题

什么是循环引用问题?看看下面的例子

a = {"key":"a"}  # 字典对象a的引用计数:1
b = {"key":"b"} # 字典对象b的引用计数:1

a["b"] = b # 字典对象b的引用计数:2
b["a"] = a # 字典对象a的引用计数:2

del a # 字典对象a的引用计数:1
del b # 字典对象b的引用计数:1

看上面的例子,明明两个变量都删除了,但是这两个对象却没有得到释放。原因是他们的引用计数都没有减少到0。而我们垃圾回收机制只有当引用计数为0的时候才会释放对象。这是一个无法解决的致命问题。这两个对象始终不会被销毁,这样就会导致内存泄漏。

那怎么解决这个问题呢?这个时候 标记-清除 就排上了用场。标记清除可以处理这种循环引用的情况。

5.2 标记-清除策略

Python采用了标记-清除策略,解决容器对象可能产生的循环引用问题。

该策略在进行垃圾回收时分成了两步,分别是:

  • 标记阶段,遍历所有的对象,如果是可达的(reachable),也就是还有对象引用它,那么就标记该对象为可达;

  • 清除阶段,再次遍历对象,如果发现某个对象没有标记为可达,则就将其回收

这里简单介绍一下标记-清除策略的流程

能让你更早下班的Python垃圾回收机制_内存空间_07

可达(活动)对象:从root集合节点有(通过链式引用)路径达到的对象节点

不可达(非活动)对象:从root集合节点没有(通过链式引用)路径到达的对象节点

流程:

  1. 首先,从root集合节点出发,沿着有向边遍历所有的对象节点

  2. 对每个对象分别标记可达对象还是不可达对象

  3. 再次遍历所有节点,对所有标记为不可达的对象进行垃圾回收、销毁。

标记-清除是一种周期性策略,相当于是一个定时任务,每隔一段时间进行一次扫描。
并且标记-清除工作时会暂停整个应用程序,等待标记清除结束后才会恢复应用程序的运行。

5.3 分代回收策略

分代回收建立标记清除的基础之上,因为我们的标记-清除策略会将我们的程序阻塞。为了减少应用程序暂停的时间,Python 通过“分代回收”(Generational Collection)策略。以空间换时间的方法提高垃圾回收效率。

分代的垃圾收集技术是在上个世纪 80 年代初发展起来的一种垃圾收集机制。

简单来说就是:对象存在时间越长,越可能不是垃圾,应该越少去收集

Python 将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python 将内存分为了 3“代”,分别为年轻代(第 0 代)、中年代(第 1 代)、老年代(第 2 代)。

那什么时候会触发分代回收呢?

import gc

print(gc.get_threshold())
# (700, 10, 10)
# 上面这个是默认的回收策略的阈值

# 也可以自己设置回收策略的阈值
gc.set_threshold(500, 5, 5)
  • 700:表示当分配对象的个数达到700时,进行一次0代回收

  • 10:当进行10次0代回收以后触发一次1代回收

  • 10:当进行10次1代回收以后触发一次2代回收

能让你更早下班的Python垃圾回收机制_垃圾回收_08

5.4 gc模块

  • gc.get_count():获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表

  • gc.get_threshold():获取gc模块中自动执行垃圾回收的频率,默认是(700, 10, 10)

  • gc.set_threshold(threshold0[,threshold1,threshold2]):设置自动执行垃圾回收的频率

  • gc.disable():python3默认开启gc机制,可以使用该方法手动关闭gc机制

  • gc.collect():手动调用垃圾回收机制回收垃圾

其实,既然我们选择了python,性能就不是最重要的了。我相信大部分的python工程师甚至都还没遇到过性能问题,因为现在的机器性能可以弥补。而对于内存管理与垃圾回收,python提供了甩手掌柜的方式让我们更关注业务层,这不是更加符合人生苦短,我用python的理念么。如果我还需要像C++那样小心翼翼的进行内存的管理,那我为什么还要用python呢?咱不就是图他的便利嘛。所以,放心去干吧。越早下班越好!

创作不易,且读且珍惜。如有错漏还请海涵并联系作者修改,内容有参考,如有侵权,请联系作者删除。如果文章对您有帮助,还请动动小手,您的支持是我最大的动力。


作者: 趣玩Python
来源:https://blog.51cto.com/u_14666251/4674779

收起阅读 »

黑科技,Python 脚本帮你找出微信上删除你好友的人

查看被删的微信好友原理就是新建群组,如果加不进来就是被删好友了(不要在群组里讲话,别人是看不见的)用的是微信网页版的接口查询结果可能会引起一些心理上的不适,请小心使用..(逃还有些小问题:结果好像有疏漏一小部分,原因不明..最终会遗留下一个只有自己的群组,需要...
继续阅读 »



查看被删的微信好友

原理就是新建群组,如果加不进来就是被删好友了(不要在群组里讲话,别人是看不见的)

用的是微信网页版的接口

查询结果可能会引起一些心理上的不适,请小心使用..(逃

还有些小问题:

结果好像有疏漏一小部分,原因不明..

最终会遗留下一个只有自己的群组,需要手工删一下

没试过被拉黑的情况

新手步骤 Mac 上步骤:

  1. 在 Mac 上操作,下载代码文件wdf.py

  2. 打开 Terminal 输入:python +空格,然后拖动刚才下载的 wdf.py 到 Terminal 后回车。格式: python wdf.py

  3. 接下来按步骤操作即可;

代码如下:

#!/usr/bin/env python
# coding=utf-8

import os
import urllib, urllib2
import re
import cookielib
import time
import xml.dom.minidom
import json
import sys
import math

DEBUG = False

MAX_GROUP_NUM = 35 # 每组人数

QRImagePath = os.getcwd() + '/qrcode.jpg'

tip = 0
uuid = ''

base_uri = ''
redirect_uri = ''

skey = ''
wxsid = ''
wxuin = ''
pass_ticket = ''
deviceId = 'e000000000000000'

BaseRequest = {}

ContactList = []
My = []

def getUUID():
global uuid

url = 'https://login.weixin.qq.com/jslogin'
params = {
'appid': 'wx782c26e4c19acffb',
'fun': 'new',
'lang': 'zh_CN',
'_': int(time.time()),
}

request = urllib2.Request(url = url, data = urllib.urlencode(params))
response = urllib2.urlopen(request)
data = response.read()

# print data

# window.QRLogin.code = 200; window.QRLogin.uuid = "oZwt_bFfRg==";
regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"'
pm = re.search(regx, data)

code = pm.group(1)
uuid = pm.group(2)

if code == '200':
return True

return False

def showQRImage():
global tip

url = 'https://login.weixin.qq.com/qrcode/' + uuid
params = {
't': 'webwx',
'_': int(time.time()),
}

request = urllib2.Request(url = url, data = urllib.urlencode(params))
response = urllib2.urlopen(request)

tip = 1

f = open(QRImagePath, 'wb')
f.write(response.read())
f.close()

if sys.platform.find('darwin') >= 0:
os.system('open %s' % QRImagePath)
elif sys.platform.find('linux') >= 0:
os.system('xdg-open %s' % QRImagePath)
else:
os.system('call %s' % QRImagePath)

print '请使用微信扫描二维码以登录'

def waitForLogin():
global tip, base_uri, redirect_uri

url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' % (tip, uuid, int(time.time()))

request = urllib2.Request(url = url)
response = urllib2.urlopen(request)
data = response.read()

# print data

# window.code=500;
regx = r'window.code=(\d+);'
pm = re.search(regx, data)

code = pm.group(1)

if code == '201': #已扫描
print '成功扫描,请在手机上点击确认以登录'
tip = 0
elif code == '200': #已登录
print '正在登录...'
regx = r'window.redirect_uri="(\S+?)";'
pm = re.search(regx, data)
redirect_uri = pm.group(1) + '&fun=new'
base_uri = redirect_uri[:redirect_uri.rfind('/')]
elif code == '408': #超时
pass
# elif code == '400' or code == '500':

return code

def login():
global skey, wxsid, wxuin, pass_ticket, BaseRequest

request = urllib2.Request(url = redirect_uri)
response = urllib2.urlopen(request)
data = response.read()

# print data

'''

0
OK
xxx
xxx
xxx
xxx
1

'''

doc = xml.dom.minidom.parseString(data)
root = doc.documentElement

for node in root.childNodes:
if node.nodeName == 'skey':
skey = node.childNodes[0].data
elif node.nodeName == 'wxsid':
wxsid = node.childNodes[0].data
elif node.nodeName == 'wxuin':
wxuin = node.childNodes[0].data
elif node.nodeName == 'pass_ticket':
pass_ticket = node.childNodes[0].data

# print 'skey: %s, wxsid: %s, wxuin: %s, pass_ticket: %s' % (skey, wxsid, wxuin, pass_ticket)

if skey == '' or wxsid == '' or wxuin == '' or pass_ticket == '':
return False

BaseRequest = {
'Uin': int(wxuin),
'Sid': wxsid,
'Skey': skey,
'DeviceID': deviceId,
}

return True

def webwxinit():

url = base_uri + '/webwxinit?pass_ticket=%s&skey=%s&r=%s' % (pass_ticket, skey, int(time.time()))
params = {
'BaseRequest': BaseRequest
}

request = urllib2.Request(url = url, data = json.dumps(params))
request.add_header('ContentType', 'application/json; charset=UTF-8')
response = urllib2.urlopen(request)
data = response.read()

if DEBUG == True:
f = open(os.getcwd() + '/webwxinit.json', 'wb')
f.write(data)
f.close()

# print data

global ContactList, My
dic = json.loads(data)
ContactList = dic['ContactList']
My = dic['User']

ErrMsg = dic['BaseResponse']['ErrMsg']
if len(ErrMsg) > 0:
print ErrMsg

Ret = dic['BaseResponse']['Ret']
if Ret != 0:
return False

return True

def webwxgetcontact():

url = base_uri + '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % (pass_ticket, skey, int(time.time()))

request = urllib2.Request(url = url)
request.add_header('ContentType', 'application/json; charset=UTF-8')
response = urllib2.urlopen(request)
data = response.read()

if DEBUG == True:
f = open(os.getcwd() + '/webwxgetcontact.json', 'wb')
f.write(data)
f.close()

# print data

dic = json.loads(data)
MemberList = dic['MemberList']

# 倒序遍历,不然删除的时候出问题..
SpecialUsers = ['newsapp', 'fmessage', 'filehelper', 'weibo', 'qqmail', 'fmessage', 'tmessage', 'qmessage', 'qqsync', 'floatbottle', 'lbsapp', 'shakeapp', 'medianote', 'qqfriend', 'readerapp', 'blogapp', 'facebookapp', 'masssendapp', 'meishiapp', 'feedsapp', 'voip', 'blogappweixin', 'weixin', 'brandsessionholder', 'weixinreminder', 'wxid_novlwrv3lqwv11', 'gh_22b87fa7cb3c', 'officialaccounts', 'notification_messages', 'wxid_novlwrv3lqwv11', 'gh_22b87fa7cb3c', 'wxitil', 'userexperience_alarm', 'notification_messages']
for i in xrange(len(MemberList) - 1, -1, -1):
Member = MemberList[i]
if Member['VerifyFlag'] & 8 != 0: # 公众号/服务号
MemberList.remove(Member)
elif Member['UserName'] in SpecialUsers: # 特殊账号
MemberList.remove(Member)
elif Member['UserName'].find('@@') != -1: # 群聊
MemberList.remove(Member)
elif Member['UserName'] == My['UserName']: # 自己
MemberList.remove(Member)

return MemberList

def createChatroom(UserNames):
MemberList = []
for UserName in UserNames:
MemberList.append({'UserName': UserName})


url = base_uri + '/webwxcreatechatroom?pass_ticket=%s&r=%s' % (pass_ticket, int(time.time()))
params = {
'BaseRequest': BaseRequest,
'MemberCount': len(MemberList),
'MemberList': MemberList,
'Topic': '',
}

request = urllib2.Request(url = url, data = json.dumps(params))
request.add_header('ContentType', 'application/json; charset=UTF-8')
response = urllib2.urlopen(request)
data = response.read()

# print data

dic = json.loads(data)
ChatRoomName = dic['ChatRoomName']
MemberList = dic['MemberList']
DeletedList = []
for Member in MemberList:
if Member['MemberStatus'] == 4: #被对方删除了
DeletedList.append(Member['UserName'])

ErrMsg = dic['BaseResponse']['ErrMsg']
if len(ErrMsg) > 0:
print ErrMsg

return (ChatRoomName, DeletedList)

def deleteMember(ChatRoomName, UserNames):
url = base_uri + '/webwxupdatechatroom?fun=delmember&pass_ticket=%s' % (pass_ticket)
params = {
'BaseRequest': BaseRequest,
'ChatRoomName': ChatRoomName,
'DelMemberList': ','.join(UserNames),
}

request = urllib2.Request(url = url, data = json.dumps(params))
request.add_header('ContentType', 'application/json; charset=UTF-8')
response = urllib2.urlopen(request)
data = response.read()

# print data

dic = json.loads(data)
ErrMsg = dic['BaseResponse']['ErrMsg']
if len(ErrMsg) > 0:
print ErrMsg

Ret = dic['BaseResponse']['Ret']
if Ret != 0:
return False

return True

def addMember(ChatRoomName, UserNames):
url = base_uri + '/webwxupdatechatroom?fun=addmember&pass_ticket=%s' % (pass_ticket)
params = {
'BaseRequest': BaseRequest,
'ChatRoomName': ChatRoomName,
'AddMemberList': ','.join(UserNames),
}

request = urllib2.Request(url = url, data = json.dumps(params))
request.add_header('ContentType', 'application/json; charset=UTF-8')
response = urllib2.urlopen(request)
data = response.read()

# print data

dic = json.loads(data)
MemberList = dic['MemberList']
DeletedList = []
for Member in MemberList:
if Member['MemberStatus'] == 4: #被对方删除了
DeletedList.append(Member['UserName'])

ErrMsg = dic['BaseResponse']['ErrMsg']
if len(ErrMsg) > 0:
print ErrMsg

return DeletedList

def main():

opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookielib.CookieJar()))
urllib2.install_opener(opener)

if getUUID() == False:
print '获取uuid失败'
return

showQRImage()
time.sleep(1)

while waitForLogin() != '200':
pass

os.remove(QRImagePath)

if login() == False:
print '登录失败'
return

if webwxinit() == False:
print '初始化失败'
return

MemberList = webwxgetcontact()

MemberCount = len(MemberList)
print '通讯录共%s位好友' % MemberCount

ChatRoomName = ''
result = []
for i in xrange(0, int(math.ceil(MemberCount / float(MAX_GROUP_NUM)))):
UserNames = []
NickNames = []
DeletedList = ''
for j in xrange(0, MAX_GROUP_NUM):
if i * MAX_GROUP_NUM + j >= MemberCount:
break

Member = MemberList[i * MAX_GROUP_NUM + j]
UserNames.append(Member['UserName'])
NickNames.append(Member['NickName'].encode('utf-8'))
                       
print '第%s组...' % (i + 1)
print ', '.join(NickNames)
print '回车键继续...'
raw_input()

# 新建群组/添加成员
if ChatRoomName == '':
(ChatRoomName, DeletedList) = createChatroom(UserNames)
else:
DeletedList = addMember(ChatRoomName, UserNames)

DeletedCount = len(DeletedList)
if DeletedCount > 0:
result += DeletedList

print '找到%s个被删好友' % DeletedCount
# raw_input()

# 删除成员
deleteMember(ChatRoomName, UserNames)

# todo 删除群组


resultNames = []
for Member in MemberList:
if Member['UserName'] in result:
NickName = Member['NickName']
if Member['RemarkName'] != '':
NickName += '(%s)' % Member['RemarkName']
resultNames.append(NickName.encode('utf-8'))

print '---------- 被删除的好友列表 ----------'
print '\n'.join(resultNames)
print '-----------------------------------'

# windows下编码问题修复
# http://blog.csdn.net/heyuxuanzee/article/details/8442718
class UnicodeStreamFilter:  
def __init__(self, target):  
self.target = target  
self.encoding = 'utf-8'  
self.errors = 'replace'  
self.encode_to = self.target.encoding  
def write(self, s):  
if type(s) == str:  
s = s.decode('utf-8')  
s = s.encode(self.encode_to, self.errors).decode(self.encode_to)  
self.target.write(s)  
 
if sys.stdout.encoding == 'cp936':  
sys.stdout = UnicodeStreamFilter(sys.stdout)

if __name__ == '__main__' :

print '本程序的查询结果可能会引起一些心理上的不适,请小心使用...'
print '回车键继续...'
raw_input()

main()

print '回车键结束'
raw_input()

作者: 0x5e(github id)

来源:https://juejin.cn/post/6844903425629487112

收起阅读 »

就业寒冬,从拉勾招聘看Python就业前景

事情的起源是这样的,某个风和日丽的下午... 习惯性的打开知乎准备划下水,看到一个问题刚好邀请回答于是就萌生了采集下某招聘网站Python岗位招聘的信息,看一下目前的薪水和岗位分布,说干就干。Chrome浏览器右键检查查看network,找到链接https:/...
继续阅读 »



1.数据采集

事情的起源是这样的,某个风和日丽的下午... 习惯性的打开知乎准备划下水,看到一个问题刚好邀请回答

img

于是就萌生了采集下某招聘网站Python岗位招聘的信息,看一下目前的薪水和岗位分布,说干就干。

先说下数据采集过程中遇到的问题,首先请求头是一定要伪装的,否则第一步就会给你弹出你的请求太频繁,请稍后再试,其次网站具有多重反爬策略,解决方案是每次先获取session然后更新我们的session进行抓取,最后拿到了想要的数据。

Chrome浏览器右键检查查看network,找到链接https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false

img

可以看到返回的数据正是页面的Python招聘详情,于是我直接打开发现直接提示{"status":false,"msg":"您操作太频繁,请稍后再访问","clientIp":"124.77.161.207","state":2402},机智的我察觉到事情并没有那么简单

img

真正的较量才刚刚开始,我们先来分析下请求的报文,

img

img

可以看到请求是以post的方式传递的,同时传递了参数

datas = {
          'first': 'false',
          'pn': x,
          'kd': 'python',
      }

同时不难发现每次点击下一页都会同时发送一条get请求

这里我点了两次,出现两条get请求

img

经过探索,发现这个get请求和我们post请求是一致的,那么问题就简单许多,整理一下思路

img

关键词:python 搜索范围:全国 数据时效:2019.05.05

#!/usr/bin/env python3.4
# encoding: utf-8
"""
Created on 19-5-05
@title: ''
@author: Xusl
"""
import json
import requests
import xlwt
import time

# 获取存储职位信息的json对象,遍历获得公司名、福利待遇、工作地点、学历要求、工作类型、发布时间、职位名称、薪资、工作年限
def get_json(url, datas):
  my_headers = {
      "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36",
      "Referer": "https://www.lagou.com/jobs/list_Python?city=%E5%85%A8%E5%9B%BD&cl=false&fromSearch=true&labelWords=&suginput=",
      "Content-Type": "application/x-www-form-urlencoded;charset = UTF-8"
  }
  time.sleep(5)
  ses = requests.session()   # 获取session
  ses.headers.update(my_headers) # 更新
  ses.get("https://www.lagou.com/jobs/list_python?city=%E5%85%A8%E5%9B%BD&cl=false&fromSearch=true&labelWords=&suginput=")
  content = ses.post(url=url, data=datas)
  result = content.json()
  info = result['content']['positionResult']['result']
  info_list = []
  for job in info:
      information = []
      information.append(job['positionId']) # 岗位对应ID
      information.append(job['city']) # 岗位对应城市
      information.append(job['companyFullName']) # 公司全名
      information.append(job['companyLabelList']) # 福利待遇
      information.append(job['district']) # 工作地点
      information.append(job['education']) # 学历要求
      information.append(job['firstType']) # 工作类型
      information.append(job['formatCreateTime']) # 发布时间
      information.append(job['positionName']) # 职位名称
      information.append(job['salary']) # 薪资
      information.append(job['workYear']) # 工作年限
      info_list.append(information)
      # 将列表对象进行json格式的编码转换,其中indent参数设置缩进值为2
      # print(json.dumps(info_list, ensure_ascii=False, indent=2))
  # print(info_list)
  return info_list

def main():
  page = int(input('请输入你要抓取的页码总数:'))
  # kd = input('请输入你要抓取的职位关键字:')
  # city = input('请输入你要抓取的城市:')

  info_result = []
  title = ['岗位id', '城市', '公司全名', '福利待遇', '工作地点', '学历要求', '工作类型', '发布时间', '职位名称', '薪资', '工作年限']
  info_result.append(title)
  for x in range(1, page+1):
      url = 'https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false'
      datas = {
          'first': 'false',
          'pn': x,
          'kd': 'python',
      }
      try:
          info = get_json(url, datas)
          info_result = info_result + info
          print("第%s页正常采集" % x)
      except Exception as msg:
          print("第%s页出现问题" % x)
       
      # 创建workbook,即excel
      workbook = xlwt.Workbook(encoding='utf-8')
      # 创建表,第二参数用于确认同一个cell单元是否可以重设值
      worksheet = workbook.add_sheet('lagouzp', cell_overwrite_ok=True)
      for i, row in enumerate(info_result):
          # print(row)
          for j, col in enumerate(row):
              # print(col)
              worksheet.write(i, j, col)
      workbook.save('lagouzp.xls')

if __name__ == '__main__':
  main()

日志记录

img

当然存储于excel当然是不够的,之前一直用matplotlib做数据可视化,这次换个新东西pyecharts

2.了解pyecharts

pyecharts是一款将python与echarts结合的强大的数据可视化工具,包含多种图表

  • Bar(柱状图/条形图)

  • Bar3D(3D 柱状图)

  • Boxplot(箱形图)

  • EffectScatter(带有涟漪特效动画的散点图)

  • Funnel(漏斗图)

  • Gauge(仪表盘)

  • Geo(地理坐标系)

  • Graph(关系图)

  • HeatMap(热力图)

  • Kline(K线图)

  • Line(折线/面积图)

  • Line3D(3D 折线图)

  • Liquid(水球图)

  • Map(地图)

  • Parallel(平行坐标系)

  • Pie(饼图)

  • Polar(极坐标系)

  • Radar(雷达图)

  • Sankey(桑基图)

  • Scatter(散点图)

  • Scatter3D(3D 散点图)

  • ThemeRiver(主题河流图)

  • WordCloud(词云图)

用户自定义

  • Grid 类:并行显示多张图

  • Overlap 类:结合不同类型图表叠加画在同张图上

  • Page 类:同一网页按顺序展示多图

  • Timeline 类:提供时间线轮播多张图

另外需要注意的是从版本0.3.2 开始,为了缩减项目本身的体积以及维持 pyecharts 项目的轻量化运行,pyecharts 将不再自带地图 js 文件。如用户需要用到地图图表(Geo、Map),可自行安装对应的地图文件包。

  1. 全球国家地图: echarts-countries-pypkg (1.9MB): 世界地图和 213 个国家,包括中国地图

  2. 中国省级地图: echarts-china-provinces-pypkg (730KB):23 个省,5 个自治区

  3. 中国市级地图: echarts-china-cities-pypkg (3.8MB):370 个中国城市

也可以使用命令进行安装

pip install echarts-countries-pypkg
pip install echarts-china-provinces-pypkg
pip install echarts-china-cities-pypkg

3.数据可视化(代码+展示)

  • 各城市招聘数量

from pyecharts import Bar

city_nms_top10 = ['北京', '上海', '深圳', '成都', '杭州', '广州', '武汉', '南京', '苏州', '郑州', '天津', '西安', '东莞', '珠海', '合肥', '厦门', '宁波','南宁', '重庆', '佛山', '大连', '哈尔滨', '长沙', '福州', '中山']
city_nums_top10 = [149, 95, 77, 22, 17, 17, 16, 13, 7, 5, 4, 4, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1]

bar = Bar("Python岗位", "各城市数量")
bar.add("数量", city_nms, city_nums, is_more_utils=True)
# bar.print_echarts_options() # 该行只为了打印配置项,方便调试时使用
bar.render('Python岗位各城市数量.html')  # 生成本地 HTML 文件

img

  • 地图分布展示(这个场景意义不大,不过多分析)

from pyecharts import Geo

city_datas = [('北京', 149), ('上海', 95), ('深圳', 77), ('成都', 22), ('杭州', 17), ('广州', 17), ('武汉', 16), ('南京', 13), ('苏州', 7), ('郑州', 5), ('天津', 4), ('西安', 4), ('东莞', 3), ('珠海', 2), ('合肥', 2), ('厦门', 2), ('宁波', 1), ('南宁', 1), ('重庆', 1), ('佛山', 1), ('大连', 1), ('哈尔滨', 1), ('长沙', 1), ('福州', 1), ('中山', 1)]
geo = Geo("Python岗位城市分布地图", "数据来源拉勾", title_color="#fff",
          title_pos="center", width=1200,
          height=600, background_color='#404a59')
attr, value = geo.cast(city_datas)
geo.add("", attr, value, visual_range=[0, 200], visual_text_color="#fff",symbol_size=15, is_visualmap=True)
geo.render("Python岗位城市分布地图_scatter.html")
geo = Geo("Python岗位城市分布地图", "数据来源拉勾", title_color="#fff",
          title_pos="center", width=1200,
          height=600, background_color='#404a59')
attr, value = geo.cast(city_datas)
geo.add("", attr, value, type="heatmap", visual_range=[0,10],visual_text_color="#fff",symbol_size=15,is_visualmap=True)
geo.render("Python岗位城市分布地图_heatmap.html")

img

img

  • 各个城市招聘情况

from pyecharts import Pie

city_nms_top10 = ['北京', '上海', '深圳', '成都', '广州', '杭州', '武汉', '南京', '苏州', '郑州']
city_nums_top10 = [149, 95, 77, 22, 17, 17, 16, 13, 7, 5]
pie = Pie()
pie.add("", city_nms_top10, city_nums_top10, is_label_show=True)
# pie.show_config()
pie.render('Python岗位各城市分布饼图.html')

img

北上深的岗位明显碾压其它城市,这也反映出为什么越来越多的it从业人员毕业以后相继奔赴一线城市,除了一线城市的薪资高于二三线这个因素外,还有一个最重要的原因供需关系,因为一线岗位多,可选择性也就比较高,反观二三线的局面,很有可能你跳个几次槽,发现同行业能呆的公司都待过了...

  • 薪资范围

    img

由此可见,python的岗位薪资多数在10k~20k,想从事Python行业的可以把工作年限和薪资结合起来参考一下。

  • 学历要求 + 工作年限

    img

从工作年限来看,1-3年或者3-5年工作经验的招聘比较多,而应届生和一年以下的寥寥无几,对实习生实在不太友好,学历也普遍要求本科,多数公司都很重视入职人员学历这点毋容置疑,虽然学历不代表一切,但是对于一个企业来说,想要短时间内判断一个人的能力,最快速有效的方法无疑是从学历入手。学历第一关,面试第二关。

但是,这不代表学历不高的人就没有好的出路,现在的大学生越来越多,找工作也越来越难,竞争越来越激烈,即使具备高学历,也不能保证你一定可以找到满意的工作,天道酬勤,特别是it这个行业,知识的迭代,比其他行业来的更频密。不断学习,拓展自己学习的广度和深度,才是最正确的决定。

就业寒冬来临,我们需要的是理性客观的看待,而不是盲目地悲观或乐观。从以上数据分析,如果爱好Python,仍旧可以入坑,不过要注意一个标签有工作经验,就算没有工作经验,自己在学习Python的过程中一定要尝试独立去做一个完整的项目,爬虫也好,数据分析也好,亦或者是开发,都要尝试独立去做一套系统,在这个过程中培养自己思考和解决问题的能力。持续不断的学习,才是对自己未来最好的投资,也是度过寒冬最正确的姿势。


作者:一只写程序的猿
来源:https://juejin.cn/post/6844903837698883597

收起阅读 »

Fiddler抓取抖音视频数据

本文仅供参考学习,禁止用于任何形式的商业用途,违者自行承担责任。准备工作:手机(安卓、ios都可以)/安卓模拟器,今天主要以安卓模拟器为主,操作过程一致。抓包工具:Fiddel 下载地址:(https://www.telerik.com/download/fi...
继续阅读 »



本文仅供参考学习,禁止用于任何形式的商业用途,违者自行承担责任。

准备工作:

  1. 手机(安卓、ios都可以)/安卓模拟器,今天主要以安卓模拟器为主,操作过程一致。

  2. 抓包工具:Fiddel 下载地址:(https://www.telerik.com/download/fiddler

  3. 编程工具:pycharm

  4. 安卓模拟器上安装抖音(逍遥安装模拟器)

一、fiddler配置

在tools中的options中,按照图中勾选后点击Actions


配置远程链接:

选择允许监控远程链接,端口可以随意设置,只要别重复就行,默认8888


然后:重启fiddler!!!这样配置才能生效。

二、安卓模拟器/手机配置

首先查看本机的IP:在cmd中输入ipconfig,记住这个IP


手机确保和电脑在同一局域网下。

手机配置:配置已连接的WiFi,代理选择手动,然后输入上图ip端口号为8888

模拟器配置:设置中长按已连接wifi,代理选择手动,然后输入上图ip端口号为8888



代理设置好后,在浏览器中输入你设置的ip:端口,例如10.10.16.194:8888,就会打开fiddler的页面。然后点击fiddlerRoot certificate安装证书,要不手机会认为环境不安全。

证书名称随便设,可能还需要设置一个锁屏密码。


接下来就可以在fiddler中抓到手机/模拟器软件的包了。

三、抖音抓包

打开抖音,然后观察fiddler中所有的包


其中有个包,包类型为json(json就是网页返回的数据,具体百度),主机地址如图,包大小一般不小,这个就是视频包。

点击这个json包,在fidder右侧,点击解码,我们将视频包的json解码

解码后:点击aweme_list,其中每个大括号代表一个视频,这个和bilibili弹幕或者快手一样,每次加载一点出来,等你看完预加载的,再重新加载一些。


Json是一个字典,我们的视频链接在:aweme_list中,每个视频下的video下的play_addr下的url_list中,一共有6个url,是完全一样的视频,可能是为了应付不同环境,但是一般第3或4个链接的视频不容易出问题,复制链接,浏览器中粘贴就能看到视频了。


接下来解决几个问题

1、视频数量,每个包中只有这么几个视频,那如何抓取更多呢?

这时候需要借助模拟器的模拟鼠标翻页,让模拟器一直翻页,这样就不断会出现json包了。


2、如何json保存在本地使用

一种方法可以手动复制粘贴,但是这样很low。

所以我们使用fidder自带的脚本,在里面添加规则,当视频json包刷出来后自动保存json包。

自定义规则包:

链接:https://pan.baidu.com/s/1wmtUUMChzuSDZFYGSyUhCg

提取码:7z0l

点击规则脚本,然后将自定义规则放在如图所示位置:


这个脚本有两点需要修改的:

(1)第一行的网址

这个是从视频包的url中摘出来的,抖音会时不时更新这个url,所以不能用了也要去更新:

比如现在的已经和昨天不同了,记着修改。

(2)路径,那个是我设置json包保存的地址,自己一定要去修改,并创建文件夹,修改完记着点保存。


打开设置好模拟器和脚本后,等待一会,就可以看到文件夹中保存的包了:

四、爬虫脚本

接下来在pycharm中写脚本获取json包里的视频链接:

导包:

import os,json,requests

伪装头:

headers = {‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36’}

逻辑代码:

运行代码:


效果:

源码:

import os, json, requests# 伪装头
headers = {
   'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'
}
videos_list = os.listdir(
   'C:/Users/HEXU/Desktop/抖音数据爬取/抖音爬取资料/raw_data/')# 获取文件夹内所有json包名
count = 1# 计数, 用来作为视频名字
for videos in videos_list: #循环json列表, 对每个json包进行操作
a = open('./抖音爬取资料/raw_data/{}'.format(videos), encoding = 'utf-8')# 打开json包
content = json.load(a)['aweme_list']# 取出json包中所有视频
for video in content: #循环视频列表, 选取每个视频
video_url = video['video']['play_addr']['url_list'][4]# 获取视频url, 每个视频有6个url, 我选的第5个
videoMp4 = requests.request('get', video_url, headers = headers).content# 获取视频二进制代码
with open('./抖音爬取资料/VIDEO/{}.mp4'.format(count), 'wb') as f: #
   以二进制方式写入路径, 记住要先创建路径
f.write(videoMp4)# 写入
print('视频{}下载完成'.format(count))# 下载提示
count += 1# 计数 + 1
作者:冬晨夕阳
来源:https://blog.51cto.com/lixi/3022373 收起阅读 »

Python操作Redis

Part1前言前面我们都是使用 Redis 客户端对 Redis 进行使用的,但是实际工作中,我们大多数情况下都是通过代码来使用 Redis 的,由于小编对 Python 比较熟悉...
继续阅读 »

Part1前言

前面我们都是使用 Redis 客户端对 Redis 进行使用的,但是实际工作中,我们大多数情况下都是通过代码来使用 Redis 的,由于小编对 Python 比较熟悉,所以我们今天就一起来学习下如何使用 Python 来操作 Redis

Part2环境准备

  • Redis 首先需要安装好。
  • Python 安装好(建议使用 Python3)。
  • Redis 的 Python 库安装好(pip install redis)。

Part3开始实践

1小试牛刀

例:我们计划通过 Python 连接到 Redis。然后写入一个 kv,最后将查询到的 v 打印出来。

直接连接

#!/usr/bin/python3

import redis # 导入redis模块

r = redis.Redis(host='localhost', port=6379, password="pwd@321", decode_responses=True) # host是redis主机,password为认证密码,redis默认端口是6379
r.set('name', 'phyger-from-python-redis') # key是"name" value是"phyger-from-python-redis" 将键值对存入redis缓存
print(r['name']) # 第一种:取出键name对应的值
print(r.get('name')) # 第二种:取出键name对应的值
print(type(r.get('name')))


执行结果
服务端查看客户端列表

其中的 get 为连接池最后一个执行的命令。

连接池

通常情况下,需要连接 redis 时,会创建一个连接,基于这个连接进行 redis 操作,操作完成后去释放。正常情况下,这是没有问题的,但是并发量较高的情况下,频繁的连接创建和释放对性能会有较高的影响,于是连接池发挥作用。

连接池的原理:预先创建多个连接,当进行 redis 操作时,直接获取已经创建好的连接进行操作。完成后,不会释放这个连接,而是让其返回连接池,用于后续 redis 操作!这样避免连续创建和释放,从而提高了性能!

#!/usr/bin/python3

import redis,time # 导入redis模块,通过python操作redis 也可以直接在redis主机的服务端操作缓存数据库

pool = redis.ConnectionPool(host='localhost', port=6379, password="pwd@321", decode_responses=True) # host是redis主机,需要redis服务端和客户端都起着 redis默认端口是6379
r = redis.Redis(connection_pool=pool)
r.set('name', 'phyger-from-python-redis')
print(r['name'])
print(r.get('name')) # 取出键name对应的值
print(type(r.get('name')))


执行结果

你会发现,在实际使用中直连和使用连接池的效果是一样的,只是在高并发的时候会有明显的区别。

2基操实践

对于众多的 Redis 命令,我们在此以 SET 命令为例进行展示。

格式: set(name, value, ex=None, px=None, nx=False, xx=False)

在 redis-py 中 set 命令的参数:

参数名释义
ex过期时间(m)
px过期时间(ms)
nx如果为真,则只有 name 不存在时,当前 set 操作才执行
xx如果为真,则只有 name 存在时,当前 set 操作才执行

ex

我们计划创建一个 kv 并且设置其 ex 为 3,期待 3 秒后此 k 的 v 会变为 None

#!/usr/bin/python3

import redis,time # 导入redis模块,通过python操作redis 也可以直接在redis主机的服务端操作缓存数据库

pool = redis.ConnectionPool(host='localhost', port=6379, password="pwd@321", decode_responses=True) # host是redis主机,需要redis服务端和客户端都起着 redis默认端口是6379
r = redis.Redis(connection_pool=pool)
r.set('name', 'phyger-from-python-redis',ex=3)
print(r['name']) # 应当有v
time.sleep(3)
print(r.get('name')) # 应当无v
print(type(r.get('name')))


3秒过期

nx

由于 px 的单位太短,我们就不做演示,效果和 ex 相同。

我们计划去重复 set 前面已经 set 过的 name,不出意外的话,在 nx 为真时,我们将会 set 失败。但是人如果 set 不存在的 name1,则会成功。

#!/usr/bin/python3

import redis,time # 导入redis模块,通过python操作redis 也可以直接在redis主机的服务端操作缓存数据库

pool = redis.ConnectionPool(host='localhost', port=6379, password="pwd@321", decode_responses=True) # host是redis主机,需要redis服务端和客户端都起着 redis默认端口是6379
r = redis.Redis(connection_pool=pool)
r.set('name', 'phyger-0',nx=3) # set失败
print(r['name']) # 应当不生效
r.set('name1', 'phyger-1',nx=3) # set成功
print(r.get('name1')) # 应当生效
print(type(r.get('name')))


只有不存在的k才会被set

如上,你会发现 name 的 set 未生效,因为 name 已经存在于数据库中。而 name1 的 set 已经生效,因为 name1 是之前在数据库中不存在的。

xx

我们计划去重复 set 前面已经 set 过的 name,不出意外的话,在 nx 为真时,我们将会 set 成功。但是人如果 set 不存在的 name2,则会失败。

#!/usr/bin/python3

import redis,time # 导入redis模块,通过python操作redis 也可以直接在redis主机的服务端操作缓存数据库

pool = redis.ConnectionPool(host='localhost', port=6379, password="pwd@321", decode_responses=True) # host是redis主机,需要redis服务端和客户端都起着 redis默认端口是6379
r = redis.Redis(connection_pool=pool)
r.set('name', 'phyger-0',xx=3) # set失败
print(r['name']) # 应当变了
r.set('name2', 'phyger-1',xx=3) # set成功
print(r.get('name2')) # 应当没有set成功
print(type(r.get('name')))


只有存在的k才会被set

以上,就是今天全部的内容,更多信息建议参考 redis 官方文档。


作者:phyger
来源:https://mp.weixin.qq.com/s/bsv57OPKubD2dz0Wskn6eQ

收起阅读 »

Python列表和集合的查找原理

集合与列表查找对比关于大量数据查找,效率差距到底有多大?先看一组实例:import timeimport randomnums = [random.randint(0, 2000000) for i in range(1000)]list_test = lis...
继续阅读 »

集合与列表查找对比

关于大量数据查找,效率差距到底有多大?

先看一组实例:

import time
import random
nums = [random.randint(0, 2000000) for i in range(1000)]
list_test = list(range(1000000))
set_test = set(list_test)
count_list, count_set = 0, 0
t1 = time.time() #测试在列表中进行查找
for num in nums:
if num in list_test:
count_list += 1
t2 = time.time()
for num in nums: #测试在集合中进行查找
if num in set_test:
count_set += 1
t3 = time.time() #测试在集合中进行查找
print('找到个数,列表:{},集合:{}'.format(count_list, count_set))
print('使用时间,列表:{:.4f}s'.format(t2 - t1))
print('使用时间,集合:{:.4f}s'.format(t3 - t2))

输出结果为:

找到个数,列表:528,集合:528
使用时间,列表:7.9329s
使用时间,集合:0.0010s

对于大数据集量来说,我们清晰地看到,集合的查找效率远远的高于列表,那么本文接下来会从Python底层数据结构的角度分析为何出现如此情况。

list列表的原理

Python中的list作为一个常用数据结构,在很多程序中被用来当做数组使用,可能很多人都觉得list无非就是一个动态数组,就像C++中的vector或者Go中的slice一样。但事实真的是这样的吗?

我们来思考一个简单的问题,Python中的list允许我们存储不同类型的数据,既然类型不同,那内存占用空间就就不同,不同大小的数据对象又是如何存入数组中呢?

比如下面的代码中,我们分别在数组中存储了一个字符串,一个整形,以及一个字典对象,假如是数组实现,则需要将数据存储在相邻的内存空间中,而索引访问就变成一个相当困难的事情了,毕竟我们无法猜测每个元素的大小,从而无法定位想要的元素位置。

>>> test = ["hello world", 456, {}]
>>> test
['hello world', 456, {}]

是通过链表结构实现的吗?毕竟链表支持动态的调整,借助于指针可以引用不同类型的数据。但是这样的话使用下标索引数据的时候,需要依赖于遍历的方式查找,O(n)的时间复杂度访问效率实在是太低。

同时使用链表的开销也较大,每个数据项除了维护本地数据指针外,还要维护一个next指针,因此还要额外分配8字节数据,同时链表分散性使其无法像数组一样利用CPU的缓存来高效的执行数据读写。

实现的细节可以从其Python的源码中找到, 定义如下:

typedef struct {
PyObject_VAR_HEAD
PyObject **ob_item;
Py_ssize_t allocated;
} PyListObject;

内部list的实现的是一个C结构体,该结构体中的obitem是一个指针数组,存储了所有对象的指针数据,allocated是已分配内存的数量, PyObjectVAR_HEAD是一个宏扩展包含了更多扩展属性用于管理数组,比如引用计数以及数组大小等内容。

所以我们可以看出,用动态数组作为第一层数据结构,动态数组里存储的是指针,指向对应的数据。

既然是一个动态数组,则必然会面临一个问题,如何进行容量的管理,大部分的程序语言对于此类结构使用动态调整策略,也就是当存储容量达到一定阈值的时候,扩展容量,当存储容量低于一定的阈值的时候,缩减容量。

道理很简单,但实施起来可没那么容易,什么时候扩容,扩多少,什么时候执行回收,每次又要回收多少空闲容量,这些都是在实现过程中需要明确的问题。

假如我们使用一种最简单的策略:超出容量加倍,低于一半容量减倍。这种策略会有什么问题呢?设想一下当我们在容量已满的时候进行一次插入,随即删除该元素,交替执行多次,那数组数据岂不是会不断地被整体复制和回收,已经无性能可言了。

对于Python list的动态调整规则程序中定义如下, 当追加数据容量已满的时候,通过下面的方式计算再次分配的空间大小,创建新的数组,并将所有数据复制到新的数组中。这是一种相对数据增速较慢的策略,回收的时候则当容量空闲一半的时候执行策略,获取新的缩减后容量大小。

具体规则如下:

new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6)
new_allocated += newsize

动态数组扩容规则是:当出现数组存满时,扩充容量新加入的长度和额外3个,如果新加入元素大于9时,则扩6额外。

其实对于Python列表这种数据结构的动态调整,在其他语言中也都存在,只是大家可能在日常使用中并没有意识到,了解了动态调整规则,我们可以通过比如手动分配足够的空间,来减少其动态分配带来的迁移成本,使得程序运行的更高效。

另外如果事先知道存储在列表中的数据类型都相同,比如都是整形或者字符等类型,可以考虑使用arrays库,或者numpy库,两者都提供更直接的数组内存存储模型,而不是上面的指针引用模型,因此在访问和存储效率上面会更高效一些。

从上面的数据结构可以得出,Python list的查找时间复杂度为O(n),因为作为一个动态数组,需要遍历每一个元素去找到目标元素,故而是一种较为低效的查找方式。

set集合的原理

说到集合,就不得不提到Python中的另一种数据结构,就是字典。字典和集合有异曲同工之妙。

在Python中,字典是通过散列表或说哈希表实现的。字典也被称为关联数组,还称为哈希数组等。也就是说,字典也是一个数组,但数组的索引是键经过哈希函数处理后得到的散列值。

哈希函数的目的是使键均匀地分布在数组中,并且可以在内存中以O(1)的时间复杂度进行寻址,从而实现快速查找和修改。哈希表中哈希函数的设计困难在于将数据均匀分布在哈希表中,从而尽量减少哈希碰撞和冲突。由于不同的键可能具有相同的哈希值,即可能出现冲突,高级的哈希函数能够使冲突数目最小化。

Python中并不包含这样高级的哈希函数,几个重要(用于处理字符串和整数)的哈希函数是常见的几个类型。

通常情况下建立哈希表的具体过程如下:

  • 数据添加:把key通过哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。

  • 数据查询:再次使用哈希函数将key转换为对应的数组下标,并定位到数组的位置获取value。

哈希函数就是一个映射,因此哈希函数的设定很灵活,只要使得任何关键字由此所得的哈希函数值都落在表长允许的范围之内即可。本质上看哈希函数不可能做成一个一对一的映射关系,其本质是一个多对一的映射,这也就引出了下面一个概念——哈希冲突或者说哈希碰撞。哈希碰撞是不可避免的,但是一个好的哈希函数的设计需要尽量避免哈希碰撞。

Python中使用开放地址法解决冲突

CPython使用伪随机探测(pseudo-random probing)的散列表(hash table)作为字典的底层数据结构。由于这个实现细节,只有可哈希的对象才能作为字典的键。字典的三个基本操作(添加元素,获取元素和删除元素)的平均事件复杂度为O(1)。

Python中所有不可变的内置类型都是可哈希的。可变类型(如列表,字典和集合)就是不可哈希的,因此不能作为字典的键。

常见的哈希碰撞解决方法:

  1. 开放寻址法(open addressing)
    开放寻址法中,所有的元素都存放在散列表里,当产生哈希冲突时,通过一个探测函数计算出下一个候选位置,如果下一个获选位置还是有冲突,那么不断通过探测函数往下找,直到找个一个空槽来存放待插入元素。开放地址的意思是除了哈希函数得出的地址可用,当出现冲突的时候其他的地址也一样可用,常见的开放地址思想的方法有线性探测再散列,二次探测再散列等,这些方法都是在第一选择被占用的情况下的解决方法。
  2. 再哈希法
    这个方法是按顺序规定多个哈希函数,每次查询的时候按顺序调用哈希函数,调用到第一个为空的时候返回不存在,调用到此键的时候返回其值。
  3. 链地址法
    将所有关键字哈希值相同的记录都存在同一线性链表中,这样不需要占用其他的哈希地址,相同的哈希值在一条链表上,按顺序遍历就可以找到。
  4. 公共溢出区
    其基本思想是:所有关键字和基本表中关键字为相同哈希值的记录,不管他们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表。
  5. 装填因子α
    一般情况下,处理冲突方法相同的哈希表,其平均查找长度依赖于哈希表的装填因子。哈希表的装填因子定义为表中填入的记录数和哈希表长度的比值,也就是标志着哈希表的装满程度。直观看来,α越小,发生冲突的可能性就越小,反之越大。一般0.75比较合适,涉及数学推导。

在Python中一个key-value是一个entry,entry有三种状态:

  1. Unused:me_key == me_value == NULL

Unused是entry的初始状态,key和value都为NULL。插入元素时,Unused状态转换成Active状态。这是me_key为NULL的唯一情况。

  1. Active:me_key != NULL and me_key != dummy 且 me_value != NULL

插入元素后,entry就成了Active状态,这是me_value唯一不为NULL的情况,删除元素时Active状态可转换成Dummy状态。

  1. Dummy:me_key == dummy 且 me_value == NULL

此处的Dummy对象实际上一个PyStringObject对象,仅作为指示标志。Dummy状态的元素可以在插入元素的时候将它变成Active状态,但它不可能再变成Unused状态。

为什么entry有Dummy状态呢?

这是因为采用开放寻址法中,遇到哈希冲突时会找到下一个合适的位置,例如某元素经过哈希计算应该插入到A处,但是此时A处有元素的,通过探测函数计算得到下一个位置B,仍然有元素,直到找到位置C为止,此时ABC构成了探测链,查找元素时如果hash值相同,那么也是顺着这条探测链不断往后找,当删除探测链中的某个元素时,比如B,如果直接把B从哈希表中移除,即变成Unused状态,那么C就不可能再找到了,因为AC之间出现了断裂的现象,正是如此才出现了第三种状态-Dummy,Dummy是一种类似的伪删除方式,保证探测链的连续性。

set集合和dict一样也是基于散列表的,只是他的表元只包含键的引用,而没有对值的引用,其他的和dict基本上是一致的,所以在此就不再多说了。并且dict要求键必须是能被哈希的不可变对象,因此普通的set无法作为dict的键,必须选择被“冻结”的不可变集合类:frozenset。顾名思义,一旦初始化,集合内数据不可修改。

一般情况下普通的顺序表数组存储结构也可以认为是简单的哈希表,虽然没有采用哈希函数(取余),但同样可以在O(1)时间内进行查找和修改。但是这种方法存在两个问题:

  • 扩展性不强
  • 浪费空间

dict是用来存储键值对结构的数据的,set其实也是存储的键值对,只是默认键和值是相同的。Python中的dict和set都是通过散列表来实现的。下面来看与dict相关的几个比较重要的问题:

  • dict中的数据是无序存放的。操作的时间复杂度,插入、查找和删除都可以在O(1)的时间复杂度。这是因为查找相当于将查找值通过哈希函数运算之后,直接得到对应的桶位置(不考虑哈希冲突的理想情况下),故而复杂度为O(1)。

  • 由于键的限制,只有可哈希的对象才能作为字典的键和set的值。可hash的对象即Python中的不可变对象和自定义的对象。可变对象(列表、字典、集合)是不能作为字典的键和set的值的。

与list相比:list的查找和删除的时间复杂度是O(n),添加的时间复杂度是O(1)。但是dict使用hashtable内存的开销更大。为了保证较少的冲突,hashtable的装载因子,一般要小于0.75,在Python中当装载因子达到2/3的时候就会自动进行扩容。

参考资料:

Python dict和set的底层原理:https://blog.csdn.net/liuweiyuxiang/article/details/98943272

python 图解Python List数据结构:https://blog.csdn.net/u014029783/article/details/107992840

作者:严天宇
来源:https://mp.weixin.qq.com/s/wvgf7GpbCoeDsLOp1WAFPg
收起阅读 »

Python运算符优先级及结合性

当多个运算符出现在一起需要进行运算时,Python 会先比较各个运算符的优先级,按照优先级从高到低的顺序依次执行;当遇到优先级相同的运算符时,再根据结合性决定先执行哪个运算符:如果是左结合性就先执行左边的运算符,如果是右结合性就先执行右边的运算符。运算符的优先...
继续阅读 »

当多个运算符出现在一起需要进行运算时,Python 会先比较各个运算符的优先级,按照优先级从高到低的顺序依次执行;

当遇到优先级相同的运算符时,再根据结合性决定先执行哪个运算符:如果是左结合性就先执行左边的运算符,如果是右结合性就先执行右边的运算符。

运算符的优先级

在数学运算2 + 4 * 3中,要先计算乘法,再计算加法,否则结果就是错误的。所谓优先级,就是当多个运算符出现在一起时,需要先执行哪个运算符,那么这个运算符的优先级就更高。 

Python中运算符优先级如下表所示,括号的优先级是最高的,无论任何时候优先计算括号里面的内容,逻辑运算符的优先级最低,在表中处在同一行运算符的优先级一致,上一层级的优先级高于下一层级的。算术运算符可以分为四种,幂运算最高,其次是正负号,然后是 “* / // %”,最后才是加减“+ -”。

运算符

描述

()

括号

**

幂运算

~

按位取反

+、-

正号、负号

、/、 %、 //

乘、除、取模、取整除

、-

加、减

>> 、<<

右移、左移

&

按位“与”

、|

按位“异或”,按位“或”

<=  、< 、>、 >=

比较运算符

==、!=

等于、不等于

=、%=、/=、//=、-=、+=、*=、**=

赋值运算符

is、is not

身份运算符

in、not in

成员运算符

and or not

逻辑运算符

运算符的结合性

在多种运算符在一起进行运算时,除了要考虑优先级,有时候还需要考虑结合性。当同时出现多个优先级相同的运算符时,先执行左边的叫左结合性,先执行右边的叫右结合性。如:5 / 2 * 4,由于/*的优先级相同,所以只能参考运算符的结合性了,/*都是左结合性的,所以先计算除法,再计算乘法,结果是10.0。Python中大部分运算符都具有左结合性,其中,幂运算**、正负号、赋值运算符等具有右结合性。

>>> 5 / 2* 4# 左结合性运算符
10.0
>>> 2 ** 2 ** 3# 右结合性,等同于2 ** (2 **3)
256

虽然Python运算符存在优先级的关系,但写程序时不建议写很长的表达式,过分依赖运算符的优先级,比如:2 ** -1 % 3 / 5 ** 3 *4,这样的表达式会大大降低程序的可读性。因此,建议写程序时,遵守以下两点原则,保证运算逻辑清晰明了。

  1. 尽量不要把一个表达式写的过长过于复杂,如果计算过程的确需要,可以尝试将它拆分几部分来写。
  2. 尽量多使用()来控制运算符的执行顺序,使用()可以让运算的先后顺序变得十分清楚。



作者:刘文飞 

来源:https://mp.weixin.qq.com/s/fXzg2L6emlEVCCT-t4Pk6Q





收起阅读 »