I don't think there's anything Python can do that Common Lisp can't in terms of dynamic-ness!
This is a quote from python.org:
>These languages are close to Python in their dynamic semantics, but so different in their approach to syntax that a comparison becomes almost a religious argument: is Lisp's lack of syntax an advantage or a disadvantage? It should be noted that Python has introspective capabilities similar to those of Lisp, and Python programs can construct and execute program fragments on the fly. Usually, real-world properties are decisive: Common Lisp is big (in every sense), and the Scheme world is fragmented between many incompatible versions, where Python has a single, free, compact implementation.
I believe there are dynamic things Common Lisp can do that python can't, like modifying and creating classes, inheritance and methods at runtime, even with effects propagating out to already existing class instances!
Common Lisp keeps the stupid dynamic parts out of language areas that are connected to critical code execution paths. For instance, no aspect of lexical variables is dynamic. But you have dynamic variables, which are separate in such a way that a compiler can easily tell the difference.
I believe there are areas of CLOS which are stupid dynamic; but even there, the specification tries to tread carefully. Firstly, you don't have to use CLOS in a Lisp program; and if you need data structures with named slots, structs may suffice.
Importantly, Common Lisp keeps a kind of basic type versus class type separation in the language. You don't feel it because it's not obnoxious, like int versus Integer in Java. Built in basic object types like integers and strings all have a CLOS class in Common Lisp. But, the class of that class (the metaclass) is not the same as that of a class which the application defines with defclass. The Lisp compiler doesn't have to worry about silly monkey patching being perpetrated on a string or integer.
In some areas of the language, it's clear that the designers were trying to avoid bringing in dynamic behavior that would interfere with performance. For instance, conditions are defined in such a way that they are "class-like" objects, but without the actual requirement that they be CLOS instances.
The meta-object protocol (MOP) was also kept out of the language. I'm not sure whether the MOP is "stupid dynamic" because it also seems to hold the keys to avoiding "stupid dynamic" in that if you don't like some particular dynamism in a given class, maybe you can design your own meta-class which avoids it. It might be possible using MOP to, say, have an object system where the inherited slots of a derived class are at the same offset in the underlying vector storage as in the parent class, so accesses can be optimized. Maybe you can ban multiple inheritance in that meta-class.
It's not. Common Lisp was designed to enable Lisp applications to be delivered with reasonable performance, first in 1984, when an expensive computer might have had 1 to 10 Megabytes (!) of memory and a CPU with 8 Mhz / 1 Million instructions per second. You'll then see a bunch of different implementations, sometimes within the same running Lisp and able to use different execution modes in the same program:
* source interpreted Lisp -> a Lisp Interpreter executes the code from traversing the s-expressions of the source code -> this is usually slow to execute, but there are also very convenient debug features available
* compiled Lisp code -> a Lisp compiler (often incremental) compiles Lisp code to faster code: byte code for a VM, C code for a C compiler or machine code for a CPU. -> often this keeps a lot of the dynamic features
* optimized compiled Lisp code -> like above, but the code may contain optimization hints (like type declarations or other annotations) -> the compiler uses this provided information or infers its own to create optimized code.
For "optimized compiled Lisp code" the compiler may remove all or some of dynamic features (like late binding of functions, allowing data of generic types to be passed, runtime type checks, runtime dispatch, runtime overflow detection, removal of debug information, tail call optimization, ...). It may also inline code. The portions where such optimizations are applied span from certain parts of functions to whole programs.
Common Lisp also has normal function calls and generic function calls (CLOS) -> the latter are usually a lot slower and people are experimenting with ways to make it fast (-> by removing dynamism where possible).
So, speed in Common Lisp is not one thing, but a continuum. Typically one would run compiled code, where possible, and run optimized code only where necessary (-> in parts of the code). For example one could run a user interface in unoptimized very dynamic compiled code and certain numeric routines in optimized compiled code.
CL-USER> (defun foo (a b)
(declare (optimize (speed 3) (safety 0))
(fixnum a b))
(the fixnum (+ a (the fixnum (* b 42)))))
CL-USER> (disassemble #'foo)
; disassembly for FOO
; Size: 28 bytes. Origin: #x70068A0918 ; FOO
; 18: 5C0580D2 MOVZ TMP, #42
; 1C: 6B7D1C9B MUL R1, R1, TMP
; 20: 4A010B8B ADD R0, R0, R1
; 24: FB031AAA MOV CSP, CFP
; 28: 5A7B40A9 LDP CFP, LR, [CFP]
; 2C: BF0300F1 CMP NULL, #0
; 30: C0035FD6 RET
NIL
As you can see, with optimization instructions and type hints, the code gets compiled to tight machine code (here ARM64). Without those, the compiled code looks very different, much larger, with runtime type checks and generic arithmetic.
With declarations which are promises from the programmer to the compiler (I promise this is true, on penalty of undefined behavior), you can fix a lot of "stupid dynamic".
Python could have a declaration which says, "this function/module doesn't participate in anything stupidly dynamic, like access to parent locals". If it calls some code which tries to access parent locals, the behavior is undefined.
That's kind of a bad thing because in Lisp I don't have to declare anything unsafe to a compiler just to have reasonably efficient local variables that can be optimized away and all that.
I'm not exactly sure how anything you said supports that CL isn't a dynamic language.
When using SBCL for example, none of CLs dynamic features are restricted from the programmer in any way. So whether it's compiled to native code or not has no bearing at all on how dynamic the language is.
Can you explain to me why a Python compiler couldn't implement optimizations similar to SBCL?
>It's not.
Is Python more powerful than CL in some way that I am not aware of?
I tried to explain that the optimized version of Common Lisp is less dynamic than the non-optimized version. The speed advantage often comes because the compile code is less or not dynamic. Late binding for example makes code slower because of another indirection. An optimizing compiler can remove late binding. The code will be faster, but there might no longer be a runtime lookup of the function anymore.
> When using SBCL for example, none of CLs dynamic features are restricted from the programmer in any way.
Sure, but it will be slower in benchmarks. The excellent benchmark numbers of SBCL is in part a result of being able to cleverly remove dynamic features.
I don't think there's anything Python can do that Common Lisp can't in terms of dynamic-ness!
This is a quote from python.org:
>These languages are close to Python in their dynamic semantics, but so different in their approach to syntax that a comparison becomes almost a religious argument: is Lisp's lack of syntax an advantage or a disadvantage? It should be noted that Python has introspective capabilities similar to those of Lisp, and Python programs can construct and execute program fragments on the fly. Usually, real-world properties are decisive: Common Lisp is big (in every sense), and the Scheme world is fragmented between many incompatible versions, where Python has a single, free, compact implementation.
https://www.python.org/doc/essays/comparisons/
I believe there are dynamic things Common Lisp can do that python can't, like modifying and creating classes, inheritance and methods at runtime, even with effects propagating out to already existing class instances!