Sign in

Reuven M. Lerner

@lernerpython.com
3.6K followers 600 following 918 posts

Helping you become more confident with Python and Pandas since 1995. • Courses: LernerPython.com • Newsletters: BetterDevelopersWeekly.com • BambooWeekly.com • Books: PythonWorkout.com • PandasWorkout.com • Videos: YouTube.com/reuvenlerner

PostsRepliesMedia
Reuven M. Lerner @lernerpython.com · 6m
AI coding is easy, right? Write a #ClaudeCode prompt, and you're done. Um, no: It's new techniques to learn and master Join the 6th cohort of HOPPy (Hands-on projects in #Python). You'll implement a game of your own design, with me mentoring. More info: buff.ly/cKGgcyf
000
Reuven M. Lerner @lernerpython.com · 11h
Replace NaN values in a #Python #Pandas data frame with fillna: 1. Pass it a single value to replace everywhere df.fillna(999) 2. Pass a series/dict whose keys/index match columns; values fill matching columns. df.fillna(df.mean()) df.fillna({'x':999, 'y':888, 'z':777})
000
Reuven M. Lerner @lernerpython.com · 29/09/2026
Are you using #ClaudeCode? As you know, building robust, maintainable software is more than prompts. At tomorrow's Advanced Claude Code workshop, you'll learn to build more robust, maintainable code. Take your Claude Code to the next level: Learn more: buff.ly/qwOKjsW
011
Reuven M. Lerner @lernerpython.com · 29/09/2026
Drop rows with *only* NaN values in a #Python #Pandas data frame with the how='all' keyword argument: df.dropna(how='all') This removes completely empty rows, leaving rows containing even one non-NaN value (aka thresh=1).
020
Reuven M. Lerner @lernerpython.com · 28/09/2026
I'm excited to announce the next HOPPy (Hands-On Projects in #Python), a 5-week course in which you'll use #ClaudeCode to write a game that *you* design. No theory — just learning by doing. Want to hear more? Come to Tuesday's free HOPPy 5 info session: buff.ly/VvPbxHO
020
Reuven M. Lerner @lernerpython.com · 28/09/2026
Drop rows containing NaN in a #Python #Pandas data frame, but only in specific columns, with "subset": df.dropna() # drops rows containing NaN df.dropna(subset='x') # drops rows where column x is NaN df.dropna(subset=['x', 'y']) # drops rows where x or y is NaN
030
Reuven M. Lerner @lernerpython.com · 27/09/2026
You can remove NaN from a #Python #Pandas data frame with dropna, but be careful: It removes rows with even one NaN, which can be overkill. Pass "thresh" to allow some rows to remain, even if they contain NaN: df.dropna(thresh=2) # keeps rows with 2 or more non-NaN values
150
Reuven M. Lerner @lernerpython.com · 26/09/2026
Want to remove NaN from a #Python #Pandas series? Three basic options, all returning a new series: - s.dropna, NaNs (and their indexes) go away - s.fillna(n), NaNs are replaced by n - s.interpolate, NaNs are replaced via a linear function
020
Reuven M. Lerner @lernerpython.com · 25/09/2026
Coming to #Python #Pandas from NumPy? You'll reach for np.isnan: s = Series([10, np.nan, 30]) s.loc[ ~np.isnan(s) ] # returns 10, 30 Unfortunately, this works. Better, use s.isna (or s.isnull). But the best way to drop NaN? Use dropna: s.dropna() # returns 10, 30
020
Reuven M. Lerner @lernerpython.com · 25/09/2026
It's easy to write #Python with AI. But it's not as easy to build maintainable, well-engineered applications. On Wednesday, join my hands-on, advanced Claude Code workshop. Topics include custom skills, testing, and telemetry. More info at: buff.ly/qwOKjsW
010
Reuven M. Lerner @lernerpython.com · 24/09/2026
Want to write NaN in #Python #Pandas, but are annoyed that NumPy removed it? Or: Tired of importing NumPy, just for np.nan? I learned *yesterday* that you can say: float('nan') or float('NaN') Both return a perfectly usable NaN, good in NumPy and Pandas. Wow. 🤯
021
Reuven M. Lerner @lernerpython.com · 23/09/2026
How often are AI incidents happening? Are they becoming more frequent? Who is getting harmed? In the latest Bamboo Weekly, we analyze the AI incident database using #Python #Pandas — plus joins, JSON, pivot tables, and Plotly. More info: buff.ly/BO8uMFy
000
Reuven M. Lerner @lernerpython.com · 23/09/2026
PyArrow dtypes in #Python #Pandas are nullable (with pd.NA): s = Series([10, pd.NA, 30], dtype='int64[pyarrow]') s is: 0 10 1 <NA> 2 30 dtype: int64[pyarrow] The dtype is int64, but allows nulls. (Use np.nan? It's turned into pd.NA.)
010
Reuven M. Lerner @lernerpython.com · 22/09/2026
You have a #Python #Pandas series with ints + NaN. You don't want float forced on you. Solution: Use the "extension" type Int64 (note Initial Caps) and pd.NA: s = Series([10, pd.NA, 30], dtype='Int64') # must name explicitly s is: 0 10 1 <NA> 2 30 dtype: Int64
020
Reuven M. Lerner @lernerpython.com · 21/09/2026
Values in a #Python #Pandas series all have the same dtype. np.nan is a float, so (1) the series gets a float dtype, or (2) it's "object," meaning "something unspecific." Annoying for integers. Awful for other types. For this, we need nullable types! (Coming tomorrow...)
020
Reuven M. Lerner @lernerpython.com · 20/09/2026
Missing data? NumPy calls it nan. #Python #Pandas displays it as NaN. But: Pandas doesn't define pd.nan or pd.NaN. NumPy removed np.NaN in version 2.0. So you have to refer to np.nan from within Pandas, and it'll be displayed as NaN.
020
Reuven M. Lerner @lernerpython.com · 19/09/2026
NumPy is very strict about nan. But #Python #Pandas throws nan values away: s = Series([10, 20, 30, np.nan, 50]) s.sum() # np.float64(110.0) s.mean() # np.float64(27.5) s.count() # np.int64(4) Get stricter behavior with skipna=False: s.sum(skipna=False) # np.float64(nan)
030
Reuven M. Lerner @lernerpython.com · 18/09/2026
Missing data in #Python #Pandas? We use nan ("not a number"), which comes from NumPy. np.nan is a float, but not a normal one: np.nan + 10 # nan np.nan * 12345 # nan sum([10, 20, np.nan]) # nan np.nan == 0 # False np.nan == np.nan # False
010
Reuven M. Lerner @lernerpython.com · 17/09/2026
What is a "callable" in #Python? Typically, a function or class. But really, it's anything with __call__ defined: class MyClass: def __call__(self): return 'Hi!' m = MyClass() m() # 'Hi!' The "callable" builtin basically returns True if it finds __call__.
010
Reuven M. Lerner @lernerpython.com · 16/09/2026
If you invoke +=, #Python prefers __iadd__ to __add__: __iadd__ returns (a modified) self, not a new object -- so id doesn't change: class MyClass: def __init__(self, x): self.x = x def __iadd__(self, other): self.x = self.x + other.x return self
130
Reuven M. Lerner @lernerpython.com · 15/09/2026
In just 24 hours, this month's hands-on Claude Code #Python workshops begin — 2 intro and 1 advanced. Come and learn how to create maintainable, robust, and secure software — not just vibe-coding on a whim. Learn more at buff.ly/Ra3TBkG.
010
Reuven M. Lerner @lernerpython.com · 15/09/2026
If you invoke +=, #Python can use __add__. MyClass implements __add__ (calling print for debugging): m1 = MyClass(10) m2 = MyClass(20) m1 += m2 # prints "Now in MyClass.__add__" m1 now refers to a new object, and its repr is: MyClass instance, vars(self)={'x': 30}
010
Reuven M. Lerner @lernerpython.com · 14/09/2026
Reading lots of newsletters? Want to convert them into a personal PDF? Check out Newsprint, my #Python project that reads your mailbox, grabs starred messages, lets you choose non-starred ones, and creates a PDF. Give it a whirl: buff.ly/s5Bm02C
010
Reuven M. Lerner @lernerpython.com · 14/09/2026
How does the "in" operator work in #Python? - If an object defines __contains__, then its (boolean) result is returned (coerced to bool). - If not, then Python iterates over it with __iter__ Can you find and return a result faster than __iter__? Then define __contains__.
030
Reuven M. Lerner @lernerpython.com · 13/09/2026
Consider this #Python code: s1 = {10, 20, 30} s1_alias = s1 s2 = {20, 30, 40} s1 = s1 | s2 What is s1_alias? Still {10, 20, 30}, because a new set was assigned to s1. If we used s1 |= s2, then s1_alias would change -- because |= (aka __ior__) mutates the left-hand value.
130
Reuven M. Lerner @lernerpython.com · 12/09/2026
Sets in #Python use the | operator (aka the __or__ magic method) for "union": s1 = {10, 20, 30} s2 = {20, 30, 40} s1 | s2 # returns {10, 20, 30, 40} Want to assign the result to s1? Use |= instead, which runs the __ior__ method: s1 |= s2 s1 is now {10, 20, 30, 40}
031
Reuven M. Lerner @lernerpython.com · 11/09/2026
In #Python, we use "or" for conditions. | is bitwise (not boolean) "or": x = 10 # 0b1010 y = 13 # 0b1101 x | y # 15, or 0b1111 | runs __or__. On dicts, | combines. d1 = {'a':10, 'b':2} d2 = {'b':10, 'c':30} d1 | d2 # {'a': 10, 'b': 10, 'c': 30}, right side wins
010
Reuven M. Lerner @lernerpython.com · 11/09/2026
Do you receive lots of e-mail newsletters? Then check out Newsprint, my latest open-source #Python project. Newsprint turns e-mail newsletters into a personalized PDF "newspaper," suitable for printing and offline reading. Check it out, and let me know what you think: buff.ly/s5Bm02C
pypi.org
newsprint
Fetch starred newsletters over IMAP, reduce them to article content, and print them quarter-sheet, four to a side, duplex.
210
Reuven M. Lerner @lernerpython.com · 10/09/2026
The % in #Python, on numbers, is modulo: x = 10 y = 3 x % y # 1 x.__mod__(y) # same thing, 1 But str uses __mod__ for interpolation: s = 'Hi, %d' s % y # 'Hi, 3' s.__mod__(y) # Same thing, 'Hi, 3' Same operator, same magic method — but totally different.
020
Reuven M. Lerner @lernerpython.com · 09/09/2026
How are the schools in your country? The results from PISA 2025 are out, ranking education systems in dozens of countries. In the latest Bamboo Weekly, we use #Python #Pandas to better understand some of this data. Level up your data-analysis skills every Wednesday: buff.ly/cMTVKZf
020
Reuven M. Lerner @lernerpython.com · 09/09/2026
Just as + in #Python invokes __add__, other operators invoke other magic methods: • - is __sub__ • * is __mul__ • / is __truediv__ • // is __floordiv__ • % is __mod__ • ** is __pow__ Your methods can do whatever you want. But do try to stick to conventional expectations.
020
Reuven M. Lerner @lernerpython.com · 08/09/2026
If you tell #Python x + 5 it'll run x.__add__(5), which handles the int 5. But what about 5 + x int.__add__ doesn't know how to handle x. So it returns NotImplemented — and Python turns it around, calling: x.__radd__(5) That's the "reverse add" magic method!
040
Reuven M. Lerner @lernerpython.com · 08/09/2026
"Computers don't do what you want them to do. They do what you tell them to do." That's still true with agents. The gap just got bigger — and validation matters more than the results. I'm giving 3 #ClaudeCode + #Python workshops this month. Join my free info session on Sept 14: buff.ly/IKiazYj
010
Reuven M. Lerner @lernerpython.com · 07/09/2026
Use duck typing, and your #Python object knows how to + with other types: class C: def __init__(self, x): self.x = x def __add__(self, o): if hasattr(o, 'x'): return C(self.x + o.x) else: return C(self.x + o)
010
Reuven M. Lerner @lernerpython.com · 06/09/2026
If your #Python class implements __add__, it should usually return a new instance of your class, not an int: class C: def __init__(self, x): self.x = x def __add__(self, other): return C(self.x + other.x) c1 = C(10) c2 = C(15) vars(c1 + c2) # {'x':25}
020
Reuven M. Lerner @lernerpython.com · 05/09/2026
How can your #Python object support +? Implement __add__: class C: def __init__(self, x): self.x = x def __add__(self, other): return self.x + other.x c1 = C(10) c2 = C(15) c1 + c2 # 25
000
Reuven M. Lerner @lernerpython.com · 04/09/2026
Operators in #Python are turned into "magic" method calls: x + y # becomes x.__add__(y) So: x = 10 y = 'hello' x + y # unsupported operand type(s) for +: 'int' and 'str' y + x # can only concatenate str (not "int") to str Different errors from different methods!
120
Reuven M. Lerner @lernerpython.com · 03/09/2026
A #Python function's code is on its __code__ attribute. Which means you can do this: def add(a, b): return a + b def sub(a, b): return a - b add.__code__, sub.__code__ = sub.__code__, add.__code__ add(50, 20) # 30 sub(50, 20) # 70 Impractical ... but fun!
031
Reuven M. Lerner @lernerpython.com · 02/09/2026
Because #Python functions' defaults are kept in __defaults__, avoid mutable defaults: def add1(x=[]): x.append(1) # same as: add1.__defaults__[0].append(1) ‼️ return x add1() # [1] add1() # [1, 1] add1() # [1, 1, 1] 🤯 add1.__defaults__ # ([1, 1, 1],)
000
Reuven M. Lerner @lernerpython.com · 01/09/2026
Python uses #Python to implement Python! def add(x=5, y=10): return x + y Now you can call it as: add() # 15 add(8) # 18 add(8, 3) # 11 The defaults are stored in a tuple: add.__defaults__ # (5, 10) Missing args are assigned from __defaults__.
010
Reuven M. Lerner @lernerpython.com · 31/08/2026
If a #Python function adds 10 to an argument, co_consts includes 10. But the compiler sees a tiny number, and emits LOAD_SMALL_INT instead of LOAD_CONST. But if you're adding 1m to an argument? That's big enough to use LOAD_CONST, using the index in co_consts.
020
Reuven M. Lerner @lernerpython.com · 30/08/2026
Python uses #Python to implement Python! An inline int/string in your function is stored in co_consts (a tuple) at compile time. Each constant is stored only once, even if used multiple times in the function: def plus10(n): return n + 10 plus10.__code__.co_consts # (10,)
020
Reuven M. Lerner @lernerpython.com · 29/08/2026
Class attributes in #Python are normally in a dict called __dict__: class MyClass: x = 100 def __init__(self): self.y = 200 Notice: 'x' in MyClass.__dict__ # True '__init__' in MyClass.__dict__ # True 'y' in MyClass.__dict__ # False - instance, not class!
030
Reuven M. Lerner @lernerpython.com · 28/08/2026
When a #Python generator yields, its stack frame remains — inspect it for the current line and local variables: def fib(): x = 0 y = 1 while True: yield x x, y = y, x+y g = fib() next(g) g.gi_frame.f_locals # returns {'x': 0, 'y': 1}
021
Reuven M. Lerner @lernerpython.com · 27/08/2026
Ugh -- the US and Canada are in a trade war. But what does each country export to the other? How much? Which states/provinces trade the most? And what do they trade, anyway? The latest Bamboo Weekly poses 5 #Python #Pandas challenges about US/Canada trade data. Try it: bambooweekly.com
000
Reuven M. Lerner @lernerpython.com · 27/08/2026
Python uses #Python to implement Python! Given: def add(a:int, b:int) -> dict[str,int]: return {'a':a, 'b':b, 'total':a+b} Where is this stored? in a dict, add.__annotations__: {'a': int, 'b': int, 'return': dict[str, int]}
010
Reuven M. Lerner @lernerpython.com · 26/08/2026
Python uses #Python to implement Python! class A: pass class B(A): pass class C(A): pass class D(B, C): pass Parent classes are in the __bases__ tuple: print(B.__bases__) # (<class '__main__.A'>,) print(D.__bases__) # (<class '__main__.B'>, <class '__main__.C'>)
030
Reuven M. Lerner @lernerpython.com · 26/08/2026
MIT just called for an educational revolution. As an educator and #Python programmer who has struggled with the impact of AI for several years, I'm quite impressed. Here are my reactions to the most important and far-reaching AI-in-education document I've seen to date: buff.ly/Aa1LTfh
buff.ly
MIT just called for an educational revolution - LernerPython
As someone who teaches programming for a living, I’ve spent the last few years wrestling with the educational implications of AI. I’m changing everything I do to adjust to our […]
020
Reuven M. Lerner @lernerpython.com · 25/08/2026
Python uses #Python to implement Python! Generator functions set a bit in co_flags at compile time, telling Python to run it differently: def myfunc(): return 1 def mygen(): yield 1 bin(myfunc.__code__.co_flags) # '0b11' bin(mygen.__code__.co_flags) # '0b100011'
041
Reuven M. Lerner @lernerpython.com · 24/08/2026
Python uses #Python to implement Python! co_varnames names the local variables, and co_argcount says how many of those are parameters: def add(a, b): total = a + b return total add.__code__.co_varnames # ('a', 'b', 'total') add.__code__.co_argcount # 2
021