今日已更新 143 条资讯 | 累计 40588 条内容
关于我们

标签:#python

找到 1205 篇相关文章

AI 资讯

Building StudySift Without Third-Party Dependencies

Building StudySift Without Third-Party Dependencies Introduction What if a useful study tool could be built without installing a single third-party package? For the Zero Dependency Hackathon, I built StudySift , a command-line tool that converts lecture transcripts into structured, revision-friendly study notes. The idea is simple: give StudySift a transcript and automatically extract useful information such as keywords, definitions, examples, and important points. The interesting part was the constraint. The project had to run using Python's standard library only , with no third-party runtime dependencies. The Problem Lecture transcripts can be long and difficult to revise. Important definitions, examples, keywords, and important statements can be spread throughout the transcript. Students often have to manually read the entire transcript, identify important sentences, and create their own notes. I wanted to reduce this manual work. StudySift takes a text transcript as input and processes it into organized notes. The basic workflow is: Lecture Transcript ↓ StudySift ↓ ┌─────────────────┐ │ Definitions │ │ Important Points│ │ Examples │ │ Keywords │ └─────────────────┘ **What I Built** StudySift is a Python command-line tool. The user provides a transcript file: python src/main.py examples/lecture.txt StudySift processes the transcript through several stages: 1. Read the input file 2. Split the text into sentences 3. Extract words 4. Remove common words 5. Count word frequencies 6. Detect definitions 7. Detect examples 8. Identify important sentences 9. Score sentences 10. Sort sentences by importance 11. Generate structured notes The goal is not to pretend that a collection of simple rules is a complete natural-language understanding system. Instead, StudySift is a lightweight and transparent approach to turning transcripts into useful revision material. **The Zero-Dependency Challenge** The biggest constraint was that StudySift could not depend on third-party runt

2026-09-06 原文 →
AI 资讯

Local Business Lead Scrapers on Apify Compared (September 2026)

Most local business lead scrapers on Apify are Google Maps scrapers with a website-crawling step bolted on. lukaskrivka/google-maps-with-contact-details is the most used (87,957 users, 4.63 stars). flash_scraper/local-business-leads is the outlier: it discovers businesses on OpenStreetMap instead of Google Maps, and includes MX email verification in its $3 per 1,000. Every figure below was read from Apify's public Store API ( GET /v2/store ) on 2026-09-05 — including every user count, so they are all on the same footing. The per-actor endpoint ( GET /v2/acts/<id> ) can read one higher: it gives flash_scraper/local-business-leads 33 rather than 32, and code-node-tools 33 as well. Prices, users and ratings change; the Pricing tab on each actor page is authoritative. Disclosure: I publish flash_scraper/local-business-leads , one of the actors compared here. Its limits are listed in the same detail as everyone else's, including the one that will disqualify it for many buyers. How prices are normalised These actors bill per event, and the events differ in kind, which makes headline prices misleading. Some charge per place found. Some charge separately for the website crawl that actually produces the email. Some charge again to verify that the email is deliverable. The table lists the primary per-result event multiplied by 1,000 at the free-plan rate , then names the add-on events, because a $5 per 1,000 place price with a $100 per 1,000 email-verification add-on is not a $5 tool. Paid Apify plans get tiered discounts on several of these actors, ours included — and on the add-on events the discount can be enormous. lukaskrivka's three $100-per-1,000 add-ons fall to $4.00 (email verification), $7.50 (lead enrichment) and $10.00 (social-profile enrichment) per 1,000 on Bronze, and lower again above it (Store pricing record read 2026-09-05). Our own free-plan-to-Diamond spread is about 30 percent. So if you are on a paid plan, re-read every figure below off the Pricing tab:

2026-09-06 原文 →
AI 资讯

My MCP Security Scanner Missed 2026's Worst MCP RCE: Here Is the One-Rule Fix

