跳到主要内容

django静态文件管理

· 阅读需 5 分钟
Jason Lee

前言

可以分为开发和部署两个阶段进行介绍。

开发

STATIC_URL = "static/"(默认设置好的),这一项是告诉django,静态文件存在每一个应用的哪一个文件夹下,方便后面收集静态文件到一处。还有一个用途是,给出资源的引用位置。例如,如果STATIC_URL设置为'/static/',则可以通过http://yourdomain.com/static/css/styles.css来访问名为styles.css的CSS文件。

另外,如果某些静态文件不属于任何一个应用,或者所在文件夹名字和STATIC_URL不同,可以将文件夹名放在STATICFILES_DIRS:

STATICFILES_DIRSSTATICFILES_DIRSSTATICFILES_DIRSSTATICFILES_DIRSSTATICFILES_DIRS = [
BASE_DIR / "static",
"/var/www/static/",
]

这两项都和STATIC_URL不同。

当需要在开发阶段提供静态文件服务时,可以在根应用的urls.py里设置:

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
# ... the rest of your URLconf goes here ...
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

还有一种用户上传的静态文件,用MEDIA_URL,类似于STATIC_URL.

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
# ... the rest of your URLconf goes here ...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

部署

首先要明确,部署阶段时的静态文件管理到底为什么要和开发阶段区分开来?

在开发阶段,开发人员通常会使用各种工具和技术来编写和测试代码。他们经常需要修改代码和调试,以确保功能的正确实现。在这个阶段,开发人员通常希望在每次修改代码后能够快速地查看结果,以便及时调试和验证功能。为了实现这个目标,他们通常会使用本地开发服务器,在其中运行代码并动态生成页面。在开发过程中,静态文件(如HTML、CSS、JavaScript、图像等)可能会被直接从开发环境的文件系统加载,或者通过开发服务器进行实时编译和转换。

然而,当代码准备好部署到生产环境时,情况就会有所不同。在部署阶段,我们通常希望优化网站或应用程序的性能和加载速度。这就涉及到对静态文件的管理和优化。

静态文件管理在部署阶段的目标是将这些文件进行优化和准备,以便在生产环境中提供最佳的性能和用户体验。这可能包括压缩和合并CSS和JavaScript文件、优化图像大小和格式、设置缓存策略以减少加载时间等。

另外,静态文件管理还可以包括将文件上传到内容分发网络(Content Delivery Network,CDN)上,以确保文件在全球范围内的快速传输和分发。

通过将开发阶段和部署阶段的静态文件管理分离开来,可以使开发人员专注于开发和调试代码,而不必担心性能优化和部署的细节。这也有助于确保在生产环境中获得最佳的性能和用户体验。

STATIC_ROOT:collectstatic 将收集静态文件进行部署的目录的绝对路径。例如: "/var/www/example.com/static/",而搜集的源头就是上述的STATIC_URL和STATICFILES_DIRS。

参考资料

https://docs.djangoproject.com/zh-hans/4.2/ref/settings/#std-setting-STATIC_URL

基于docker的django+vue网站部署

· 阅读需 5 分钟
Jason Lee

前言

部署和开发是两个完全不同的工作。得益于 docker 等虚拟化技术,现在的部署已经简单许多了。尽管作者是用原生 linux 环境做示范,但是我选用 docker 作为部署工具。主要以查找博客和问 chatgpt 来学习,中间由于对 nginx 不了解,还看了相关的视频教程。大概花了三四天时间,从本地 win 环境,到最终的云主机。现在,我终于可以说,自己是一个全栈工程师了,真正从应用的开发到落地都有了粗浅的涉及。

总体流程图

这是两容器之间的配合示意图。外部的请求从宿主机的 8000 端口进来,然后被 docker 投射到容器 1,交给 nginx 处理。nginx 根据请求的 url,判断是动态还是静态请求。如果是静态,则去找 vue 项目打包来的文件夹 dist 内的资源并返回;如果是动态资源,通过 http 方式转发给容器 2 的端口 8000。uwsgi 处理完逻辑后,将可能有的响应转回给 nginx,再返回给用户。

项目结构

