What stays the same? What changes?
The patterns are the same. The language techniques change.
Are there guiding principles like SOLID,
Yes. Indeed, they remain the guiding principles. Nothing changes.
or canonical patterns (perhaps entirely new ones) that a dynamic language newbie should know?
Some things are unique. Mostly the impact is that the implementation techniques change.
A pattern is -- well -- a pattern. Not a law. Not a subroutine. Not a macro. It's just a good idea that gets repeated because it's a good idea.
Good ideas don't go out of style or change dramatically.
Other notes. Python is not "weakly typed". It's more strongly-typed than Java or C++ because there's no cast operation. [Yes, there is a way to fudge the class associated with an object, but it's not the kind of thing that's done except to prove a fussy, legalistic point.]
Also. Most design patterns are based on different ways to exploit polymorphism.
Look at State or Command or Memento as examples. They have class hierarchies to create a polymorphic states, commands or mementos of state changes. Nothing changes significantly when you do this in Python. Minor changes include the relaxation of the precise class hierarchy because polymorphism in Python depends on common methods not common ancestors.
Also, some patterns are simply an attempt to achieve late binding. Most Factory-related patterns are an attempt to allow easy change to a class hierarchy without recompiling every C++ module in the application. This isn't as interesting optimization in a dynamic language. However, a Factory as a way to conceal implementation details still has huge value.
Some patterns are an attempt to drive the compiler and linker. Singleton, for example, exists to create confusing globals but at least encapsulate them. Python singleton classes aren't a pleasant prospect. But Python modules already are singletons, so many of us just use a module and avoid trying to mess with a Singleton class.
Best Answer
Strong / weak typing and static / dynamic typing are orthogonal.
Strong / weak is about whether the type of a value matters, functionally speaking. In a weakly-typed language, you can take two strings that happen to be filled with digits and perform integer addition on them; in a strongly-typed language, this is an error (unless you cast or convert the values to the correct types first). Strong / weak typing is not a black-and-white thing; most languages are neither 100% strict nor 100% weak.
Static / dynamic typing is about whether types bind to values or to identifiers. In a dynamically-typed language, you can assign any value to any variable, regardless of type; static typing defines a type for every identifier, and assigning from a different type is either an error, or it results in an implicit cast. Some languages take a hybrid approach, allowing for statically declared types as well as untyped identifiers ('variant'). There is also type inference, a mechanism where static typing is possible without explicitly declaring the type of everything, by having the compiler figure out the types (Haskell uses this extensively, C# exposes it through the
var
keyword).Weak dynamic programming allows for a pragmatic approach; the language doesn't get in your way most of the time, but it won't step in when you're shooting yourself in the foot either. Strong static typing, by contrast, pushes the programmer to express certain expectations about values explicitly in the code, in a way that allows the compiler or interpreter to detect a class of errors. With a good type system, a programmer can define exactly what can and cannot be done to a value, and if, by accident, someone tries somethine undesired, the type system can often prevent it and show exactly where and why things go wrong.