The hook A few months back I shipped mcpscan , a static analyzer that scans MCP (Model Context Protocol) servers for the vulnerability classes that keep showing up in this ecosystem: command injection, SSRF, and path traversal. Rule MCP007 was supposed to be the path traversal catch-all. This week I sat down with my own research notes and ran a simple gut-check: would MCP007 have caught the four real path-traversal CVEs disclosed against MCP servers this year? It would have missed every single one. Including the worst one. Real-world context Here is what actually shipped as CVEs in 2026, all in MCP servers, all sharing the same root cause: CVE Server Sink Impact CVE-2026-40576 excel-mcp-server file write Path traversal CVE-2026-84201 appium-mcp-server write_file Path traversal CVE-2026-44336 PraisonAI MCP Python .pth write RCE via site-packages injection CVE-2026-27825 mcp-atlassian confluence_download_attachment CVSS 9.1 , unauthenticated RCE (chained with SSRF CVE-2026-27826 to overwrite ~/.ssh/authorized_keys or drop a cron entry) Four different maintainers, four different tools, the exact same blind spot: a file path built from caller-controlled input, written without a directory-boundary check. The bug in mcp-atlassian is the nastiest: no auth needed, no restart needed, straight to a shell. So I opened my own rule file and read the docstring out loud: MCP007: path traversal in file-reading tools. There it is. My rule was scoped to reads from day one, and every real-world exploit this year happened on the write side. A scanner whose entire job is catching this bug class was structurally blind to the half of it that is actually landing CVSS 9+ scores. Architecture: how MCP007 actually works The rules in mcpscan are simple on purpose: line-scan regex matching without an AST, so they run fast across any language mcpscan supports. Each rule has three regex layers: ┌─────────────────────────────────────────────┐ │ 1. SINK: does this line call a │ │ file-open/read fun

2026-09-05 原文 →
AI 资讯

How to Get YouTube Transcripts as a Developer (4 Methods That Work in 2026)

How to Get YouTube Transcripts as a Developer (4 Methods That Work in 2026) YouTube transcripts unlock a lot: AI video summarizers, searchable course databases, RAG over video libraries, dataset generation for fine-tuning, repurposing videos into articles. But getting transcripts programmatically is full of sharp edges: disabled captions, rate limits, datacenter IP blocks, and YouTube's ever-changing frontend. This guide walks through every practical method with working code. Method 4 is the managed service I run. Skip ahead if you just want the API call. The DIY methods below are real and will serve you well for small jobs. What you're actually fetching YouTube stores captions as timed tracks in two flavors: Manual captions : uploaded by creators, best accuracy Auto-generated captions : YouTube's speech recognition, most videos Each track is text plus timing ( text/start/duration ), servable as SRT, VTT, or YouTube's timedtext XML. Everything below ultimately resolves to that shape. Method 1: youtube-transcript-api (Python) The standard open-source library. Start here for scripts and prototypes. pip install youtube-transcript-api from youtube_transcript_api import YouTubeTranscriptApi video_id = " dQw4w9WgXcQ " # the ID from the watch URL transcript = YouTubeTranscriptApi . get_transcript ( video_id ) for entry in transcript : print ( f " [ { entry [ ' start ' ] : . 2 f } s] { entry [ ' text ' ] } " ) It returns a list of dicts, one {'text', 'start', 'duration'} per segment. For other languages, list what's available first, then fetch or translate: tl = YouTubeTranscriptApi . list_transcripts ( video_id ) for t in tl : print ( t . language_code , " generated: " , t . is_generated ) transcript = YouTubeTranscriptApi . get_transcript ( video_id , languages = [ " id " , " en " ]) track = tl . find_transcript ([ " en " ]) translated = track . translate ( " id " ). fetch () # free, server-side Handle the caption-less case explicitly instead of catching bare Exception .

2026-09-05 原文 →
AI 资讯

I built a link shortener with FastAPI and htmx (no JS framework) — the parts that were actually hard