wey
├── docker-compose.yml
├── wey-frontend
│ ├── dist
│ ├── Dockerfile
│ ├── index.html
│ ├── nginx.conf
│ ├── node_modules
│ ├── package-lock.json
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ ├── README.md
│ ├── src
│ ├── tailwind.config.js
│ └── vite.config.js
└── wey_backend
├── account
├── chat
├── db.sqlite3
├── Dockerfile
├── manage.py
├── media
├── notification
├── pip.conf ## 给容器2的pip换源
├── post
├── requirements.txt
├── scripts
├── search
├── uwsgi.ini
└── wey_backend

容器 1 配置

# nginx.conf

server {
listen 80;
server_name 127.0.0.1;

location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}

location /api/ {
proxy_pass http://django:8000;
}

location /static {
alias '/var/www/mysite/assets/static';
}

location /media {
alias '/var/www/mysite/assets/media';
}

location /admin/ {
proxy_pass http://django:8000;
}

}

对应的 dockerfile:

# 使用一个基础的 Node.js 镜像作为构建环境
FROM node:14 as builder

# 设置工作目录
WORKDIR /app

# 复制 package.json 和 package-lock.json 文件到容器中
COPY package*.json ./

# 安装依赖
RUN npm install

# 复制前端应用源代码到容器中
COPY . .

# 执行构建命令
RUN npm run build

# 创建一个新的容器镜像
FROM nginx:latest

# 复制构建产物到 Nginx 的默认静态文件目录
COPY --from=builder /app/dist /usr/share/nginx/html

# 将自定义的 nginx.conf 文件复制到容器中,覆盖默认配置
# remove the default conf
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 暴露容器的 80 端口
EXPOSE 80


容器 2 配置

[uwsgi]

module = wey_backend.wsgi
py-autoreload=1
http-timeout = 86400

http=0.0.0.0:8000

master=true
processes = 4
threads=2

chmod-socket=666

vaccum=true
die-on-term=true

对应的 dockerfile 配置:

FROM python:3.11
RUN mkdir /code
WORKDIR /code
COPY . /code

COPY pip.conf /root/.pip/pip.conf

# uwsgi setup
RUN pip install uwsgi
RUN pip install -r requirements.txt

EXPOSE 8000

CMD ["uwsgi", "--ini", "uwsgi.ini"]

docker-compose

docker compose 是在有多个容器且容器之间存在依赖关系时适用。它取代的是命令行构建镜像和创建容器的方式,使得部署更简洁。比如在 nginx 中,ports:"8000:80",是在建立端口映射。volumes:是在把特定的目录在整个 docker 应用进程内建立一个索引,实现文件共享。

version: "3.9"
services:
nginx:
build: ./wey-frontend/
restart: always
ports:
- "8000:80"
volumes:
- web_static:/var/www/mysite/assets/static
- web_media:/var/www/mysite/assets/media
depends_on:
- django


django:
build: ./wey_backend/
restart: always
expose:
- 8000
command: >
sh -c "python manage.py collectstatic --noinput
&& uwsgi --ini uwsgi.ini"
volumes:
- web_static:/var/www/mysite/assets/static
- web_media:/var/www/mysite/assets/media

volumes:
web_static:
web_media:

总结

这篇文章实现了用 docker 来部署 django+vue 的前后端分离网站,并用 docker-compose 来简化了部署。

为什么中间遭遇了较大的挫折并且一度想放弃呢?反思的结果是,自己一开始就把多个容器放在一起考虑,导致头绪纷乱无章。后面在尝试了测试两个容器是否能独立运行,然后联合运行,才成功。

pytorch tutorial 笔记+注解

· 阅读需 9 分钟
Jason Lee

前言

pytorch,一个熟悉又陌生的朋友。以前用到它时,常常是哪里不会查哪里;等过一阵子又忘记了用法还得重新查。虽然毕业论文课题用到了框架,但是还处在拾人牙慧的阶段。这一次,经过前段时间 python 和 django 的学习,将提炼出来的学习经验用于 pytorch,先将官方文档的 tutorial 看完,然后看视频从零开始做一个项目。

Tensor

Tensor 和 numpy 的 array 很相似,意味着一些 api 的名字和功能相同。介绍 Tensor 的创建时,官网列举的来源包含:python list、numpy array、继承另一个 tensor 的形状和数据类型、随机/全 1/全 0。但是,在实际训练中,你面对的是这样的 Tensor:

batch_size = 64

# Create data loaders.
train_dataloader = DataLoader(training_data, batch_size=batch_size)
test_dataloader = DataLoader(test_data, batch_size=batch_size)

