A language like Python, as well as several functional languages, just to name a few, delimiting a block of commands (which can generate new scope or not) is basically done by indentation (the displacement of spaces at the beginning of the line).
When the parser identifies that there are additional spaces at the beginning of the line in relation to the previous line, it considers that there was an indent . When he identifies that a new line has returned to the previous column that was before at the previous level (it goes stacking it), then it considers that there was a dedent . The indent tag functions as the open key, and the dedent tag works as the key closes. You may not be seeing anything but the compiler knows that there is a tag there that has been calculated.
Obviously you need to format the code well so everything works fine and parser does not get lost. Of course some rules can make this easier.
Python still uses the colon in the row prior to the block to identify that it will have a block ahead which even facilitates the parser at least to better understand what the intention was and to anticipate > lookahead ).
This has nothing to do with duck typing (as requested by the original question).