"A URL shortener" sounds like a weekend project. Slug in, long URL out, 302 , done. That's what I thought too. Then real usage showed up: links opened inside Instagram's in-app browser and didn't convert, bot traffic wrecked the analytics, and one link needed to send a US visitor somewhere different from an EU visitor. Suddenly the "trivial" part was 5% of the work. I built the whole thing on FastAPI + Redis + MySQL + htmx , deliberately with no frontend framework . This post is about the parts that turned out to be interesting — the redirect hot path, geo/device routing, and escaping in-app browsers — and why htmx was the right call for a one-person team. Disclosure: I build tapurl.io , a link shortener for marketers. This is a write-up of the engineering behind it, not a pitch — everything below is patterns you can apply to any shortener. The redirect is a hot path, so treat it like one Every other page in the app can be a bit slow. The redirect cannot. It sits in front of someone's click, and it runs on every click, so it has to be a tight, predictable read. The naive version hits your database for every redirect: @app.get ( " /{slug} " ) async def redirect ( slug : str ): link = await db . fetch_link ( slug ) # DB round-trip on every click if not link : raise HTTPException ( 404 ) return RedirectResponse ( link . destination , status_code = 302 ) That's fine until you have traffic. The slug-to-link lookup is a near-perfect cache candidate — a slug maps to the same link record every time. So the real path reads from Redis first and only falls back to MySQL on a miss: async def resolve ( slug : str ) -> Link | None : cached = await redis . get ( f " link: { slug } " ) if cached : return Link . parse_raw ( cached ) link = await db . fetch_link ( slug ) if link : await redis . set ( f " link: { slug } " , link . json (), ex = 3600 ) return link Two things worth saying out loud: Cache the lookup, not the decision. You cache the link record, but the actual destination

2026-09-05 原文 →
AI 资讯

I Kept Deleting Logs for 48 Hours. The Inodes Were Already Gone.

Have you ever watched a two-kilobyte write fail with No space left on device while df -h still showed free gigabytes? I did, and I spent the next forty-eight hours cleaning the wrong evidence. This is the reconstructed field notebook from that session, including the commands I ran, the ones that misled me, and the checklist I now run before I blame the disk. Nothing here is a benchmark, a quota promise, or a claim about hardware I did not measure. I was iterating on a small Python worker that dumped JSON sidecars next to each run. The worker itself was unremarkable. The failure mode was not. Hour 0: the write that should have been boring The first traceback looked like a disk problem, so I treated it like a disk problem. Would you have done anything else with ENOSPC staring at you from a three-line stack? I would not, and that is exactly how the next two days started. OSError: [Errno 28] No space left on device: 'runs/2026-09-05T07-12-04.json' I ran the obvious command, got a comforting number, and closed the wrong investigation. df -h reported plenty of space on the root filesystem, and /tmp looked equally relaxed. I even created a dummy file in $HOME by hand, which succeeded, so I told myself the worker path was special. df -h df -h /tmp /var /home touch ~/probe-ok.txt && ls -l ~/probe-ok.txt That last touch was the trap. Can a filesystem accept a file in one directory and refuse a tiny file in another while still having blocks to spare? Yes, and inode exhaustion is the boring reason. I did not ask that question for twelve hours. What I tried first, and why it felt reasonable I treated the symptom as log rot, because that is the story operators tell each other. I truncated worker logs, deleted old JSON sidecars I could see, and reran the job with a smaller batch. The write still failed, sometimes on file number twenty, sometimes on file number four. Truncated worker.log and debug.log with : > file instead of deleting the path. Removed a handful of large .jsonl fil

2026-09-05 原文 →
AI 资讯

Translating 300-Page Books with Claude: Taming Token Limits and Chunking Strategies