for X, y in test_dataloader:
print(f"Shape of X [N, C, H, W]: {X.shape}")
print(f"Shape of y: {y.shape} {y.dtype}")
break

X,y 都是 tensor。要理解 X 从哪里来的,我们从自定义 dataset 入手:

class CustomImageDataset(Dataset):
def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
self.img_labels = pd.read_csv(annotations_file)
self.img_dir = img_dir
self.transform = transform
self.target_transform = target_transform

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

def __getitem__(self, idx):
img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
image = read_image(img_path)
label = self.img_labels.iloc[idx, 1]
if self.transform:
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label

read_image 的全称是 torchvision.io.read_image,将 jpeg 或者 png 读取为 uint8 的 tensor。在别的实例中,有可能在transform中添加一个 ToTensor(),ToTensor converts a PIL image or NumPy ndarray into a FloatTensor. and scales the image’s pixel intensity values in the range [0., 1.].

tensor 的属性只有三个:shape、dtype、device。其中 shape 和 size()经常搞混,得到错误提示:xxx 数据类型没有 shape 属性/size()方法,这里对他们做一个区分。

在 Python 和 PyTorch 中,以下数据类型具有 shape 属性: NumPy 数组(numpy.ndarray):NumPy 是一个用于科学计算的 Python 库,其数组具有 shape 属性。可以使用 ndarray.shape 来获取数组的形状。 PyTorch 张量(torch.Tensor):PyTorch 是一个深度学习框架,其中的张量对象具有 shape 属性。可以使用 tensor.shape 来获取张量的形状。 需要注意的是,PyTorch 的张量类型包括 CPU 张量和 CUDA 张量,它们都具有 shape 属性。 在 PyTorch 中,size()方法和 shape 属性实际上是等价的,它们都用于获取张量的形状。因此,在 PyTorch 张量上使用 size()方法和使用 shape 属性将返回相同的结果。 在 PyTorch 中,shape 和 size()都返回一个 torch.Size 对象,它是一个元组子类,可以像元组一样进行索引操作。例如,对于形状为(2, 3)的张量,可以通过 shape[0]或 size()[0]来访问第一个维度的大小。 对于其他 Python 数据类型,如元组、列表或字符串,没有 shape 属性或 size()方法,可以使用 len()函数来获取元素的数量或长度。

数据加载

数据加载需要两个类:Dataset 和 DataLoader:

from torch.utils.data import Dataset, DataLoader

Dataset

根据数据集的特点进行数据加载、预处理和标准化等操作。以前面的自定义 dataset 为例,它首先继承了 Dataset 类,然后分别实现了init, len, getitem方法。其中,getitem方法是最重要的,它可以实现文件从硬盘到内存的读取,然后转化为 tensor,以及可选的 transform 预处理步骤。

Dataloader

DataLoader是一个数据加载器类,用于将 Dataset 中的数据分批加载到模型中进行训练或推理。它提供了多线程数据加载、批处理和数据随机打乱等功能。通过使用 DataLoader,你可以方便地迭代整个数据集,并按照指定的批次大小获取数据。

DataLoader 类接收一个 Dataset 对象作为参数,并可以配置以下参数:

  • batch_size:指定每个批次的样本数量。
  • shuffle:指定是否对数据进行随机打乱。
  • num_workers:指定用于数据加载的线程数。

最重要的是,DataLoader 是一种可迭代序列,可以用 next 进行数据的读取:

train_features, train_labels = next(iter(train_dataloader))

在实际代码中,常使用 enumerate()函数。它返回一个生成器对象,该生成器生成索引-元素对:

for batch, (X, y) in enumerate(dataloader):
X, y = X.to(device), y.to(device)

在上面这个例子中,batch 就是第几个 batch 的意思,(X, y)就是 Dataset 返回的元素和标签。所以上述代码将遍历一次整个数据集,按 batch 返回数据和 batch 的索引。

在这里可以引入 epoch 和 batch 的概念。epoch 表示完整扫描整个数据集的次数,batchsize 表示每次 epoch 中,计算一次损失需要读取的样本数量。而 batch 就是样本总数除以 batchsize 的取整。所以,一个 epoch 中会有若干个 batch。