How we built a reliable pipeline to split long texts for LLM translation without losing context or breaking the bank At LectuLibre, we translate entire books using Claude. The challenge: a 300-page book is roughly 90,000–120,000 words, which translates to 120,000–160,000 tokens. While Claude 3 models have a 200k context window, sending an entire book in one API call is impractical. It's slow, expensive, and often degrades translation quality due to attention dilution. We needed a robust chunking strategy that preserved context and stayed within token limits. The Problem: One Book, Too Many Tokens When we first started building LectuLibre, we naively assumed we could just pass the whole book to Claude and get a translation back. We quickly hit three walls: Rate limits : A single request with 150k tokens triggered API timeouts and 429 errors. Cost : Even if it worked, processing 150k tokens per request with Opus would cost over $13 per book, and most of the input would be wasted on repeated context. Quality : Long contexts tend to make the model "forget" early chapters, leading to inconsistent character names and terminology. Clearly, chunking was necessary. But how do you split a book without losing narrative flow? First Attempt: Naive Splitting by Paragraphs Our initial approach was simple: split the text into chunks of roughly 10,000 tokens by paragraphs. We used a regex to split on double newlines and then concatenated paragraphs until we hit the token limit. import re def split_into_paragraphs ( text : str ) -> list [ str ]: return re . split ( r ' \n\s*\n ' , text ) def chunk_by_paragraphs ( paragraphs : list [ str ], max_tokens : int = 10000 ) -> list [ str ]: chunks = [] current_chunk = [] current_tokens = 0 for para in paragraphs : # Estimate tokens using character count / 4 (quick and dirty) para_tokens = len ( para ) // 4 if current_tokens + para_tokens > max_tokens and current_chunk : chunks . append ( ' \n\n ' . join ( current_chunk )) current_chunk = []

2026-09-05 原文 →
AI 资讯

Shadow-Compare the Agent Patch. Merge Only Classified Divergences.

A green test run is not a behavior spec. An agent patch can keep every existing assertion passing and still change encodings, error types, empty-input handling, or the bytes written to stdout. Shadow-compare the candidate against a frozen baseline on the same corpus. Merge only after every divergence is classified in an accepted-delta ledger. This article is a testing workflow, not a model bake-off. The harness below is labeled as a proposed, runnable pattern. It does not claim production timings, model names, or pass rates. Why green CI misses the patch Agent patches optimize for the tests they can see. Hidden behavior lives in branches the suite never names: trailing newlines, NaN keys, timezone-naive stamps, None versus [] . Those are cheap to alter. They are expensive to notice after merge. A dual-run gate treats the old artifact as the oracle for unspecified behavior. Specified behavior still belongs in ordinary tests. The ledger exists for the remainder: diffs you accept on purpose, and diffs you refuse. Do not use this as a substitute for code review. Use it as a filter that review should not have to do by hand. Artifact: baseline, candidate, ledger Three files define the contract. baseline/ — a pinned checkout, wheel, or container digest. Not main at HEAD. candidate/ — the agent patch, applied on top of the same pin. delta_ledger.yaml — every previously classified output divergence, keyed by fixture id. Proposed layout: shadow/ corpus/ # deterministic fixtures only 001_empty.json 002_unicode.json 003_nested_null.json delta_ledger.yaml canonicalize.py shadow_compare.py The corpus must be I/O-free. No clocks. No DNS. No home-directory probes. If a fixture needs time, inject it. If it needs a filesystem, pass a temp root the harness owns. Step 1 — Freeze the baseline as an artifact Record the exact bytes you will rerun. A git SHA is enough when the tree is hermetic. Prefer a built artifact when native extensions or generated code are in play. git rev-parse HEAD

2026-09-05 原文 →
AI 资讯

Why "why did our infra costs jump in Q2?" doesn't fit a graph

TL;DR : some questions don't have a fixed path through your data (search docs, hit a table, compute, verify, answer — in whatever order/combination the question needs), and drawing a graph for that class of question means either enumerating every path up front or hiding an if/else forest inside one node. ctxloom replaces the graph with typed artifacts and agents that react to their appearance — below is the same use case built both ways, side by side. The problem Picture a typical question from a finance lead in an internal chat assistant: "Why did our infra costs jump in Q2?" Answering this honestly requires: Finding relevant documents — the pricing guide, the discount policy (Confluence/docs). Pulling structured data — a CSV/table of monthly spend (GitLab/S3/DB). Computing an aggregate — not "roughly", an exact number from the table. Cross-checking textual claims against the numbers — not letting the model invent a cause the data doesn't support. Returning the answer together with proof: where each part came from. The next question — "what if we hadn't moved to the Pro plan?" — needs a different path: a different source, a different calculation, a different verification chain. There is no universal graph for this class of questions — you can draw a graph for one specific question, but not for the class. This is exactly what typical graph frameworks (LangGraph, CrewAI, etc.) make you pay for in complexity: either you draw a graph for every possible path up front, or you end up with a hidden branching if/else inside one node that nobody can later explain. How this looks in ctxloom ctxloom has no execution graph — it has artifacts (typed, versioned objects) and agents that react to their appearance . The breakdown above is just a chain of artifacts: Question │ ▼ SourceRef (ranked references to sources) │ ▼ TypedDoc / Spreadsheet (lazily resolved content) │ ├──► Evidence (facts extracted from text) │ │ │ ▼ │ Claim (a statement + verification against Evidence) │ └──► C

2026-09-05 原文 →
开发者

I Compared 4 Dungeon Generation Algorithms. One of Them Never Works.

Four algorithms. Same grid. Very different dungeons. I implemented BSP trees, cellular automata, random walk, and room placement, ran each one 20 times on an 80x40 grid, and measured everything: connectivity, open space, path length, speed. The Results Algorithm Open Space Connected Rooms Path Length Speed BSP Tree 42.1% 100% 1.0 105 steps 0.88 ms Cellular Automata 55.8% 0% 15.2 78 steps 52.8 ms Random Walk 35.0% 100% 1.0 73 steps 274.7 ms Room Placement 18.9% 100% 1.0 81 steps 0.29 ms The big surprise: cellular automata never produces a connected map. Zero percent connectivity across 20 runs. Every single cave system has unreachable areas. The Maps BSP Tree (structured rooms, always connected) ################################################################################ ################################################################################ #####.........#####.............###################################....#......## #####.........#####.............##..........##############........#....#......## #####...........................##..........##############....................## #####.........#####.............##..........##############.............#......## #####.........#####.............##..........##############........#....#......## ##########.#######################..........##############........#....#......## ##########.#######################..........################..################## ######..........##################..........################..################## ######..........##################..........################..######..........## ######..........##################..........################..######..........## ######..........##################..........################..######..........## ######.............###############..........################..######..........## ######..........##.###############..........################..######..........## ######..........##.###############..........################..######..........##

2026-09-05 原文 →
AI 资讯

Catch Tool Calls That Invent Missing Arguments

Agents fail quietly when they fill omitted tool arguments instead of refusing, and fluency-based evals often reward that invention. A compact negative golden set, scored by argument-diff rather than prose quality, catches those silent substitutions before they reach production traces. This article treats that failure as a testable contract, not as a prompt-tuning anecdote, and it stays useful without any vendor product. Recent developer discussion around agent workflows keeps returning to one operational surprise that chat logs tend to hide. Models do not only choose the wrong tool; they complete incomplete requests by guessing identifiers, dates, and scopes that nobody supplied. That behavior looks like initiative in a chat log, yet it resembles a clerk forging a zip code to stamp the form complete. The package then leaves the dock with valid-looking paperwork and the wrong city printed on the label. A conventional golden-answer harness scores the final sentence, which is the wrong surface for tool-using agents. The dangerous artifact is the tool payload, because downstream systems will execute invented primary keys with perfect syntax. If your eval suite only checks that a transfer looks helpful, it will greenlight a call that moved the wrong account. The pattern below is a proposal you can run locally, and it does not claim production metrics. It also does not depend on a particular model family or on a hosted evaluation service. You should treat every numeric threshold in the grader as a starting point rather than a published benchmark. Negative goldens assert a hole, not a pretty answer A positive golden case says the model should produce a known good action given a complete request. A negative golden case says the opposite: given a hole in the input, the model must not paper over that hole. The assertion is closer to a check constraint than to a writing rubric, because the failure is an illegal completion. Fluency still matters for users, but it is a poor prox