批次大小的选择涉及到多个因素,包括内存限制、计算资源、模型性能等。较大的批次大小可以提高计算效率,但可能需要更多的内存,并且可能导致模型的收敛速度变慢。较小的批次大小可以减少内存占用,但可能导致计算效率降低。 通常,选择适当的批次大小需要进行实验和调整。一般而言,较大的批次大小在具有较大训练集和较强计算能力的情况下可以获得更好的性能,而较小的批次大小对于内存受限的情况或者需要更好的模型泛化能力的情况可能更合适。

读取数据时的 cpu 和 gpu

Dataset 中,数据通常会被预处理、转换为张量等操作,并在 CPU 上进行。然后,通过 DataLoader 将处理后的数据以指定的批量大小加载到内存中,并返回一个或多个批量的迭代器。在训练过程中,可以将这些批量数据移动到 GPU 上,并将其传递给模型进行训练或推断。完整的示例代码:

import torch
from torch.utils.data import Dataset, DataLoader

class MyDataset(Dataset):
def __init__(self, data):
self.data = data

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

def __getitem__(self, index):
# 数据预处理和转换操作在CPU上进行
sample = self.data[index]
processed_sample = preprocess(sample)
tensor_sample = torch.tensor(processed_sample, dtype=torch.float32)
return tensor_sample

# 创建数据集
dataset = MyDataset(data)

# 创建数据加载器
batch_size = 32
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)

# 检查GPU是否可用
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 别忘了将网络模型也放到gpu中(如果可用)
model = Model().to(device)

# 在训练过程中,将批量数据移动到GPU上进行计算
for batch in dataloader:
inputs = batch.to(device)
targets = get_targets(batch) # 假设有一个函数用于获取目标值
targets = targets.to(device)

# 在GPU上进行模型的训练或推断
outputs = model(inputs)
loss = loss_function(outputs, targets)
optimizer.zero_grad()
loss.backward()
optimizer.step()

子组件外面一层的div为什么要加v-if

· 阅读需 2 分钟
Jason Lee

项目中有这样一段代码:

<div class="p-4 bg-white border border-gray-200 rounded-lg mb-4" v-if="post.id">
<PostItem v-bind:post="post" />
</div>

PostItem作为子组件被包裹在一层div中,外层div起到控制样式的作用。如果去掉v-if,前端报错:PostItem内无法获得post数据。一开始,我想不明白:为什么post在script中赋值,而不能直接使用?

chatgpt给出的回答是:

如果去掉子组件外面一层的div中的v-if指令,可能会导致父组件无法正确地向子组件传值。这是因为v-if指令会影响组件的渲染时机,如果去掉v-if指令,组件会在父组件渲染时一并渲染,此时父组件传递给子组件的数据可能还没有准备好,导致子组件无法正确获取数据。

当加上v-if指令后,只有在v-if条件为真时,子组件才会被渲染,这样可以保证父组件传递给子组件的数据已经准备好了。因此,如果要在父组件向子组件传值时,建议在子组件外面加一层带有v-if指令的div来控制子组件的渲染时机,以保证子组件能够正确地获取父组件传递的数据。

django get和post获取请求参数或路由变量

· 阅读需 2 分钟
Jason Lee

前言

前后端分离的情况下,数据通过 axios 传输到后端,如何取出携带的数据?

get 请求

get 请求里面,可能携带的参数位置都在 url 上,分别是路径上的参数和查询参数:

类视图中的获取方式如下:

def get(self, request, *args, **kwargs):
user_id = kwargs.get('id') # 路径参数
filter_type = request.GET.get('query') # 查询参数

而在 urls.py 中,pk 要作为参数的名字被设置:

path('friend/request/<uuid:pk>/', api.FriendshipRequestView.as_view(), name='friendshiprequest'),

post 请求

两种可能的参数,一种是通过表单传来的数据,另一种是通过 json 格式传来的数据:

axios.post('/api/login/', this.form) # 通过表单
axios.post('api/post/', {'body': this.body}) # 通过json

他们的获取方式分别是:

def post(self, request,  *args, **kwargs):
form = request.POST # 表单
body = request.data.get('body') # json

request.data 也可以获得表单数据,所以建议还是都用 request.data

django默认和自定义的用户身份验证

· 阅读需 3 分钟
Jason Lee

前言

在做项目时,身份验证是目前疑虑最多的地方:为什么这里要新建一个User模型?为什么要重写管理器?这篇文章通过查阅文档,尝试从最佳实践的角度给出答案。

默认验证方式

模型 视图函数 表单

注册 登录 登出

默认User模型

默认User模型主要包含以下字段:username、password、email、first_name、last_name。也就是说,如果有别的字段的需求(比如用户头像),就要自定义用户模型。目前暂不涉及权限,等到后续项目中接触到了,再添加。

视图函数

注册

没有内置的注册视图,有内置的注册表单UserCreationForm。UserCreationForm的要求字段为用户名、邮箱和两次密码输入。

# views.py
class SignUpView(FormView):
template_name = 'core/signup.html'
form_class = forms.SignUpForm
success_url = '/login/'

def form_valid(self, form):
form.save()
return super().form_valid(form)

# forms.py
class SignUpForm(UserCreationForm):
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']

username = forms.CharField(widget=forms.TextInput(attrs={
'placeholder': 'Your username',
'class': 'w-full py-4 px-6 rounded-xl'
}))
email = forms.CharField(widget=forms.EmailInput(attrs={
'placeholder': 'Your email address',
'class': 'w-full py-4 px-6 rounded-xl'
}))
password1 = forms.CharField(widget=forms.PasswordInput(attrs={
'placeholder': 'Your password',
'class': 'w-full py-4 px-6 rounded-xl'
}))
password2 = forms.CharField(widget=forms.PasswordInput(attrs={
'placeholder': 'Repeat password',
'class': 'w-full py-4 px-6 rounded-xl'
}))

登录

有内置视图LoginView,有内置的表单AuthenticationForm

# forms.py
class LogInForm(AuthenticationForm):
username = forms.CharField(widget=forms.TextInput(attrs={
'placeholder': 'Your username',
'class': 'w-full py-4 px-6 rounded-xl'
}))
password = forms.CharField(widget=forms.PasswordInput(attrs={
'placeholder': 'Your password',
'class': 'w-full py-4 px-6 rounded-xl'
}))

# urls.py
path('login/', auth_views.LoginView.as_view(template_name='core/login.html', authentication_form=LogInForm), name='login'),

# settings.py
LOGIN_REDIRECT_URL = '/'

登出

有内置视图LogoutView,无需视图

# urls.py
path('logout/', auth_views.LogoutView.as_view(), name='logout')

# settings.py
LOGOUT_REDIRECT_URL = '/'

自定义验证方式

有两个可以自定义的地方:认证后端和User模型

认证后端

前面提到的authenticate和login方法都是django默认认证后端提供的。

User模型

一般有两个需要扩展:模型本身和管理器(manager)

# models.py

class CustomUserManager(UserManager):
def _create_user(self, name, email, password, **extra_fields):
if not email:
raise ValueError("You have not provided a valid e-mail address")

email = self.normalize_email(email)
user = self.model(email=email, name=name, **extra_fields)
user.set_password(password)
user.save(using=self._db)

return user

def create_user(self, name=None, email=None, password=None, **extra_fields):
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(name, email, password, **extra_fields)