2026-09-05 原文 →
AI 资讯

The scanner read 2581 files and reported zero. The defect was on line 403.

On 2026-09-04 I pointed a scanner at langchain-ai/langchain . Shallow clone of the default branch, HEAD 79cab2d , read only. It walked 2581 files and printed zero sites. Its own control had passed immediately before the run, with two positive fixtures seen and four negative fixtures clean, so the zero was a measurement rather than a crash. Then I opened one file by hand. libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py , line 403: def _should_interrupt ( self , tool_call , config , state , runtime ) -> bool : """ Return False if the `when` predicate rejects this tool call, True otherwise. """ when = config . get ( " when " ) if when is None : return True ... return when ( req ) when is supplied by the caller. It is declared NotRequired[Callable[[ToolCallRequest], bool]] on line 195 and documented as returning True to interrupt or False to auto-approve. Its result is handed back unchanged. A predicate that falls off a branch returns None , and the caller on line 436 reads: if not self . _should_interrupt ( tool_call , config , state , runtime ): continue None is falsy. The interrupt is skipped and the tool call proceeds with nobody looking at it. The annotation says bool ; nothing at runtime makes that true. Why the machine stayed quiet I took the failure apart instead of guessing at it. Three causes, each sufficient on its own: Vocabulary. 22 lines in that file matched the approval vocabulary the scanner looks for. Not one of them put line 403 inside its window. The nearest match was 26 lines away and sat in a comment. This project calls the decision interrupt , not approval. Window. The -> bool annotation is on line 378. The return is on 403. That is 25 lines apart, and the window was 12. Signals. Widened to 55 lines, the three behaviour signals still matched nothing on that line. The file walk was innocent. The file is .py , 18256 bytes, and no skip rule matched it. It was read. What I got wrong The window of 12 lines had no measurement behind it

2026-09-04 原文 →
AI 资讯

Fair Queue for a Shared Free AI Server: 5-Dev Postmortem

Five independent clients on one free AI server will produce 429s and a thundering herd unless you add a fair queue. We fixed it with a client-side asyncio queue that capped concurrency at two, prioritized interactive work, and dropped 429s from 23 to 0 on a 100-request mixed workload. Disclosure: This article was prepared as part of MonkeyCode's product outreach. What Failed When Five Developers Shared One Server We shared one MonkeyCode free server for code review and refactoring. Each of us ran our own scripts. Nobody coordinated. The first symptom was latency: requests that took two seconds started taking thirty. Then came the 429s. Then came the retries. Retries made everything worse. The server spent more time rejecting requests than answering them. The timeline compressed quickly: Day 1: two developers, no issues Day 3: four developers, latency doubles Day 5: five developers, 429s appear Day 6: retries cause a thundering herd Day 7: the team stops using the server The root cause was not the server. It was the absence of coordination. Five independent clients hammered one endpoint. Each client assumed it was the only user. The server had no way to prioritize. HTTP 429 is the standard “too many requests” signal; we treated it as a retry cue instead of backpressure. That is how a shared free endpoint turns into a retry storm. The deeper problem was architectural. Each of us built a separate integration. Each integration had its own retry logic. Under load those retries multiplied. The server received about five times the intended traffic, not because we needed five times the work, but because five clients were guessing independently. Contrast the two modes we actually ran: Uncoordinated: five scripts, five retry loops, unbounded in-flight calls, no shared view of queue depth. Coordinated: one process, one priority heap, two in-flight calls, explicit rejection when the queue is full. The first mode failed in a week. The second mode is what we shipped. How We Built