def create_superuser(self, name=None, email=None, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
return self._create_user(name, email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField(unique=True)
name = models.CharField(max_length=255, blank=True, default='')
avatar = models.ImageField(upload_to='avatars', blank=True, null=True)

is_active = models.BooleanField(default=True)
is_superuser = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)

date_joined = models.DateTimeField(default=timezone.now)
last_login = models.DateTimeField(blank=True, null=True)

objects = CustomUserManager()

USERNAME_FIELD = 'email'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = []

JWT认证

· 阅读需 5 分钟
Jason Lee

前言

各种认证方式一直是我头痛的点。奇怪的是,几乎所有的 web 开发教程都默认你已经会了,而不会专门教学,只是一笔带过。你只能自己去找博客看。参考视频教程

jwt 归为一种身份验证(authentication)的方式。

令牌(token)

这个词挺神奇的。transformer 结构里有 token 这个概念,这里也有。不过他们的含义应该完全不同。在 web 开发中,token 指的是令牌,或者凭证。

最开始,人们使用 session+cookie 的认证方式。这种情景下,服务器存储用户的相关信息,仅返回给浏览器一个 session-id。浏览器下次请求,将在请求中的 cookie 中携带 session-id,服务器接收到这个 id,并且能查找到相关用户的信息,则认证成功。

但是这种模式越来越不适合当代多服务器的 web 模式。因为这相当于要求每个服务器都要存储一个用户信息表。所以,需要一种新的认证方式。

针对上述问题,一种解决方案是,不在服务器单独建用户信息表,而是将用户信息(token)存起来发给浏览器,浏览器下次来直接带完整的用户信息。然后,因为服务器这时候已经不存储任何用户信息,且所有信息完全来自用户,为了防止用户携带假信息进行欺诈,在发给浏览器的信息中加入签名进行防伪。

至于 JWT 的存储方式,可以是 cookie,也可以是 localstorage 等。

实现方式

上图是 jwt 的结构。三段式,分别是头部、负荷和签名。头部有两个字段:生成签名用到的加密算法和 token 类型。负荷中的 iat 指的是 issue at,签发时间。签名框里的意思是,头部加负荷,再加上密钥,一同经过 sha256 算法,生成了签名。

chatgpt 的解释:

JWT 的验证签名原理如下:

服务端在接收到 JWT 后,会对头部和载荷进行验证,验证包括以下几个步骤:

a. 解析出 JWT 中的头部和载荷,并检查头部中的算法是否为可信的算法。

b. 对头部和载荷使用相同的算法和密钥进行签名,得到签名结果。

c. 将服务端得到的签名结果和 JWT 中附带的签名结果进行比较,如果两个签名结果相同,则认为 JWT 是有效的,否则认为 JWT 是无效的。

通过以上验证过程,JWT 的签名可以保证数据的完整性和真实性,从而确保 JWT 的安全性。值得注意的是,JWT 并不加密,只是通过签名来验证数据的真实性和完整性,因此在使用 JWT 时,应该注意保护 JWT 的传输过程,避免 JWT 被篡改或窃取。

其他

  • 在软件售卖场景中,有把 jwt 作为许可证来使用的。即软件使用过程中不断去读取 jwt token 文件,如果验证出过期,则软件无法使用。与一般的登录场景类似的是,许可证也是由服务端(软件卖方)签发,每次使用都要检查 token。

我在2023年回顾python3基础

· 阅读需 5 分钟
Jason Lee

到底什么是数字、元组、字符串的不可变性?

不可变性,指的是不能改变原有位置上的元素;不包括在空位置上创建元素。所以,元组都可以连接其他同类元素使得自己变得更长,而不能改变原来的元素。

数字、字符串的不可变性:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变 a 的值,相当于新生成了 a。字符串类似。

生成器和迭代器

生成器和迭代器都可以提高性能和内存效率,因为它们可以一次生成一个元素,而不必在内存中存储整个数据集。通过理解生成器和迭代器的工作原理,我们可以更有效地处理大型数据集。

假如有一个场景,我们深度学习训练时读取数据,由于训练图片很多,不可能一次性读进内存,所以采用生成器或迭代器的方式,一次读取几张图像。

生成器和迭代器实现的事情都是类似的,即一次只输出少量的数据,只是他们的创建方式有差异。

迭代器

迭代器是一种对象,它可以用于迭代序列中的元素。迭代器具有一个__next__()方法,该方法返回序列中的下一个元素,并在没有更多元素时引发StopIteration异常。

创建迭代器的方法:一个从现有序列中直接创造:iter(seq),另一种需要创建一个类(由类实例化出对象),类带有__iter____next__方法:

class FibonacciIterator:
def __init__(self):
self.a = 0
self.b = 1

def __iter__(self):
return self

def __next__(self):
result = self.b
self.a, self.b = self.b, self.a + self.b
return result

fib_iter = FibonacciIterator()
for i in range(10):
print(next(fib_iter))
## 另外一种调用方式
for value in fib_iter:
print(value)

生成器

生成器是一种特殊的函数,它使用yield关键字来产生一个值,并暂停函数的执行。每次调用生成器函数时,它都会从上一次停止的位置继续执行,直到遇到yield语句。生成器是一种简单而强大的工具,可以在处理大型数据集时提高性能和内存效率。

def fibonacci():
a, b = 0, 1
while True:
yield b
a, b = b, a + b

f = fibonacci()
for i in range(10):
print(next(f))
## 也可以:
for value in f:
print(value)

可以看出,生成器在这个例子中会简短一些。同时可以看出,调用的时候,生成器和迭代器的两种方式都一样,分别是固定次数(可能会超出边界而引发StopIteration异常,以及forin循环。创建时的不同是:生成器是一个函数,而迭代器是一个对象。

不定长参数

不定长参数就是那种在定义函数或者方法时,括号里的参数带一个或者两个星星的。一个星星被放入元组,实参不带参数关键字;两个星星被放入字典,实参带关键字,关键字被当作字典的键名。

def printinfo( arg1, *vartuple, **vardict ):
"打印任何传入的参数"
print ("输出: ")
print (arg1)
print(vartuple)
print(vardict)

printinfo( 70, 6, a=60, b=50)

out:

输出:
70
(6,)
{'a': 60, 'b': 50}

异常处理

raise 抛出异常,程序把异常打印出来(抛给用户),并停止后续代码的运行。如果没有raise,程序将继续运行。

flex/grid布局在tailwindcss中的使用

· 阅读需 4 分钟
Jason Lee

前言

tailwindcss 使得不用离开 html 界面就可以完成 css coding。常见的样式多写几次自然就记住了,如果不会的,还可以查文档,但是遇到布局相关的 flex 和 grid,需要一定的知识。

flex

菜鸟教程的基础上,做一点总结。

基本组成,flex container 和 flex item。他们可以分别设置属性。

容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做 main start,结束位置叫做 main end;交叉轴的开始位置叫做 cross start,结束位置叫做 cross end。项目默认沿主轴排列。单个项目占据的主轴空间叫做 main size,占据的交叉轴空间叫做 cross size。

所以想到 flex 布局,应该在脑海中想起的画面是:一个容器内有两条轴,一条横轴从左到右,一条竖轴从上到下。项目也是按上述顺序排列。

flex 容器的属性

  • flex-direction:决定主轴是哪一条及其方向
  • flex-wrap:主轴空间不够时是否换行
  • flex-flow:flex-direction 和 flex-flow 一起设置
  • justify-content:主轴的对齐方式
  • align-items:交叉轴的对齐方式
  • align-content:align-content 属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用

这里对 align-content 做出一点说明。所谓的多根轴线,是指在元素较多,形成换行后,每一行元素所在的轴,而不是指主轴和交叉轴:

如图,每一根红线都是一个轴。也就是说,align-content 定义了多行元素的整体对齐方式。作为对比,align-items 的对象是当行元素:

换句话说,多行元素用 align-content,单行元素用 align-items。

flex 项目属性

去除掉不常用的 order,有五个属性:

  • flex-grow:当有剩余空间时,项目放大比例
  • flex-shrink:空间不足时,缩小比例
  • flex-basis:定义了在分配多余空间之前,项目占据的主轴空间(main size)
  • flex:上述三个属性的快捷设置
  • align-self:对单个项目脱离 align-items 的对齐方式

关键是理解剩余空间。首先空间都是针对主轴来说的。

grid

grid 适合设置多行多列的,目前我遇到的情况是,设置好几个列,还有列之间的间隔,然后确定实际的一个元素占几列,就可以完成绝大部分任务了。

flex 和 grid 的 tailwindcss 写法

flex

<div class="flex 主轴对齐 交叉轴对齐 元素间距">
...
</div>

grid

<div class="max-w-7xl mx-auto(居中) grid grid-cols-n gap-n">
<div class="col-span-x">
...
</div>
<div class="col-span-n-x">
...
</div>
</div>

django-rest-framework使用入门

· 阅读需 2 分钟
Jason Lee

浏览器行为

当我用drf提供的浏览器可视化界面,发现了一个奇怪的问题。同样是访问http://127.0.0.1:8000/snippets/,如果我之前填了一个表单并提交,那么按刷新按钮,会再提交一次,也就是会在数据库中添加一条和上一次一样的数据。只有在地址栏回车一下,才会真正回到列表中。这说明,如果页面中有表单,刷新按钮可能会执行post请求,而浏览器回车才能保证发出的get请求。

到底什么是序列化

python的数据格式(字典、列表等),转化为可以传输的字符串(JSON、HTML等),就是序列化。序列化类的实例并不是直接生成json数据,而是将模型实例转为一种python字典,然后由视图函数的JSONRenderer().render()函数转化为json字符串。

反过来,如果接受到了json,按照下列方式:

import io

stream = io.BytesIO(content)
data = JSONParser().parse(stream)

serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>

和表单的处理很相似。