2026-09-04 原文 →
AI 资讯

The Data Boundary Problem: Using a Free Server Without Leaking Your Prompts

A free server is a data boundary decision, not a cost decision. Every prompt you send to a managed endpoint leaves your network. For a coding agent, that means source code, environment variables, and internal architecture notes travel to someone else's infrastructure. The question is not whether the endpoint is trustworthy; the question is whether you can make the boundary explicit. MonkeyCode's free server option is generous in tokens and removes the ops burden of self-hosting. Disclosure: This article was prepared as part of MonkeyCode's product outreach. But generosity does not change the physics of data flow. The moment your agent calls a remote endpoint, the prompt is out of your control. What you can control is what goes into the prompt. This article is a practical guide to building a privacy gate between your agent and a free server. The gate is a local proxy that sanitizes prompts, redacts secrets, and logs every request. It does not make the server trustworthy; it makes your exposure measurable. The threat model Before writing code, define what you are protecting. For most teams, the sensitive material in prompts falls into three categories: hardcoded credentials, proprietary code snippets, and internal names or URLs. Each category has a different risk profile. Credentials are the worst. A leaked API key in a prompt is a direct compromise. Proprietary code is a legal and competitive risk. Internal names are subtler: they reveal architecture and naming conventions that an attacker can use for phishing or targeted attacks. A free server does not automatically read or store your prompts, but you cannot verify that. The boundary you build must assume the server is an untrusted observer. That assumption drives the design. The privacy gate The gate is a small FastAPI service that sits between your agent and the free server. It accepts OpenAI-compatible requests, rewrites them, forwards them, and returns the response. The rewriting step is where the boundary is en

2026-09-04 原文 →
AI 资讯

Matplotlib - Session 2

Turning Data Into Decisions Bar charts, histograms, scatter plots, subplots, and plotting straight from pandas Previously learned to draw a line — literally. we now know how to create a figure, style it, and save it. But real analyst work rarely stops at trends over time. You'll need to compare categories , understand distributions , spot relationships between variables , and show several views of the data at once . That's exactly what today covers. Grab a coffee — let's turn raw numbers into charts that actually tell a story. 1. Bar Charts: Comparing Categories When to use one Bar charts are your go-to whenever you're comparing discrete categories against each other — regions, products, departments, months. If someone asks "which one is bigger?", a bar chart answers it instantly. The code import matplotlib.pyplot as plt regions = [ " North " , " South " , " East " , " West " ] revenue = [ 420 , 380 , 510 , 290 ] fig , ax = plt . subplots ( figsize = ( 7 , 5 )) ax . bar ( regions , revenue , color = " teal " ) ax . set_title ( " Revenue by Region " ) ax . set_xlabel ( " Region " ) ax . set_ylabel ( " Revenue ($K) " ) plt . show () A useful variant: horizontal bars When category names are long, flip the chart with barh() — it's far easier to read than squeezing labels sideways: fig , ax = plt . subplots ( figsize = ( 7 , 5 )) ax . barh ( regions , revenue , color = " darkorange " ) ax . set_title ( " Revenue by Region " ) ax . set_xlabel ( " Revenue ($K) " ) plt . show () Rule of thumb: categories on the x-axis → bar() . Long labels or many categories → barh() . 2. Histograms: Understanding Distributions Bar chart vs. histogram — don't mix them up This trips up almost every beginner: a bar chart compares separate categories. A histogram shows how continuous numeric data is distributed by grouping values into ranges called bins . There are no gaps between histogram bars by convention, because the x-axis is continuous, not categorical. The code import matplotlib.pyplot

2026-09-04 原文 →